diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index b80d5acc5d..65d0b6217f 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -26,9 +26,9 @@ jobs: node-version: '>=25.2.1' - name: Set up Go - uses: actions/setup-go@v6 + uses: actions/setup-go@v7 with: - go-version: '>=1.26.6' + go-version: '>=1.27.0' # This step usually is not needed because the /ui/dist is pregenerated locally # but its here to ensure that each release embeds the latest admin ui artifacts. diff --git a/CHANGELOG.md b/CHANGELOG.md index c58ba35973..4498b70945 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,1239 +1,34 @@ -## v0.39.11 +## v0.40.0 -- Fixed "API preview" examples ([#7782](https://github.com/pocketbase/pocketbase/issues/7782), [#7785](https://github.com/pocketbase/pocketbase/issues/7785)). +- Propagate console command errors and recovered panics to `app.Start()` so that the program can exit with non-zero code while still ensuring that `app.OnTerminate` hook was triggered _(responsible for the app graceful shutdown handling)_. + _⚠️ Note that this could be a slight breaking change in case you are chaining PocketBase commands and relied on the previous `0` exit status for `Command.RunE` returned errors._ + _Or in other words, if you have `./pocketbase invalid && someothercommand` and previously relied that `someothercommand` will be always executed then this is no longer the case and you'll have to adjust it or replace `&&` with `;`._ -- Other minor UI improvements (fixed sortable `dragend` event handling, allow ESC to workaround TAB trap for the rule fields, allow new duplicated collection to edit the collection of relation fields, updated shablon, updated npm dev dependencies, etc.). +- Added quotes around the default `Content-Disposition` serving filename in case custom name with special characters is provided. -- Bumped `golang.org/x/*` deps and the min Go GitHub action version to 1.26.6 because it comes with some [minor bug and security fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.26.6). +- Added `Cross-Origin-Opener-Policy:same-origin` to the default security response headers. + _This is an extra precaution to prevent tab-nabbing in case custom UI plugins use `target="_blank"` without `rel="noopener"`._ +- Added `Record.GetInt64(field)` helper (note that the serializable max safe integer of the `number` field is ~2^53-1). -## v0.39.10 +- Added `Store.Keys()` method that returns a slice with all of the store keys. -- Reverted the auto panic recover handling for the cli commands to preserve the old behavior and allow panic to force exit with non-zero code ([#7781](https://github.com/pocketbase/pocketbase/issues/7781)). - _Proper command non-zero exit support will be available with the next v0.40/v0.41 release._ +- Added new `DELETE /api/logs` endpoint and UI control to delete all logs without changing the `maxDays` retention setting. -- Minor UI improvements (added placeholder loader for the logs chart, npm dev deps update, etc.). +- Added new log settings option to limit the max `Log.Data` size that will be saved in the database (default to ~16KB). + _This is an extra precaution for the cases when logging user supplied data without validating it beforehand._ + _If the resulting `Log.Data` json is above the limit, it is truncated to the last valid decoded character and an extra `"__pb_truncated__":true` log data entry will be added.`_ + _Additionally, for just in case the log message is also truncated at max 8k characters._ -- Updated `modernc.org/sqlite` to v1.55.0 (doc changes). +- Added new `filesystem` low-level helper methods: + - `filesystem.NewWriter(key, opts)` to allow direct file create from an `io.Reader` value. + - `filesystem.OnNewWriter()` hook to allow listening for new/to-be-created files _(it is not exposed in `core.App` instance for now to avoid introducing breaking changes)_. + - `filesystem.OnDelete()` hook to allow listening for deleted files _(it is not exposed in `core.App` instance for now to avoid introducing breaking changes)_. +- Optimized backups to no longer transaction lock the database during backup generation ([#7799](https://github.com/pocketbase/pocketbase/discussions/7799#discussioncomment-18108244)). -## v0.39.9 +- Updated `modernc.org/sqlite` to 1.57.0 and registered by default the new `_defensive=1` DSN query parameter to enable [SQLite's defensive mode](https://sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigdefensive). -- Fixed `Shift + Click` range bulk selection not working in Firefox ([#7771](https://github.com/pocketbase/pocketbase/issues/7771)) - -- Updated goja and its related dependencies _(fixes for TypedArray and regexp2 dep regression for the reported empty string match with lookahead patterns)_. - -- Minor filter (fexpr) improvements _(optimization for large string literals and fix for control characters handling)_. - - -## v0.39.8 - -- Properly reset JSVM global `$app` overwrite so that pooled executors always get a clean state. - -- Minor UI improvements: - - prevent resetting number inputs with leading 0 while still typing (normalized in `onchange`) - - added support for `Shift + Click` range bulk selection ([#7759](https://github.com/pocketbase/pocketbase/issues/7759)) - -- Bumped `golang.org/x/*` indirect dependencies as there are some minor security fixes. - -- Updated `modernc.org/sqlite` to v1.54.0 ([SQLite 3.53.3](https://sqlite.org/src/timeline?from=version-3.53.2&to=version-3.53.3&to2=branch-3.53)). - - -## v0.39.7 - -- Replaced `github.com/go-ozzo/ozzo-validation` with the fork `github.com/pocketbase/ozzo-validation` since the original library has recently changed ownership and the new maintainer cannot be trusted. - _There are plans to create eventually a new validation library from scratch more suited for our needs in PocketBase because ozzo-validation is known to have some minor performance and obscure regex issues, but until then we'll stick with the fork (and if you use `ozzo-validation` in your own Go code, I'd suggest to swap the imports with the fork)_. - -- Fixed missing import collection `fields` property access ([#7760](https://github.com/pocketbase/pocketbase/issues/7760)). - -- Fixed View collection `*` validator and added more friendly error messages ([#7761](https://github.com/pocketbase/pocketbase/issues/7761)). - -- ⚠️ Security fix for unhandled panic in internal worker goroutines ([#7762](https://github.com/pocketbase/pocketbase/discussions/7762)). - _To prevent this from showing again, all existing internal worker functions were wrapped with [`routine.SafeWrap(f)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/routine#SafeWrap) (auto recovers and returns any eventual panic as regular error)._ - - -## v0.39.6 - -- Added `Cc` and `Bcc` recipients to the dev `sendmail` command for consistency with the SMTP mailer. - -- Added extra hardening options to the Microsoft OAuth2 provider allowing developers to specify the preferred safe email extraction method. - -- Updated goja and the related `golang.org/x/*` dependencies _(`WeakMap` regression fixes)_. - -- Bumped the min Go GitHub action version to 1.26.5 as it includes some [minor security fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.26.5). - - -## v0.39.5 - -- Limit with ellipsis long `url` field values. - -- Readded the "fullscreen" `editor` field option and preloaded the TinyMCE component for slightly faster initial rendering ([#7746](https://github.com/pocketbase/pocketbase/issues/7746)). - -- Updated goja (`TypedArray` fixes). - - -## v0.39.4 - -- Removed `redirectURL` required validator from the code->token exchange endpoint (aka. `authWithOAuth2Code()`) ([#7734](https://github.com/pocketbase/pocketbase/issues/7734)). - _Note that OAuth2 providers have their own validations and whether it is allowed to be empty or not could depend on the configured OAuth2 app (in most cases it is required and the redirect address must match with the initial value submitted with the authorization request)._ - -- Enabled sorting by the first _implicit_ presentable relation field ([#7735](https://github.com/pocketbase/pocketbase/discussions/7735)). - -- Other minor UI fixes (tooltip clear on hovered element removal, optional before element sortable fix, etc.). - -- Updated goja and the related `golang.org/x/*` dependencies (regex support improvements). - - -## v0.39.3 - -- Fixed JS error on `file` settings `maxSelect` change ([#7731](https://github.com/pocketbase/pocketbase/issues/7731)). - -- Apply the `Ctrl+S` record panel save shortcut only if it is the current top open modal. - -- Fixed `number` settings validator to not ignore 0 `max` value. - -- Normalized field settings validation error messages and tooltips. - - -## v0.39.2 - -- Fixed records list UI sorting ([#7724](https://github.com/pocketbase/pocketbase/issues/7724)). - -- Don't clear the date input on invalid value while still typing ([#7726](https://github.com/pocketbase/pocketbase/issues/7726)). - -- Return `filepath.SkipDir` in the `pb_hooks` dirs watcher to avoid unnecessary iterating over `node_modules` and `.*` prefixed hidden dirs (`.DS_Store`, `.git`, etc.). - -- Show the "Affected rows" SQL console message only if non-empty to avoid ambiguity with drivers that don't support returning the affected rows count. - -- Updated `modernc.org/sqlite` to v1.52.0 ([SQLite 3.53.2](https://sqlite.org/src/timeline?from=version-3.53.0&to=version-3.53.2&to2=branch-3.53&y=ci)). - - -## v0.39.1 - -- Fixed multiple select options wrapping ([#7720](https://github.com/pocketbase/pocketbase/issues/7720)). - -- Return the hidden record data fields for superusers realtime subscribers ([#7721](https://github.com/pocketbase/pocketbase/issues/7721)). - -- Added default panic-recover handling for the cron jobs to avoid terminating the server on panic. - -- Bumped the min Go GitHub action version to 1.26.4 as it includes some [minor security fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.26.4). - - -## v0.39.0 - -- Added new "SQL console" section under _Settings > Debug_ allowing executing any raw SQL query from the UI ([#2236](https://github.com/pocketbase/pocketbase/issues/2236); [#7638](https://github.com/pocketbase/pocketbase/discussions/7638)). - _Note that this is intended for one-off analytic queries, the occasional `VACUUM`/`PRAGMA optimize` or debug purposes and not as the primary interface for interacting with your PocketBase data because it can break your application if not used with proper care!_ - -- Send system email alerts to superusers in case of an error with the automated backups ([#7698](https://github.com/pocketbase/pocketbase/issues/7698)). - -- Various minor improvements and fixes: - - fixed logs bulk selection export error - - optimized logs and records list rendering - - allowed word breaking in labels - - text contrast improvements - - registered missing `oidc2` and `oidc3` option fields - - updated default email template texts for consistency - - updated `modernc.org/sqlite` to v1.51.0 - - etc. - - -## v0.38.2 - -- Added `RealtimeConnectRequestEvent.MaxTimeout` field to specify the absolute max duration a realtime connection can remain open (default to 30mins). - _This is in addition to the `IdeTimeout` of 5mins in order to prevent misuse and to allow the GC to run more regularly._ - -- Added extra checks for the connected user IP in the realtime APIs to prevent bruteforce guest subscription update attempts and to serve as an extra protection for the "all-in-one" OAuth2 realtime handler. - -- Don't reset the records list pagination on record update ([#7694](https://github.com/pocketbase/pocketbase/issues/7694)). - -- Updated all `golang.org/x/` packages to cover the recent [security fixes](https://groups.google.com/g/golang-announce/c/PdiGK3xulk4) _(none of them should be a critical issue in PocketBase but nonetheless it is advised to update)_. - - -## v0.38.1 - -- Silenced the superuser IPs confirmation if there is no change. - -- Updated the _experimental_ UI extensions APIs to allow top-level `await` in the initialization script. - -- Force unset the auth state of existing realtime connections on user password, collection secret, etc. changes. - _This is not strictly necessary because the realtime connections have short-lived idle timeout by design but nonetheless it was implemented to minimize the attack vectors._ - -- Added error marker for each collection tab and fixed the styles of the raw errors tooltip. - -- Fixed indexes collection update error ([#7689](https://github.com/pocketbase/pocketbase/issues/7689)). - _⚠️ The fix comes with a system migration that resaves all collections with indexes to ensure that all indexes are normalized and available in the `Collection.Indexes` field (it will also include indexes created manually via the sqlite3 cli or other external tool)._ - _If you are using a test `pb_data` for your Go automation tests you may want to apply the migration to it too so that it runs only once and not for each execution of your tests, aka. you could run once `go run main.go migrate up --dir="/path/to/test_pb_data"`._ - -- Updated `modernc.org/sqlite` to v1.50.1 (SQLite 3.53.1). - -- Other minor fixes (_updated API preview examples, fixed code comment typos, etc._). - - -## v0.38.0 - -- Fixed UI logs pagination when no custom range is specified. - -- Fixed default CSP not allowing audio/video previews ([#7677](https://github.com/pocketbase/pocketbase/issues/7677)). - -- Serve fixed `Content-Type` for `.xlsx`, `.docx` and `.pptx` files to allow previews on iOS ([#7467](https://github.com/pocketbase/pocketbase/discussions/7467)). - -- Changed settings app URL input to `type="text"` for compatibility with earlier versions ([#7681](https://github.com/pocketbase/pocketbase/issues/7681)). - -- Added an internal watcher to sync various runtime states between multiple PocketBase processes (e.g. memory store) using the same `pb_data`. - _This is helpful in case for example a separate PocketBase console command change the collections or application settings while the server is still running._ - _The watcher is debounced and implemented by watching the special `pb_data/.notify` dir as a workaround to avoid depending on OS and SQLite driver specific APIs._ - -- Added new [Superuser IPs/CIDR subnets whitelist setting](https://pocketbase.io/docs/going-to-production/#limit-superusers-to-specific-ipssubnets). - The optional setting can be changed from the UI under _Dasboard > Settings > Application > Superuser IPs_. - To avoid lockout in case your superuser IP change, the ips whitelist can be updated also via the `superuser ips` console command: - ```sh - # note: --dir is optional and defaults to pb_data next to the executable - - # clear whitelisted IPs - ./pocketbase superuser ips --dir=/custom/path/to/pb_data - - # OR change the whitelisted IPs to 127.0.0.1 and 10.0.0.0 (replace with your real IP(s)) - ./pocketbase superuser ips 127.0.0.1 10.0.0.0 --dir=/custom/path/to/pb_data - ``` - -- Added rate limit option to exclude IPs/CIDR subnets ([#6410](https://github.com/pocketbase/pocketbase/issues/6410)). - -- Bumped min Go GitHub action version to 1.26.3 because it comes with some [minor bug and security fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.26.3). - - -## v0.37.5 - -- Fixed password fields not being detected as changed ([#7670](https://github.com/pocketbase/pocketbase/issues/7670)). - -- Added the local time zone name next to the `date` field label. - -- Reload trusted proxy info UI after settings save. - -- Other minor improvements (skips the duplicated record ids from the `IN` expand list, reordered confirm-email-change error checks to minimize enumeration attacks, etc.). - - -## v0.37.4 - -- Added backups list scroll container ([#7655](https://github.com/pocketbase/pocketbase/issues/7655)). - -- Optimized record upsert and preview modals data loading to minimize layout jumps. - -- Fixed SMTP IPv6 network address format ([#7659](https://github.com/pocketbase/pocketbase/issues/7659)). - -- Fixed autocomplete selection not properly updating the underlying input value ([#7664](https://github.com/pocketbase/pocketbase/issues/7664)). - -- Added `ghupdate.BaseURL` config option ([#7665](https://github.com/pocketbase/pocketbase/issues/7665)). - -- Added dummy bcrypt password check for the failure auth path to minimize enumeration timing attacks when registrations are disabled. - -- Adjusted Bitbucket, GitHub, GitLab and Gitea/Forgejo OAuth2 providers to better reflect recent API updates and doc references. - _In case the userinfo data is not sufficient, some of the providers now send a separate list emails request in order to minimize eventual linking security issues caused by custom onpremise setups (e.g. Gitea/Forgejo allows skipping the email verification if an ENV variable is configured)._ - -- ⚠️ Fixed a pre-hijacking OAuth2 linking vulnerability ([#7662](https://github.com/pocketbase/pocketbase/discussions/7662); thanks @Alardiians for reporting it privately). - -- Bumped Go and npm dependencies. - - -## v0.37.3 - -- Fixed total count load on page back/forward navigation. - -- Fixed `editor` floating dialogs position when scrolling ([#7653](https://github.com/pocketbase/pocketbase/issues/7653)). - -- Enabled text wrapping for the API rule fields. - -- Added view query sample loading indicator. - -- Other minor light UI contrast and styles improvements. - - -## v0.37.2 - -- Fixed autoexpandable input in Firefox ([#7648](https://github.com/pocketbase/pocketbase/discussions/7648)). - -- Slightly adjusted the dark theme colors for better readability ([#7648](https://github.com/pocketbase/pocketbase/discussions/7648)). - -- Removed unnecessary tags stripping from the displayed log attributes ([#7649](https://github.com/pocketbase/pocketbase/issues/7649)). - -- Workarounded Safari freeze caused by a buggy CSS popover property ([#7650](https://github.com/pocketbase/pocketbase/issues/7650)). - - -## v0.37.1 - -- Minor UI bugfixes: - - Fixed `number` field input values normalization ([#7646](https://github.com/pocketbase/pocketbase/issues/7646)). - - Allow opening collections in new tab with middle click. - - Show collection name in the page title on initial load. - - -## v0.37.0 - -- New UI rewritten from scratch and with support for external customization in mind. - > Note that as explained in [#7612](https://github.com/pocketbase/pocketbase/discussions/7612) the new UI kit and extensions APIs will intentionally remain undocumented until "Stage 2 completion" _(there no ETAs)_. - - The new UI also introduced several other small improvements: - - ~2MB smaller bundle size. - - Dark mode and theming support. - - Basic responsive/mobile support _(it is far from perfect but certainly more usable than before)_. - - Help text option for the collection fields. - - Lifted the max nested level restriction of presentable relations _(children are lazy loaded)_. - - Lighter rules autocomplete. - - Live view query preview. - - Insert of an audio/video embed tag in the richtext editor from a collection file. - - Option to bulk export records as JSON. - - Local search history for all searchbars. - - API rules overview across all collections. - - Very basic ERD-like visualization for the collections structure and relations. - - New stepped logs chart visualization with panning support. - - `listAuthMethods()` (aka. `/api/collection/{col}/auth-methods`) now returns the OAuth2 provider logo for each provider as inlined SVG string in its response data. - _⚠️ Note that if your app for whatever reason rely on the dashboard OAuth2 logos available under `/_/images/oauth2/*` they are still available for now but will be removed in future versions and it is recommended to use the new inline SVGs!_ - -- Added optional `no_ui` build tag to exclude the UI from bundling with the executable ([#7548](https://github.com/pocketbase/pocketbase/issues/7548)). - ```sh - go build -tags no_ui - ``` - -- Exported the internal JSVM bind functions ([#7600](https://github.com/pocketbase/pocketbase/discussions/7600)). - ```go - jsvm.BindCore(vm) - jsvm.BindDbx(vm) - jsvm.BindSecurity(vm) - jsvm.BindOS(vm) - jsvm.BindFilepath(vm) - jsvm.BindHTTP(vm) - jsvm.BindFilesystem(vm) - jsvm.BindForms(vm) - jsvm.BindMails(vm) - jsvm.BindApis(vm) - ``` - -- Updated `modernc.org/sqlite` to v1.49.1 (SQLite 3.53.0). - - -## v0.36.9 - -- Updated the Discord `AuthUser.Name` field to use `global_name` ([#7603](https://github.com/pocketbase/pocketbase/pull/7603); thanks @HansHans135). - -- Fixed settings SMTP password clear persistence. - -- Added extra OAuth2 checks when downloading the avatar URL to prevent internal network probing requests in case of a malicious/vulnerable vendor. - -- Updated `modernc.org/sqlite` to v1.48.2 _(vfs and other error path related fixes)_. - -- Updated min Go GitHub action version to 1.26.2 because it comes with some [minor security fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.26.2). - -- Other small improvements _(updated `$apis.static` JSVM documentation, fixed comment typos, added missing file close on seek error, etc.)_. - - -## v0.36.8 - -- Fixed OAuth2 client secret reset when serializing a cached collection model. - -- Bumped all Go and npm deps. - _This should also silence recent spam reports and security scanners regarding `golang.org/x/image` [CVE-2026-33809](https://www.cve.org/CVERecord?id=CVE-2026-33809) (it is not an issue in PocketBase because we don't support TIFF thumbs)._ - - -## v0.36.7 - -- Fixed high memory usage with large file uploads ([#7572](https://github.com/pocketbase/pocketbase/discussions/7572)). - -- Updated the rate limiter reset rules to follow a more traditional fixed window strategy _(aka. to be more close to how it is presented in the UI - allow max X user requests under Ys)_ since several users complained that the older algorithm was not intuitive and not suitable for large intervals. - _Approximated sliding window strategy was also suggested as a better compromise option to help minimize traffic spikes right after reset but the additional tracking could introduce some overhead and for now it is left aside until we have more tests._ - -- Updated `modernc.org/sqlite` to v1.46.2 and SQLite 3.51.3. - _⚠️ SQLite 3.51.3 fixed a [database corruption bug](https://sqlite.org/wal.html#walresetbug) that is very unlikely to happen (with PocketBase even more so because we queue on app level all writes and explicit transactions through a single db connection), but still it is advised to upgrade._ - -- Updated other minor Go and npm deps. - _The min Go version in the go.mod of the package was also bumped to Go 1.25.0 because some of the newer dep versions require it._ - - -## v0.36.6 - -- Set `NumberField.OnlyInt:true` for the generated View collection schema fields when a view column expression is known to return int-only values ([#7538](https://github.com/pocketbase/pocketbase/issues/7538)). - -- Documented the `unmarshal` JSVM helper ([#7543](https://github.com/pocketbase/pocketbase/issues/7543)). - -- Added extra read check after the `Store.GetOrSet` write lock to prevent races overwriting an already existing value. - -- Added empty records check for the additional client-side filter's ListRule constraint that was introduced in v0.32.0 ([presentator#206](https://github.com/presentator/presentator/issues/206)). - -- Set a fixed `routine.FireAndForget()` debug stack trace limit to 2KB. - -- Bumped min Go GitHub action version to 1.26.1 because it comes with some [minor bug and security fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.26.1). - -- Typos and other minor doc fixes. - - -## v0.36.5 - -- Disabled collection and fields name normalization while in IME mode ([#7532](https://github.com/pocketbase/pocketbase/pull/7532); thanks @miaopan607). - -- Updated `modernc.org/sqlite` to v1.46.1 _(resets connection state on Tx.Commit failure)_. - - -## v0.36.4 - -- Made the optional `Bearer` token prefix case-insensitive ([#7525](https://github.com/pocketbase/pocketbase/pull/7525); thanks @benjamesfleming). - -- Enabled `$filesystem.s3(...)` and `$filesystem.local(...)` JSVM bindings ([#7526](https://github.com/pocketbase/pocketbase/issues/7526)). - - -## v0.36.3 - -- Added `Accept-Encoding: identity` to the S3 requests per the suggestion in [#7523](https://github.com/pocketbase/pocketbase/issues/7523). - _This should help fixing the 0-bytes file response when S3 API compression is enabled._ - -- Bumped min Go GitHub action version to 1.26.0 _(it comes with minor [GC performance improvements](https://go.dev/doc/go1.26#runtime))_. - -- Other minor fixes _(updated `modernc.org/sqlite` to v1.45.0, updated `goja_nodejs` adding `Buffer.concat`, updated the arguments of `app.DeleteTable(...)`, `app.DeleteView(...)` and other similar methods to make it more clear that they are dangerous and shouldn't be used with untrusted input, etc.)_. - - -## v0.36.2 - -- Updated `modernc.org/sqlite` to v1.44.3 _(race check fix)_, `goja` _(circular references fix)_ and other go deps. - -- Other minor fixes _(updated tests to silence some of the race detector errors, updated `FindFirstRecordByData` with more clear error message when missing or invalid key is used, etc.)_. - - -## v0.36.1 - -- Reverted the `DISTINCT` with `GROUP BY` replacement optimization from v0.36.0 as it was reported to negatively impact the indexes utilization for some queries -and the minor performance boost that you may get when used on large records is not enough to justify the more common use ([#7461](https://github.com/pocketbase/pocketbase/discussions/7461)). - _A better generic deduplication optimization for large records (aka. records with large `text`/`json` fields or many small ones) will be researched but there are no ETAs._ - -- Updated `modernc.org/sqlite` to v1.44.2 _(SQLite 3.51.2)_. - -- Fixed code comment typos. - - -## v0.36.0 - -- List query and API rules optimizations: - - Removed unnecessary correlated subquery expression when using back-relations via single `relation` field. - - Replaced `DISTINCT` with `GROUP BY id` when rows deduplication is needed and when deemed safe. - _This should help with having a more stable and predictable performance even if the collection records are on the larger side._ - - For some queries and data sets the above 2 optimizations have shown significant improvements but if you notice a performance degradation after upgrading, - please open a Q&A discussion with export of your collections structure and the problematic request so that it can be analyzed. - -- Added [`strftime(format, timevalue, modifiers...)`](https://pocketbase.io/docs/api-rules-and-filters/#strftimeformat-time-value-modifiers-) date formatting filter and API rules function. - It works similarly to the [SQLite `strftime` builtin function](https://sqlite.org/lang_datefunc.html) - with the main difference that NULL results will be normalized for consistency with the non-nullable PocketBase `text` and `date` fields. - Multi-match expressions are also supported and works the same as if the collection field is referenced, for example: - ```js - // requires ANY/AT-LEAST-ONE-OF multiRel records to have "created" date matching the formatted string "2026-01" - strftime('%Y-%m', multiRel.created) ?= '2026-01' - - // requires ALL multiRel records to have "created" date matching the formatted string "2026-01" - strftime('%Y-%m', multiRel.created) = '2026-01' - ``` - -- ⚠️ Minor changes to the `search.ResolverResult` struct _(mostly used internally)_: - - Replaced `NoCoalesce` field with the more explicit `NullFallback` _(`NullFallbackDisabled` is the same as `NoCoalesce:true`)_. - - Replaced the expression interface of the `MultiMatchSubQuery` field with the concrete struct type `search.MultiMatchSubquery` to avoid excessive type assertions and allow direct mutations of the field. - -- Updated `modernc.org/sqlite` to v1.44.1 _(SQLite 3.51.1)_. - -- Bumped min Go GitHub action version to 1.25.6 because it comes with some [minor security fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.25.6). - - -## v0.35.1 - -- Updated `modernc.org/sqlite` to v1.43.0 _(query cancellation race fix)_. - -- Other minor UI fixes (normalized relations picker selection and confirmation message when `maxSelect=0/1`, updated node deps). - - -## v0.35.0 - -- Added `nullString()`, `nullInt()`, `nullFloat()`, `nullBool`, `nullArray()`, `nullObject()` JSVM helpers for scanning nullable columns ([#7396](https://github.com/pocketbase/pocketbase/issues/7396)). - -- Store the correct `image/png` as attrs content type when generating a thumb fallback _(e.g. for `webp`)_. - -- Trimmed custom uploaded file name and extension from leftover `.` characters after `filesystem.File` normalization. - _This was done to prevent issues with external files sync programs that may have special handling for "invisible" files._ - -- Updated `modernc.org/sqlite` _(v1.41.0 includes prepared statements optimization)_ and other minor Go deps. - - -## v0.34.2 - -- Bumped JS SDK to v0.26.5 to fix Safari AbortError detection introduced with the previous release ([#7369](https://github.com/pocketbase/pocketbase/issues/7369)). - - -## v0.34.1 - -- Added missing `:` char to the autocomplete regex ([#7353](https://github.com/pocketbase/pocketbase/pull/7353); thanks @ouvreboite). - -- Added "Copy raw JSON" collection dropdown option ([#7357](https://github.com/pocketbase/pocketbase/issues/7357)). - -- Updated Go deps and JS SDK. - -- Bumped min Go GitHub action version to 1.25.5 because it comes with some [minor security fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.25.5). - _The runner action was also updated to `actions/setup-go@v6` since the previous v5 Go source seems [no longer accessible](https://github.com/actions/setup-go/pull/665#issuecomment-3416693714)._ - - -## v0.34.0 - -- Added `@request.body.someField:changed` modifier. - It could be used when you want to ensure that a body field either wasn't submitted or was submitted with the same value. - Or in other words, if you want to disallow a field change the below 2 expressions would be equivalent: - ```js - // (old) - (@request.body.someField:isset = false || @request.body.someField = someField) - - // (new) - @request.body.someField:changed = false - ``` - -- Added `MailerRecordEvent.Meta["info"]` property for the `OnMailerRecordAuthAlertSend` hook. - -- Updated the backup restore popup with a short info about the performed restore steps. - -- Updated Go deps. - - -## v0.33.0 - -- Added extra `id` characters validation in addition to the user specified regex pattern ([#7312](https://github.com/pocketbase/pocketbase/issues/7312)). - _The following special characters are always forbidden: `./\|"'``<>:?*%$\n\r\t\0 `. Common reserved Windows file names such as `aux`, `prn`, `con`, `nul`, `com1-9`, `lpt1-9` are also not allowed._ - _The list is not exhaustive but it should help minimizing eventual filesystem compatibility issues in case of wildcards or other loose regex patterns._ - -- Added `{ALERT_INFO}` placeholder to the auth alert mail template ([#7314](https://github.com/pocketbase/pocketbase/issues/7314)). - _⚠️ `mails.SendRecordAuthAlert(app, authRecord, info)` also now accepts a 3rd `info` string argument._ - -- Updated Go deps. - - -## v0.32.0 - -- ⚠️ Added extra List/Search API rules checks for the client-side `filter`/`sort` relations. - - This is continuation of the effort to eliminate the risk of information disclosure _(and eventually the side-channel attacks that may originate from that)_. - - So far this was accepted tradeoff between performance, usability and correctness since the solutions at the time weren't really practical _(especially with the back-relations as mentioned in ["Security and performance" section in #4417](https://github.com/pocketbase/pocketbase/discussions/4417))_, but with v0.23+ changes we can implement the extra checks without littering the code too much, with very little impact on the performance and at the same time ensuring better out of the box security _(especially for the cases where users operate with sensitive fields like "code", "token", "secret", etc.)_. - - Similar to the previous release, probably for most users with already configured API rules this change won't be breaking, but if you have an _intermediate/junction collection_ that is "locked" (superusers-only) we no longer will allow the client-side relation filter to pass through it and you'll have to set its List/Search API rule to enable the current user to search in it. - - For example, if you have a client-side filter that targets `rel1.rel2.token`, the client must have not only List/Search API rule access to the main collection BUT also to the collections referenced by "rel1" and "rel2" relation fields. - - Note that this change is only for the **client-side** `filter`/`sort` and doesn't affect the execution of superuser requests, API rules and `expand` - they continue to work the same as it is. - - An optional environment variable to toggle this behavior was considered but for now I think having 2 ways of resolving client-side filters would introduce maintenance burden and can even cause confusion (this change should actually make things more intuitive and clear because we can simply say something like _"you can search by a collection X field only if you have List/Search API rule access to it"_ no matter whether the targeted collection is the request's main collection, the first or last relation from the filter chain, etc.). - - If you stumble on an error or extreme query performance degradation as a result of the extra checks, please open a Q&A discussion with the failing request and export of your collections configuration as JSON (_Settings > Export collections_) and I'll try to investigate it. - -- Increased the default SQLite `PRAGMA cache_size` to ~32MB. - -- Fixed deadlock when manually triggering the `OnTerminate` hook ([#7305](https://github.com/pocketbase/pocketbase/pull/7305); thanks @yerTools). - -- Fixed some code comment typos, regenerated the JSVM types and updated npm dependencies. - -- Updated `modernc.org/sqlite` to 1.40.0. - - -## v0.31.0 - -- Visualize presentable multiple `relation` fields ([#7260](https://github.com/pocketbase/pocketbase/issues/7260)). - -- Support Ed25519 in the optional OIDC `id_token` signature validation ([#7252](https://github.com/pocketbase/pocketbase/issues/7252); thanks @shynome). - -- Added `ApiScenario.DisableTestAppCleanup` optional field to skip the auto test app cleanup and leave it up to the developers to do the cleanup manually ([#7267](https://github.com/pocketbase/pocketbase/discussions/7267)). - -- Added `FileDownloadRequestEvent.ThumbError` field that is populated in case of a thumb generation failure (e.g. unsupported format, timing out, etc.), allowing developers to reject the thumb fallback and/or supply their own custom thumb generation ([#7268](https://github.com/pocketbase/pocketbase/discussions/7268)). - -- ⚠️ Disallow client-side filtering and sorting of relations where the collection of the last targeted relation field has superusers-only List/Search API rule to further minimize the risk of eventual side-channel attack. - _This should be a non-breaking change for most users, but if you want the old behavior, please open a new Q&A discussion with details about your use case to evaluate making it configurable._ - _Note also that as mentioned in the "Security and performance" section of [#4417](https://github.com/pocketbase/pocketbase/discussions/4417) and [#5863](https://github.com/pocketbase/pocketbase/discussions/5863), the easiest and recommended solution to protect security sensitive fields (tokens, codes, passwords, etc.) is to mark them as "Hidden" (aka. make them non-API filterable)._ - -- Regenerated JSVM types and updated npm and Go deps. - - -## v0.30.4 - -- Fixed `json` field CSS regression introduced with the overflow workaround in v0.30.3 ([#7259](https://github.com/pocketbase/pocketbase/issues/7259)). - - -## v0.30.3 - -- Fixed legacy identitity field priority check when a username is a valid email address ([#7256](https://github.com/pocketbase/pocketbase/issues/7256)). - -- Workaround autocomplete overflow issue with Firefox 144 ([#7223](https://github.com/pocketbase/pocketbase/issues/7223)). - -- Updated `modernc.org/sqlite` to 1.39.1 (SQLite 3.50.4). - - -## v0.30.2 - -- Bumped min Go GitHub action version to 1.24.8 since it comes with some [minor security fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.24.8+label%3ACherryPickApproved). - - -## v0.30.1 - -- ⚠️ Excluded the `lost+found` directory from the backups ([#7208](https://github.com/pocketbase/pocketbase/pull/7208); thanks @lbndev). - _If for some reason you want to keep it, you can restore it by editing the `e.Exclude` list of the `OnBackupCreate` and `OnBackupRestore` hooks._ - -- Minor tests improvements (disabled initial superuser creation for the test app to avoid cluttering the std output, added more tests for the `s3.Uploader.MaxConcurrency`, etc.). - -- Updated `modernc.org/sqlite` and other Go dependencies. - - -## v0.30.0 - -- Eagerly escape the S3 request path following the same rules as in the S3 signing header ([#7153](https://github.com/pocketbase/pocketbase/issues/7153)). - -- Added Lark OAuth2 provider ([#7130](https://github.com/pocketbase/pocketbase/pull/7130); thanks @mashizora). - -- Increased test tokens `exp` claim to minimize eventual issues with reproducible builds ([#7123](https://github.com/pocketbase/pocketbase/issues/7123)). - -- Added `os.Root` bindings to the JSVM ([`$os.openRoot`](https://pocketbase.io/jsvm/functions/_os.openRoot.html), [`$os.openInRoot`](https://pocketbase.io/jsvm/functions/_os.openInRoot.html)). - -- Added `osutils.IsProbablyGoRun()` helper to loosely check if the program was started using `go run`. - -- Various minor UI improvements (updated collections indexes UI, enabled seconds in the datepicker, updated helper texts, etc.). - -- ⚠️ Updated the minimum package Go version to 1.24.0 and bumped Go dependencies. - - -## v0.29.3 - -- Try to forward Apple OAuth2 POST redirect user's name so that it can be returned (and eventually assigned) with the success response of the all-in-one auth call ([#7090](https://github.com/pocketbase/pocketbase/issues/7090)). - -- Fixed `RateLimitRule.Audience` code comment ([#7098](https://github.com/pocketbase/pocketbase/pull/7098); thanks @iustin05). - -- Mocked `syscall.Exec` when building for WASM ([#7116](https://github.com/pocketbase/pocketbase/pull/7116); thanks @joas8211). - _Note that WASM is not officially supported PocketBase build target and many things may not work as expected._ - -- Registered missing `$filesystem`, `$mails`, `$template` and `__hooks` bindings in the JSVM migrations ([#7125](https://github.com/pocketbase/pocketbase/issues/7125)). - -- Regenerated JSVM types to include methods from structs with single generic parameter. - -- Updated Go dependencies. - - -## v0.29.2 - -- Bumped min Go GitHub action version to 1.23.12 since it comes with some [minor fixes for the runtime and `database/sql` package](https://github.com/golang/go/issues?q=milestone%3AGo1.23.12+label%3ACherryPickApproved). - - -## v0.29.1 - -- Updated the X/Twitter provider to return the `confirmed_email` field and to use the `x.com` domain ([#7035](https://github.com/pocketbase/pocketbase/issues/7035)). - -- Added Box.com OAuth2 provider ([#7056](https://github.com/pocketbase/pocketbase/pull/7056); thanks @blakepatteson). - -- Updated `modernc.org/sqlite` to 1.38.2 (SQLite 3.50.3). - -- Fixed example List API response ([#7049](https://github.com/pocketbase/pocketbase/pull/7049); thanks @williamtguerra). - - -## v0.29.0 - -- Enabled calling the `/auth-refresh` endpoint with nonrenewable tokens. - _When used with nonrenewable tokens (e.g. impersonate) the endpoint will simply return the same token with the up-to-date user data associated with it._ - -- Added the triggered rate rimit rule in the error log `details`. - -- Added optional `ServeEvent.Listener` field to initialize a custom network listener (e.g. `unix`) instead of the default `tcp` ([#3233](https://github.com/pocketbase/pocketbase/discussions/3233)). - -- Fixed request data unmarshalization for the `DynamicModel` array/object fields ([#7022](https://github.com/pocketbase/pocketbase/discussions/7022)). - -- Fixed Dashboard page title `-` escaping ([#6982](https://github.com/pocketbase/pocketbase/issues/6982)). - -- Other minor improvements (updated first superuser console text when running with `go run`, clarified trusted IP proxy header label, wrapped the backup restore in a transaction as an extra precaution, updated deps, etc.). - - -## v0.28.4 - -- Added global JSVM `toBytes()` helper to return the bytes slice representation of a value such as io.Reader or string, _other types are first serialized to Go string_ ([#6935](https://github.com/pocketbase/pocketbase/issues/6935)). - -- Fixed `security.RandomStringByRegex` random distribution ([#6947](https://github.com/pocketbase/pocketbase/pull/6947); thanks @yerTools). - -- Minor docs and typos fixes. - - -## v0.28.3 - -- Skip sending empty `Range` header when fetching blobs from S3 ([#6914](https://github.com/pocketbase/pocketbase/pull/6914)). - -- Updated Go deps and particularly `modernc.org/sqlite` to 1.38.0 (SQLite 3.50.1). - -- Bumped GitHub action min Go version to 1.23.10 as it comes with some [minor security `net/http` fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.23.10+label%3ACherryPickApproved). - - -## v0.28.2 - -- Loaded latin-ext charset for the default text fonts ([#6869](https://github.com/pocketbase/pocketbase/issues/6869)). - -- Updated view query CAST regex to properly recognize multiline expressions ([#6860](https://github.com/pocketbase/pocketbase/pull/6860); thanks @azat-ismagilov). - -- Updated Go and npm dependencies. - - -## v0.28.1 - -- Fixed `json_each`/`json_array_length` normalizations to properly check for array values ([#6835](https://github.com/pocketbase/pocketbase/issues/6835)). - - -## v0.28.0 - -- Write the default response body of `*Request` hooks that are wrapped in a transaction after the related transaction completes to allow propagating the transaction error ([#6462](https://github.com/pocketbase/pocketbase/discussions/6462#discussioncomment-12207818)). - -- Updated `app.DB()` to automatically routes raw write SQL statements to the nonconcurrent db pool ([#6689](https://github.com/pocketbase/pocketbase/discussions/6689)). - _For the rare cases when it is needed users still have the option to explicitly target the specific pool they want using `app.ConcurrentDB()`/`app.NonconcurrentDB()`._ - -- ⚠️ Changed the default `json` field max size to 1MB. - _Users still have the option to adjust the default limit from the collection field options but keep in mind that storing large strings/blobs in the database is known to cause performance issues and should be avoided when possible._ - -- ⚠️ Soft-deprecated and replaced `filesystem.System.GetFile(fileKey)` with `filesystem.System.GetReader(fileKey)` to avoid the confusion with `filesystem.File`. - _The old method will still continue to work for at least until v0.29.0 but you'll get a console warning to replace it with `GetReader`._ - -- Added new `filesystem.System.GetReuploadableFile(fileKey, preserveName)` method to return an existing blob as a `*filesystem.File` value ([#6792](https://github.com/pocketbase/pocketbase/discussions/6792)). - _This method could be useful in case you want to clone an existing Record file and assign it to a new Record (e.g. in a Record duplicate action)._ - -- Other minor improvements (updated the GitHub release min Go version to 1.23.9, updated npm and Go deps, etc.) - - -## v0.27.2 - -- Added workers pool when cascade deleting record files to minimize _"thread exhaustion"_ errors ([#6780](https://github.com/pocketbase/pocketbase/discussions/6780)). - -- Updated the `:excerpt` fields modifier to properly account for multibyte characters ([#6778](https://github.com/pocketbase/pocketbase/issues/6778)). - -- Use `rowid` as count column for non-view collections to minimize the need of having the id field in a covering index ([#6739](https://github.com/pocketbase/pocketbase/discussions/6739)) - - -## v0.27.1 - -- Updated example `geoPoint` API preview body data. - -- Added JSVM `new GeoPointField({ ... })` constructor. - -- Added _partial_ WebP thumbs generation (_the thumbs will be stored as PNG_; [#6744](https://github.com/pocketbase/pocketbase/pull/6744)). - -- Updated npm dev dependencies. - - -## v0.27.0 - -- ⚠️ Moved the Create and Manage API rule checks out of the `OnRecordCreateRequest` hook finalizer, **aka. now all CRUD API rules are checked BEFORE triggering their corresponding `*Request` hook**. - This was done to minimize the confusion regarding the firing order of the request operations, making it more predictable and consistent with the other record List/View/Update/Delete request actions. - It could be a minor breaking change if you are relying on the old behavior and have a Go `tests.ApiScenario` that is testing a Create API rule failure and expect `OnRecordCreateRequest` to be fired. In that case for example you may have to update your test scenario like: - ```go - tests.ApiScenario{ - Name: "Example test that checks a Create API rule failure" - Method: http.MethodPost, - URL: "/api/collections/example/records", - ... - // old: - ExpectedEvents: map[string]int{ - "*": 0, - "OnRecordCreateRequest": 1, - }, - // new: - ExpectedEvents: map[string]int{"*": 0}, - } - ``` - If you are having difficulties adjusting your code, feel free to open a [Q&A discussion](https://github.com/pocketbase/pocketbase/discussions) with the failing/problematic code sample. - -- Added [new `geoPoint` field](https://pocketbase.io/docs/collections/#geopoint) for storing `{"lon":x,"lat":y}` geographic coordinates. - In addition, a new [`geoDistance(lonA, lotA, lonB, lotB)` function](https://pocketbase.io/docs/api-rules-and-filters/#geodistancelona-lata-lonb-latb) was also implemented that could be used to apply an API rule or filter constraint based on the distance (in km) between 2 geo points. - -- Updated the `select` field UI to accommodate better larger lists and RTL languages ([#4674](https://github.com/pocketbase/pocketbase/issues/4674)). - -- Updated the mail attachments auto MIME type detection to use `gabriel-vasile/mimetype` for consistency and broader sniffing signatures support. - -- Forced `text/javascript` Content-Type when serving `.js`/`.mjs` collection uploaded files with the `/api/files/...` endpoint ([#6597](https://github.com/pocketbase/pocketbase/issues/6597)). - -- Added second optional JSVM `DateTime` constructor argument for specifying a default timezone as TZ identifier when parsing the date string as alternative to a fixed offset in order to better handle daylight saving time nuances ([#6688](https://github.com/pocketbase/pocketbase/discussions/6688)): - ```js - // the same as with CET offset: new DateTime("2025-10-26 03:00:00 +01:00") - new DateTime("2025-10-26 03:00:00", "Europe/Amsterdam") // 2025-10-26 02:00:00.000Z - - // the same as with CEST offset: new DateTime("2025-10-26 01:00:00 +02:00") - new DateTime("2025-10-26 01:00:00", "Europe/Amsterdam") // 2025-10-25 23:00:00.000Z - ``` - -- Soft-deprecated the `$http.send`'s `result.raw` field in favor of `result.body` that contains the response body as plain bytes slice to avoid the discrepancies between Go and the JSVM when casting binary data to string. - -- Updated `modernc.org/sqlite` to 1.37.0. - -- Other minor improvements (_removed the superuser fields from the auth record create/update body examples, allowed programmatically updating the auth record password from the create/update hooks, fixed collections import error response, etc._). - - -## v0.26.6 - -- Allow OIDC `email_verified` to be int or boolean string since some OIDC providers like AWS Cognito has non-standard userinfo response ([#6657](https://github.com/pocketbase/pocketbase/pull/6657)). - -- Updated `modernc.org/sqlite` to 1.36.3. - - -## v0.26.5 - -- Fixed canonical URI parts escaping when generating the S3 request signature ([#6654](https://github.com/pocketbase/pocketbase/issues/6654)). - - -## v0.26.4 - -- Fixed `RecordErrorEvent.Error` and `CollectionErrorEvent.Error` sync with `ModelErrorEvent.Error` ([#6639](https://github.com/pocketbase/pocketbase/issues/6639)). - -- Fixed logs details copy to clipboard action. - -- Updated `modernc.org/sqlite` to 1.36.2. - - -## v0.26.3 - -- Fixed and normalized logs error serialization across common types for more consistent logs error output ([#6631](https://github.com/pocketbase/pocketbase/issues/6631)). - - -## v0.26.2 - -- Updated `golang-jwt/jwt` dependency because it comes with a [minor security fix](https://github.com/golang-jwt/jwt/security/advisories/GHSA-mh63-6h87-95cp). - - -## v0.26.1 - -- Removed the wrapping of `io.EOF` error when reading files since currently `io.ReadAll` doesn't check for wrapped errors ([#6600](https://github.com/pocketbase/pocketbase/issues/6600)). - - -## v0.26.0 - -- ⚠️ Replaced `aws-sdk-go-v2` and `gocloud.dev/blob` with custom lighter implementation ([#6562](https://github.com/pocketbase/pocketbase/discussions/6562)). - As a side-effect of the dependency removal, the binary size has been reduced with ~10MB and builds ~30% faster. - _Although the change is expected to be backward-compatible, I'd recommend to test first locally the new version with your S3 provider (if you use S3 for files storage and backups)._ - -- ⚠️ Prioritized the user submitted non-empty `createData.email` (_it will be unverified_) when creating the PocketBase user during the first OAuth2 auth. - -- Load the request info context during password/OAuth2/OTP authentication ([#6402](https://github.com/pocketbase/pocketbase/issues/6402)). - This could be useful in case you want to target the auth method as part of the MFA and Auth API rules. - For example, to disable MFA for the OAuth2 auth could be expressed as `@request.context != "oauth2"` MFA rule. - -- Added `store.Store.SetFunc(key, func(old T) new T)` to set/update a store value with the return result of the callback in a concurrent safe manner. - -- Added `subscription.Message.WriteSSE(w, id)` for writing an SSE formatted message into the provided writer interface (_used mostly to assist with the unit testing_). - -- Added `$os.stat(file)` JSVM helper ([#6407](https://github.com/pocketbase/pocketbase/discussions/6407)). - -- Added log warning for `async` marked JSVM handlers and resolve when possible the returned `Promise` as fallback ([#6476](https://github.com/pocketbase/pocketbase/issues/6476)). - -- Allowed calling `cronAdd`, `cronRemove` from inside other JSVM handlers ([#6481](https://github.com/pocketbase/pocketbase/discussions/6481)). - -- Bumped the default request read and write timeouts to 5mins (_old 3mins_) to accommodate slower internet connections and larger file uploads/downloads. - _If you want to change them you can modify the `OnServe` hook's `ServeEvent.ReadTimeout/WriteTimeout` fields as shown in [#6550](https://github.com/pocketbase/pocketbase/discussions/6550#discussioncomment-12364515)._ - -- Normalized the `@request.auth.*` and `@request.body.*` back relations resolver to always return `null` when the relation field is pointing to a different collection ([#6590](https://github.com/pocketbase/pocketbase/discussions/6590#discussioncomment-12496581)). - -- Other minor improvements (_fixed query dev log nested parameters output, reintroduced `DynamicModel` object/array props reflect types caching, updated Go and npm deps, etc._) - - -## v0.25.9 - -- Fixed `DynamicModel` object/array props reflect type caching ([#6563](https://github.com/pocketbase/pocketbase/discussions/6563)). - - -## v0.25.8 - -- Added a default leeway of 5 minutes for the Apple/OIDC `id_token` timestamp claims check to account for clock-skew ([#6529](https://github.com/pocketbase/pocketbase/issues/6529)). - It can be further customized if needed with the `PB_ID_TOKEN_LEEWAY` env variable (_the value must be in seconds, e.g. "PB_ID_TOKEN_LEEWAY=60" for 1 minute_). - - -## v0.25.7 - -- Fixed `@request.body.jsonObjOrArr.*` values extraction ([#6493](https://github.com/pocketbase/pocketbase/discussions/6493)). - - -## v0.25.6 - -- Restore the missing `meta.isNew` field of the OAuth2 success response ([#6490](https://github.com/pocketbase/pocketbase/issues/6490)). - -- Updated npm dependencies. - - -## v0.25.5 - -- Set the current working directory as a default goja script path when executing inline JS strings to allow `require(m)` traversing parent `node_modules` directories. - -- Updated `modernc.org/sqlite` and `modernc.org/libc` dependencies. - - -## v0.25.4 - -- Downgraded `aws-sdk-go-v2` to the version before the default data integrity checks because there have been reports for non-AWS S3 providers in addition to Backblaze (IDrive, R2) that no longer or partially work with the latest AWS SDK changes. - - While we try to enforce `when_required` by default, it is not enough to disable the new AWS SDK integrity checks entirely and some providers will require additional manual adjustments to make them compatible with the latest AWS SDK (e.g. removing the `x-aws-checksum-*` headers, unsetting the checksums calculation or reinstantiating the old MD5 checksums for some of the required operations, etc.) which as a result leads to a configuration mess that I'm not sure it would be a good idea to introduce. - - This unfornuatelly is not a PocketBase or Go specific issue and the official AWS SDKs for other languages are in the same situation (even the latest aws-cli). - - For those of you that extend PocketBase with Go: if your S3 vendor doesn't support the [AWS Data integrity checks](https://docs.aws.amazon.com/sdkref/latest/guide/feature-dataintegrity.html) and you are updating with `go get -u`, then make sure that the `aws-sdk-go-v2` dependencies in your `go.mod` are the same as in the repo: - ``` - // go.mod - github.com/aws/aws-sdk-go-v2 v1.36.1 - github.com/aws/aws-sdk-go-v2/config v1.28.10 - github.com/aws/aws-sdk-go-v2/credentials v1.17.51 - github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.48 - github.com/aws/aws-sdk-go-v2/service/s3 v1.72.2 - - // after that run - go clean -modcache && go mod tidy - ``` - _The versions pinning is temporary until the non-AWS S3 vendors patch their implementation or until I manage to find time to remove/replace the `aws-sdk-go-v2` dependency (I'll consider prioritizing it for the v0.26 or v0.27 release)._ - - -## v0.25.3 - -- Added a temporary exception for Backblaze S3 endpoints to exclude the new `aws-sdk-go-v2` checksum headers ([#6440](https://github.com/pocketbase/pocketbase/discussions/6440)). - - -## v0.25.2 - -- Fixed realtime delete event not being fired for `RecordProxy`-ies and added basic realtime record resolve automated tests ([#6433](https://github.com/pocketbase/pocketbase/issues/6433)). - - -## v0.25.1 - -- Fixed the batch API Preview success sample response. - -- Bumped GitHub action min Go version to 1.23.6 as it comes with a [minor security fix](https://github.com/golang/go/issues?q=milestone%3AGo1.23.6+label%3ACherryPickApproved) for the ppc64le build. - - -## v0.25.0 - -- ⚠️ Upgraded Google OAuth2 auth, token and userinfo endpoints to their latest versions. - _For users that don't do anything custom with the Google OAuth2 data or the OAuth2 auth URL, this should be a non-breaking change. The exceptions that I could find are:_ - - `/v3/userinfo` auth response changes: - ``` - meta.rawUser.id => meta.rawUser.sub - meta.rawUser.verified_email => meta.rawUser.email_verified - ``` - - `/v2/auth` query parameters changes: - If you are specifying custom `approval_prompt=force` query parameter for the OAuth2 auth URL, you'll have to replace it with **`prompt=consent`**. - -- Added Trakt OAuth2 provider ([#6338](https://github.com/pocketbase/pocketbase/pull/6338); thanks @aidan-) - -- Added support for case-insensitive password auth based on the related UNIQUE index field collation ([#6337](https://github.com/pocketbase/pocketbase/discussions/6337)). - -- Enforced `when_required` for the new AWS SDK request and response checksum validations to allow other non-AWS vendors to catch up with new AWS SDK changes (see [#6313](https://github.com/pocketbase/pocketbase/discussions/6313) and [aws/aws-sdk-go-v2#2960](https://github.com/aws/aws-sdk-go-v2/discussions/2960)). - _You can set the environment variables `AWS_REQUEST_CHECKSUM_CALCULATION` and `AWS_RESPONSE_CHECKSUM_VALIDATION` to `when_supported` if your S3 vendor supports the [new default integrity protections](https://docs.aws.amazon.com/sdkref/latest/guide/feature-dataintegrity.html)._ - -- Soft-deprecated `Record.GetUploadedFiles` in favor of `Record.GetUnsavedFiles` to minimize the ambiguities what the method do ([#6269](https://github.com/pocketbase/pocketbase/discussions/6269)). - -- Replaced archived `github.com/AlecAivazis/survey` dependency with a simpler `osutils.YesNoPrompt(message, fallback)` helper. - -- Upgraded to `golang-jwt/jwt/v5`. - -- Added JSVM `new Timezone(name)` binding for constructing `time.Location` value ([#6219](https://github.com/pocketbase/pocketbase/discussions/6219)). - -- Added `inflector.Camelize(str)` and `inflector.Singularize(str)` helper methods. - -- Use the non-transactional app instance during the realtime records delete access checks to ensure that cascade deleted records with API rules relying on the parent will be resolved. - -- Other minor improvements (_replaced all `bool` exists db scans with `int` for broader drivers compatibility, updated API Preview sample error responses, updated UI dependencies, etc._) - - -## v0.24.4 - -- Fixed fields extraction for view query with nested comments ([#6309](https://github.com/pocketbase/pocketbase/discussions/6309)). - -- Bumped GitHub action min Go version to 1.23.5 as it comes with some [minor security fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.23.5). - - -## v0.24.3 - -- Fixed incorrectly reported unique validator error for fields starting with name of another field ([#6281](https://github.com/pocketbase/pocketbase/pull/6281); thanks @svobol13). - -- Reload the created/edited records data in the RecordsPicker UI. - -- Updated Go dependencies. - - -## v0.24.2 - -- Fixed display fields extraction when there are multiple "Presentable" `relation` fields in a single related collection ([#6229](https://github.com/pocketbase/pocketbase/issues/6229)). - - -## v0.24.1 - -- Added missing time macros in the UI autocomplete. - -- Fixed JSVM types for structs and functions with multiple generic parameters. - - -## v0.24.0 - -- ⚠️ Removed the "dry submit" when executing the collections Create API rule - (you can find more details why this change was introduced and how it could affect your app in https://github.com/pocketbase/pocketbase/discussions/6073). - For most users it should be non-breaking change, BUT if you have Create API rules that uses self-references or view counters you may have to adjust them manually. - With this change the "multi-match" operators are also normalized in case the targeted collection doesn't have any records - (_or in other words, `@collection.example.someField != "test"` will result to `true` if `example` collection has no records because it satisfies the condition that all available "example" records mustn't have `someField` equal to "test"_). - As a side-effect of all of the above minor changes, the record create API performance has been also improved ~4x times in high concurrent scenarios (500 concurrent clients inserting total of 50k records - [old (58.409064001s)](https://github.com/pocketbase/benchmarks/blob/54140be5fb0102f90034e1370c7f168fbcf0ddf0/results/hetzner_cax41_cgo.md#creating-50000-posts100k-reqs50000-conc500-rulerequestauthid----requestdatapublicisset--true) vs [new (13.580098262s)](https://github.com/pocketbase/benchmarks/blob/7df0466ac9bd62fe0a1056270d20ef82012f0234/results/hetzner_cax41_cgo.md#creating-50000-posts100k-reqs50000-conc500-rulerequestauthid----requestbodypublicisset--true)). - -- ⚠️ Changed the type definition of `store.Store[T any]` to `store.Store[K comparable, T any]` to allow support for custom store key types. - For most users it should be non-breaking change, BUT if you are calling `store.New[any](nil)` instances you'll have to specify the store key type, aka. `store.New[string, any](nil)`. - -- Added `@yesterday` and `@tomorrow` datetime filter macros. - -- Added `:lower` filter modifier (e.g. `title:lower = "lorem"`). - -- Added `mailer.Message.InlineAttachments` field for attaching inline files to an email (_aka. `cid` links_). - -- Added cache for the JSVM `arrayOf(m)`, `DynamicModel`, etc. dynamic `reflect` created types. - -- Added auth collection select for the settings "Send test email" popup ([#6166](https://github.com/pocketbase/pocketbase/issues/6166)). - -- Added `record.SetRandomPassword()` to simplify random password generation usually used in the OAuth2 or OTP record creation flows. - _The generated ~30 chars random password is assigned directly as bcrypt hash and ignores the `password` field plain value validators like min/max length or regex pattern._ - -- Added option to list and trigger the registered app level cron jobs via the Web API and UI. - -- Added extra validators for the collection field `int64` options (e.g. `FileField.MaxSize`) restricting them to the max safe JSON number (2^53-1). - -- Added option to unset/overwrite the default PocketBase superuser installer using `ServeEvent.InstallerFunc`. - -- Added `app.FindCachedCollectionReferences(collection, excludeIds)` to speedup records cascade delete almost twice for projects with many collections. - -- Added `tests.NewTestAppWithConfig(config)` helper if you need more control over the test configurations like `IsDev`, the number of allowed connections, etc. - -- Invalidate all record tokens when the auth record email is changed programmatically or by a superuser ([#5964](https://github.com/pocketbase/pocketbase/issues/5964)). - -- Eagerly interrupt waiting for the email alert send in case it takes longer than 15s. - -- Normalized the hidden fields filter checks and allow targeting hidden fields in the List API rule. - -- Fixed "Unique identify fields" input not refreshing on unique indexes change ([#6184](https://github.com/pocketbase/pocketbase/issues/6184)). - - -## v0.23.12 - -- Added warning logs in case of mismatched `modernc.org/sqlite` and `modernc.org/libc` versions ([#6136](https://github.com/pocketbase/pocketbase/issues/6136#issuecomment-2556336962)). - -- Skipped the default body size limit middleware for the backup upload endpoint ([#6152](https://github.com/pocketbase/pocketbase/issues/6152)). - - -## v0.23.11 - -- Upgraded `golang.org/x/net` to 0.33.0 to fix [CVE-2024-45338](https://www.cve.org/CVERecord?id=CVE-2024-45338). - _PocketBase uses the vulnerable functions primarily for the auto html->text mail generation, but most applications shouldn't be affected unless you are manually embedding unrestricted user provided value in your mail templates._ - - -## v0.23.10 - -- Renew the superuser file token cache when clicking on the thumb preview or download link ([#6137](https://github.com/pocketbase/pocketbase/discussions/6137)). - -- Upgraded `modernc.org/sqlite` to 1.34.3 to fix "disk io" error on arm64 systems. - _If you are extending PocketBase with Go and upgrading with `go get -u` make sure to manually set in your go.mod the `modernc.org/libc` indirect dependency to v1.55.3, aka. the exact same version the driver is using._ - - -## v0.23.9 - -- Replaced `strconv.Itoa` with `strconv.FormatInt` to avoid the int64->int conversion overflow on 32-bit platforms ([#6132](https://github.com/pocketbase/pocketbase/discussions/6132)). - - -## v0.23.8 - -- Fixed Model->Record and Model->Collection hook events sync for nested and/or inner-hook transactions ([#6122](https://github.com/pocketbase/pocketbase/discussions/6122)). - -- Other minor improvements (updated Go and npm deps, added extra escaping for the default mail record params in case the emails are stored as html files, fixed code comment typos, etc.). - - -## v0.23.7 - -- Fixed JSVM exception -> Go error unwrapping when throwing errors from non-request hooks ([#6102](https://github.com/pocketbase/pocketbase/discussions/6102)). - - -## v0.23.6 - -- Fixed `$filesystem.fileFromURL` documentation and generated type ([#6058](https://github.com/pocketbase/pocketbase/issues/6058)). - -- Fixed `X-Forwarded-For` header typo in the suggested UI "Common trusted proxy" headers ([#6063](https://github.com/pocketbase/pocketbase/pull/6063)). - -- Updated the `text` field max length validator error message to make it more clear ([#6066](https://github.com/pocketbase/pocketbase/issues/6066)). - -- Other minor fixes (updated Go deps, skipped unnecessary validator check when the default primary key pattern is used, updated JSVM types, etc.). - - -## v0.23.5 - -- Fixed UI logs search not properly accounting for the "Include requests by superusers" toggle when multiple search expressions are used. - -- Fixed `text` field max validation error message ([#6053](https://github.com/pocketbase/pocketbase/issues/6053)). - -- Other minor fixes (comment typos, JSVM types update). - -- Updated Go deps and the min Go release GitHub action version to 1.23.4. - - -## v0.23.4 - -- Fixed `autodate` fields not refreshing when calling `Save` multiple times on the same `Record` instance ([#6000](https://github.com/pocketbase/pocketbase/issues/6000)). - -- Added more descriptive test OTP id and failure log message ([#5982](https://github.com/pocketbase/pocketbase/discussions/5982)). - -- Moved the default UI CSP from meta tag to response header ([#5995](https://github.com/pocketbase/pocketbase/discussions/5995)). - -- Updated Go and npm dependencies. - - -## v0.23.3 - -- Fixed Gzip middleware not applying when serving static files. - -- Fixed `Record.Fresh()`/`Record.Clone()` methods not properly cloning `autodate` fields ([#5973](https://github.com/pocketbase/pocketbase/discussions/5973)). - - -## v0.23.2 - -- Fixed `RecordQuery()` custom struct scanning ([#5958](https://github.com/pocketbase/pocketbase/discussions/5958)). - -- Fixed `--dev` log query print formatting. - -- Added support for passing more than one id in the `Hook.Unbind` method for consistency with the router. - -- Added collection rules change list in the confirmation popup - (_to avoid getting annoying during development, the rules confirmation currently is enabled only when using https_). - - -## v0.23.1 - -- Added `RequestEvent.Blob(status, contentType, bytes)` response write helper ([#5940](https://github.com/pocketbase/pocketbase/discussions/5940)). - -- Added more descriptive error messages. - - -## v0.23.0 - -> [!NOTE] -> You don't have to upgrade to PocketBase v0.23.0 if you are not planning further developing -> your existing app and/or are satisfied with the v0.22.x features set. There are no identified critical issues -> with PocketBase v0.22.x yet and in the case of critical bugs and security vulnerabilities, the fixes -> will be backported for at least until Q1 of 2025 (_if not longer_). -> -> **If you don't plan upgrading make sure to pin the SDKs version to their latest PocketBase v0.22.x compatible:** -> - JS SDK: `<0.22.0` -> - Dart SDK: `<0.19.0` - -> [!CAUTION] -> This release introduces many Go/JSVM and Web APIs breaking changes! -> -> Existing `pb_data` will be automatically upgraded with the start of the new executable, -> but custom Go or JSVM (`pb_hooks`, `pb_migrations`) and JS/Dart SDK code will have to be migrated manually. -> Please refer to the below upgrade guides: -> - Go: https://pocketbase.io/v023upgrade/go/. -> - JSVM: https://pocketbase.io/v023upgrade/jsvm/. -> -> If you had already switched to some of the earlier ` - Go: https://pocketbase.io/v023upgrade/go/. -> - JSVM: https://pocketbase.io/v023upgrade/jsvm/. - -#### SDKs changes - -- [JS SDK v0.22.0](https://github.com/pocketbase/js-sdk/blob/master/CHANGELOG.md) -- [Dart SDK v0.19.0](https://github.com/pocketbase/dart-sdk/blob/master/CHANGELOG.md) - -#### Web APIs changes - -- New `POST /api/batch` endpoint. - -- New `GET /api/collections/meta/scaffolds` endpoint. - -- New `DELETE /api/collections/{collection}/truncate` endpoint. - -- New `POST /api/collections/{collection}/request-otp` endpoint. - -- New `POST /api/collections/{collection}/auth-with-otp` endpoint. - -- New `POST /api/collections/{collection}/impersonate/{id}` endpoint. - -- ⚠️ If you are constructing requests to `/api/*` routes manually remove the trailing slash (_there is no longer trailing slash removal middleware registered by default_). - -- ⚠️ Removed `/api/admins/*` endpoints because admins are converted to `_superusers` auth collection records. - -- ⚠️ Previously when uploading new files to a multiple `file` field, new files were automatically appended to the existing field values. - This behaviour has changed with v0.23+ and for consistency with the other multi-valued fields when uploading new files they will replace the old ones. If you want to prepend or append new files to an existing multiple `file` field value you can use the `+` prefix or suffix: - ```js - "documents": [file1, file2] // => [file1_name, file2_name] - "+documents": [file1, file2] // => [file1_name, file2_name, old1_name, old2_name] - "documents+": [file1, file2] // => [old1_name, old2_name, file1_name, file2_name] - ``` - -- ⚠️ Removed `GET /records/{id}/external-auths` and `DELETE /records/{id}/external-auths/{provider}` endpoints because this is now handled by sending list and delete requests to the `_externalAuths` collection. - -- ⚠️ Changes to the app settings model fields and response (+new options such as `trustedProxy`, `rateLimits`, `batch`, etc.). The app settings Web APIs are mostly used by the Dashboard UI and rarely by the end users, but if you want to check all settings changes please refer to the [Settings Go struct](https://github.com/pocketbase/pocketbase/blob/develop/core/settings_model.go#L121). - -- ⚠️ New flatten Collection model and fields structure. The Collection model Web APIs are mostly used by the Dashboard UI and rarely by the end users, but if you want to check all changes please refer to the [Collection Go struct](https://github.com/pocketbase/pocketbase/blob/develop/core/collection_model.go#L308). - -- ⚠️ The top level error response `code` key was renamed to `status` for consistency with the Go APIs. - The error field key remains `code`: - ```js - { - "status": 400, // <-- old: "code" - "message": "Failed to create record.", - "data": { - "title": { - "code": "validation_required", - "message": "Missing required value." - } - } - } - ``` - -- ⚠️ New fields in the `GET /api/collections/{collection}/auth-methods` response. - _The old `authProviders`, `usernamePassword`, `emailPassword` fields are still returned in the response but are considered deprecated and will be removed in the future._ - ```js - { - "mfa": { - "duration": 100, - "enabled": true - }, - "otp": { - "duration": 0, - "enabled": false - }, - "password": { - "enabled": true, - "identityFields": ["email", "username"] - }, - "oauth2": { - "enabled": true, - "providers": [{"name": "gitlab", ...}, {"name": "google", ...}] - }, - // old fields... - } - ``` - -- ⚠️ Soft-deprecated the OAuth2 auth success `meta.avatarUrl` field in favour of `meta.avatarURL`. +- Bumped the min Go version to 1.27.0 and migrated to the new `encoding/json/v2` package. + _⚠️ Please note that Go 1.27.0 retrofitted `encoding/json` to use the v2 package under the hood but unfortunately is not fully backward compatible._ + _I recommend to not push blindly an update on production and to test your PocketBase application first locally to see if everything works correctly._ diff --git a/CHANGELOG_8_15.md b/CHANGELOG_08_15.md similarity index 100% rename from CHANGELOG_8_15.md rename to CHANGELOG_08_15.md diff --git a/CHANGELOG_16_22.md b/CHANGELOG_16_22.md index 420ddc1e77..bfe0c17fd1 100644 --- a/CHANGELOG_16_22.md +++ b/CHANGELOG_16_22.md @@ -2,6 +2,11 @@ > For the most recent versions, please refer to [CHANGELOG.md](./CHANGELOG.md) --- +## v0.22.53 + +- Workaround `encoding/json` infinite recursion when used with Go 1.27 ([#7808](https://github.com/pocketbase/pocketbase/issues/7808)). + + ## v0.22.52 - (_Backported from v0.39.11_) Bumped `golang.org/x/*` deps and the min Go GitHub action version to 1.26.6 because it comes with some [minor bug and security fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.26.6). diff --git a/CHANGELOG_23_39.md b/CHANGELOG_23_39.md new file mode 100644 index 0000000000..98f9809972 --- /dev/null +++ b/CHANGELOG_23_39.md @@ -0,0 +1,1243 @@ +> List with changes from v0.23.x to v0.39.x. +> For the most recent versions, please refer to [CHANGELOG.md](./CHANGELOG.md) +--- + +## v0.39.11 + +- Fixed "API preview" examples ([#7782](https://github.com/pocketbase/pocketbase/issues/7782), [#7785](https://github.com/pocketbase/pocketbase/issues/7785)). + +- Other minor UI improvements (fixed sortable `dragend` event handling, allow ESC to workaround TAB trap for the rule fields, allow new duplicated collection to edit the collection of relation fields, updated shablon, updated npm dev dependencies, etc.). + +- Bumped `golang.org/x/*` deps and the min Go GitHub action version to 1.26.6 because it comes with some [minor bug and security fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.26.6). + + +## v0.39.10 + +- Reverted the auto panic recover handling for the cli commands to preserve the old behavior and allow panic to force exit with non-zero code ([#7781](https://github.com/pocketbase/pocketbase/issues/7781)). + _Proper command non-zero exit support will be available with the next v0.40/v0.41 release._ + +- Minor UI improvements (added placeholder loader for the logs chart, npm dev deps update, etc.). + +- Updated `modernc.org/sqlite` to v1.55.0 (doc changes). + + +## v0.39.9 + +- Fixed `Shift + Click` range bulk selection not working in Firefox ([#7771](https://github.com/pocketbase/pocketbase/issues/7771)) + +- Updated goja and its related dependencies _(fixes for TypedArray and regexp2 dep regression for the reported empty string match with lookahead patterns)_. + +- Minor filter (fexpr) improvements _(optimization for large string literals and fix for control characters handling)_. + + +## v0.39.8 + +- Properly reset JSVM global `$app` overwrite so that pooled executors always get a clean state. + +- Minor UI improvements: + - prevent resetting number inputs with leading 0 while still typing (normalized in `onchange`) + - added support for `Shift + Click` range bulk selection ([#7759](https://github.com/pocketbase/pocketbase/issues/7759)) + +- Bumped `golang.org/x/*` indirect dependencies as there are some minor security fixes. + +- Updated `modernc.org/sqlite` to v1.54.0 ([SQLite 3.53.3](https://sqlite.org/src/timeline?from=version-3.53.2&to=version-3.53.3&to2=branch-3.53)). + + +## v0.39.7 + +- Replaced `github.com/go-ozzo/ozzo-validation` with the fork `github.com/pocketbase/ozzo-validation` since the original library has recently changed ownership and the new maintainer cannot be trusted. + _There are plans to create eventually a new validation library from scratch more suited for our needs in PocketBase because ozzo-validation is known to have some minor performance and obscure regex issues, but until then we'll stick with the fork (and if you use `ozzo-validation` in your own Go code, I'd suggest to swap the imports with the fork)_. + +- Fixed missing import collection `fields` property access ([#7760](https://github.com/pocketbase/pocketbase/issues/7760)). + +- Fixed View collection `*` validator and added more friendly error messages ([#7761](https://github.com/pocketbase/pocketbase/issues/7761)). + +- ⚠️ Security fix for unhandled panic in internal worker goroutines ([#7762](https://github.com/pocketbase/pocketbase/discussions/7762)). + _To prevent this from showing again, all existing internal worker functions were wrapped with [`routine.SafeWrap(f)`](https://pkg.go.dev/github.com/pocketbase/pocketbase/tools/routine#SafeWrap) (auto recovers and returns any eventual panic as regular error)._ + + +## v0.39.6 + +- Added `Cc` and `Bcc` recipients to the dev `sendmail` command for consistency with the SMTP mailer. + +- Added extra hardening options to the Microsoft OAuth2 provider allowing developers to specify the preferred safe email extraction method. + +- Updated goja and the related `golang.org/x/*` dependencies _(`WeakMap` regression fixes)_. + +- Bumped the min Go GitHub action version to 1.26.5 as it includes some [minor security fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.26.5). + + +## v0.39.5 + +- Limit with ellipsis long `url` field values. + +- Readded the "fullscreen" `editor` field option and preloaded the TinyMCE component for slightly faster initial rendering ([#7746](https://github.com/pocketbase/pocketbase/issues/7746)). + +- Updated goja (`TypedArray` fixes). + + +## v0.39.4 + +- Removed `redirectURL` required validator from the code->token exchange endpoint (aka. `authWithOAuth2Code()`) ([#7734](https://github.com/pocketbase/pocketbase/issues/7734)). + _Note that OAuth2 providers have their own validations and whether it is allowed to be empty or not could depend on the configured OAuth2 app (in most cases it is required and the redirect address must match with the initial value submitted with the authorization request)._ + +- Enabled sorting by the first _implicit_ presentable relation field ([#7735](https://github.com/pocketbase/pocketbase/discussions/7735)). + +- Other minor UI fixes (tooltip clear on hovered element removal, optional before element sortable fix, etc.). + +- Updated goja and the related `golang.org/x/*` dependencies (regex support improvements). + + +## v0.39.3 + +- Fixed JS error on `file` settings `maxSelect` change ([#7731](https://github.com/pocketbase/pocketbase/issues/7731)). + +- Apply the `Ctrl+S` record panel save shortcut only if it is the current top open modal. + +- Fixed `number` settings validator to not ignore 0 `max` value. + +- Normalized field settings validation error messages and tooltips. + + +## v0.39.2 + +- Fixed records list UI sorting ([#7724](https://github.com/pocketbase/pocketbase/issues/7724)). + +- Don't clear the date input on invalid value while still typing ([#7726](https://github.com/pocketbase/pocketbase/issues/7726)). + +- Return `filepath.SkipDir` in the `pb_hooks` dirs watcher to avoid unnecessary iterating over `node_modules` and `.*` prefixed hidden dirs (`.DS_Store`, `.git`, etc.). + +- Show the "Affected rows" SQL console message only if non-empty to avoid ambiguity with drivers that don't support returning the affected rows count. + +- Updated `modernc.org/sqlite` to v1.52.0 ([SQLite 3.53.2](https://sqlite.org/src/timeline?from=version-3.53.0&to=version-3.53.2&to2=branch-3.53&y=ci)). + + +## v0.39.1 + +- Fixed multiple select options wrapping ([#7720](https://github.com/pocketbase/pocketbase/issues/7720)). + +- Return the hidden record data fields for superusers realtime subscribers ([#7721](https://github.com/pocketbase/pocketbase/issues/7721)). + +- Added default panic-recover handling for the cron jobs to avoid terminating the server on panic. + +- Bumped the min Go GitHub action version to 1.26.4 as it includes some [minor security fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.26.4). + + +## v0.39.0 + +- Added new "SQL console" section under _Settings > Debug_ allowing executing any raw SQL query from the UI ([#2236](https://github.com/pocketbase/pocketbase/issues/2236); [#7638](https://github.com/pocketbase/pocketbase/discussions/7638)). + _Note that this is intended for one-off analytic queries, the occasional `VACUUM`/`PRAGMA optimize` or debug purposes and not as the primary interface for interacting with your PocketBase data because it can break your application if not used with proper care!_ + +- Send system email alerts to superusers in case of an error with the automated backups ([#7698](https://github.com/pocketbase/pocketbase/issues/7698)). + +- Various minor improvements and fixes: + - fixed logs bulk selection export error + - optimized logs and records list rendering + - allowed word breaking in labels + - text contrast improvements + - registered missing `oidc2` and `oidc3` option fields + - updated default email template texts for consistency + - updated `modernc.org/sqlite` to v1.51.0 + - etc. + + +## v0.38.2 + +- Added `RealtimeConnectRequestEvent.MaxTimeout` field to specify the absolute max duration a realtime connection can remain open (default to 30mins). + _This is in addition to the `IdeTimeout` of 5mins in order to prevent misuse and to allow the GC to run more regularly._ + +- Added extra checks for the connected user IP in the realtime APIs to prevent bruteforce guest subscription update attempts and to serve as an extra protection for the "all-in-one" OAuth2 realtime handler. + +- Don't reset the records list pagination on record update ([#7694](https://github.com/pocketbase/pocketbase/issues/7694)). + +- Updated all `golang.org/x/` packages to cover the recent [security fixes](https://groups.google.com/g/golang-announce/c/PdiGK3xulk4) _(none of them should be a critical issue in PocketBase but nonetheless it is advised to update)_. + + +## v0.38.1 + +- Silenced the superuser IPs confirmation if there is no change. + +- Updated the _experimental_ UI extensions APIs to allow top-level `await` in the initialization script. + +- Force unset the auth state of existing realtime connections on user password, collection secret, etc. changes. + _This is not strictly necessary because the realtime connections have short-lived idle timeout by design but nonetheless it was implemented to minimize the attack vectors._ + +- Added error marker for each collection tab and fixed the styles of the raw errors tooltip. + +- Fixed indexes collection update error ([#7689](https://github.com/pocketbase/pocketbase/issues/7689)). + _⚠️ The fix comes with a system migration that resaves all collections with indexes to ensure that all indexes are normalized and available in the `Collection.Indexes` field (it will also include indexes created manually via the sqlite3 cli or other external tool)._ + _If you are using a test `pb_data` for your Go automation tests you may want to apply the migration to it too so that it runs only once and not for each execution of your tests, aka. you could run once `go run main.go migrate up --dir="/path/to/test_pb_data"`._ + +- Updated `modernc.org/sqlite` to v1.50.1 (SQLite 3.53.1). + +- Other minor fixes (_updated API preview examples, fixed code comment typos, etc._). + + +## v0.38.0 + +- Fixed UI logs pagination when no custom range is specified. + +- Fixed default CSP not allowing audio/video previews ([#7677](https://github.com/pocketbase/pocketbase/issues/7677)). + +- Serve fixed `Content-Type` for `.xlsx`, `.docx` and `.pptx` files to allow previews on iOS ([#7467](https://github.com/pocketbase/pocketbase/discussions/7467)). + +- Changed settings app URL input to `type="text"` for compatibility with earlier versions ([#7681](https://github.com/pocketbase/pocketbase/issues/7681)). + +- Added an internal watcher to sync various runtime states between multiple PocketBase processes (e.g. memory store) using the same `pb_data`. + _This is helpful in case for example a separate PocketBase console command change the collections or application settings while the server is still running._ + _The watcher is debounced and implemented by watching the special `pb_data/.notify` dir as a workaround to avoid depending on OS and SQLite driver specific APIs._ + +- Added new [Superuser IPs/CIDR subnets whitelist setting](https://pocketbase.io/docs/going-to-production/#limit-superusers-to-specific-ipssubnets). + The optional setting can be changed from the UI under _Dasboard > Settings > Application > Superuser IPs_. + To avoid lockout in case your superuser IP change, the ips whitelist can be updated also via the `superuser ips` console command: + ```sh + # note: --dir is optional and defaults to pb_data next to the executable + + # clear whitelisted IPs + ./pocketbase superuser ips --dir=/custom/path/to/pb_data + + # OR change the whitelisted IPs to 127.0.0.1 and 10.0.0.0 (replace with your real IP(s)) + ./pocketbase superuser ips 127.0.0.1 10.0.0.0 --dir=/custom/path/to/pb_data + ``` + +- Added rate limit option to exclude IPs/CIDR subnets ([#6410](https://github.com/pocketbase/pocketbase/issues/6410)). + +- Bumped min Go GitHub action version to 1.26.3 because it comes with some [minor bug and security fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.26.3). + + +## v0.37.5 + +- Fixed password fields not being detected as changed ([#7670](https://github.com/pocketbase/pocketbase/issues/7670)). + +- Added the local time zone name next to the `date` field label. + +- Reload trusted proxy info UI after settings save. + +- Other minor improvements (skips the duplicated record ids from the `IN` expand list, reordered confirm-email-change error checks to minimize enumeration attacks, etc.). + + +## v0.37.4 + +- Added backups list scroll container ([#7655](https://github.com/pocketbase/pocketbase/issues/7655)). + +- Optimized record upsert and preview modals data loading to minimize layout jumps. + +- Fixed SMTP IPv6 network address format ([#7659](https://github.com/pocketbase/pocketbase/issues/7659)). + +- Fixed autocomplete selection not properly updating the underlying input value ([#7664](https://github.com/pocketbase/pocketbase/issues/7664)). + +- Added `ghupdate.BaseURL` config option ([#7665](https://github.com/pocketbase/pocketbase/issues/7665)). + +- Added dummy bcrypt password check for the failure auth path to minimize enumeration timing attacks when registrations are disabled. + +- Adjusted Bitbucket, GitHub, GitLab and Gitea/Forgejo OAuth2 providers to better reflect recent API updates and doc references. + _In case the userinfo data is not sufficient, some of the providers now send a separate list emails request in order to minimize eventual linking security issues caused by custom onpremise setups (e.g. Gitea/Forgejo allows skipping the email verification if an ENV variable is configured)._ + +- ⚠️ Fixed a pre-hijacking OAuth2 linking vulnerability ([#7662](https://github.com/pocketbase/pocketbase/discussions/7662); thanks @Alardiians for reporting it privately). + +- Bumped Go and npm dependencies. + + +## v0.37.3 + +- Fixed total count load on page back/forward navigation. + +- Fixed `editor` floating dialogs position when scrolling ([#7653](https://github.com/pocketbase/pocketbase/issues/7653)). + +- Enabled text wrapping for the API rule fields. + +- Added view query sample loading indicator. + +- Other minor light UI contrast and styles improvements. + + +## v0.37.2 + +- Fixed autoexpandable input in Firefox ([#7648](https://github.com/pocketbase/pocketbase/discussions/7648)). + +- Slightly adjusted the dark theme colors for better readability ([#7648](https://github.com/pocketbase/pocketbase/discussions/7648)). + +- Removed unnecessary tags stripping from the displayed log attributes ([#7649](https://github.com/pocketbase/pocketbase/issues/7649)). + +- Workarounded Safari freeze caused by a buggy CSS popover property ([#7650](https://github.com/pocketbase/pocketbase/issues/7650)). + + +## v0.37.1 + +- Minor UI bugfixes: + - Fixed `number` field input values normalization ([#7646](https://github.com/pocketbase/pocketbase/issues/7646)). + - Allow opening collections in new tab with middle click. + - Show collection name in the page title on initial load. + + +## v0.37.0 + +- New UI rewritten from scratch and with support for external customization in mind. + > Note that as explained in [#7612](https://github.com/pocketbase/pocketbase/discussions/7612) the new UI kit and extensions APIs will intentionally remain undocumented until "Stage 2 completion" _(there no ETAs)_. + + The new UI also introduced several other small improvements: + - ~2MB smaller bundle size. + - Dark mode and theming support. + - Basic responsive/mobile support _(it is far from perfect but certainly more usable than before)_. + - Help text option for the collection fields. + - Lifted the max nested level restriction of presentable relations _(children are lazy loaded)_. + - Lighter rules autocomplete. + - Live view query preview. + - Insert of an audio/video embed tag in the richtext editor from a collection file. + - Option to bulk export records as JSON. + - Local search history for all searchbars. + - API rules overview across all collections. + - Very basic ERD-like visualization for the collections structure and relations. + - New stepped logs chart visualization with panning support. + - `listAuthMethods()` (aka. `/api/collection/{col}/auth-methods`) now returns the OAuth2 provider logo for each provider as inlined SVG string in its response data. + _⚠️ Note that if your app for whatever reason rely on the dashboard OAuth2 logos available under `/_/images/oauth2/*` they are still available for now but will be removed in future versions and it is recommended to use the new inline SVGs!_ + +- Added optional `no_ui` build tag to exclude the UI from bundling with the executable ([#7548](https://github.com/pocketbase/pocketbase/issues/7548)). + ```sh + go build -tags no_ui + ``` + +- Exported the internal JSVM bind functions ([#7600](https://github.com/pocketbase/pocketbase/discussions/7600)). + ```go + jsvm.BindCore(vm) + jsvm.BindDbx(vm) + jsvm.BindSecurity(vm) + jsvm.BindOS(vm) + jsvm.BindFilepath(vm) + jsvm.BindHTTP(vm) + jsvm.BindFilesystem(vm) + jsvm.BindForms(vm) + jsvm.BindMails(vm) + jsvm.BindApis(vm) + ``` + +- Updated `modernc.org/sqlite` to v1.49.1 (SQLite 3.53.0). + + +## v0.36.9 + +- Updated the Discord `AuthUser.Name` field to use `global_name` ([#7603](https://github.com/pocketbase/pocketbase/pull/7603); thanks @HansHans135). + +- Fixed settings SMTP password clear persistence. + +- Added extra OAuth2 checks when downloading the avatar URL to prevent internal network probing requests in case of a malicious/vulnerable vendor. + +- Updated `modernc.org/sqlite` to v1.48.2 _(vfs and other error path related fixes)_. + +- Updated min Go GitHub action version to 1.26.2 because it comes with some [minor security fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.26.2). + +- Other small improvements _(updated `$apis.static` JSVM documentation, fixed comment typos, added missing file close on seek error, etc.)_. + + +## v0.36.8 + +- Fixed OAuth2 client secret reset when serializing a cached collection model. + +- Bumped all Go and npm deps. + _This should also silence recent spam reports and security scanners regarding `golang.org/x/image` [CVE-2026-33809](https://www.cve.org/CVERecord?id=CVE-2026-33809) (it is not an issue in PocketBase because we don't support TIFF thumbs)._ + + +## v0.36.7 + +- Fixed high memory usage with large file uploads ([#7572](https://github.com/pocketbase/pocketbase/discussions/7572)). + +- Updated the rate limiter reset rules to follow a more traditional fixed window strategy _(aka. to be more close to how it is presented in the UI - allow max X user requests under Ys)_ since several users complained that the older algorithm was not intuitive and not suitable for large intervals. + _Approximated sliding window strategy was also suggested as a better compromise option to help minimize traffic spikes right after reset but the additional tracking could introduce some overhead and for now it is left aside until we have more tests._ + +- Updated `modernc.org/sqlite` to v1.46.2 and SQLite 3.51.3. + _⚠️ SQLite 3.51.3 fixed a [database corruption bug](https://sqlite.org/wal.html#walresetbug) that is very unlikely to happen (with PocketBase even more so because we queue on app level all writes and explicit transactions through a single db connection), but still it is advised to upgrade._ + +- Updated other minor Go and npm deps. + _The min Go version in the go.mod of the package was also bumped to Go 1.25.0 because some of the newer dep versions require it._ + + +## v0.36.6 + +- Set `NumberField.OnlyInt:true` for the generated View collection schema fields when a view column expression is known to return int-only values ([#7538](https://github.com/pocketbase/pocketbase/issues/7538)). + +- Documented the `unmarshal` JSVM helper ([#7543](https://github.com/pocketbase/pocketbase/issues/7543)). + +- Added extra read check after the `Store.GetOrSet` write lock to prevent races overwriting an already existing value. + +- Added empty records check for the additional client-side filter's ListRule constraint that was introduced in v0.32.0 ([presentator#206](https://github.com/presentator/presentator/issues/206)). + +- Set a fixed `routine.FireAndForget()` debug stack trace limit to 2KB. + +- Bumped min Go GitHub action version to 1.26.1 because it comes with some [minor bug and security fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.26.1). + +- Typos and other minor doc fixes. + + +## v0.36.5 + +- Disabled collection and fields name normalization while in IME mode ([#7532](https://github.com/pocketbase/pocketbase/pull/7532); thanks @miaopan607). + +- Updated `modernc.org/sqlite` to v1.46.1 _(resets connection state on Tx.Commit failure)_. + + +## v0.36.4 + +- Made the optional `Bearer` token prefix case-insensitive ([#7525](https://github.com/pocketbase/pocketbase/pull/7525); thanks @benjamesfleming). + +- Enabled `$filesystem.s3(...)` and `$filesystem.local(...)` JSVM bindings ([#7526](https://github.com/pocketbase/pocketbase/issues/7526)). + + +## v0.36.3 + +- Added `Accept-Encoding: identity` to the S3 requests per the suggestion in [#7523](https://github.com/pocketbase/pocketbase/issues/7523). + _This should help fixing the 0-bytes file response when S3 API compression is enabled._ + +- Bumped min Go GitHub action version to 1.26.0 _(it comes with minor [GC performance improvements](https://go.dev/doc/go1.26#runtime))_. + +- Other minor fixes _(updated `modernc.org/sqlite` to v1.45.0, updated `goja_nodejs` adding `Buffer.concat`, updated the arguments of `app.DeleteTable(...)`, `app.DeleteView(...)` and other similar methods to make it more clear that they are dangerous and shouldn't be used with untrusted input, etc.)_. + + +## v0.36.2 + +- Updated `modernc.org/sqlite` to v1.44.3 _(race check fix)_, `goja` _(circular references fix)_ and other go deps. + +- Other minor fixes _(updated tests to silence some of the race detector errors, updated `FindFirstRecordByData` with more clear error message when missing or invalid key is used, etc.)_. + + +## v0.36.1 + +- Reverted the `DISTINCT` with `GROUP BY` replacement optimization from v0.36.0 as it was reported to negatively impact the indexes utilization for some queries +and the minor performance boost that you may get when used on large records is not enough to justify the more common use ([#7461](https://github.com/pocketbase/pocketbase/discussions/7461)). + _A better generic deduplication optimization for large records (aka. records with large `text`/`json` fields or many small ones) will be researched but there are no ETAs._ + +- Updated `modernc.org/sqlite` to v1.44.2 _(SQLite 3.51.2)_. + +- Fixed code comment typos. + + +## v0.36.0 + +- List query and API rules optimizations: + - Removed unnecessary correlated subquery expression when using back-relations via single `relation` field. + - Replaced `DISTINCT` with `GROUP BY id` when rows deduplication is needed and when deemed safe. + _This should help with having a more stable and predictable performance even if the collection records are on the larger side._ + + For some queries and data sets the above 2 optimizations have shown significant improvements but if you notice a performance degradation after upgrading, + please open a Q&A discussion with export of your collections structure and the problematic request so that it can be analyzed. + +- Added [`strftime(format, timevalue, modifiers...)`](https://pocketbase.io/docs/api-rules-and-filters/#strftimeformat-time-value-modifiers-) date formatting filter and API rules function. + It works similarly to the [SQLite `strftime` builtin function](https://sqlite.org/lang_datefunc.html) + with the main difference that NULL results will be normalized for consistency with the non-nullable PocketBase `text` and `date` fields. + Multi-match expressions are also supported and works the same as if the collection field is referenced, for example: + ```js + // requires ANY/AT-LEAST-ONE-OF multiRel records to have "created" date matching the formatted string "2026-01" + strftime('%Y-%m', multiRel.created) ?= '2026-01' + + // requires ALL multiRel records to have "created" date matching the formatted string "2026-01" + strftime('%Y-%m', multiRel.created) = '2026-01' + ``` + +- ⚠️ Minor changes to the `search.ResolverResult` struct _(mostly used internally)_: + - Replaced `NoCoalesce` field with the more explicit `NullFallback` _(`NullFallbackDisabled` is the same as `NoCoalesce:true`)_. + - Replaced the expression interface of the `MultiMatchSubQuery` field with the concrete struct type `search.MultiMatchSubquery` to avoid excessive type assertions and allow direct mutations of the field. + +- Updated `modernc.org/sqlite` to v1.44.1 _(SQLite 3.51.1)_. + +- Bumped min Go GitHub action version to 1.25.6 because it comes with some [minor security fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.25.6). + + +## v0.35.1 + +- Updated `modernc.org/sqlite` to v1.43.0 _(query cancellation race fix)_. + +- Other minor UI fixes (normalized relations picker selection and confirmation message when `maxSelect=0/1`, updated node deps). + + +## v0.35.0 + +- Added `nullString()`, `nullInt()`, `nullFloat()`, `nullBool`, `nullArray()`, `nullObject()` JSVM helpers for scanning nullable columns ([#7396](https://github.com/pocketbase/pocketbase/issues/7396)). + +- Store the correct `image/png` as attrs content type when generating a thumb fallback _(e.g. for `webp`)_. + +- Trimmed custom uploaded file name and extension from leftover `.` characters after `filesystem.File` normalization. + _This was done to prevent issues with external files sync programs that may have special handling for "invisible" files._ + +- Updated `modernc.org/sqlite` _(v1.41.0 includes prepared statements optimization)_ and other minor Go deps. + + +## v0.34.2 + +- Bumped JS SDK to v0.26.5 to fix Safari AbortError detection introduced with the previous release ([#7369](https://github.com/pocketbase/pocketbase/issues/7369)). + + +## v0.34.1 + +- Added missing `:` char to the autocomplete regex ([#7353](https://github.com/pocketbase/pocketbase/pull/7353); thanks @ouvreboite). + +- Added "Copy raw JSON" collection dropdown option ([#7357](https://github.com/pocketbase/pocketbase/issues/7357)). + +- Updated Go deps and JS SDK. + +- Bumped min Go GitHub action version to 1.25.5 because it comes with some [minor security fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.25.5). + _The runner action was also updated to `actions/setup-go@v6` since the previous v5 Go source seems [no longer accessible](https://github.com/actions/setup-go/pull/665#issuecomment-3416693714)._ + + +## v0.34.0 + +- Added `@request.body.someField:changed` modifier. + It could be used when you want to ensure that a body field either wasn't submitted or was submitted with the same value. + Or in other words, if you want to disallow a field change the below 2 expressions would be equivalent: + ```js + // (old) + (@request.body.someField:isset = false || @request.body.someField = someField) + + // (new) + @request.body.someField:changed = false + ``` + +- Added `MailerRecordEvent.Meta["info"]` property for the `OnMailerRecordAuthAlertSend` hook. + +- Updated the backup restore popup with a short info about the performed restore steps. + +- Updated Go deps. + + +## v0.33.0 + +- Added extra `id` characters validation in addition to the user specified regex pattern ([#7312](https://github.com/pocketbase/pocketbase/issues/7312)). + _The following special characters are always forbidden: `./\|"'``<>:?*%$\n\r\t\0 `. Common reserved Windows file names such as `aux`, `prn`, `con`, `nul`, `com1-9`, `lpt1-9` are also not allowed._ + _The list is not exhaustive but it should help minimizing eventual filesystem compatibility issues in case of wildcards or other loose regex patterns._ + +- Added `{ALERT_INFO}` placeholder to the auth alert mail template ([#7314](https://github.com/pocketbase/pocketbase/issues/7314)). + _⚠️ `mails.SendRecordAuthAlert(app, authRecord, info)` also now accepts a 3rd `info` string argument._ + +- Updated Go deps. + + +## v0.32.0 + +- ⚠️ Added extra List/Search API rules checks for the client-side `filter`/`sort` relations. + + This is continuation of the effort to eliminate the risk of information disclosure _(and eventually the side-channel attacks that may originate from that)_. + + So far this was accepted tradeoff between performance, usability and correctness since the solutions at the time weren't really practical _(especially with the back-relations as mentioned in ["Security and performance" section in #4417](https://github.com/pocketbase/pocketbase/discussions/4417))_, but with v0.23+ changes we can implement the extra checks without littering the code too much, with very little impact on the performance and at the same time ensuring better out of the box security _(especially for the cases where users operate with sensitive fields like "code", "token", "secret", etc.)_. + + Similar to the previous release, probably for most users with already configured API rules this change won't be breaking, but if you have an _intermediate/junction collection_ that is "locked" (superusers-only) we no longer will allow the client-side relation filter to pass through it and you'll have to set its List/Search API rule to enable the current user to search in it. + + For example, if you have a client-side filter that targets `rel1.rel2.token`, the client must have not only List/Search API rule access to the main collection BUT also to the collections referenced by "rel1" and "rel2" relation fields. + + Note that this change is only for the **client-side** `filter`/`sort` and doesn't affect the execution of superuser requests, API rules and `expand` - they continue to work the same as it is. + + An optional environment variable to toggle this behavior was considered but for now I think having 2 ways of resolving client-side filters would introduce maintenance burden and can even cause confusion (this change should actually make things more intuitive and clear because we can simply say something like _"you can search by a collection X field only if you have List/Search API rule access to it"_ no matter whether the targeted collection is the request's main collection, the first or last relation from the filter chain, etc.). + + If you stumble on an error or extreme query performance degradation as a result of the extra checks, please open a Q&A discussion with the failing request and export of your collections configuration as JSON (_Settings > Export collections_) and I'll try to investigate it. + +- Increased the default SQLite `PRAGMA cache_size` to ~32MB. + +- Fixed deadlock when manually triggering the `OnTerminate` hook ([#7305](https://github.com/pocketbase/pocketbase/pull/7305); thanks @yerTools). + +- Fixed some code comment typos, regenerated the JSVM types and updated npm dependencies. + +- Updated `modernc.org/sqlite` to 1.40.0. + + +## v0.31.0 + +- Visualize presentable multiple `relation` fields ([#7260](https://github.com/pocketbase/pocketbase/issues/7260)). + +- Support Ed25519 in the optional OIDC `id_token` signature validation ([#7252](https://github.com/pocketbase/pocketbase/issues/7252); thanks @shynome). + +- Added `ApiScenario.DisableTestAppCleanup` optional field to skip the auto test app cleanup and leave it up to the developers to do the cleanup manually ([#7267](https://github.com/pocketbase/pocketbase/discussions/7267)). + +- Added `FileDownloadRequestEvent.ThumbError` field that is populated in case of a thumb generation failure (e.g. unsupported format, timing out, etc.), allowing developers to reject the thumb fallback and/or supply their own custom thumb generation ([#7268](https://github.com/pocketbase/pocketbase/discussions/7268)). + +- ⚠️ Disallow client-side filtering and sorting of relations where the collection of the last targeted relation field has superusers-only List/Search API rule to further minimize the risk of eventual side-channel attack. + _This should be a non-breaking change for most users, but if you want the old behavior, please open a new Q&A discussion with details about your use case to evaluate making it configurable._ + _Note also that as mentioned in the "Security and performance" section of [#4417](https://github.com/pocketbase/pocketbase/discussions/4417) and [#5863](https://github.com/pocketbase/pocketbase/discussions/5863), the easiest and recommended solution to protect security sensitive fields (tokens, codes, passwords, etc.) is to mark them as "Hidden" (aka. make them non-API filterable)._ + +- Regenerated JSVM types and updated npm and Go deps. + + +## v0.30.4 + +- Fixed `json` field CSS regression introduced with the overflow workaround in v0.30.3 ([#7259](https://github.com/pocketbase/pocketbase/issues/7259)). + + +## v0.30.3 + +- Fixed legacy identitity field priority check when a username is a valid email address ([#7256](https://github.com/pocketbase/pocketbase/issues/7256)). + +- Workaround autocomplete overflow issue with Firefox 144 ([#7223](https://github.com/pocketbase/pocketbase/issues/7223)). + +- Updated `modernc.org/sqlite` to 1.39.1 (SQLite 3.50.4). + + +## v0.30.2 + +- Bumped min Go GitHub action version to 1.24.8 since it comes with some [minor security fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.24.8+label%3ACherryPickApproved). + + +## v0.30.1 + +- ⚠️ Excluded the `lost+found` directory from the backups ([#7208](https://github.com/pocketbase/pocketbase/pull/7208); thanks @lbndev). + _If for some reason you want to keep it, you can restore it by editing the `e.Exclude` list of the `OnBackupCreate` and `OnBackupRestore` hooks._ + +- Minor tests improvements (disabled initial superuser creation for the test app to avoid cluttering the std output, added more tests for the `s3.Uploader.MaxConcurrency`, etc.). + +- Updated `modernc.org/sqlite` and other Go dependencies. + + +## v0.30.0 + +- Eagerly escape the S3 request path following the same rules as in the S3 signing header ([#7153](https://github.com/pocketbase/pocketbase/issues/7153)). + +- Added Lark OAuth2 provider ([#7130](https://github.com/pocketbase/pocketbase/pull/7130); thanks @mashizora). + +- Increased test tokens `exp` claim to minimize eventual issues with reproducible builds ([#7123](https://github.com/pocketbase/pocketbase/issues/7123)). + +- Added `os.Root` bindings to the JSVM ([`$os.openRoot`](https://pocketbase.io/jsvm/functions/_os.openRoot.html), [`$os.openInRoot`](https://pocketbase.io/jsvm/functions/_os.openInRoot.html)). + +- Added `osutils.IsProbablyGoRun()` helper to loosely check if the program was started using `go run`. + +- Various minor UI improvements (updated collections indexes UI, enabled seconds in the datepicker, updated helper texts, etc.). + +- ⚠️ Updated the minimum package Go version to 1.24.0 and bumped Go dependencies. + + +## v0.29.3 + +- Try to forward Apple OAuth2 POST redirect user's name so that it can be returned (and eventually assigned) with the success response of the all-in-one auth call ([#7090](https://github.com/pocketbase/pocketbase/issues/7090)). + +- Fixed `RateLimitRule.Audience` code comment ([#7098](https://github.com/pocketbase/pocketbase/pull/7098); thanks @iustin05). + +- Mocked `syscall.Exec` when building for WASM ([#7116](https://github.com/pocketbase/pocketbase/pull/7116); thanks @joas8211). + _Note that WASM is not officially supported PocketBase build target and many things may not work as expected._ + +- Registered missing `$filesystem`, `$mails`, `$template` and `__hooks` bindings in the JSVM migrations ([#7125](https://github.com/pocketbase/pocketbase/issues/7125)). + +- Regenerated JSVM types to include methods from structs with single generic parameter. + +- Updated Go dependencies. + + +## v0.29.2 + +- Bumped min Go GitHub action version to 1.23.12 since it comes with some [minor fixes for the runtime and `database/sql` package](https://github.com/golang/go/issues?q=milestone%3AGo1.23.12+label%3ACherryPickApproved). + + +## v0.29.1 + +- Updated the X/Twitter provider to return the `confirmed_email` field and to use the `x.com` domain ([#7035](https://github.com/pocketbase/pocketbase/issues/7035)). + +- Added Box.com OAuth2 provider ([#7056](https://github.com/pocketbase/pocketbase/pull/7056); thanks @blakepatteson). + +- Updated `modernc.org/sqlite` to 1.38.2 (SQLite 3.50.3). + +- Fixed example List API response ([#7049](https://github.com/pocketbase/pocketbase/pull/7049); thanks @williamtguerra). + + +## v0.29.0 + +- Enabled calling the `/auth-refresh` endpoint with nonrenewable tokens. + _When used with nonrenewable tokens (e.g. impersonate) the endpoint will simply return the same token with the up-to-date user data associated with it._ + +- Added the triggered rate rimit rule in the error log `details`. + +- Added optional `ServeEvent.Listener` field to initialize a custom network listener (e.g. `unix`) instead of the default `tcp` ([#3233](https://github.com/pocketbase/pocketbase/discussions/3233)). + +- Fixed request data unmarshalization for the `DynamicModel` array/object fields ([#7022](https://github.com/pocketbase/pocketbase/discussions/7022)). + +- Fixed Dashboard page title `-` escaping ([#6982](https://github.com/pocketbase/pocketbase/issues/6982)). + +- Other minor improvements (updated first superuser console text when running with `go run`, clarified trusted IP proxy header label, wrapped the backup restore in a transaction as an extra precaution, updated deps, etc.). + + +## v0.28.4 + +- Added global JSVM `toBytes()` helper to return the bytes slice representation of a value such as io.Reader or string, _other types are first serialized to Go string_ ([#6935](https://github.com/pocketbase/pocketbase/issues/6935)). + +- Fixed `security.RandomStringByRegex` random distribution ([#6947](https://github.com/pocketbase/pocketbase/pull/6947); thanks @yerTools). + +- Minor docs and typos fixes. + + +## v0.28.3 + +- Skip sending empty `Range` header when fetching blobs from S3 ([#6914](https://github.com/pocketbase/pocketbase/pull/6914)). + +- Updated Go deps and particularly `modernc.org/sqlite` to 1.38.0 (SQLite 3.50.1). + +- Bumped GitHub action min Go version to 1.23.10 as it comes with some [minor security `net/http` fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.23.10+label%3ACherryPickApproved). + + +## v0.28.2 + +- Loaded latin-ext charset for the default text fonts ([#6869](https://github.com/pocketbase/pocketbase/issues/6869)). + +- Updated view query CAST regex to properly recognize multiline expressions ([#6860](https://github.com/pocketbase/pocketbase/pull/6860); thanks @azat-ismagilov). + +- Updated Go and npm dependencies. + + +## v0.28.1 + +- Fixed `json_each`/`json_array_length` normalizations to properly check for array values ([#6835](https://github.com/pocketbase/pocketbase/issues/6835)). + + +## v0.28.0 + +- Write the default response body of `*Request` hooks that are wrapped in a transaction after the related transaction completes to allow propagating the transaction error ([#6462](https://github.com/pocketbase/pocketbase/discussions/6462#discussioncomment-12207818)). + +- Updated `app.DB()` to automatically routes raw write SQL statements to the nonconcurrent db pool ([#6689](https://github.com/pocketbase/pocketbase/discussions/6689)). + _For the rare cases when it is needed users still have the option to explicitly target the specific pool they want using `app.ConcurrentDB()`/`app.NonconcurrentDB()`._ + +- ⚠️ Changed the default `json` field max size to 1MB. + _Users still have the option to adjust the default limit from the collection field options but keep in mind that storing large strings/blobs in the database is known to cause performance issues and should be avoided when possible._ + +- ⚠️ Soft-deprecated and replaced `filesystem.System.GetFile(fileKey)` with `filesystem.System.GetReader(fileKey)` to avoid the confusion with `filesystem.File`. + _The old method will still continue to work for at least until v0.29.0 but you'll get a console warning to replace it with `GetReader`._ + +- Added new `filesystem.System.GetReuploadableFile(fileKey, preserveName)` method to return an existing blob as a `*filesystem.File` value ([#6792](https://github.com/pocketbase/pocketbase/discussions/6792)). + _This method could be useful in case you want to clone an existing Record file and assign it to a new Record (e.g. in a Record duplicate action)._ + +- Other minor improvements (updated the GitHub release min Go version to 1.23.9, updated npm and Go deps, etc.) + + +## v0.27.2 + +- Added workers pool when cascade deleting record files to minimize _"thread exhaustion"_ errors ([#6780](https://github.com/pocketbase/pocketbase/discussions/6780)). + +- Updated the `:excerpt` fields modifier to properly account for multibyte characters ([#6778](https://github.com/pocketbase/pocketbase/issues/6778)). + +- Use `rowid` as count column for non-view collections to minimize the need of having the id field in a covering index ([#6739](https://github.com/pocketbase/pocketbase/discussions/6739)) + + +## v0.27.1 + +- Updated example `geoPoint` API preview body data. + +- Added JSVM `new GeoPointField({ ... })` constructor. + +- Added _partial_ WebP thumbs generation (_the thumbs will be stored as PNG_; [#6744](https://github.com/pocketbase/pocketbase/pull/6744)). + +- Updated npm dev dependencies. + + +## v0.27.0 + +- ⚠️ Moved the Create and Manage API rule checks out of the `OnRecordCreateRequest` hook finalizer, **aka. now all CRUD API rules are checked BEFORE triggering their corresponding `*Request` hook**. + This was done to minimize the confusion regarding the firing order of the request operations, making it more predictable and consistent with the other record List/View/Update/Delete request actions. + It could be a minor breaking change if you are relying on the old behavior and have a Go `tests.ApiScenario` that is testing a Create API rule failure and expect `OnRecordCreateRequest` to be fired. In that case for example you may have to update your test scenario like: + ```go + tests.ApiScenario{ + Name: "Example test that checks a Create API rule failure" + Method: http.MethodPost, + URL: "/api/collections/example/records", + ... + // old: + ExpectedEvents: map[string]int{ + "*": 0, + "OnRecordCreateRequest": 1, + }, + // new: + ExpectedEvents: map[string]int{"*": 0}, + } + ``` + If you are having difficulties adjusting your code, feel free to open a [Q&A discussion](https://github.com/pocketbase/pocketbase/discussions) with the failing/problematic code sample. + +- Added [new `geoPoint` field](https://pocketbase.io/docs/collections/#geopoint) for storing `{"lon":x,"lat":y}` geographic coordinates. + In addition, a new [`geoDistance(lonA, lotA, lonB, lotB)` function](https://pocketbase.io/docs/api-rules-and-filters/#geodistancelona-lata-lonb-latb) was also implemented that could be used to apply an API rule or filter constraint based on the distance (in km) between 2 geo points. + +- Updated the `select` field UI to accommodate better larger lists and RTL languages ([#4674](https://github.com/pocketbase/pocketbase/issues/4674)). + +- Updated the mail attachments auto MIME type detection to use `gabriel-vasile/mimetype` for consistency and broader sniffing signatures support. + +- Forced `text/javascript` Content-Type when serving `.js`/`.mjs` collection uploaded files with the `/api/files/...` endpoint ([#6597](https://github.com/pocketbase/pocketbase/issues/6597)). + +- Added second optional JSVM `DateTime` constructor argument for specifying a default timezone as TZ identifier when parsing the date string as alternative to a fixed offset in order to better handle daylight saving time nuances ([#6688](https://github.com/pocketbase/pocketbase/discussions/6688)): + ```js + // the same as with CET offset: new DateTime("2025-10-26 03:00:00 +01:00") + new DateTime("2025-10-26 03:00:00", "Europe/Amsterdam") // 2025-10-26 02:00:00.000Z + + // the same as with CEST offset: new DateTime("2025-10-26 01:00:00 +02:00") + new DateTime("2025-10-26 01:00:00", "Europe/Amsterdam") // 2025-10-25 23:00:00.000Z + ``` + +- Soft-deprecated the `$http.send`'s `result.raw` field in favor of `result.body` that contains the response body as plain bytes slice to avoid the discrepancies between Go and the JSVM when casting binary data to string. + +- Updated `modernc.org/sqlite` to 1.37.0. + +- Other minor improvements (_removed the superuser fields from the auth record create/update body examples, allowed programmatically updating the auth record password from the create/update hooks, fixed collections import error response, etc._). + + +## v0.26.6 + +- Allow OIDC `email_verified` to be int or boolean string since some OIDC providers like AWS Cognito has non-standard userinfo response ([#6657](https://github.com/pocketbase/pocketbase/pull/6657)). + +- Updated `modernc.org/sqlite` to 1.36.3. + + +## v0.26.5 + +- Fixed canonical URI parts escaping when generating the S3 request signature ([#6654](https://github.com/pocketbase/pocketbase/issues/6654)). + + +## v0.26.4 + +- Fixed `RecordErrorEvent.Error` and `CollectionErrorEvent.Error` sync with `ModelErrorEvent.Error` ([#6639](https://github.com/pocketbase/pocketbase/issues/6639)). + +- Fixed logs details copy to clipboard action. + +- Updated `modernc.org/sqlite` to 1.36.2. + + +## v0.26.3 + +- Fixed and normalized logs error serialization across common types for more consistent logs error output ([#6631](https://github.com/pocketbase/pocketbase/issues/6631)). + + +## v0.26.2 + +- Updated `golang-jwt/jwt` dependency because it comes with a [minor security fix](https://github.com/golang-jwt/jwt/security/advisories/GHSA-mh63-6h87-95cp). + + +## v0.26.1 + +- Removed the wrapping of `io.EOF` error when reading files since currently `io.ReadAll` doesn't check for wrapped errors ([#6600](https://github.com/pocketbase/pocketbase/issues/6600)). + + +## v0.26.0 + +- ⚠️ Replaced `aws-sdk-go-v2` and `gocloud.dev/blob` with custom lighter implementation ([#6562](https://github.com/pocketbase/pocketbase/discussions/6562)). + As a side-effect of the dependency removal, the binary size has been reduced with ~10MB and builds ~30% faster. + _Although the change is expected to be backward-compatible, I'd recommend to test first locally the new version with your S3 provider (if you use S3 for files storage and backups)._ + +- ⚠️ Prioritized the user submitted non-empty `createData.email` (_it will be unverified_) when creating the PocketBase user during the first OAuth2 auth. + +- Load the request info context during password/OAuth2/OTP authentication ([#6402](https://github.com/pocketbase/pocketbase/issues/6402)). + This could be useful in case you want to target the auth method as part of the MFA and Auth API rules. + For example, to disable MFA for the OAuth2 auth could be expressed as `@request.context != "oauth2"` MFA rule. + +- Added `store.Store.SetFunc(key, func(old T) new T)` to set/update a store value with the return result of the callback in a concurrent safe manner. + +- Added `subscription.Message.WriteSSE(w, id)` for writing an SSE formatted message into the provided writer interface (_used mostly to assist with the unit testing_). + +- Added `$os.stat(file)` JSVM helper ([#6407](https://github.com/pocketbase/pocketbase/discussions/6407)). + +- Added log warning for `async` marked JSVM handlers and resolve when possible the returned `Promise` as fallback ([#6476](https://github.com/pocketbase/pocketbase/issues/6476)). + +- Allowed calling `cronAdd`, `cronRemove` from inside other JSVM handlers ([#6481](https://github.com/pocketbase/pocketbase/discussions/6481)). + +- Bumped the default request read and write timeouts to 5mins (_old 3mins_) to accommodate slower internet connections and larger file uploads/downloads. + _If you want to change them you can modify the `OnServe` hook's `ServeEvent.ReadTimeout/WriteTimeout` fields as shown in [#6550](https://github.com/pocketbase/pocketbase/discussions/6550#discussioncomment-12364515)._ + +- Normalized the `@request.auth.*` and `@request.body.*` back relations resolver to always return `null` when the relation field is pointing to a different collection ([#6590](https://github.com/pocketbase/pocketbase/discussions/6590#discussioncomment-12496581)). + +- Other minor improvements (_fixed query dev log nested parameters output, reintroduced `DynamicModel` object/array props reflect types caching, updated Go and npm deps, etc._) + + +## v0.25.9 + +- Fixed `DynamicModel` object/array props reflect type caching ([#6563](https://github.com/pocketbase/pocketbase/discussions/6563)). + + +## v0.25.8 + +- Added a default leeway of 5 minutes for the Apple/OIDC `id_token` timestamp claims check to account for clock-skew ([#6529](https://github.com/pocketbase/pocketbase/issues/6529)). + It can be further customized if needed with the `PB_ID_TOKEN_LEEWAY` env variable (_the value must be in seconds, e.g. "PB_ID_TOKEN_LEEWAY=60" for 1 minute_). + + +## v0.25.7 + +- Fixed `@request.body.jsonObjOrArr.*` values extraction ([#6493](https://github.com/pocketbase/pocketbase/discussions/6493)). + + +## v0.25.6 + +- Restore the missing `meta.isNew` field of the OAuth2 success response ([#6490](https://github.com/pocketbase/pocketbase/issues/6490)). + +- Updated npm dependencies. + + +## v0.25.5 + +- Set the current working directory as a default goja script path when executing inline JS strings to allow `require(m)` traversing parent `node_modules` directories. + +- Updated `modernc.org/sqlite` and `modernc.org/libc` dependencies. + + +## v0.25.4 + +- Downgraded `aws-sdk-go-v2` to the version before the default data integrity checks because there have been reports for non-AWS S3 providers in addition to Backblaze (IDrive, R2) that no longer or partially work with the latest AWS SDK changes. + + While we try to enforce `when_required` by default, it is not enough to disable the new AWS SDK integrity checks entirely and some providers will require additional manual adjustments to make them compatible with the latest AWS SDK (e.g. removing the `x-aws-checksum-*` headers, unsetting the checksums calculation or reinstantiating the old MD5 checksums for some of the required operations, etc.) which as a result leads to a configuration mess that I'm not sure it would be a good idea to introduce. + + This unfornuatelly is not a PocketBase or Go specific issue and the official AWS SDKs for other languages are in the same situation (even the latest aws-cli). + + For those of you that extend PocketBase with Go: if your S3 vendor doesn't support the [AWS Data integrity checks](https://docs.aws.amazon.com/sdkref/latest/guide/feature-dataintegrity.html) and you are updating with `go get -u`, then make sure that the `aws-sdk-go-v2` dependencies in your `go.mod` are the same as in the repo: + ``` + // go.mod + github.com/aws/aws-sdk-go-v2 v1.36.1 + github.com/aws/aws-sdk-go-v2/config v1.28.10 + github.com/aws/aws-sdk-go-v2/credentials v1.17.51 + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.48 + github.com/aws/aws-sdk-go-v2/service/s3 v1.72.2 + + // after that run + go clean -modcache && go mod tidy + ``` + _The versions pinning is temporary until the non-AWS S3 vendors patch their implementation or until I manage to find time to remove/replace the `aws-sdk-go-v2` dependency (I'll consider prioritizing it for the v0.26 or v0.27 release)._ + + +## v0.25.3 + +- Added a temporary exception for Backblaze S3 endpoints to exclude the new `aws-sdk-go-v2` checksum headers ([#6440](https://github.com/pocketbase/pocketbase/discussions/6440)). + + +## v0.25.2 + +- Fixed realtime delete event not being fired for `RecordProxy`-ies and added basic realtime record resolve automated tests ([#6433](https://github.com/pocketbase/pocketbase/issues/6433)). + + +## v0.25.1 + +- Fixed the batch API Preview success sample response. + +- Bumped GitHub action min Go version to 1.23.6 as it comes with a [minor security fix](https://github.com/golang/go/issues?q=milestone%3AGo1.23.6+label%3ACherryPickApproved) for the ppc64le build. + + +## v0.25.0 + +- ⚠️ Upgraded Google OAuth2 auth, token and userinfo endpoints to their latest versions. + _For users that don't do anything custom with the Google OAuth2 data or the OAuth2 auth URL, this should be a non-breaking change. The exceptions that I could find are:_ + - `/v3/userinfo` auth response changes: + ``` + meta.rawUser.id => meta.rawUser.sub + meta.rawUser.verified_email => meta.rawUser.email_verified + ``` + - `/v2/auth` query parameters changes: + If you are specifying custom `approval_prompt=force` query parameter for the OAuth2 auth URL, you'll have to replace it with **`prompt=consent`**. + +- Added Trakt OAuth2 provider ([#6338](https://github.com/pocketbase/pocketbase/pull/6338); thanks @aidan-) + +- Added support for case-insensitive password auth based on the related UNIQUE index field collation ([#6337](https://github.com/pocketbase/pocketbase/discussions/6337)). + +- Enforced `when_required` for the new AWS SDK request and response checksum validations to allow other non-AWS vendors to catch up with new AWS SDK changes (see [#6313](https://github.com/pocketbase/pocketbase/discussions/6313) and [aws/aws-sdk-go-v2#2960](https://github.com/aws/aws-sdk-go-v2/discussions/2960)). + _You can set the environment variables `AWS_REQUEST_CHECKSUM_CALCULATION` and `AWS_RESPONSE_CHECKSUM_VALIDATION` to `when_supported` if your S3 vendor supports the [new default integrity protections](https://docs.aws.amazon.com/sdkref/latest/guide/feature-dataintegrity.html)._ + +- Soft-deprecated `Record.GetUploadedFiles` in favor of `Record.GetUnsavedFiles` to minimize the ambiguities what the method do ([#6269](https://github.com/pocketbase/pocketbase/discussions/6269)). + +- Replaced archived `github.com/AlecAivazis/survey` dependency with a simpler `osutils.YesNoPrompt(message, fallback)` helper. + +- Upgraded to `golang-jwt/jwt/v5`. + +- Added JSVM `new Timezone(name)` binding for constructing `time.Location` value ([#6219](https://github.com/pocketbase/pocketbase/discussions/6219)). + +- Added `inflector.Camelize(str)` and `inflector.Singularize(str)` helper methods. + +- Use the non-transactional app instance during the realtime records delete access checks to ensure that cascade deleted records with API rules relying on the parent will be resolved. + +- Other minor improvements (_replaced all `bool` exists db scans with `int` for broader drivers compatibility, updated API Preview sample error responses, updated UI dependencies, etc._) + + +## v0.24.4 + +- Fixed fields extraction for view query with nested comments ([#6309](https://github.com/pocketbase/pocketbase/discussions/6309)). + +- Bumped GitHub action min Go version to 1.23.5 as it comes with some [minor security fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.23.5). + + +## v0.24.3 + +- Fixed incorrectly reported unique validator error for fields starting with name of another field ([#6281](https://github.com/pocketbase/pocketbase/pull/6281); thanks @svobol13). + +- Reload the created/edited records data in the RecordsPicker UI. + +- Updated Go dependencies. + + +## v0.24.2 + +- Fixed display fields extraction when there are multiple "Presentable" `relation` fields in a single related collection ([#6229](https://github.com/pocketbase/pocketbase/issues/6229)). + + +## v0.24.1 + +- Added missing time macros in the UI autocomplete. + +- Fixed JSVM types for structs and functions with multiple generic parameters. + + +## v0.24.0 + +- ⚠️ Removed the "dry submit" when executing the collections Create API rule + (you can find more details why this change was introduced and how it could affect your app in https://github.com/pocketbase/pocketbase/discussions/6073). + For most users it should be non-breaking change, BUT if you have Create API rules that uses self-references or view counters you may have to adjust them manually. + With this change the "multi-match" operators are also normalized in case the targeted collection doesn't have any records + (_or in other words, `@collection.example.someField != "test"` will result to `true` if `example` collection has no records because it satisfies the condition that all available "example" records mustn't have `someField` equal to "test"_). + As a side-effect of all of the above minor changes, the record create API performance has been also improved ~4x times in high concurrent scenarios (500 concurrent clients inserting total of 50k records - [old (58.409064001s)](https://github.com/pocketbase/benchmarks/blob/54140be5fb0102f90034e1370c7f168fbcf0ddf0/results/hetzner_cax41_cgo.md#creating-50000-posts100k-reqs50000-conc500-rulerequestauthid----requestdatapublicisset--true) vs [new (13.580098262s)](https://github.com/pocketbase/benchmarks/blob/7df0466ac9bd62fe0a1056270d20ef82012f0234/results/hetzner_cax41_cgo.md#creating-50000-posts100k-reqs50000-conc500-rulerequestauthid----requestbodypublicisset--true)). + +- ⚠️ Changed the type definition of `store.Store[T any]` to `store.Store[K comparable, T any]` to allow support for custom store key types. + For most users it should be non-breaking change, BUT if you are calling `store.New[any](nil)` instances you'll have to specify the store key type, aka. `store.New[string, any](nil)`. + +- Added `@yesterday` and `@tomorrow` datetime filter macros. + +- Added `:lower` filter modifier (e.g. `title:lower = "lorem"`). + +- Added `mailer.Message.InlineAttachments` field for attaching inline files to an email (_aka. `cid` links_). + +- Added cache for the JSVM `arrayOf(m)`, `DynamicModel`, etc. dynamic `reflect` created types. + +- Added auth collection select for the settings "Send test email" popup ([#6166](https://github.com/pocketbase/pocketbase/issues/6166)). + +- Added `record.SetRandomPassword()` to simplify random password generation usually used in the OAuth2 or OTP record creation flows. + _The generated ~30 chars random password is assigned directly as bcrypt hash and ignores the `password` field plain value validators like min/max length or regex pattern._ + +- Added option to list and trigger the registered app level cron jobs via the Web API and UI. + +- Added extra validators for the collection field `int64` options (e.g. `FileField.MaxSize`) restricting them to the max safe JSON number (2^53-1). + +- Added option to unset/overwrite the default PocketBase superuser installer using `ServeEvent.InstallerFunc`. + +- Added `app.FindCachedCollectionReferences(collection, excludeIds)` to speedup records cascade delete almost twice for projects with many collections. + +- Added `tests.NewTestAppWithConfig(config)` helper if you need more control over the test configurations like `IsDev`, the number of allowed connections, etc. + +- Invalidate all record tokens when the auth record email is changed programmatically or by a superuser ([#5964](https://github.com/pocketbase/pocketbase/issues/5964)). + +- Eagerly interrupt waiting for the email alert send in case it takes longer than 15s. + +- Normalized the hidden fields filter checks and allow targeting hidden fields in the List API rule. + +- Fixed "Unique identify fields" input not refreshing on unique indexes change ([#6184](https://github.com/pocketbase/pocketbase/issues/6184)). + + +## v0.23.12 + +- Added warning logs in case of mismatched `modernc.org/sqlite` and `modernc.org/libc` versions ([#6136](https://github.com/pocketbase/pocketbase/issues/6136#issuecomment-2556336962)). + +- Skipped the default body size limit middleware for the backup upload endpoint ([#6152](https://github.com/pocketbase/pocketbase/issues/6152)). + + +## v0.23.11 + +- Upgraded `golang.org/x/net` to 0.33.0 to fix [CVE-2024-45338](https://www.cve.org/CVERecord?id=CVE-2024-45338). + _PocketBase uses the vulnerable functions primarily for the auto html->text mail generation, but most applications shouldn't be affected unless you are manually embedding unrestricted user provided value in your mail templates._ + + +## v0.23.10 + +- Renew the superuser file token cache when clicking on the thumb preview or download link ([#6137](https://github.com/pocketbase/pocketbase/discussions/6137)). + +- Upgraded `modernc.org/sqlite` to 1.34.3 to fix "disk io" error on arm64 systems. + _If you are extending PocketBase with Go and upgrading with `go get -u` make sure to manually set in your go.mod the `modernc.org/libc` indirect dependency to v1.55.3, aka. the exact same version the driver is using._ + + +## v0.23.9 + +- Replaced `strconv.Itoa` with `strconv.FormatInt` to avoid the int64->int conversion overflow on 32-bit platforms ([#6132](https://github.com/pocketbase/pocketbase/discussions/6132)). + + +## v0.23.8 + +- Fixed Model->Record and Model->Collection hook events sync for nested and/or inner-hook transactions ([#6122](https://github.com/pocketbase/pocketbase/discussions/6122)). + +- Other minor improvements (updated Go and npm deps, added extra escaping for the default mail record params in case the emails are stored as html files, fixed code comment typos, etc.). + + +## v0.23.7 + +- Fixed JSVM exception -> Go error unwrapping when throwing errors from non-request hooks ([#6102](https://github.com/pocketbase/pocketbase/discussions/6102)). + + +## v0.23.6 + +- Fixed `$filesystem.fileFromURL` documentation and generated type ([#6058](https://github.com/pocketbase/pocketbase/issues/6058)). + +- Fixed `X-Forwarded-For` header typo in the suggested UI "Common trusted proxy" headers ([#6063](https://github.com/pocketbase/pocketbase/pull/6063)). + +- Updated the `text` field max length validator error message to make it more clear ([#6066](https://github.com/pocketbase/pocketbase/issues/6066)). + +- Other minor fixes (updated Go deps, skipped unnecessary validator check when the default primary key pattern is used, updated JSVM types, etc.). + + +## v0.23.5 + +- Fixed UI logs search not properly accounting for the "Include requests by superusers" toggle when multiple search expressions are used. + +- Fixed `text` field max validation error message ([#6053](https://github.com/pocketbase/pocketbase/issues/6053)). + +- Other minor fixes (comment typos, JSVM types update). + +- Updated Go deps and the min Go release GitHub action version to 1.23.4. + + +## v0.23.4 + +- Fixed `autodate` fields not refreshing when calling `Save` multiple times on the same `Record` instance ([#6000](https://github.com/pocketbase/pocketbase/issues/6000)). + +- Added more descriptive test OTP id and failure log message ([#5982](https://github.com/pocketbase/pocketbase/discussions/5982)). + +- Moved the default UI CSP from meta tag to response header ([#5995](https://github.com/pocketbase/pocketbase/discussions/5995)). + +- Updated Go and npm dependencies. + + +## v0.23.3 + +- Fixed Gzip middleware not applying when serving static files. + +- Fixed `Record.Fresh()`/`Record.Clone()` methods not properly cloning `autodate` fields ([#5973](https://github.com/pocketbase/pocketbase/discussions/5973)). + + +## v0.23.2 + +- Fixed `RecordQuery()` custom struct scanning ([#5958](https://github.com/pocketbase/pocketbase/discussions/5958)). + +- Fixed `--dev` log query print formatting. + +- Added support for passing more than one id in the `Hook.Unbind` method for consistency with the router. + +- Added collection rules change list in the confirmation popup + (_to avoid getting annoying during development, the rules confirmation currently is enabled only when using https_). + + +## v0.23.1 + +- Added `RequestEvent.Blob(status, contentType, bytes)` response write helper ([#5940](https://github.com/pocketbase/pocketbase/discussions/5940)). + +- Added more descriptive error messages. + + +## v0.23.0 + +> [!NOTE] +> You don't have to upgrade to PocketBase v0.23.0 if you are not planning further developing +> your existing app and/or are satisfied with the v0.22.x features set. There are no identified critical issues +> with PocketBase v0.22.x yet and in the case of critical bugs and security vulnerabilities, the fixes +> will be backported for at least until Q1 of 2025 (_if not longer_). +> +> **If you don't plan upgrading make sure to pin the SDKs version to their latest PocketBase v0.22.x compatible:** +> - JS SDK: `<0.22.0` +> - Dart SDK: `<0.19.0` + +> [!CAUTION] +> This release introduces many Go/JSVM and Web APIs breaking changes! +> +> Existing `pb_data` will be automatically upgraded with the start of the new executable, +> but custom Go or JSVM (`pb_hooks`, `pb_migrations`) and JS/Dart SDK code will have to be migrated manually. +> Please refer to the below upgrade guides: +> - Go: https://pocketbase.io/v023upgrade/go/. +> - JSVM: https://pocketbase.io/v023upgrade/jsvm/. +> +> If you had already switched to some of the earlier ` - Go: https://pocketbase.io/v023upgrade/go/. +> - JSVM: https://pocketbase.io/v023upgrade/jsvm/. + +#### SDKs changes + +- [JS SDK v0.22.0](https://github.com/pocketbase/js-sdk/blob/master/CHANGELOG.md) +- [Dart SDK v0.19.0](https://github.com/pocketbase/dart-sdk/blob/master/CHANGELOG.md) + +#### Web APIs changes + +- New `POST /api/batch` endpoint. + +- New `GET /api/collections/meta/scaffolds` endpoint. + +- New `DELETE /api/collections/{collection}/truncate` endpoint. + +- New `POST /api/collections/{collection}/request-otp` endpoint. + +- New `POST /api/collections/{collection}/auth-with-otp` endpoint. + +- New `POST /api/collections/{collection}/impersonate/{id}` endpoint. + +- ⚠️ If you are constructing requests to `/api/*` routes manually remove the trailing slash (_there is no longer trailing slash removal middleware registered by default_). + +- ⚠️ Removed `/api/admins/*` endpoints because admins are converted to `_superusers` auth collection records. + +- ⚠️ Previously when uploading new files to a multiple `file` field, new files were automatically appended to the existing field values. + This behaviour has changed with v0.23+ and for consistency with the other multi-valued fields when uploading new files they will replace the old ones. If you want to prepend or append new files to an existing multiple `file` field value you can use the `+` prefix or suffix: + ```js + "documents": [file1, file2] // => [file1_name, file2_name] + "+documents": [file1, file2] // => [file1_name, file2_name, old1_name, old2_name] + "documents+": [file1, file2] // => [old1_name, old2_name, file1_name, file2_name] + ``` + +- ⚠️ Removed `GET /records/{id}/external-auths` and `DELETE /records/{id}/external-auths/{provider}` endpoints because this is now handled by sending list and delete requests to the `_externalAuths` collection. + +- ⚠️ Changes to the app settings model fields and response (+new options such as `trustedProxy`, `rateLimits`, `batch`, etc.). The app settings Web APIs are mostly used by the Dashboard UI and rarely by the end users, but if you want to check all settings changes please refer to the [Settings Go struct](https://github.com/pocketbase/pocketbase/blob/develop/core/settings_model.go#L121). + +- ⚠️ New flatten Collection model and fields structure. The Collection model Web APIs are mostly used by the Dashboard UI and rarely by the end users, but if you want to check all changes please refer to the [Collection Go struct](https://github.com/pocketbase/pocketbase/blob/develop/core/collection_model.go#L308). + +- ⚠️ The top level error response `code` key was renamed to `status` for consistency with the Go APIs. + The error field key remains `code`: + ```js + { + "status": 400, // <-- old: "code" + "message": "Failed to create record.", + "data": { + "title": { + "code": "validation_required", + "message": "Missing required value." + } + } + } + ``` + +- ⚠️ New fields in the `GET /api/collections/{collection}/auth-methods` response. + _The old `authProviders`, `usernamePassword`, `emailPassword` fields are still returned in the response but are considered deprecated and will be removed in the future._ + ```js + { + "mfa": { + "duration": 100, + "enabled": true + }, + "otp": { + "duration": 0, + "enabled": false + }, + "password": { + "enabled": true, + "identityFields": ["email", "username"] + }, + "oauth2": { + "enabled": true, + "providers": [{"name": "gitlab", ...}, {"name": "google", ...}] + }, + // old fields... + } + ``` + +- ⚠️ Soft-deprecated the OAuth2 auth success `meta.avatarUrl` field in favour of `meta.avatarURL`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8322df883f..6e8b5d8e4b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,7 +13,7 @@ This document describes how to prepare a PR for a change in the main repository. ## Prerequisites -- Go 1.25+ (for making changes in the Go code) +- Go 1.27+ (for making changes in the Go code) - Node 24+ (for making changes in the Superuser UI) If you haven't already, you can fork the main repository and clone your fork so that you can work locally: diff --git a/README.md b/README.md index dc4b8fd344..dd7dc596db 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ your own custom app specific business logic and still have a single portable exe Here is a minimal example: -0. [Install Go 1.25+](https://go.dev/doc/install) (_if you haven't already_) +0. [Install Go 1.27+](https://go.dev/doc/install) (_if you haven't already_) 1. Create a new project directory with the following `main.go` file inside it: ```go @@ -92,32 +92,37 @@ _For more details please refer to [Extend with Go](https://pocketbase.io/docs/go To build the minimal standalone executable, like the prebuilt ones in the releases page, you can simply run `go build` inside the `examples/base` directory: -0. [Install Go 1.25+](https://go.dev/doc/install) (_if you haven't already_) +0. [Install Go 1.27+](https://go.dev/doc/install) (_if you haven't already_) 1. Clone/download the repo 2. Navigate to `examples/base` -3. Run `GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build` - (_https://go.dev/doc/install/source#environment_) +3. Run `CGO_ENABLED=0 go build` to build a binary for your current environment + _(or to target other platforms use `GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build`; see https://go.dev/doc/install/source#environment)_ 4. Start the created executable by running `./base serve`. Note that the supported build targets by the pure Go SQLite driver at the moment are: -``` -darwin amd64 -darwin arm64 -freebsd amd64 -freebsd arm64 -linux 386 -linux amd64 -linux arm -linux arm64 -linux loong64 -linux ppc64le -linux riscv64 -linux s390x -windows 386 -windows amd64 -windows arm64 -``` +| GOOS | GOARCH | +|---------|---------| +| darwin | amd64 | +| darwin | arm64 | +| freebsd | 386 | +| freebsd | amd64 | +| freebsd | arm | +| freebsd | arm64 | +| linux | 386 | +| linux | amd64 | +| linux | arm | +| linux | arm64 | +| linux | loong64 | +| linux | ppc64le | +| linux | riscv64 | +| linux | s390x | +| netbsd | amd64 | +| openbsd | amd64 | +| openbsd | arm64 | +| windows | 386 | +| windows | amd64 | +| windows | arm64 | ### Testing @@ -134,7 +139,7 @@ Check also the [Testing guide](http://pocketbase.io/docs/testing) to learn how t If you discover a security vulnerability within PocketBase, please send an e-mail to **support at pocketbase.io**. -All reports will be promptly addressed and you'll be credited in the fix release notes. +You could find more details in the project [Security policy](https://github.com/pocketbase/pocketbase/security/policy). ## Contributing diff --git a/apis/batch.go b/apis/batch.go index 61d7bcd41e..3f7367faeb 100644 --- a/apis/batch.go +++ b/apis/batch.go @@ -2,7 +2,7 @@ package apis import ( "bytes" - "encoding/json" + "encoding/json/v2" "errors" "io" "mime/multipart" diff --git a/apis/collection_test.go b/apis/collection_test.go index 27051826d8..ff9a59ac5a 100644 --- a/apis/collection_test.go +++ b/apis/collection_test.go @@ -1617,7 +1617,7 @@ func TestCollectionOAuth2Providers(t *testing.T) { }, ExpectedStatus: 200, ExpectedContent: []string{ - `{"name":"oidc3","displayName":"OIDC","logo":"\u003csvg`, + `{"name":"oidc3","displayName":"OIDC","logo":" 0 { + t.Fatalf("Expected all logs to be deleted, found: %v", found) + } + }, + ExpectedStatus: 204, + ExpectedEvents: map[string]int{"*": 0}, + }, + } + + for _, scenario := range scenarios { + scenario.Test(t) + } +} diff --git a/apis/middlewares.go b/apis/middlewares.go index c4df9aefd0..d033f23751 100644 --- a/apis/middlewares.go +++ b/apis/middlewares.go @@ -293,6 +293,7 @@ func securityHeaders() *hook.Handler[*core.RequestEvent] { e.Response.Header().Set("X-XSS-Protection", "1; mode=block") e.Response.Header().Set("X-Content-Type-Options", "nosniff") e.Response.Header().Set("X-Frame-Options", "SAMEORIGIN") + e.Response.Header().Set("Cross-Origin-Opener-Policy", "same-origin") // @todo consider a default HSTS? // (see also https://webkit.org/blog/8146/protecting-against-hsts-abuse/) diff --git a/apis/middlewares_rate_limit_test.go b/apis/middlewares_rate_limit_test.go index 3114c25d26..af19d1e19f 100644 --- a/apis/middlewares_rate_limit_test.go +++ b/apis/middlewares_rate_limit_test.go @@ -1,8 +1,10 @@ package apis_test import ( + "fmt" "net/http/httptest" "testing" + "testing/synctest" "time" "github.com/pocketbase/pocketbase/apis" @@ -127,27 +129,29 @@ func TestDefaultRateLimitMiddleware(t *testing.T) { {"/rate/guest", 0, true, 429}, } - for _, s := range scenarios { - t.Run(s.url, func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + for i, s := range scenarios { + prefix := fmt.Sprintf("[%s:%d] ", s.url, i+1) + rec := httptest.NewRecorder() req := httptest.NewRequest("GET", s.url, nil) if s.authenticated { auth, err := app.FindAuthRecordByEmail("users", "test@example.com") if err != nil { - t.Fatal(err) + t.Fatalf(prefix+"%v", err) } token, err := auth.NewAuthToken() if err != nil { - t.Fatal(err) + t.Fatalf(prefix+"%v", err) } req.Header.Add("Authorization", token) } if s.wait > 0 { - time.Sleep(time.Duration(s.wait) * time.Millisecond) + synctest.Sleep(time.Duration(s.wait) * time.Millisecond) } mux.ServeHTTP(rec, req) @@ -155,10 +159,10 @@ func TestDefaultRateLimitMiddleware(t *testing.T) { result := rec.Result() if result.StatusCode != s.expectedStatus { - t.Fatalf("Expected response status %d, got %d", s.expectedStatus, result.StatusCode) + t.Fatalf(prefix+"Expected response status %d, got %d", s.expectedStatus, result.StatusCode) } - }) - } + } + }) } func TestDefaultRateLimitMiddlewareSkipChecks(t *testing.T) { diff --git a/apis/realtime.go b/apis/realtime.go index 455fa47895..d6d8b58bcf 100644 --- a/apis/realtime.go +++ b/apis/realtime.go @@ -2,7 +2,7 @@ package apis import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "log/slog" diff --git a/apis/realtime_test.go b/apis/realtime_test.go index 8e05163cad..fa3094bcd5 100644 --- a/apis/realtime_test.go +++ b/apis/realtime_test.go @@ -2,7 +2,7 @@ package apis_test import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -1171,7 +1171,9 @@ func TestRealtimeRecordResolve(t *testing.T) { var mu sync.Mutex notify := func(clientId string, eventData []byte) { - data := struct{ Action string }{} + data := struct { + Action string `json:"action"` + }{} _ = json.Unmarshal(eventData, &data) mu.Lock() diff --git a/apis/record_auth_methods_test.go b/apis/record_auth_methods_test.go index 0ea1bd678e..5ae9d8cfaa 100644 --- a/apis/record_auth_methods_test.go +++ b/apis/record_auth_methods_test.go @@ -54,7 +54,7 @@ func TestRecordAuthMethodsList(t *testing.T) { `"providers":[{`, `"name":"google"`, `"name":"gitlab"`, - `"logo":"\u003csvg`, + `"logo":" 50 { + appName = appName[:50] + } + + return fmt.Sprintf( + "%s%s_%s.zip", + prefix, + appName, + time.Now().UTC().Format("20060102150405"), + ) +} + +// registerAutobackupHooks registers the autobackup app serve hooks. +func (app *BaseApp) registerAutobackupHooks() { + const jobId = "__pbAutoBackup__" + + loadJob := func() { + rawSchedule := app.Settings().Backups.Cron + if rawSchedule == "" { + app.Cron().Remove(jobId) + return + } + + app.Cron().Add(jobId, rawSchedule, func() { + const autoPrefix = "@auto_pb_backup_" + + name := generateBackupName(app, autoPrefix) + + if err := app.CreateBackup(context.Background(), name); err != nil { + app.Logger().Error( + "[Backup cron] Failed to create backup", + slog.String("name", name), + slog.String("error", err.Error()), + ) + + alertError := sendSystemAlertToAllSuperusers( + app, + "Autobackup failure", + "Failed to create/upload automated backup. Raw error:\n"+err.Error(), + ) + if alertError != nil { + app.Logger().Warn( + "[Backup cron] Failed to send backup error alerts", + slog.String("name", name), + slog.String("error", alertError.Error()), + ) + } + } + + maxKeep := app.Settings().Backups.CronMaxKeep + + if maxKeep == 0 { + return // no explicit limit + } + + fsys, err := app.NewBackupsFilesystem() + if err != nil { + app.Logger().Error( + "[Backup cron] Failed to initialize the backup filesystem", + slog.String("error", err.Error()), + ) + return + } + defer fsys.Close() + + files, err := fsys.List(autoPrefix) + if err != nil { + app.Logger().Error( + "[Backup cron] Failed to list autogenerated backups", + slog.String("error", err.Error()), + ) + return + } + + if maxKeep >= len(files) { + return // nothing to remove + } + + // sort desc + sort.Slice(files, func(i, j int) bool { + return files[i].ModTime.After(files[j].ModTime) + }) + + // keep only the most recent n auto backup files + toRemove := files[maxKeep:] + + for _, f := range toRemove { + if err := fsys.Delete(f.Key); err != nil { + app.Logger().Error( + "[Backup cron] Failed to remove old autogenerated backup", + slog.String("key", f.Key), + slog.String("error", err.Error()), + ) + } + } + }) + } + + app.OnBootstrap().BindFunc(func(e *BootstrapEvent) error { + if err := e.Next(); err != nil { + return err + } + + loadJob() + + return nil + }) + + app.OnSettingsReload().BindFunc(func(e *SettingsReloadEvent) error { + if err := e.Next(); err != nil { + return err + } + + loadJob() + + return nil + }) +} diff --git a/core/backup_create.go b/core/backup_create.go new file mode 100644 index 0000000000..85996fc54e --- /dev/null +++ b/core/backup_create.go @@ -0,0 +1,437 @@ +package core + +import ( + "archive/zip" + "compress/flate" + "context" + "errors" + "fmt" + "io" + "io/fs" + "log/slog" + "os" + "path" + "path/filepath" + "strings" + "sync" + "time" + + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/tools/filesystem" + "github.com/pocketbase/pocketbase/tools/hook" + "github.com/pocketbase/pocketbase/tools/security" + "github.com/pocketbase/pocketbase/tools/store" +) + +var errIsDir = errors.New("the specified path is a directory and not a regular file") + +// CreateBackup creates a new backup of the current app pb_data directory. +// +// If name is empty, it will be autogenerated. +// If backup with the same name exists, the new backup file will replace it. +// +// To safely perform the backup, it is recommended to have free disk space +// for at least 2x the size of the pb_data directory. +// +// By default backups are stored in pb_data/backups +// (the backups directory itself is excluded from the generated backup). +// +// Backups can be stored on S3 if it is configured in app.Settings().Backups. +// When using S3 storage for the uploaded collection files, you have to +// take care manually to backup those since they are not part of the pb_data. +// +// DB write locks are minimal and isolated only for the duration of the +// VACUUM INTO statement that creates a live copy of the app database. +// +// The backup works as follow: +// +// 1. Start listening for DELETED storage files. +// In case a file is being deleted while the backup is still ongoing, +// we directly copy it in the zip before the deletion and mark it as "excluded". +// +// 2. Copy the main database with VACUUM INTO, write it in the zip and mark it as "excluded". +// +// 3. Stop listening for DELETED files. +// +// 4. Start listening for NEW storage files and mark all new files from this point as "excluded". +// +// 5. Copy the logs database with VACUUM INTO, write it in the zip and mark it as "excluded". +// +// 6. Copy the rest of the pb_data files in the zip while ignoring the "excluded" list (it should be concurrent safe). +// +// 7. Stop listening for NEW storage files. +// +// While there is a risk for a race condition between steps 1, 2 and 3, it is an +// acceptable trade-off between performance and correctness because in +// the worst case there will be some unused storage files in the backup that don't do any harm. +func (app *BaseApp) CreateBackup(ctx context.Context, name string) error { + if app.Store().Has(StoreKeyActiveBackup) { + return errors.New("try again later - another backup/restore operation has already been started") + } + + app.Store().Set(StoreKeyActiveBackup, name) + defer app.Store().Remove(StoreKeyActiveBackup) + + startTime := time.Now() + + event := new(BackupEvent) + event.App = app + event.Context = ctx + event.Name = name + // default root dir entries to exclude from the backup generation + event.Exclude = []string{ + LocalBackupsDirName, + LocalTempDirName, + LocalNotifyDirName, + LocalAutocertCacheDirName, + lostFoundDirName, + } + + return app.OnBackupCreate().Trigger(event, func(e *BackupEvent) error { + if e.Name == "" { + e.Name = generateBackupName(e.App, "pb_backup_") + } + + app.Logger().Debug("[" + e.Name + "] zip archive started") + + // create backup zip + // (it needs to be inside the current pb_data to avoid "cross-device link" errors) + // ----------------------------------------------------------- + tempZipPath := filepath.Join(app.DataDir(), LocalTempDirName, "pb_backup_"+security.PseudorandomString(6)) + err := createZip(e, tempZipPath) + if err != nil { + return err + } + defer os.Remove(tempZipPath) + + // persist the backup in the backups filesystem + // ----------------------------------------------------------- + fsys, err := e.App.NewBackupsFilesystem() + if err != nil { + return err + } + defer fsys.Close() + + fsys.SetContext(e.Context) + + file, err := filesystem.NewFileFromPath(tempZipPath) + if err != nil { + return err + } + file.OriginalName = e.Name + file.Name = file.OriginalName + + err = fsys.UploadFile(file, file.Name) + if err != nil { + return err + } + + app.Logger().Debug( + "["+e.Name+"] zip archive completed", + slog.Float64("execTime", float64(time.Since(startTime))/float64(time.Millisecond)), + ) + + return nil + }) +} + +func createZip(be *BackupEvent, tempZipPath string) error { + logPrefix := "[" + be.Name + "] " + + // make sure that the special temp directory exists + localTempDir := filepath.Dir(tempZipPath) + if err := os.MkdirAll(localTempDir, os.ModePerm); err != nil { + return fmt.Errorf(logPrefix+"failed to create temp dir: %w", err) + } + + const tempFilesHookId = "__pbTempBackupFilesystemWatcher__" + defer func() { + // unbind again in cacase of an error + be.App.onFilesystemDelete().Unbind(tempFilesHookId) + be.App.onFilesystemNewWriter().Unbind(tempFilesHookId) + }() + + zipper, err := newZipWriter(tempZipPath) + if err != nil { + return err + } + defer zipper.close() + + excluded := store.New[string, struct{}](nil) + for _, name := range be.Exclude { + excluded.Set(normalizePathExclude(name), struct{}{}) + } + + // init deleted files tracker + // --------------------------------------------------------------- + be.App.onFilesystemDelete().Bind(&hook.Handler[*FilesystemDeleteEvent]{ + Id: tempFilesHookId, + Priority: -99, + Func: func(e *FilesystemDeleteEvent) error { + // note: the zip header name allow only forward slashes + zipPath := path.Join(LocalStorageDirName, e.FileKey) + + if excluded.Has(normalizePathExclude(zipPath)) || be.App.Settings().S3.Enabled { + return e.Next() + } + + localPath := filepath.Join( + be.App.DataDir(), + LocalStorageDirName, + e.FileKey, + ) + + // copy to zip before delete + err := zipper.copyFileToZip(localPath, zipPath) + if err != nil { + // it is ok to ignore directories + if !errors.Is(err, errIsDir) { + be.App.Logger().Warn( + logPrefix+"failed to copy file in backup zip before delete", + slog.Any("error", err), + slog.String("file", e.FileKey), + ) + } + } else { + // mark that it was already copied + excluded.Set(normalizePathExclude(zipPath), struct{}{}) + } + + // proceed with the normal deletion + return e.Next() + }, + }) + + // copy data.db + // --------------------------------------------------------------- + dataStartTime := time.Now() + tempDataDBPath := filepath.Join(localTempDir, dataDBFilename) + + _, err = be.App.ConcurrentDB().NewQuery("VACUUM INTO {:path}").Bind(dbx.Params{"path": tempDataDBPath}).Execute() + if err != nil { + return err + } + + // eagerly stop listening for deleted files since we already have what we needed + be.App.onFilesystemDelete().Unbind(tempFilesHookId) + + be.App.Logger().Debug( + logPrefix+dataDBFilename+" copy completed", + slog.Float64("execTime", float64(time.Since(dataStartTime))/float64(time.Millisecond)), + ) + + err = zipper.copyFileToZip(tempDataDBPath, dataDBFilename) + if err != nil { + _ = os.Remove(tempDataDBPath) + return err + } + _ = os.Remove(tempDataDBPath) + + excluded.Set(normalizePathExclude(dataDBFilename), struct{}{}) + excluded.Set(normalizePathExclude(dataDBFilename+"-wal"), struct{}{}) + excluded.Set(normalizePathExclude(dataDBFilename+"-shm"), struct{}{}) + + // init to-be-created files tracker + // --------------------------------------------------------------- + be.App.onFilesystemNewWriter().Bind(&hook.Handler[*FilesystemNewWriterEvent]{ + Id: tempFilesHookId, + Priority: -99, + Func: func(e *FilesystemNewWriterEvent) error { + if !be.App.Settings().S3.Enabled { + // mark for exclude even if the writer eventually fails + // (all record files have random name so collisions are unlikely) + name := normalizePathExclude(filepath.Join(LocalStorageDirName, e.FileKey)) + excluded.Set(name, struct{}{}) + } + + return e.Next() + }, + }) + + // copy auxiliary.db + // --------------------------------------------------------------- + auxStartTime := time.Now() + tempAuxDBPath := filepath.Join(localTempDir, auxDBFilename) + + _, err = be.App.AuxConcurrentDB().NewQuery("VACUUM INTO {:path}").Bind(dbx.Params{"path": tempAuxDBPath}).Execute() + if err != nil { + return err + } + + be.App.Logger().Debug( + logPrefix+auxDBFilename+" copy completed", + slog.Float64("execTime", float64(time.Since(auxStartTime))/float64(time.Millisecond)), + ) + + err = zipper.copyFileToZip(tempAuxDBPath, auxDBFilename) + if err != nil { + _ = os.Remove(tempAuxDBPath) + return err + } + _ = os.Remove(tempAuxDBPath) + + excluded.Set(normalizePathExclude(auxDBFilename), struct{}{}) + excluded.Set(normalizePathExclude(auxDBFilename+"-wal"), struct{}{}) + excluded.Set(normalizePathExclude(auxDBFilename+"-shm"), struct{}{}) + + // try to run manual checkpoints to ensure that all wal writes during the + // previous VACUUM INTO are transferred and don't accumulate + // (errors are ignore because some drivers may not support the wal_checkpoint pragma) + // --------------------------------------------------------------- + _, _ = be.App.NonconcurrentDB().NewQuery("PRAGMA wal_checkpoint(TRUNCATE)").Execute() + _, _ = be.App.AuxNonconcurrentDB().NewQuery("PRAGMA wal_checkpoint(TRUNCATE)").Execute() + + // copy the rest of the pb_data + // --------------------------------------------------------------- + err = zipper.copyDirToZip(os.DirFS(be.App.DataDir()), excluded) + if err != nil { + return err + } + + return zipper.close() +} + +// normalize the provided file path to always end with forward slash +func normalizePathExclude(filePath string) string { + return path.Clean(filePath) + "/" +} + +type zipWriter struct { + mu sync.Mutex + w *zip.Writer + f *os.File + closed bool +} + +func newZipWriter(zipFilePath string) (*zipWriter, error) { + f, err := os.Create(zipFilePath) + if err != nil { + return nil, err + } + + w := zip.NewWriter(f) + w.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) { + return flate.NewWriter(out, flate.BestSpeed) + }) + + return &zipWriter{ + w: w, + f: f, + }, nil +} + +func (z *zipWriter) close() error { + z.mu.Lock() + defer z.mu.Unlock() + + if z.closed { + return nil + } + + z.closed = true + + return errors.Join(z.w.Close(), z.f.Close()) +} + +func (z *zipWriter) copyFileToZip(localPath string, zipPath string) error { + info, err := os.Stat(localPath) + if err != nil { + return err + } + + if info.IsDir() { + return errIsDir + } + + h, err := zip.FileInfoHeader(info) + if err != nil { + return err + } + + h.Name = zipPath + h.Method = zip.Deflate + + z.mu.Lock() + defer z.mu.Unlock() + + if z.closed { + return errors.New("zip writer is already closed") + } + + fw, err := z.w.CreateHeader(h) + if err != nil { + return err + } + + f, err := os.Open(localPath) + if err != nil { + return err + } + defer f.Close() + + _, err = io.Copy(fw, f) + + return err +} + +func (z *zipWriter) copyDirToZip(fsys fs.FS, excludedPrefixes *store.Store[string, struct{}]) error { + return fs.WalkDir(fsys, ".", func(name string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + // skip excluded prefixes + if excludedPrefixes != nil { + check := normalizePathExclude(name) + prefixes := excludedPrefixes.Keys() // refetch to avoid races + for _, prefix := range prefixes { + if strings.HasPrefix(check, prefix) { + if d.IsDir() { + return fs.SkipDir + } + return nil + } + } + } + + if d.IsDir() { + return nil + } + + info, err := d.Info() + if err != nil { + return err + } + + h, err := zip.FileInfoHeader(info) + if err != nil { + return err + } + + h.Name = name + h.Method = zip.Deflate + + z.mu.Lock() + defer z.mu.Unlock() + + if z.closed { + // note: fs.WalkDir perform direct comparison with the value + return fs.SkipAll + } + + fw, err := z.w.CreateHeader(h) + if err != nil { + return err + } + + f, err := fsys.Open(name) + if err != nil { + return err + } + defer f.Close() + + _, err = io.Copy(fw, f) + + return err + }) +} diff --git a/core/base_backup.go b/core/backup_restore.go similarity index 51% rename from core/base_backup.go rename to core/backup_restore.go index 12d66c1033..c4a17ccafa 100644 --- a/core/base_backup.go +++ b/core/backup_restore.go @@ -9,117 +9,12 @@ import ( "os" "path/filepath" "runtime" - "sort" - "time" "github.com/pocketbase/pocketbase/tools/archive" - "github.com/pocketbase/pocketbase/tools/filesystem" - "github.com/pocketbase/pocketbase/tools/inflector" "github.com/pocketbase/pocketbase/tools/osutils" "github.com/pocketbase/pocketbase/tools/security" ) -const ( - StoreKeyActiveBackup = "@activeBackup" -) - -// CreateBackup creates a new backup of the current app pb_data directory. -// -// If name is empty, it will be autogenerated. -// If backup with the same name exists, the new backup file will replace it. -// -// The backup is executed within a transaction, meaning that new writes -// will be temporary "blocked" until the backup file is generated. -// -// To safely perform the backup, it is recommended to have free disk space -// for at least 2x the size of the pb_data directory. -// -// By default backups are stored in pb_data/backups -// (the backups directory itself is excluded from the generated backup). -// -// When using S3 storage for the uploaded collection files, you have to -// take care manually to backup those since they are not part of the pb_data. -// -// Backups can be stored on S3 if it is configured in app.Settings().Backups. -func (app *BaseApp) CreateBackup(ctx context.Context, name string) error { - if app.Store().Has(StoreKeyActiveBackup) { - return errors.New("try again later - another backup/restore operation has already been started") - } - - app.Store().Set(StoreKeyActiveBackup, name) - defer app.Store().Remove(StoreKeyActiveBackup) - - event := new(BackupEvent) - event.App = app - event.Context = ctx - event.Name = name - // default root dir entries to exclude from the backup generation - event.Exclude = []string{ - LocalBackupsDirName, - LocalTempDirName, - LocalNotifyDirName, - LocalAutocertCacheDirName, - lostFoundDirName, - } - - return app.OnBackupCreate().Trigger(event, func(e *BackupEvent) error { - // generate a default name if missing - if e.Name == "" { - e.Name = generateBackupName(e.App, "pb_backup_") - } - - // make sure that the special temp directory exists - // note: it needs to be inside the current pb_data to avoid "cross-device link" errors - localTempDir := filepath.Join(e.App.DataDir(), LocalTempDirName) - if err := os.MkdirAll(localTempDir, os.ModePerm); err != nil { - return fmt.Errorf("failed to create a temp dir: %w", err) - } - - // archive pb_data in a temp directory, excluding the "backups" and the temp dirs - // - // run in transaction to temporary block other writes (transactions uses the NonconcurrentDB connection) - // --- - tempPath := filepath.Join(localTempDir, "pb_backup_"+security.PseudorandomString(6)) - createErr := e.App.RunInTransaction(func(txApp App) error { - return txApp.AuxRunInTransaction(func(txApp App) error { - // run manual checkpoint and truncate the WAL files - // (errors are ignored because it is not that important and the PRAGMA may not be supported by the used driver) - txApp.DB().NewQuery("PRAGMA wal_checkpoint(TRUNCATE)").Execute() - txApp.AuxDB().NewQuery("PRAGMA wal_checkpoint(TRUNCATE)").Execute() - - return archive.Create(txApp.DataDir(), tempPath, e.Exclude...) - }) - }) - if createErr != nil { - return createErr - } - defer os.Remove(tempPath) - - // persist the backup in the backups filesystem - // --- - fsys, err := e.App.NewBackupsFilesystem() - if err != nil { - return err - } - defer fsys.Close() - - fsys.SetContext(e.Context) - - file, err := filesystem.NewFileFromPath(tempPath) - if err != nil { - return err - } - file.OriginalName = e.Name - file.Name = file.OriginalName - - if err := fsys.UploadFile(file, file.Name); err != nil { - return err - } - - return nil - }) -} - // RestoreBackup restores the backup with the specified name and restarts // the current running application process. // @@ -299,124 +194,3 @@ func (app *BaseApp) RestoreBackup(ctx context.Context, name string) error { return nil }) } - -// registerAutobackupHooks registers the autobackup app serve hooks. -func (app *BaseApp) registerAutobackupHooks() { - const jobId = "__pbAutoBackup__" - - loadJob := func() { - rawSchedule := app.Settings().Backups.Cron - if rawSchedule == "" { - app.Cron().Remove(jobId) - return - } - - app.Cron().Add(jobId, rawSchedule, func() { - const autoPrefix = "@auto_pb_backup_" - - name := generateBackupName(app, autoPrefix) - - if err := app.CreateBackup(context.Background(), name); err != nil { - app.Logger().Error( - "[Backup cron] Failed to create backup", - slog.String("name", name), - slog.String("error", err.Error()), - ) - - alertError := sendSystemAlertToAllSuperusers( - app, - "Autobackup failure", - "Failed to create/upload automated backup. Raw error:\n"+err.Error(), - ) - if alertError != nil { - app.Logger().Warn( - "[Backup cron] Failed to send backup error alerts", - slog.String("name", name), - slog.String("error", alertError.Error()), - ) - } - } - - maxKeep := app.Settings().Backups.CronMaxKeep - - if maxKeep == 0 { - return // no explicit limit - } - - fsys, err := app.NewBackupsFilesystem() - if err != nil { - app.Logger().Error( - "[Backup cron] Failed to initialize the backup filesystem", - slog.String("error", err.Error()), - ) - return - } - defer fsys.Close() - - files, err := fsys.List(autoPrefix) - if err != nil { - app.Logger().Error( - "[Backup cron] Failed to list autogenerated backups", - slog.String("error", err.Error()), - ) - return - } - - if maxKeep >= len(files) { - return // nothing to remove - } - - // sort desc - sort.Slice(files, func(i, j int) bool { - return files[i].ModTime.After(files[j].ModTime) - }) - - // keep only the most recent n auto backup files - toRemove := files[maxKeep:] - - for _, f := range toRemove { - if err := fsys.Delete(f.Key); err != nil { - app.Logger().Error( - "[Backup cron] Failed to remove old autogenerated backup", - slog.String("key", f.Key), - slog.String("error", err.Error()), - ) - } - } - }) - } - - app.OnBootstrap().BindFunc(func(e *BootstrapEvent) error { - if err := e.Next(); err != nil { - return err - } - - loadJob() - - return nil - }) - - app.OnSettingsReload().BindFunc(func(e *SettingsReloadEvent) error { - if err := e.Next(); err != nil { - return err - } - - loadJob() - - return nil - }) -} - -func generateBackupName(app App, prefix string) string { - appName := inflector.Snakecase(app.Settings().Meta.AppName) - if len(appName) > 50 { - appName = appName[:50] - } - - return fmt.Sprintf( - "%s%s_%s.zip", - prefix, - appName, - time.Now().UTC().Format("20060102150405"), - ) -} diff --git a/core/base_backup_test.go b/core/backup_test.go similarity index 98% rename from core/base_backup_test.go rename to core/backup_test.go index 47d00e33fb..9a1eb85bdc 100644 --- a/core/base_backup_test.go +++ b/core/backup_test.go @@ -126,11 +126,7 @@ func verifyBackupContent(app core.App, path string) error { expectedRootEntries := []string{ "storage", "data.db", - "data.db-shm", - "data.db-wal", "auxiliary.db", - "auxiliary.db-shm", - "auxiliary.db-wal", ".gitignore", } diff --git a/core/base.go b/core/base.go index 0fbcb6aa7e..3d0f41c11f 100644 --- a/core/base.go +++ b/core/base.go @@ -44,6 +44,9 @@ const ( // @todo consider removing after backups refactoring lostFoundDirName string = "lost+found" + + dataDBFilename string = "data.db" + auxDBFilename string = "auxiliary.db" ) // FilesManager defines an interface with common methods that files manager models should implement. @@ -146,6 +149,17 @@ type BaseApp struct { onMailerRecordOTPSend *hook.Hook[*MailerRecordEvent] onMailerRecordAuthAlertSend *hook.Hook[*MailerRecordEvent] + // filesystem event hooks + // + // @todo 1: + // intentionally not exposed since the events are too "chatty" and + // can cause unnecessary userland tests breaking changes; + // reevaluate once refactoring the file_field + // + // @todo 2: if exposed consider registering the same for the backup filesystem + _onFilesystemNewWriter *hook.Hook[*FilesystemNewWriterEvent] + _onFilesystemDelete *hook.Hook[*FilesystemDeleteEvent] + // realtime api event hooks onRealtimeConnectRequest *hook.Hook[*RealtimeConnectRequestEvent] onRealtimeMessageSend *hook.Hook[*RealtimeMessageEvent] @@ -294,6 +308,10 @@ func (app *BaseApp) initHooks() { app.onMailerRecordOTPSend = &hook.Hook[*MailerRecordEvent]{} app.onMailerRecordAuthAlertSend = &hook.Hook[*MailerRecordEvent]{} + // filesystem event hooks + app._onFilesystemNewWriter = &hook.Hook[*FilesystemNewWriterEvent]{} + app._onFilesystemDelete = &hook.Hook[*FilesystemDeleteEvent]{} + // realtime API event hooks app.onRealtimeConnectRequest = &hook.Hook[*RealtimeConnectRequestEvent]{} app.onRealtimeMessageSend = &hook.Hook[*RealtimeMessageEvent]{} @@ -712,9 +730,10 @@ func (app *BaseApp) NewMailClient() mailer.Mailer { // // NB! Make sure to call Close() on the returned result // after you are done working with it. -func (app *BaseApp) NewFilesystem() (*filesystem.System, error) { +func (app *BaseApp) NewFilesystem() (fsys *filesystem.System, err error) { if app.settings != nil && app.settings.S3.Enabled { - return filesystem.NewS3( + // S3 + fsys, err = filesystem.NewS3( app.settings.S3.Bucket, app.settings.S3.Region, app.settings.S3.Endpoint, @@ -722,10 +741,41 @@ func (app *BaseApp) NewFilesystem() (*filesystem.System, error) { app.settings.S3.Secret, app.settings.S3.ForcePathStyle, ) + } else { + // local filesystem + fsys, err = filesystem.NewLocal(filepath.Join(app.DataDir(), LocalStorageDirName)) + } + if err != nil { + return nil, err } - // fallback to local filesystem - return filesystem.NewLocal(filepath.Join(app.DataDir(), LocalStorageDirName)) + // attach delete hook + if app._onFilesystemDelete.Length() > 0 { + fsys.OnDelete().BindFunc(func(originalEvent *filesystem.DeleteEvent) error { + appEvent := new(FilesystemDeleteEvent) + appEvent.DeleteEvent = originalEvent + appEvent.App = app + + return app._onFilesystemDelete.Trigger(appEvent, func(fde *FilesystemDeleteEvent) error { + return originalEvent.Next() + }) + }) + } + + // attach write hook + if app._onFilesystemNewWriter.Length() > 0 { + fsys.OnNewWriter().BindFunc(func(originalEvent *filesystem.NewWriterEvent) error { + appEvent := new(FilesystemNewWriterEvent) + appEvent.NewWriterEvent = originalEvent + appEvent.App = app + + return app._onFilesystemNewWriter.Trigger(appEvent, func(fwe *FilesystemNewWriterEvent) error { + return originalEvent.Next() + }) + }) + } + + return fsys, nil } // NewBackupsFilesystem creates a new local or S3 filesystem instance @@ -1016,6 +1066,18 @@ func (app *BaseApp) OnMailerRecordAuthAlertSend(tags ...string) *hook.TaggedHook return hook.NewTaggedHook(app.onMailerRecordAuthAlertSend, tags...) } +// ------------------------------------------------------------------- +// Filesystem event hooks +// ------------------------------------------------------------------- + +func (app *BaseApp) onFilesystemNewWriter() *hook.Hook[*FilesystemNewWriterEvent] { + return app._onFilesystemNewWriter +} + +func (app *BaseApp) onFilesystemDelete() *hook.Hook[*FilesystemDeleteEvent] { + return app._onFilesystemDelete +} + // ------------------------------------------------------------------- // Realtime API event hooks // ------------------------------------------------------------------- @@ -1173,7 +1235,7 @@ func (app *BaseApp) OnBatchRequest() *hook.Hook[*BatchRequestEvent] { // ------------------------------------------------------------------- func (app *BaseApp) initDataDB() error { - dbPath := filepath.Join(app.DataDir(), "data.db") + dbPath := filepath.Join(app.DataDir(), dataDBFilename) concurrentDB, err := app.config.DBConnect(dbPath) if err != nil { @@ -1235,7 +1297,7 @@ func normalizeSQLLog(sql string) string { func (app *BaseApp) initAuxDB() error { // note: renamed to "auxiliary" because "aux" is a reserved Windows filename // (see https://github.com/pocketbase/pocketbase/issues/5607) - dbPath := filepath.Join(app.DataDir(), "auxiliary.db") + dbPath := filepath.Join(app.DataDir(), auxDBFilename) concurrentDB, err := app.config.DBConnect(dbPath) if err != nil { diff --git a/core/base_test.go b/core/base_test.go index 5555e020d4..0e383f8d5e 100644 --- a/core/base_test.go +++ b/core/base_test.go @@ -7,6 +7,7 @@ import ( "os" "slices" "testing" + "testing/synctest" "time" _ "unsafe" @@ -302,31 +303,34 @@ func TestBaseAppLoggerWrites(t *testing.T) { }) t.Run("test batch logs writes", func(t *testing.T) { - app.Settings().Logs.MaxDays = 1 + synctest.Test(t, func(t *testing.T) { + app.Settings().Logs.MaxDays = 1 - for i := 0; i < logsThreshold-1; i++ { - app.Logger().Error("test") - } + for i := 0; i < logsThreshold-1; i++ { + app.Logger().Error("test") + } - if total := totalLogs(app, t); total != 0 { - t.Fatalf("Expected no logs, got %d", total) - } + if total := totalLogs(app, t); total != 0 { + t.Fatalf("Expected no logs, got %d", total) + } - // should trigger batch write - app.Logger().Error("test") + // should trigger batch write + app.Logger().Error("test") - // should be added for the next batch write - app.Logger().Error("test") + // should be added for the next batch write + app.Logger().Error("test") - if total := totalLogs(app, t); total != logsThreshold { - t.Fatalf("Expected %d logs, got %d", logsThreshold, total) - } + if total := totalLogs(app, t); total != logsThreshold { + t.Fatalf("Expected %d logs, got %d", logsThreshold, total) + } - // wait for ~3 secs to check the timer trigger - time.Sleep(3200 * time.Millisecond) - if total := totalLogs(app, t); total != logsThreshold+1 { - t.Fatalf("Expected %d logs, got %d", logsThreshold+1, total) - } + // wait for 3 secs to check the timer trigger + synctest.Sleep(3000 * time.Millisecond) + + if total := totalLogs(app, t); total != logsThreshold { + t.Fatalf("Expected %d logs, got %d", logsThreshold, total) + } + }) }) } diff --git a/core/collection_import.go b/core/collection_import.go index 47f15dec2a..e169a55892 100644 --- a/core/collection_import.go +++ b/core/collection_import.go @@ -4,7 +4,8 @@ import ( "cmp" "context" "database/sql" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "slices" @@ -186,7 +187,11 @@ func (app *BaseApp) ImportCollections(toImport []map[string]any, deleteMissing b ) if err := validator.run(); err != nil { // serialize the validation error(s) - serializedErr, _ := json.MarshalIndent(err, "", " ") + serializedErr, _ := json.Marshal( + err, + jsontext.WithIndentPrefix(""), + jsontext.WithIndent(" "), + ) return validation.Errors{"collections": validation.NewError( "validation_collections_import_failure", diff --git a/core/collection_import_test.go b/core/collection_import_test.go index 74df2f6eb5..3e5f952d7a 100644 --- a/core/collection_import_test.go +++ b/core/collection_import_test.go @@ -1,7 +1,7 @@ package core_test import ( - "encoding/json" + "encoding/json/v2" "strings" "testing" @@ -449,7 +449,7 @@ func TestImportCollectionsCreateRules(t *testing.T) { t.Fatal(err) } - raw, err := json.Marshal(collection) + raw, err := json.Marshal(collection, json.Deterministic(true)) if err != nil { t.Fatal(err) } diff --git a/core/collection_model.go b/core/collection_model.go index bcf9e95408..2ed040ab3d 100644 --- a/core/collection_model.go +++ b/core/collection_model.go @@ -1,7 +1,7 @@ package core import ( - "encoding/json" + "encoding/json/v2" "fmt" "strconv" "strings" @@ -522,8 +522,6 @@ func (m *Collection) unmarshalRawOptions() error { // For new/"blank" Collection models it replaces the model with a factory // instance and then unmarshal the provided data one on top of it. func (m *Collection) UnmarshalJSON(b []byte) error { - type alias *Collection - // initialize the default fields // (e.g. in case the collection was NOT created using the designated factories) if m.IsNew() && m.Type == "" { @@ -540,7 +538,8 @@ func (m *Collection) UnmarshalJSON(b []byte) error { *m = *blank } - return json.Unmarshal(b, alias(m)) + type alias Collection + return json.Unmarshal(b, (*alias)(m)) } // MarshalJSON implements the [json.Marshaler] interface. @@ -550,10 +549,12 @@ func (m *Collection) UnmarshalJSON(b []byte) error { func (m Collection) MarshalJSON() ([]byte, error) { switch m.Type { case CollectionTypeView: - return json.Marshal(struct { + alias := struct { baseCollection collectionViewOptions - }{m.baseCollection, m.collectionViewOptions}) + }{m.baseCollection, m.collectionViewOptions} + + return json.Marshal(alias, json.Deterministic(true)) case CollectionTypeAuth: alias := struct { baseCollection @@ -582,15 +583,15 @@ func (m Collection) MarshalJSON() ([]byte, error) { alias.OAuth2.Providers = redactedProviders } - return json.Marshal(alias) + return json.Marshal(alias, json.Deterministic(true)) default: - return json.Marshal(m.baseCollection) + return json.Marshal(m.baseCollection, json.Deterministic(true)) } } // String returns a string representation of the current collection. func (m Collection) String() string { - raw, _ := json.Marshal(m) + raw, _ := m.MarshalJSON() return string(raw) } diff --git a/core/collection_model_auth_options_test.go b/core/collection_model_auth_options_test.go index a0aaa3bcf8..45a00d0358 100644 --- a/core/collection_model_auth_options_test.go +++ b/core/collection_model_auth_options_test.go @@ -2,7 +2,7 @@ package core_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "strings" "testing" @@ -1016,8 +1016,8 @@ func TestOAuth2ProviderConfigInitProvider(t *testing.T) { t.Fatalf("Expected PKCE %v, got %v", *s.expectedConfig.PKCE, provider.PKCE()) } - rawMeta, _ := json.Marshal(provider.Extra()) - expectedMeta, _ := json.Marshal(s.expectedConfig.Extra) + rawMeta, _ := json.Marshal(provider.Extra(), json.Deterministic(true)) + expectedMeta, _ := json.Marshal(s.expectedConfig.Extra, json.Deterministic(true)) if !bytes.Equal(rawMeta, expectedMeta) { t.Fatalf("Expected PKCE %v, got %v", *s.expectedConfig.PKCE, provider.PKCE()) } diff --git a/core/collection_model_test.go b/core/collection_model_test.go index fec1e041e4..e68095a94c 100644 --- a/core/collection_model_test.go +++ b/core/collection_model_test.go @@ -2,7 +2,7 @@ package core_test import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "slices" @@ -618,7 +618,7 @@ func TestCollectionUnmarshalJSON(t *testing.T) { t.Fatal(err) } - rawResult, err := json.Marshal(collection) + rawResult, err := json.Marshal(collection, json.Deterministic(true)) if err != nil { t.Fatal(err) } @@ -829,7 +829,7 @@ func TestCollectionDBExport(t *testing.T) { }, { core.CollectionTypeAuth, - `{"createRule":"1=3","created":"2024-07-01 01:02:03.456Z","deleteRule":"1=5","fields":[{"help":"","hidden":false,"id":"f1_id","name":"f1","presentable":false,"required":false,"system":true,"type":"bool"},{"help":"","hidden":false,"id":"f2_id","name":"f2","presentable":false,"required":true,"system":false,"type":"bool"}],"id":"test_id","indexes":["CREATE INDEX idx1 on test_name(id)","CREATE INDEX idx2 on test_name(id)"],"listRule":"1=1","name":"test_name","options":{"authRule":null,"manageRule":"1=6","authAlert":{"enabled":false,"emailTemplate":{"subject":"","body":""}},"oauth2":{"providers":null,"mappedFields":{"id":"","name":"","username":"","avatarURL":""},"enabled":false},"passwordAuth":{"enabled":false,"identityFields":null},"mfa":{"enabled":false,"duration":0,"rule":""},"otp":{"enabled":false,"duration":0,"length":0,"emailTemplate":{"subject":"","body":""}},"authToken":{"duration":0},"passwordResetToken":{"duration":0},"emailChangeToken":{"duration":0},"verificationToken":{"duration":0},"fileToken":{"duration":0},"verificationTemplate":{"subject":"","body":""},"resetPasswordTemplate":{"subject":"","body":""},"confirmEmailChangeTemplate":{"subject":"","body":""}},"system":true,"type":"auth","updateRule":"1=4","updated":"2024-07-01 01:02:03.456Z","viewRule":"1=7"}`, + `{"createRule":"1=3","created":"2024-07-01 01:02:03.456Z","deleteRule":"1=5","fields":[{"help":"","hidden":false,"id":"f1_id","name":"f1","presentable":false,"required":false,"system":true,"type":"bool"},{"help":"","hidden":false,"id":"f2_id","name":"f2","presentable":false,"required":true,"system":false,"type":"bool"}],"id":"test_id","indexes":["CREATE INDEX idx1 on test_name(id)","CREATE INDEX idx2 on test_name(id)"],"listRule":"1=1","name":"test_name","options":{"authRule":null,"manageRule":"1=6","authAlert":{"enabled":false,"emailTemplate":{"subject":"","body":""}},"oauth2":{"providers":[],"mappedFields":{"id":"","name":"","username":"","avatarURL":""},"enabled":false},"passwordAuth":{"enabled":false,"identityFields":[]},"mfa":{"enabled":false,"duration":0,"rule":""},"otp":{"enabled":false,"duration":0,"length":0,"emailTemplate":{"subject":"","body":""}},"authToken":{"duration":0},"passwordResetToken":{"duration":0},"emailChangeToken":{"duration":0},"verificationToken":{"duration":0},"fileToken":{"duration":0},"verificationTemplate":{"subject":"","body":""},"resetPasswordTemplate":{"subject":"","body":""},"confirmEmailChangeTemplate":{"subject":"","body":""}},"system":true,"type":"auth","updateRule":"1=4","updated":"2024-07-01 01:02:03.456Z","viewRule":"1=7"}`, }, } @@ -860,7 +860,7 @@ func TestCollectionDBExport(t *testing.T) { t.Fatal(err) } - raw, err := json.Marshal(result) + raw, err := json.Marshal(result, json.Deterministic(true)) if err != nil { t.Fatal(err) } diff --git a/core/collection_query.go b/core/collection_query.go index b6e47b6b57..3821067886 100644 --- a/core/collection_query.go +++ b/core/collection_query.go @@ -3,7 +3,7 @@ package core import ( "bytes" "database/sql" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "slices" @@ -324,12 +324,12 @@ func resaveViewsWithChangedFields(app App, excludeIds ...string) error { f.SetId("") } - encodedNewFields, err := json.Marshal(newFields) + encodedNewFields, err := json.Marshal(newFields, json.Deterministic(true)) if err != nil { return err } - encodedOldFields, err := json.Marshal(oldFields) + encodedOldFields, err := json.Marshal(oldFields, json.Deterministic(true)) if err != nil { return err } diff --git a/core/collection_record_table_sync_test.go b/core/collection_record_table_sync_test.go index 48b22e10ad..ab18e54318 100644 --- a/core/collection_record_table_sync_test.go +++ b/core/collection_record_table_sync_test.go @@ -2,7 +2,7 @@ package core_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "testing" "github.com/pocketbase/dbx" @@ -278,12 +278,12 @@ func TestSingleVsMultipleValuesNormalization(t *testing.T) { t.Fatalf("Failed to load record: %v", err) } - encodedResult, err := json.Marshal(result) + encodedResult, err := json.Marshal(result, json.Deterministic(true)) if err != nil { t.Fatalf("Failed to encode result: %v", err) } - encodedExpectation, err := json.Marshal(s.expected) + encodedExpectation, err := json.Marshal(s.expected, json.Deterministic(true)) if err != nil { t.Fatalf("Failed to encode expectation: %v", err) } diff --git a/core/db_connect.go b/core/db_connect.go index 8a749b5281..e190084868 100644 --- a/core/db_connect.go +++ b/core/db_connect.go @@ -11,7 +11,7 @@ func DefaultDBConnect(dbPath string) (*dbx.DB, error) { // Note: the busy_timeout pragma must be first because // the connection needs to be set to block on busy before WAL mode // is set in case it hasn't been already set by another connection. - pragmas := "?_pragma=busy_timeout(10000)&_pragma=journal_mode(WAL)&_pragma=journal_size_limit(200000000)&_pragma=synchronous(NORMAL)&_pragma=foreign_keys(ON)&_pragma=temp_store(MEMORY)&_pragma=cache_size(-32000)" + pragmas := "?_pragma=busy_timeout(10000)&_pragma=journal_mode(WAL)&_pragma=journal_size_limit(200000000)&_pragma=synchronous(NORMAL)&_pragma=foreign_keys(ON)&_pragma=temp_store(MEMORY)&_pragma=cache_size(-32000)&_defensive=1" db, err := dbx.Open("sqlite", dbPath+pragmas) if err != nil { diff --git a/core/db_table_test.go b/core/db_table_test.go index 2dede6bedd..2731c8b84b 100644 --- a/core/db_table_test.go +++ b/core/db_table_test.go @@ -3,7 +3,7 @@ package core_test import ( "context" "database/sql" - "encoding/json" + "encoding/json/v2" "fmt" "slices" "testing" @@ -120,7 +120,11 @@ func TestTableInfo(t *testing.T) { t.Run(fmt.Sprintf("%d_%s", i, s.tableName), func(t *testing.T) { rows, _ := app.TableInfo(s.tableName) - raw, err := json.Marshal(rows) + raw, err := json.Marshal( + rows, + json.Deterministic(true), + json.FormatNilSliceAsNull(true), + ) if err != nil { t.Fatal(err) } diff --git a/core/event_request_test.go b/core/event_request_test.go index 6cfc3dc095..26a331a49a 100644 --- a/core/event_request_test.go +++ b/core/event_request_test.go @@ -1,7 +1,7 @@ package core_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -183,7 +183,7 @@ func TestRequestEventRequestInfo(t *testing.T) { t.Fatalf("Failed to resolve request info: %v", err) } - raw, err := json.Marshal(info) + raw, err := json.Marshal(info, json.Deterministic(true)) if err != nil { t.Fatalf("Failed to serialize request info: %v", err) } @@ -205,7 +205,7 @@ func TestRequestEventRequestInfo(t *testing.T) { t.Fatalf("Failed to resolve request info: %v", err) } - raw, err := json.Marshal(info) + raw, err := json.Marshal(info, json.Deterministic(true)) if err != nil { t.Fatalf("Failed to serialize request info: %v", err) } @@ -308,7 +308,7 @@ func TestRequestInfoClone(t *testing.T) { // check the original data // --- - originalRaw, err := json.Marshal(info) + originalRaw, err := json.Marshal(info, json.Deterministic(true)) if err != nil { t.Fatalf("Failed to serialize original request info: %v", err) } @@ -321,7 +321,7 @@ func TestRequestInfoClone(t *testing.T) { // check the clone data // --- - cloneRaw, err := json.Marshal(clone) + cloneRaw, err := json.Marshal(clone, json.Deterministic(true)) if err != nil { t.Fatalf("Failed to serialize clone request info: %v", err) } diff --git a/core/events.go b/core/events.go index efb226644b..81b95955ee 100644 --- a/core/events.go +++ b/core/events.go @@ -8,6 +8,7 @@ import ( "time" "github.com/pocketbase/pocketbase/tools/auth" + "github.com/pocketbase/pocketbase/tools/filesystem" "github.com/pocketbase/pocketbase/tools/hook" "github.com/pocketbase/pocketbase/tools/mailer" "github.com/pocketbase/pocketbase/tools/router" @@ -188,6 +189,24 @@ type MailerRecordEvent struct { Meta map[string]any } +// ------------------------------------------------------------------- +// Filesystem events data +// ------------------------------------------------------------------- + +type FilesystemNewWriterEvent struct { + hook.Event + *filesystem.NewWriterEvent + + App App +} + +type FilesystemDeleteEvent struct { + hook.Event + *filesystem.DeleteEvent + + App App +} + // ------------------------------------------------------------------- // Model events data // ------------------------------------------------------------------- diff --git a/core/field_file_test.go b/core/field_file_test.go index 71b29f4cc3..472fc36830 100644 --- a/core/field_file_test.go +++ b/core/field_file_test.go @@ -3,7 +3,7 @@ package core_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "slices" @@ -98,7 +98,7 @@ func TestFileFieldPrepareValue(t *testing.T) { if err != nil { t.Fatal(err) } - f1Raw, err := json.Marshal(f1) + f1Raw, err := json.Marshal(f1, json.Deterministic(true)) if err != nil { t.Fatal(err) } @@ -143,7 +143,7 @@ func TestFileFieldPrepareValue(t *testing.T) { t.Fatal(err) } - vRaw, err := json.Marshal(v) + vRaw, err := json.Marshal(v, json.Deterministic(true)) if err != nil { t.Fatal(err) } @@ -212,7 +212,7 @@ func TestFileFieldDriverValue(t *testing.T) { } } - vRaw, err := json.Marshal(v) + vRaw, err := json.Marshal(v, json.Deterministic(true)) if err != nil { t.Fatal(err) } @@ -699,7 +699,7 @@ func TestFileFieldFindGetter(t *testing.T) { v := getter(record) - raw, err := json.Marshal(v) + raw, err := json.Marshal(v, json.Deterministic(true)) if err != nil { t.Fatal(err) } @@ -816,7 +816,7 @@ func TestFileFieldFindSetter(t *testing.T) { setter(record, s.value) - raw, err := json.Marshal(record.Get(s.field.GetName())) + raw, err := json.Marshal(record.Get(s.field.GetName()), json.Deterministic(true)) if err != nil { t.Fatal(err) } @@ -915,8 +915,8 @@ func TestFileFieldIntercept(t *testing.T) { tests.TestValidationErrors(t, err, []string{"text"}) - raw, _ := json.Marshal(record.GetRaw("file_many")) - expectedRaw, _ := json.Marshal([]any{f1.Name, f3}) + raw, _ := json.Marshal(record.GetRaw("file_many"), json.Deterministic(true)) + expectedRaw, _ := json.Marshal([]any{f1.Name, f3}, json.Deterministic(true)) if !bytes.Equal(expectedRaw, raw) { t.Fatalf("Expected file field value\n%s\ngot\n%s", expectedRaw, raw) } @@ -935,8 +935,8 @@ func TestFileFieldIntercept(t *testing.T) { t.Fatalf("Expected save to succeed, got %v", err) } - raw, _ := json.Marshal(record.GetRaw("file_many")) - expectedRaw, _ := json.Marshal([]any{f1.Name, f3.Name}) + raw, _ := json.Marshal(record.GetRaw("file_many"), json.Deterministic(true)) + expectedRaw, _ := json.Marshal([]any{f1.Name, f3.Name}, json.Deterministic(true)) if !bytes.Equal(expectedRaw, raw) { t.Fatalf("Expected file field value\n%s\ngot\n%s", expectedRaw, raw) } @@ -956,8 +956,8 @@ func TestFileFieldIntercept(t *testing.T) { t.Fatalf("Expected save to succeed, got %v", err) } - raw, _ := json.Marshal(record.GetRaw("file_many")) - expectedRaw, _ := json.Marshal([]any{f3.Name, f4.Name}) + raw, _ := json.Marshal(record.GetRaw("file_many"), json.Deterministic(true)) + expectedRaw, _ := json.Marshal([]any{f3.Name, f4.Name}, json.Deterministic(true)) if !bytes.Equal(expectedRaw, raw) { t.Fatalf("Expected file field value\n%s\ngot\n%s", expectedRaw, raw) } @@ -1067,8 +1067,8 @@ func TestFileFieldInterceptTx(t *testing.T) { t.Fatalf("Expected save to succeed, got %v", err) } - raw, _ := json.Marshal(record.GetRaw("file_many")) - expectedRaw, _ := json.Marshal([]any{f1.Name, f3.Name}) + raw, _ := json.Marshal(record.GetRaw("file_many"), json.Deterministic(true)) + expectedRaw, _ := json.Marshal([]any{f1.Name, f3.Name}, json.Deterministic(true)) if !bytes.Equal(expectedRaw, raw) { t.Fatalf("Expected file field value\n%s\ngot\n%s", expectedRaw, raw) } @@ -1090,8 +1090,8 @@ func TestFileFieldInterceptTx(t *testing.T) { t.Fatalf("Expected save to succeed, got %v", err) } - raw, _ := json.Marshal(record.GetRaw("file_many")) - expectedRaw, _ := json.Marshal([]any{f3.Name, f4.Name}) + raw, _ := json.Marshal(record.GetRaw("file_many"), json.Deterministic(true)) + expectedRaw, _ := json.Marshal([]any{f3.Name, f4.Name}, json.Deterministic(true)) if !bytes.Equal(expectedRaw, raw) { t.Fatalf("Expected file field value\n%s\ngot\n%s", expectedRaw, raw) } diff --git a/core/field_geo_point_test.go b/core/field_geo_point_test.go index 2800a4f245..7597f8e1d5 100644 --- a/core/field_geo_point_test.go +++ b/core/field_geo_point_test.go @@ -2,7 +2,7 @@ package core_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "testing" @@ -57,7 +57,7 @@ func TestGeoPointFieldPrepareValue(t *testing.T) { t.Fatal(err) } - raw, err := json.Marshal(v) + raw, err := json.Marshal(v, json.Deterministic(true)) if err != nil { t.Fatal(err) } diff --git a/core/field_password_test.go b/core/field_password_test.go index 39733e624c..b7c9e019e3 100644 --- a/core/field_password_test.go +++ b/core/field_password_test.go @@ -2,7 +2,7 @@ package core_test import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "strings" @@ -496,7 +496,7 @@ func TestPasswordFieldFindSetter(t *testing.T) { setter(record, s.value) - raw, err := json.Marshal(record.Get(s.field.GetName())) + raw, err := json.Marshal(record.Get(s.field.GetName()), json.Deterministic(true)) if err != nil { t.Fatal(err) } diff --git a/core/field_relation_test.go b/core/field_relation_test.go index 501530fe91..7a05817dd0 100644 --- a/core/field_relation_test.go +++ b/core/field_relation_test.go @@ -2,7 +2,7 @@ package core_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "testing" @@ -119,7 +119,7 @@ func TestRelationFieldPrepareValue(t *testing.T) { t.Fatal(err) } - vRaw, err := json.Marshal(v) + vRaw, err := json.Marshal(v, json.Deterministic(true)) if err != nil { t.Fatal(err) } @@ -181,7 +181,7 @@ func TestRelationFieldDriverValue(t *testing.T) { } } - vRaw, err := json.Marshal(v) + vRaw, err := json.Marshal(v, json.Deterministic(true)) if err != nil { t.Fatal(err) } @@ -590,7 +590,7 @@ func TestRelationFieldFindSetter(t *testing.T) { setter(record, s.value) - raw, err := json.Marshal(record.Get(s.field.GetName())) + raw, err := json.Marshal(record.Get(s.field.GetName()), json.Deterministic(true)) if err != nil { t.Fatal(err) } diff --git a/core/field_select_test.go b/core/field_select_test.go index 093c7011c3..6e716a840e 100644 --- a/core/field_select_test.go +++ b/core/field_select_test.go @@ -2,7 +2,7 @@ package core_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "testing" @@ -119,7 +119,7 @@ func TestSelectFieldPrepareValue(t *testing.T) { t.Fatal(err) } - vRaw, err := json.Marshal(v) + vRaw, err := json.Marshal(v, json.Deterministic(true)) if err != nil { t.Fatal(err) } @@ -181,7 +181,7 @@ func TestSelectFieldDriverValue(t *testing.T) { } } - vRaw, err := json.Marshal(v) + vRaw, err := json.Marshal(v, json.Deterministic(true)) if err != nil { t.Fatal(err) } @@ -503,7 +503,7 @@ func TestSelectFieldFindSetter(t *testing.T) { setter(record, s.value) - raw, err := json.Marshal(record.Get(s.field.GetName())) + raw, err := json.Marshal(record.Get(s.field.GetName()), json.Deterministic(true)) if err != nil { t.Fatal(err) } diff --git a/core/field_test.go b/core/field_test.go index e94f5a51b5..d28f207153 100644 --- a/core/field_test.go +++ b/core/field_test.go @@ -2,7 +2,7 @@ package core_test import ( "context" - "encoding/json" + "encoding/json/v2" "reflect" "strings" "testing" diff --git a/core/fields_list.go b/core/fields_list.go index 31f22c26bc..cc6262bf15 100644 --- a/core/fields_list.go +++ b/core/fields_list.go @@ -2,7 +2,7 @@ package core import ( "database/sql/driver" - "encoding/json" + "encoding/json/v2" "fmt" "slices" "strconv" @@ -277,7 +277,7 @@ func (l *FieldsList) add(pos int, newField Field) { // String returns the string representation of the current list. func (l FieldsList) String() string { - v, _ := json.Marshal(l) + v, _ := l.MarshalJSON() return string(v) } @@ -355,12 +355,12 @@ func (l FieldsList) MarshalJSON() ([]byte, error) { wrapper = append(wrapper, data) } - return json.Marshal(wrapper) + return json.Marshal(wrapper, json.Deterministic(true)) } // Value implements the [driver.Valuer] interface. func (l FieldsList) Value() (driver.Value, error) { - data, err := json.Marshal(l) + data, err := l.MarshalJSON() return string(data), err } diff --git a/core/fields_list_test.go b/core/fields_list_test.go index 13457e3978..2b30952db8 100644 --- a/core/fields_list_test.go +++ b/core/fields_list_test.go @@ -2,7 +2,7 @@ package core_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "slices" "strconv" "strings" diff --git a/core/log_model.go b/core/log_model.go index c2c3e7aaa1..a85b711161 100644 --- a/core/log_model.go +++ b/core/log_model.go @@ -1,13 +1,23 @@ package core -import "github.com/pocketbase/pocketbase/tools/types" +import ( + "encoding/json/v2" + + "github.com/pocketbase/pocketbase/tools/types" +) var ( - _ Model = (*Log)(nil) + _ Model = (*Log)(nil) + _ DBExporter = (*Log)(nil) ) const LogsTableName = "_logs" +const ( + defaultMaxLogDataSize = 16 << 10 // ~16kb + defaultMaxLogMessageSize = 8000 +) + type Log struct { BaseModel @@ -17,6 +27,61 @@ type Log struct { Level int `db:"level" json:"level"` } -func (m *Log) TableName() string { +func (l *Log) TableName() string { return LogsTableName } + +// DBExport prepares and exports the current log model for db persistence. +// +// It also truncates the log's message and data to ensure that it is +// under app.Settings().Logs.MaxDataSize. +func (l *Log) DBExport(app App) (map[string]any, error) { + result := map[string]any{ + "id": l.Id, + "created": l.Created, + "level": l.Level, + } + + // truncate the raw message bytes + // (this is expected to be very rare so it is ok even if multi-byte chars) + if int64(len(l.Message)) > defaultMaxLogMessageSize { + result["message"] = l.Message[:defaultMaxLogMessageSize] + } else { + result["message"] = l.Message + } + + if len(l.Data) == 0 { + result["data"] = l.Data + } else { + maxDataSize := app.Settings().Logs.MaxDataSize + if maxDataSize == 0 { + maxDataSize = defaultMaxLogDataSize + } + + rawData, err := l.Data.MarshalJSON() + if err != nil { + return nil, err + } + + if int64(len(rawData)) > maxDataSize { + truncatedData := types.JSONMap[any]{} + + // ignore syntax errors in case of truncated incomplete json + // + // jsonv2 stream decodes and all "valid" attrs read up to the + // invalid part will be populated in truncatedData + _ = json.Unmarshal(rawData[:maxDataSize], &truncatedData) + + truncatedData["__pb_truncated__"] = true + + rawData, err = truncatedData.MarshalJSON() + if err != nil { + return nil, err + } + } + + result["data"] = types.JSONRaw(rawData) + } + + return result, nil +} diff --git a/core/log_model_test.go b/core/log_model_test.go new file mode 100644 index 0000000000..2e3c170f7b --- /dev/null +++ b/core/log_model_test.go @@ -0,0 +1,119 @@ +package core_test + +import ( + "bytes" + "encoding/json/v2" + "strings" + "testing" + + "github.com/pocketbase/pocketbase/core" + "github.com/pocketbase/pocketbase/tests" + "github.com/pocketbase/pocketbase/tools/types" +) + +func TestLogTableName(t *testing.T) { + t.Parallel() + + var log core.Log + + if name := log.TableName(); name != core.LogsTableName { + t.Fatalf("Expected Log table name %q, got %q", core.LogsTableName, name) + } +} + +func TestLogDBExport(t *testing.T) { + t.Parallel() + + testApp, _ := tests.NewTestApp() + defer testApp.Cleanup() + + date, err := types.ParseDateTime("2026-08-18 10:20:30.456Z") + if err != nil { + t.Fatal(err) + } + + messageLimit := 8000 + dataLimit := 16 << 10 + + scenarios := []struct { + name string + log core.Log + limit int64 // 0 -> use default + expectJSON string + }{ + { + "empty log", + core.Log{}, + 0, + `{"created":"","data":{},"id":"","level":0,"message":""}`, + }, + { + "with message and data below the default limits", + core.Log{ + BaseModel: core.BaseModel{Id: "test_id"}, + Created: date, + Level: 123, + Message: "test_message", + Data: types.JSONMap[any]{"a": "test1", "b": "test2"}, + }, + 0, + `{"created":"2026-08-18 10:20:30.456Z","data":{"a":"test1","b":"test2"},"id":"test_id","level":123,"message":"test_message"}`, + }, + { + "with message and data exactly the default limits", + core.Log{ + BaseModel: core.BaseModel{Id: "test_id"}, + Created: date, + Level: 123, + Message: strings.Repeat("a", messageLimit), + Data: types.JSONMap[any]{"a": "test1", "b": "test2", "c": strings.Repeat("a", dataLimit-32)}, + }, + 0, + `{"created":"2026-08-18 10:20:30.456Z","data":{"a":"test1","b":"test2","c":"` + strings.Repeat("a", dataLimit-32) + `"},"id":"test_id","level":123,"message":"` + strings.Repeat("a", messageLimit) + `"}`, + }, + { + "with message and data above the default limits", + core.Log{ + BaseModel: core.BaseModel{Id: "test_id"}, + Created: date, + Level: 123, + Message: strings.Repeat("a", messageLimit) + "x", // "x" should be omitted + Data: types.JSONMap[any]{"a": "test1", "b": "test2", "c": strings.Repeat("a", dataLimit-32) + "x"}, // the end will be incomplete and something like `"c":"...aaaaaax` + }, + 0, + `{"created":"2026-08-18 10:20:30.456Z","data":{"__pb_truncated__":true,"a":"test1","b":"test2","c":"` + strings.Repeat("a", dataLimit-32) + `x"},"id":"test_id","level":123,"message":"` + strings.Repeat("a", messageLimit) + `"}`, + }, + { + "with data above custom limit", + core.Log{ + BaseModel: core.BaseModel{Id: "test_id"}, + Created: date, + Level: 123, + Message: "test_message", + Data: types.JSONMap[any]{"a": "test1", "b": "test2", "c": strings.Repeat("a", (2<<10)-32) + "x"}, // the end will be incomplete and something like `"c":"...aaaaaax` + }, + 2 << 10, + `{"created":"2026-08-18 10:20:30.456Z","data":{"__pb_truncated__":true,"a":"test1","b":"test2","c":"` + strings.Repeat("a", (2<<10)-32) + `x"},"id":"test_id","level":123,"message":"test_message"}`, + }, + } + + for _, s := range scenarios { + t.Run(s.name, func(t *testing.T) { + testApp.Settings().Logs.MaxDataSize = s.limit + + result, err := s.log.DBExport(testApp) + if err != nil { + t.Fatal(err) + } + + raw, err := json.Marshal(result, json.Deterministic(true)) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(raw, []byte(s.expectJSON)) { + t.Fatalf("Expected export data\n%s\ngot\n%s", s.expectJSON, raw) + } + }) + } +} diff --git a/core/log_query_test.go b/core/log_query_test.go index df361987f5..03694cb3cc 100644 --- a/core/log_query_test.go +++ b/core/log_query_test.go @@ -1,7 +1,7 @@ package core_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "testing" "time" @@ -63,7 +63,7 @@ func TestLogsStats(t *testing.T) { t.Fatal(err) } - encoded, _ := json.Marshal(result) + encoded, _ := json.Marshal(result, json.Deterministic(true)) if string(encoded) != expected { t.Fatalf("Expected\n%q\ngot\n%q", expected, string(encoded)) } diff --git a/core/migrations_runner_test.go b/core/migrations_runner_test.go index a4c0c93fe0..d16e4e2f09 100644 --- a/core/migrations_runner_test.go +++ b/core/migrations_runner_test.go @@ -1,7 +1,7 @@ package core_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "testing" "time" diff --git a/core/record_field_resolver.go b/core/record_field_resolver.go index caabe131e4..425cc5efce 100644 --- a/core/record_field_resolver.go +++ b/core/record_field_resolver.go @@ -1,7 +1,7 @@ package core import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "slices" @@ -354,7 +354,7 @@ func (r *RecordFieldResolver) resolveStaticRequestField(path ...string) (*search // if that doesn't work, try encoding it if castErr != nil { - encoded, jsonErr := json.Marshal(v) + encoded, jsonErr := json.Marshal(v, json.Deterministic(true)) if jsonErr == nil { val = string(encoded) } diff --git a/core/record_field_resolver_runner.go b/core/record_field_resolver_runner.go index f44fd147d5..fa65893ec9 100644 --- a/core/record_field_resolver_runner.go +++ b/core/record_field_resolver_runner.go @@ -1,7 +1,7 @@ package core import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "reflect" @@ -333,7 +333,7 @@ func (r *runner) processRequestBodyEachModifier(bodyField Field) (*search.Resolv } bodyItems := toSlice(r.resolver.requestInfo.Body[bodyField.GetName()]) - bodyItemsRaw, err := json.Marshal(bodyItems) + bodyItemsRaw, err := json.Marshal(bodyItems, json.Deterministic(true)) if err != nil { return nil, fmt.Errorf("cannot serialize the data for field %q", r.activeProps[2]) } diff --git a/core/record_field_resolver_test.go b/core/record_field_resolver_test.go index f232368d40..7b9917c482 100644 --- a/core/record_field_resolver_test.go +++ b/core/record_field_resolver_test.go @@ -1,7 +1,7 @@ package core_test import ( - "encoding/json" + "encoding/json/v2" "regexp" "slices" "strings" @@ -940,7 +940,7 @@ func TestRecordFieldResolverResolveStaticRequestInfoFields(t *testing.T) { t.Fatalf("Expected parameter r.Identifier %q, got %q", paramName, r.Identifier) } - encodedParamValue, _ := json.Marshal(paramValue) + encodedParamValue, _ := json.Marshal(paramValue, json.Deterministic(true)) if string(encodedParamValue) != s.expectParamValue { t.Fatalf("Expected r.Params %#v for %s, got %#v", s.expectParamValue, r.Identifier, string(encodedParamValue)) } diff --git a/core/record_model.go b/core/record_model.go index a0c0617c0e..c8e8bba0f5 100644 --- a/core/record_model.go +++ b/core/record_model.go @@ -3,7 +3,7 @@ package core import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "log" @@ -959,6 +959,11 @@ func (m *Record) GetInt(key string) int { return cast.ToInt(m.Get(key)) } +// GetInt64 returns the data value for "key" as an int64. +func (m *Record) GetInt64(key string) int64 { + return cast.ToInt64(m.Get(key)) +} + // GetFloat returns the data value for "key" as a float64. func (m *Record) GetFloat(key string) float64 { return cast.ToFloat64(m.Get(key)) @@ -1218,12 +1223,12 @@ func areValuesEqual(a any, b any) bool { bv, ok := b.(types.JSONRaw) return ok && bytes.Equal(av, bv) default: - aRaw, err := json.Marshal(a) + aRaw, err := json.Marshal(a, json.Deterministic(true)) if err != nil { return false } - bRaw, err := json.Marshal(b) + bRaw, err := json.Marshal(b, json.Deterministic(true)) if err != nil { return false } @@ -1324,7 +1329,7 @@ func (record *Record) PublicExport() map[string]any { // // Only the data exported by `PublicExport()` will be serialized. func (m Record) MarshalJSON() ([]byte, error) { - return json.Marshal(m.PublicExport()) + return json.Marshal(m.PublicExport(), json.Deterministic(true)) } // UnmarshalJSON implements the [json.Unmarshaler] interface. diff --git a/core/record_model_test.go b/core/record_model_test.go index a65c00a5a4..04959e3d3e 100644 --- a/core/record_model_test.go +++ b/core/record_model_test.go @@ -4,7 +4,7 @@ import ( "bytes" "context" "database/sql" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "regexp" @@ -31,7 +31,7 @@ func TestNewRecord(t *testing.T) { m := core.NewRecord(collection) - rawData, err := json.Marshal(m.FieldsData()) // should be initialized with the defaults + rawData, err := json.Marshal(m.FieldsData(), json.Deterministic(true)) // should be initialized with the defaults if err != nil { t.Fatal(err) } @@ -520,7 +520,7 @@ func TestRecordMergeExpand(t *testing.T) { result := m.Expand() - raw, err := json.Marshal(result) + raw, err := json.Marshal(result, json.Deterministic(true)) if err != nil { t.Fatal(err) } @@ -566,7 +566,7 @@ func TestRecordMergeExpandNilCheck(t *testing.T) { m := core.NewRecord(collection) m.MergeExpand(s.expand) - raw, err := json.Marshal(m) + raw, err := json.Marshal(m, json.Deterministic(true)) if err != nil { t.Fatal(err) } @@ -663,7 +663,7 @@ func TestRecordFieldsData(t *testing.T) { m.Set("field2", 456) m.Set("unknown", 789) - raw, err := json.Marshal(m.FieldsData()) + raw, err := json.Marshal(m.FieldsData(), json.Deterministic(true)) if err != nil { t.Fatal(err) } @@ -691,7 +691,7 @@ func TestRecordCustomData(t *testing.T) { m.Set("field2", 456) m.Set("unknown", 789) - raw, err := json.Marshal(m.CustomData()) + raw, err := json.Marshal(m.CustomData(), json.Deterministic(true)) if err != nil { t.Fatal(err) } @@ -899,6 +899,43 @@ func TestRecordGetInt(t *testing.T) { } } +func TestRecordGetInt64(t *testing.T) { + t.Parallel() + + scenarios := []struct { + value any + expected int64 + }{ + {nil, 0}, + {"", 0}, + {[]string{"true"}, 0}, + {map[string]int{"test": 1}, 0}, + {time.Now(), 0}, + {"test", 0}, + {123, 123}, + {2.4, 2}, + {1<<63 - 1, 1<<63 - 1}, + {"123", 123}, + {"123.5", 123}, + {false, 0}, + {true, 1}, + } + + collection := core.NewBaseCollection("test") + record := core.NewRecord(collection) + + for i, s := range scenarios { + t.Run(fmt.Sprintf("%d_%#v", i, s.value), func(t *testing.T) { + record.Set("test", s.value) + + result := record.GetInt64("test") + if result != s.expected { + t.Fatalf("Expected %v, got %v", s.expected, result) + } + }) + } +} + func TestRecordGetFloat(t *testing.T) { t.Parallel() @@ -1103,14 +1140,16 @@ func TestRecordGetUnsavedFiles(t *testing.T) { t.Run(fmt.Sprintf("%d_%#v", i, s.key), func(t *testing.T) { v := record.GetUnsavedFiles(s.key) - raw, err := json.Marshal(v) + raw, err := json.Marshal(v, + json.Deterministic(true), + json.FormatNilSliceAsNull(true), + ) if err != nil { t.Fatal(err) } - rawStr := string(raw) - if rawStr != s.expected { - t.Fatalf("Expected\n%s\ngot\n%s", s.expected, rawStr) + if str := string(raw); str != s.expected { + t.Fatalf("Expected\n%s\ngot\n%s", s.expected, str) } }) } @@ -1164,7 +1203,7 @@ func TestRecordUnmarshalJSONField(t *testing.T) { t.Fatalf("Expected hasErr %v, got %v", s.expectError, hasErr) } - raw, _ := json.Marshal(s.destination) + raw, _ := json.Marshal(s.destination, json.Deterministic(true)) if v := string(raw); v != s.expectedJSON { t.Fatalf("Expected %q, got %q", s.expectedJSON, v) } @@ -1271,7 +1310,7 @@ func TestRecordDBExport(t *testing.T) { t.Fatal(err) } - raw, err := json.Marshal(result) + raw, err := json.Marshal(result, json.Deterministic(true)) if err != nil { t.Fatal(err) } @@ -1515,7 +1554,7 @@ func TestRecordPublicExportAndMarshalJSON(t *testing.T) { m.Unhide(s.unhideFields...) m.Hide(s.hideFields...) - exportResult, err := json.Marshal(m.PublicExport()) + exportResult, err := json.Marshal(m.PublicExport(), json.Deterministic(true)) if err != nil { t.Fatal(err) } diff --git a/core/record_query.go b/core/record_query.go index f653621bf9..8162575e85 100644 --- a/core/record_query.go +++ b/core/record_query.go @@ -104,7 +104,7 @@ func (app *BaseApp) RecordQuery(collectionModelOrIdentifier any) *dbx.SelectQuer return nil default: // expects []RecordProxy slice rv := reflect.ValueOf(v) - if rv.Kind() != reflect.Ptr || rv.IsNil() { + if rv.Kind() != reflect.Pointer || rv.IsNil() { return errors.New("must be a pointer") } @@ -117,7 +117,7 @@ func (app *BaseApp) RecordQuery(collectionModelOrIdentifier any) *dbx.SelectQuer et := rv.Type().Elem() var isSliceOfPointers bool - if et.Kind() == reflect.Ptr { + if et.Kind() == reflect.Pointer { isSliceOfPointers = true et = et.Elem() } @@ -182,7 +182,7 @@ func resolveRecordAllHook(collection *Collection, op func(dst any) error) ([]*Re // dereference returns the underlying value v points to. func dereference(v reflect.Value) reflect.Value { - for v.Kind() == reflect.Ptr { + for v.Kind() == reflect.Pointer { if v.IsNil() { // initialize with a new value and continue searching v.Set(reflect.New(v.Type().Elem())) diff --git a/core/record_query_expand_test.go b/core/record_query_expand_test.go index 3e23c509ac..116ea78870 100644 --- a/core/record_query_expand_test.go +++ b/core/record_query_expand_test.go @@ -3,7 +3,7 @@ package core_test import ( "context" "database/sql" - "encoding/json" + "encoding/json/v2" "errors" "strings" "testing" @@ -240,7 +240,7 @@ func TestExpandRecords(t *testing.T) { t.Errorf("Expected %d failures, got %d\n%v", s.expectExpandFailures, len(failed), failed) } - encoded, _ := json.Marshal(records) + encoded, _ := json.Marshal(records, json.Deterministic(true)) encodedStr := string(encoded) totalExpandProps := strings.Count(encodedStr, `"`+core.FieldNameExpand+`":`) totalEmptyExpands := strings.Count(encodedStr, `"`+core.FieldNameExpand+`":{}`) @@ -420,7 +420,7 @@ func TestExpandRecord(t *testing.T) { t.Errorf("Expected %d failures, got %d\n%v", s.expectExpandFailures, len(failed), failed) } - encoded, _ := json.Marshal(record) + encoded, _ := json.Marshal(record, json.Deterministic(true)) encodedStr := string(encoded) totalExpandProps := strings.Count(encodedStr, `"`+core.FieldNameExpand+`":`) totalEmptyExpands := strings.Count(encodedStr, `"`+core.FieldNameExpand+`":{}`) diff --git a/core/record_query_test.go b/core/record_query_test.go index 91c1ab174b..02a9f40808 100644 --- a/core/record_query_test.go +++ b/core/record_query_test.go @@ -1,7 +1,7 @@ package core_test import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "slices" @@ -107,7 +107,7 @@ func TestRecordQueryOne(t *testing.T) { t.Fatal(err) } - raw, err := json.Marshal(s.model) + raw, err := json.Marshal(s.model, json.Deterministic(true)) if err != nil { t.Fatal(err) } @@ -192,7 +192,7 @@ func TestRecordQueryAll(t *testing.T) { t.Fatal(err) } - raw, err := json.Marshal(s.result) + raw, err := json.Marshal(s.result, json.Deterministic(true)) if err != nil { t.Fatal(err) } diff --git a/core/settings_model.go b/core/settings_model.go index 4b0e95f0e2..1fb6033f58 100644 --- a/core/settings_model.go +++ b/core/settings_model.go @@ -2,7 +2,7 @@ package core import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "os" @@ -237,7 +237,7 @@ func (s *Settings) String() string { s.mu.RLock() defer s.mu.RUnlock() - raw, _ := json.Marshal(s) + raw, _ := s.MarshalJSON() return string(raw) } @@ -257,14 +257,7 @@ func (s *Settings) DBExport(app App) (map[string]any, error) { } result["updated"] = now - // @todo remove with encoding/json/2 - // serialize as empty array - //nolint:staticcheck - if s.settings.SuperuserIPs == nil { - s.settings.SuperuserIPs = []string{} - } - - encoded, err := json.Marshal(s.settings) + encoded, err := json.Marshal(s.settings, json.Deterministic(true)) if err != nil { return nil, err } @@ -355,13 +348,7 @@ func (s *Settings) MarshalJSON() ([]byte, error) { } } - // @todo remove with encoding/json/2 - // serialize as empty array - if copy.SuperuserIPs == nil { - copy.SuperuserIPs = []string{} - } - - return json.Marshal(copy) + return json.Marshal(copy, json.Deterministic(true)) } // ------------------------------------------------------------------- @@ -559,6 +546,12 @@ func (c MetaConfig) Validate() error { // ------------------------------------------------------------------- type LogsConfig struct { + // MaxDataSize specifies the maximum allowed serialized log data + // size before it gets truncated (see [Log.DBExport]). + // + // If zero, fallbacks to ~16kb by default. + MaxDataSize int64 `form:"maxDataSize" json:"maxDataSize"` + MaxDays int `form:"maxDays" json:"maxDays"` MinLevel int `form:"minLevel" json:"minLevel"` LogIP bool `form:"logIP" json:"logIP"` @@ -568,7 +561,9 @@ type LogsConfig struct { // Validate makes LogsConfig validatable by implementing [validation.Validatable] interface. func (c LogsConfig) Validate() error { return validation.ValidateStruct(&c, - validation.Field(&c.MaxDays, validation.Min(0)), + validation.Field(&c.MaxDataSize, validation.Min(0), validation.Max(maxSafeJSONInt)), + validation.Field(&c.MaxDays, validation.Min(0), validation.Max(maxSafeJSONInt)), + validation.Field(&c.MinLevel, validation.Max(maxSafeJSONInt)), ) } diff --git a/core/settings_model_test.go b/core/settings_model_test.go index e40c7fa076..e5a6747ccf 100644 --- a/core/settings_model_test.go +++ b/core/settings_model_test.go @@ -1,7 +1,7 @@ package core_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "os" "strings" @@ -84,7 +84,7 @@ func TestSettings_DBExport(t *testing.T) { valueStr = string(export["value"].([]byte)) } - expected := `{"superuserIPs":[],"smtp":{"enabled":false,"port":0,"host":"smtp_host","username":"smtp_username","password":"","authMethod":"","tls":false,"localName":""},"backups":{"cron":"* * * * *","cronMaxKeep":0,"s3":{"enabled":true,"bucket":"","region":"","endpoint":"","accessKey":"","forcePathStyle":false}},"s3":{"enabled":false,"bucket":"","region":"","endpoint":"s3_endpoint","accessKey":"","secret":"s3_secret","forcePathStyle":false},"meta":{"accentColor":"","appName":"test_app_name","appURL":"","senderName":"","senderAddress":"","hideControls":false},"rateLimits":{"rules":[],"excludedIPs":[],"enabled":true},"trustedProxy":{"headers":[],"useLeftmostIP":true},"batch":{"enabled":false,"maxRequests":0,"timeout":15,"maxBodySize":0},"logs":{"maxDays":123,"minLevel":0,"logIP":false,"logAuthId":false}}` + expected := `{"superuserIPs":[],"smtp":{"enabled":false,"port":0,"host":"smtp_host","username":"smtp_username","password":"","authMethod":"","tls":false,"localName":""},"backups":{"cron":"* * * * *","cronMaxKeep":0,"s3":{"enabled":true,"bucket":"","region":"","endpoint":"","accessKey":"","forcePathStyle":false}},"s3":{"enabled":false,"bucket":"","region":"","endpoint":"s3_endpoint","accessKey":"","secret":"s3_secret","forcePathStyle":false},"meta":{"accentColor":"","appName":"test_app_name","appURL":"","senderName":"","senderAddress":"","hideControls":false},"rateLimits":{"rules":[],"excludedIPs":[],"enabled":true},"trustedProxy":{"headers":[],"useLeftmostIP":true},"batch":{"enabled":false,"maxRequests":0,"timeout":15,"maxBodySize":0},"logs":{"maxDataSize":0,"maxDays":123,"minLevel":0,"logIP":false,"logAuthId":false}}` if valueStr != expected { t.Fatalf("Expected exported settings\n%s\ngot\n%s", expected, valueStr) } @@ -112,12 +112,12 @@ func TestSettingsMerge(t *testing.T) { t.Fatal(err) } - s1Encoded, err := json.Marshal(s1) + s1Encoded, err := json.Marshal(s1, json.Deterministic(true)) if err != nil { t.Fatal(err) } - s2Encoded, err := json.Marshal(s2) + s2Encoded, err := json.Marshal(s2, json.Deterministic(true)) if err != nil { t.Fatal(err) } @@ -138,12 +138,12 @@ func TestSettingsClone(t *testing.T) { t.Fatal(err) } - s1Bytes, err := json.Marshal(s1) + s1Bytes, err := json.Marshal(s1, json.Deterministic(true)) if err != nil { t.Fatal(err) } - s2Bytes, err := json.Marshal(s2) + s2Bytes, err := json.Marshal(s2, json.Deterministic(true)) if err != nil { t.Fatal(err) } @@ -174,13 +174,13 @@ func TestSettingsMarshalJSON(t *testing.T) { settings.S3.Secret = testSecret settings.Backups.S3.Secret = testSecret - raw, err := json.Marshal(settings) + raw, err := json.Marshal(settings, json.Deterministic(true)) if err != nil { t.Fatal(err) } rawStr := string(raw) - expected := `{"superuserIPs":[],"smtp":{"enabled":false,"port":0,"host":"","username":"abc","authMethod":"","tls":false,"localName":""},"backups":{"cron":"","cronMaxKeep":0,"s3":{"enabled":false,"bucket":"","region":"","endpoint":"","accessKey":"","forcePathStyle":false}},"s3":{"enabled":false,"bucket":"","region":"","endpoint":"","accessKey":"","forcePathStyle":false},"meta":{"accentColor":"","appName":"test123","appURL":"","senderName":"","senderAddress":"","hideControls":false},"rateLimits":{"rules":[],"excludedIPs":[],"enabled":false},"trustedProxy":{"headers":[],"useLeftmostIP":false},"batch":{"enabled":false,"maxRequests":0,"timeout":0,"maxBodySize":0},"logs":{"maxDays":0,"minLevel":0,"logIP":false,"logAuthId":false}}` + expected := `{"superuserIPs":[],"smtp":{"enabled":false,"port":0,"host":"","username":"abc","authMethod":"","tls":false,"localName":""},"backups":{"cron":"","cronMaxKeep":0,"s3":{"enabled":false,"bucket":"","region":"","endpoint":"","accessKey":"","forcePathStyle":false}},"s3":{"enabled":false,"bucket":"","region":"","endpoint":"","accessKey":"","forcePathStyle":false},"meta":{"accentColor":"","appName":"test123","appURL":"","senderName":"","senderAddress":"","hideControls":false},"rateLimits":{"rules":[],"excludedIPs":[],"enabled":false},"trustedProxy":{"headers":[],"useLeftmostIP":false},"batch":{"enabled":false,"maxRequests":0,"timeout":0,"maxBodySize":0},"logs":{"maxDataSize":0,"maxDays":0,"minLevel":0,"logIP":false,"logAuthId":false}}` if rawStr != expected { t.Fatalf("Expected\n%v\ngot\n%v", expected, rawStr) @@ -228,7 +228,7 @@ func TestSettingsValidate(t *testing.T) { `"rateLimits":{`, } - errBytes, _ := json.Marshal(err) + errBytes, _ := json.Marshal(err, json.Deterministic(true)) jsonErr := string(errBytes) for _, expected := range expectations { if !strings.Contains(jsonErr, expected) { @@ -309,8 +309,11 @@ func TestLogsConfigValidate(t *testing.T) { }, { "invalid data", - core.LogsConfig{MaxDays: -1}, - []string{"maxDays"}, + core.LogsConfig{ + MaxDays: -1, + MaxDataSize: -1, + }, + []string{"maxDays", "maxDataSize"}, }, { "valid data", diff --git a/core/settings_query.go b/core/settings_query.go index 2adbb3d92f..d71eb2dfec 100644 --- a/core/settings_query.go +++ b/core/settings_query.go @@ -2,7 +2,7 @@ package core import ( "database/sql" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "os" diff --git a/core/view_test.go b/core/view_test.go index 5d9dc968b3..56685579ef 100644 --- a/core/view_test.go +++ b/core/view_test.go @@ -1,7 +1,7 @@ package core_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "slices" "testing" @@ -530,7 +530,7 @@ func TestCreateViewFields(t *testing.T) { } if len(s.expectFields) != len(result) { - serialized, _ := json.Marshal(result) + serialized, _ := json.Marshal(result, json.Deterministic(true)) t.Fatalf("Expected %d fields, got %d: \n%s", len(s.expectFields), len(result), serialized) } @@ -835,7 +835,7 @@ func TestDryRunView(t *testing.T) { // check fields // --- if len(s.expectFields) != len(result.Fields) { - serialized, _ := json.Marshal(result.Fields) + serialized, _ := json.Marshal(result.Fields, json.Deterministic(true)) t.Fatalf("Expected %d fields, got %d: \n%s", len(s.expectFields), len(result.Fields), serialized) } for name, typ := range s.expectFields { diff --git a/forms/apple_client_secret_create_test.go b/forms/apple_client_secret_create_test.go index 4778d48623..66092973c5 100644 --- a/forms/apple_client_secret_create_test.go +++ b/forms/apple_client_secret_create_test.go @@ -5,7 +5,7 @@ import ( "crypto/elliptic" "crypto/rand" "crypto/x509" - "encoding/json" + "encoding/json/v2" "encoding/pem" "testing" diff --git a/forms/record_upsert_test.go b/forms/record_upsert_test.go index 4cc1fa9fd8..bd985a43c1 100644 --- a/forms/record_upsert_test.go +++ b/forms/record_upsert_test.go @@ -2,7 +2,7 @@ package forms_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "errors" "maps" "os" diff --git a/go.mod b/go.mod index 343213b650..f64da0bce8 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,15 @@ module github.com/pocketbase/pocketbase -go 1.25.0 +go 1.27 require ( github.com/disintegration/imaging v1.6.2 github.com/domodwyer/mailyak/v3 v3.6.2 - github.com/dop251/goja v0.0.0-20260722130236-0768e0998ac0 + github.com/dop251/goja v0.0.0-20260806115107-493f22071ef6 github.com/dop251/goja_nodejs v0.0.0-20260212111938-1f56ff5bcf14 github.com/fatih/color v1.19.0 github.com/fsnotify/fsnotify v1.10.1 - github.com/gabriel-vasile/mimetype v1.4.13 + github.com/gabriel-vasile/mimetype v1.4.15 github.com/ganigeorgiev/fexpr v0.6.0 github.com/golang-jwt/jwt/v5 v5.3.1 github.com/pocketbase/dbx v1.12.0 @@ -22,28 +22,29 @@ require ( golang.org/x/net v0.58.0 golang.org/x/oauth2 v0.36.0 golang.org/x/sync v0.22.0 - modernc.org/sqlite v1.55.0 + modernc.org/sqlite v1.57.0 ) require ( github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/dlclark/regexp2/v2 v2.5.2 // indirect + github.com/dlclark/regexp2/v2 v2.7.1 // indirect github.com/dop251/base64dec v0.0.0-20231022112746-c6c9f9a96217 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect - github.com/google/pprof v0.0.0-20260709232956-b9395ee17fa0 // indirect + github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 // indirect github.com/google/uuid v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/mattn/go-colorable v0.1.15 // indirect - github.com/mattn/go-isatty v0.0.23 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect github.com/ncruces/go-strftime v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/spf13/pflag v1.0.10 // indirect + github.com/stretchr/testify v1.8.0 // indirect golang.org/x/mod v0.40.0 // indirect golang.org/x/sys v0.47.0 // indirect golang.org/x/text v0.41.0 // indirect golang.org/x/tools v0.49.0 // indirect - modernc.org/libc v1.74.1 // indirect + modernc.org/libc v1.74.4 // indirect modernc.org/mathutil v1.7.1 // indirect - modernc.org/memory v1.11.0 // indirect + modernc.org/memory v1.12.0 // indirect ) diff --git a/go.sum b/go.sum index 1cafaebcfe..e7727c084a 100644 --- a/go.sum +++ b/go.sum @@ -4,18 +4,19 @@ github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:o github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= -github.com/dlclark/regexp2/v2 v2.5.2 h1:HAsucWRhsqcDzl6Ua9aR8JwYOTzrZyPrF0/FNxJVAI0= -github.com/dlclark/regexp2/v2 v2.5.2/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU= +github.com/dlclark/regexp2/v2 v2.7.1 h1:yqDtwI1ptXXvEUNpYTk2lad4jLtAcKqkzepn4savSk4= +github.com/dlclark/regexp2/v2 v2.7.1/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU= github.com/domodwyer/mailyak/v3 v3.6.2 h1:x3tGMsyFhTCaxp6ycgR0FE/bu5QiNp+hetUuCOBXMn8= github.com/domodwyer/mailyak/v3 v3.6.2/go.mod h1:lOm/u9CyCVWHeaAmHIdF4RiKVxKUT/H5XX10lIKAL6c= github.com/dop251/base64dec v0.0.0-20231022112746-c6c9f9a96217 h1:16iT9CBDOniJwFGPI41MbUDfEk74hFaKTqudrX8kenY= github.com/dop251/base64dec v0.0.0-20231022112746-c6c9f9a96217/go.mod h1:eIb+f24U+eWQCIsj9D/ah+MD9UP+wdxuqzsdLD+mhGM= -github.com/dop251/goja v0.0.0-20260722130236-0768e0998ac0 h1:1JJPIzrFPTNEHCFkIDhKV2CHBklTA/7VHJp9sVB8Em0= -github.com/dop251/goja v0.0.0-20260722130236-0768e0998ac0/go.mod h1:LiIEzozrcvNXorsG/3+ypGqdTUAqZryhzSsqi0oU/Qg= +github.com/dop251/goja v0.0.0-20260806115107-493f22071ef6 h1:Oh2rRG1un7tLlC3/NJDzKppZ4CeZGkVFJCUOTRwLpfw= +github.com/dop251/goja v0.0.0-20260806115107-493f22071ef6/go.mod h1:LiIEzozrcvNXorsG/3+ypGqdTUAqZryhzSsqi0oU/Qg= github.com/dop251/goja_nodejs v0.0.0-20260212111938-1f56ff5bcf14 h1:3U8dTgyNBhEQ/GVw0jZW5q+93Zw2gAZPRWhJ9TwV3rM= github.com/dop251/goja_nodejs v0.0.0-20260212111938-1f56ff5bcf14/go.mod h1:Tb7Xxye4LX7cT3i8YLvmPMGCV92IOi4CDZvm/V8ylc0= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -26,8 +27,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= -github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= -github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gabriel-vasile/mimetype v1.4.15 h1:05iP/CYtZ/w455R/KZM6rZ5ieAdh99UPtd+d3YzLmaI= +github.com/gabriel-vasile/mimetype v1.4.15/go.mod h1:azpTcoLcDZRNgFou5j+APrqQx9HqVPWa6ijYQIIVswQ= github.com/ganigeorgiev/fexpr v0.6.0 h1:Fza3O/QMBKEudUvxV862qe6GjxM60GJjjKytdp+VQus= github.com/ganigeorgiev/fexpr v0.6.0/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE= github.com/go-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q= @@ -41,8 +42,8 @@ github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArs github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/pprof v0.0.0-20260709232956-b9395ee17fa0 h1:du0WGc8xSKq/++e0cglxhS/mXVqsR7+c7jLEi5Vqduw= -github.com/google/pprof v0.0.0-20260709232956-b9395ee17fa0/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= +github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 h1:LMLX+LgTNWpfvCBdFebv6EsYotImrt/Ppc5cXIriCSo= +github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= @@ -55,8 +56,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY= github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.23 h1:cYwCQTQf3HB6xUC+BtyCLZNr7IzbOmoZbmssVNzSyiQ= -github.com/mattn/go-isatty v0.0.23/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= +github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -80,8 +81,11 @@ github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= @@ -111,10 +115,12 @@ golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -modernc.org/cc/v4 v4.29.0 h1:CXgwL8cvxmyzBQZzbSl/6xFtMCryb6u8IOqDci39cgc= -modernc.org/cc/v4 v4.29.0/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +modernc.org/cc/v4 v4.29.1 h1:MKgdCV3WykTSPqpVrnxdEDS0HEd2FHpKZDzxzU5LyeI= +modernc.org/cc/v4 v4.29.1/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU= modernc.org/ccgo/v4 v4.34.6/go.mod h1:SZ8YcN9NG7XVsQYdm6jYBvi8PQP1qi+kqB6OhjqI3Fk= modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= @@ -125,18 +131,18 @@ modernc.org/gc/v3 v3.1.4 h1:2g65LGVSmFQrXeITAw97x7hCRvZFcyE1uDP+7Vng7JI= modernc.org/gc/v3 v3.1.4/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= -modernc.org/libc v1.74.1 h1:bdR4VTKFMC4966QSNZ05XLGI/VwzVa2kTUX51Dm0riQ= -modernc.org/libc v1.74.1/go.mod h1:uH4t5bOx3G3g9Xcmj10YKlTcVISlRDwv8VoQJG9n8Os= +modernc.org/libc v1.74.4 h1:fX1Omw4o2/1C2iRkkIsrQTasJQldLhRmuPreXLoWs9k= +modernc.org/libc v1.74.4/go.mod h1:eeQAS9W3sZeKYMFubydxJpII9ybHWshk+7or7bLG9co= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= -modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= -modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/memory v1.12.0 h1:twkmYNkGXCvtYWzoux02jtK6eovjZbdI0uHFUYp6kuU= +modernc.org/memory v1.12.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg= modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.55.0 h1:hIFh0MCH0rGinQ/4KYb5/UbCkRkb+UP+OkLCVWa5MTM= -modernc.org/sqlite v1.55.0/go.mod h1:4ntCLuNmnH8+GNqjka1wNg7KJd5/Hi5FYp8K+XQ7GZw= +modernc.org/sqlite v1.57.0 h1:qNQP6xnx5M0ISNtlnxoOX0+cD5bJ0/gr9aMmndFczzg= +modernc.org/sqlite v1.57.0/go.mod h1:yCJ2cmAaIkHQ25oXWrF8H4O1lIfPYPR26yCEDj2P3pQ= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/golangci.yml b/golangci.yml index 7eef299f90..24ca0cbe88 100644 --- a/golangci.yml +++ b/golangci.yml @@ -7,7 +7,7 @@ linters: enable: - asasalint - asciicheck - - gomodguard + - gomodguard_v2 - goprintffuncname - govet - ineffassign @@ -20,6 +20,11 @@ linters: - unconvert - unused - whitespace + exclusions: + rules: + - path: _test\.go + linters: + - prealloc formatters: enable: - gofmt diff --git a/migrations/1717233556_v0.23_migrate.go b/migrations/1717233556_v0.23_migrate.go index c6b9818ab6..6be14bfb75 100644 --- a/migrations/1717233556_v0.23_migrate.go +++ b/migrations/1717233556_v0.23_migrate.go @@ -1,7 +1,7 @@ package migrations import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "os" diff --git a/modernc_versions_check.go b/modernc_versions_check.go index 18a8eaaef6..fa17b4ab15 100644 --- a/modernc_versions_check.go +++ b/modernc_versions_check.go @@ -10,8 +10,8 @@ import ( ) const ( - expectedDriverVersion = "v1.55.0" - expectedLibcVersion = "v1.74.1" + expectedDriverVersion = "v1.57.0" + expectedLibcVersion = "v1.74.4" // ModerncDepsCheckHookId is the id of the hook that performs the modernc.org/* deps checks. // It could be used for removing/unbinding the hook if you don't want the checks. diff --git a/plugins/ghupdate/ghupdate.go b/plugins/ghupdate/ghupdate.go index 0a4c961804..340af1ac42 100644 --- a/plugins/ghupdate/ghupdate.go +++ b/plugins/ghupdate/ghupdate.go @@ -8,7 +8,7 @@ package ghupdate import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" diff --git a/plugins/jsvm/binds.go b/plugins/jsvm/binds.go index 77db1cb505..df31022234 100644 --- a/plugins/jsvm/binds.go +++ b/plugins/jsvm/binds.go @@ -3,7 +3,7 @@ package jsvm import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "errors" "io" "io/fs" @@ -352,7 +352,7 @@ func BindCore(vm *goja.Runtime) { } // as a last attempt try to json encode the value - rawBytes, _ := json.Marshal(raw) + rawBytes, _ := json.Marshal(raw, json.Deterministic(true)) return rawBytes, nil } @@ -381,7 +381,7 @@ func BindCore(vm *goja.Runtime) { } // as a last attempt try to json encode the value - rawBytes, _ := json.Marshal(raw) + rawBytes, _ := json.Marshal(raw, json.Deterministic(true)) return string(rawBytes), nil } @@ -1217,13 +1217,13 @@ func newDynamicModel(shape map[string]any) any { case reflect.Map: raw, _ := json.Marshal(v) newV := types.JSONMap[any]{} - newV.Scan(raw) + _ = newV.Scan(raw) v = newV vt = reflect.TypeOf(v) case reflect.Slice, reflect.Array: raw, _ := json.Marshal(v) newV := types.JSONArray[any]{} - newV.Scan(raw) + _ = newV.Scan(raw) v = newV vt = reflect.TypeOf(newV) case reflect.Pointer: diff --git a/plugins/jsvm/binds_test.go b/plugins/jsvm/binds_test.go index 45d3e46b43..0d8ca9b90e 100644 --- a/plugins/jsvm/binds_test.go +++ b/plugins/jsvm/binds_test.go @@ -2,7 +2,7 @@ package jsvm import ( "bytes" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -505,12 +505,12 @@ func TestBindCoreMailerMessage(t *testing.T) { t.Fatalf("Expected mailer.Message, got %v", m) } - raw, err := json.Marshal(m) + raw, err := json.Marshal(m, json.Deterministic(true)) if err != nil { t.Fatal(err) } - expected := `{"from":{"Name":"test_from","Address":"test_from@example.com"},"to":[{"Name":"test_to1","Address":"test_to1@example.com"},{"Name":"test_to2","Address":"test_to2@example.com"}],"bcc":[{"Name":"test_bcc1","Address":"test_bcc1@example.com"},{"Name":"test_bcc2","Address":"test_bcc2@example.com"}],"cc":[{"Name":"test_cc1","Address":"test_cc1@example.com"},{"Name":"test_cc2","Address":"test_cc2@example.com"}],"subject":"test_subject","html":"test_html","text":"test_text","headers":{"header1":"a","header2":"b"},"attachments":null,"inlineAttachments":null}` + expected := `{"from":{"Name":"test_from","Address":"test_from@example.com"},"to":[{"Name":"test_to1","Address":"test_to1@example.com"},{"Name":"test_to2","Address":"test_to2@example.com"}],"bcc":[{"Name":"test_bcc1","Address":"test_bcc1@example.com"},{"Name":"test_bcc2","Address":"test_bcc2@example.com"}],"cc":[{"Name":"test_cc1","Address":"test_cc1@example.com"},{"Name":"test_cc2","Address":"test_cc2@example.com"}],"subject":"test_subject","html":"test_html","text":"test_text","headers":{"header1":"a","header2":"b"},"attachments":{},"inlineAttachments":{}}` if string(raw) != expected { t.Fatalf("Expected \n%s, \ngot \n%s", expected, raw) @@ -1178,7 +1178,7 @@ func TestBindApisErrors(t *testing.T) { t.Errorf("[%s] Expected Message %q, got %q", s.js, s.expectMessage, apiErr.Message) } - dataRaw, _ := json.Marshal(apiErr.RawData()) + dataRaw, _ := json.Marshal(apiErr.RawData(), json.Deterministic(true)) if string(dataRaw) != s.expectData { t.Errorf("[%s] Expected Data %q, got %q", s.js, s.expectData, dataRaw) } @@ -1439,7 +1439,7 @@ func TestBindHTTPSend(t *testing.T) { res.Header().Add("X-Custom", "custom_header") res.Header().Add("Set-Cookie", "sessionId=123456") - infoRaw, _ := json.Marshal(info) + infoRaw, _ := json.Marshal(info, json.Deterministic(true)) // write back the submitted request res.Write(infoRaw) diff --git a/plugins/jsvm/form_data_test.go b/plugins/jsvm/form_data_test.go index dbf0f40365..6c8c46074a 100644 --- a/plugins/jsvm/form_data_test.go +++ b/plugins/jsvm/form_data_test.go @@ -2,7 +2,7 @@ package jsvm import ( "bytes" - "encoding/json" + "encoding/json/v2" "strings" "testing" @@ -166,7 +166,7 @@ func TestFormDataEntries(t *testing.T) { entries := data.Entries() - rawEntries, err := json.Marshal(entries) + rawEntries, err := json.Marshal(entries, json.Deterministic(true)) if err != nil { t.Fatal(err) } diff --git a/plugins/jsvm/internal/types/generated/types.d.ts b/plugins/jsvm/internal/types/generated/types.d.ts index 1273f77ab8..c5bc298871 100644 --- a/plugins/jsvm/internal/types/generated/types.d.ts +++ b/plugins/jsvm/internal/types/generated/types.d.ts @@ -1,4 +1,4 @@ -// 1784468372 +// 1787427627 // GENERATED CODE - DO NOT MODIFY BY HAND // ------------------------------------------------------------------- @@ -1646,6 +1646,9 @@ namespace os { * Process stores the information about a process created by [StartProcess]. */ interface Process { + /** + * Pid is the operating system process ID. + */ pid: number } /** @@ -1779,6 +1782,19 @@ namespace os { */ signal(sig: Signal): void } + interface Process { + /** + * WithHandle calls a supplied function f with a valid process handle + * as an argument. The handle is guaranteed to refer to process p + * until f returns, even if p terminates. This function cannot be used + * after [Process.Release] or [Process.Wait]. + * + * If process handles are not supported or a handle is not available, + * it returns [ErrNoHandle]. Currently, process handles are supported + * on Linux 5.4 or later (pidfd) and Windows. + */ + withHandle(f: (handle: number) => void): void + } interface ProcessState { /** * UserTime returns the user CPU time of the exited process and its children. @@ -1948,8 +1964,8 @@ namespace os { * than ReadFrom. This is used to permit ReadFrom to call io.Copy * without leading to a recursive call to ReadFrom. */ - type _shzlRfc = noReadFrom&File - interface fileWithoutReadFrom extends _shzlRfc { + type _sslGkXI = noReadFrom&File + interface fileWithoutReadFrom extends _sslGkXI { } interface File { /** @@ -1993,8 +2009,8 @@ namespace os { * than WriteTo. This is used to permit WriteTo to call io.Copy * without leading to a recursive call to WriteTo. */ - type _sUNqiOb = noWriteTo&File - interface fileWithoutWriteTo extends _sUNqiOb { + type _sCtCHXP = noWriteTo&File + interface fileWithoutWriteTo extends _sCtCHXP { } interface File { /** @@ -2311,6 +2327,7 @@ namespace os { * A successful call returns err == nil, not err == EOF. * Because ReadFile reads the whole file, it does not treat an EOF from Read * as an error to be reported. + * If there is an error, it will be of type [*PathError]. */ (name: string): string|Array } @@ -2940,8 +2957,8 @@ namespace os { * * The methods of File are safe for concurrent use. */ - type _seZIqsq = file - interface File extends _seZIqsq { + type _sDdvgBd = file + interface File extends _sDdvgBd { } /** * A FileInfo describes a file and is returned by [Stat] and [Lstat]. @@ -3015,14 +3032,16 @@ namespace filepath { * '[' [ '^' ] { character-range } ']' * character class (must be non-empty) * c matches character c (c != '*', '?', '\\', '[') - * '\\' c matches character c + * '\\' c matches character c (except on Windows) * * character-range: * c matches character c (c != '\\', '-', ']') - * '\\' c matches character c + * '\\' c matches character c (except on Windows) * lo '-' hi matches character c for lo <= c <= hi * ``` * + * Path segments in the pattern must be separated by [Separator]. + * * Match requires pattern to match all of name, not just a substring. * The only possible returned error is [ErrBadPattern], when pattern * is malformed. @@ -3202,16 +3221,17 @@ namespace filepath { } interface rel { /** - * Rel returns a relative path that is lexically equivalent to targpath when - * joined to basepath with an intervening separator. That is, - * [Join](basepath, Rel(basepath, targpath)) is equivalent to targpath itself. - * On success, the returned path will always be relative to basepath, - * even if basepath and targpath share no elements. - * An error is returned if targpath can't be made relative to basepath or if - * knowing the current working directory would be necessary to compute it. - * Rel calls [Clean] on the result. + * Rel returns a relative path that is lexically equivalent to targPath when + * joined to basePath with an intervening separator. That is, + * [Join](basePath, Rel(basePath, targPath)) is equivalent to targPath itself. + * + * The returned path will always be relative to basePath, even if basePath and + * targPath share no elements. Rel calls [Clean] on the result. + * + * An error is returned if targPath can't be made relative to basePath + * or if knowing the current working directory would be necessary to compute it. */ - (basepath: string, targpath: string): string + (basePath: string, targPath: string): string } /** * WalkFunc is the type of the function called by [Walk] to visit each @@ -3310,6 +3330,10 @@ namespace filepath { * If the path is empty, Dir returns ".". * If the path consists entirely of separators, Dir returns a single separator. * The returned path does not end in a separator unless it is the root directory. + * + * On Windows, given a volume-only name such as "C:", Dir returns "C:.", + * the current directory on drive C. To obtain the drive's root "C:\", + * use [VolumeName] combined with a separator. */ (path: string): string } @@ -3333,111 +3357,6 @@ namespace filepath { } } -/** - * Package template is a thin wrapper around the standard html/template - * and text/template packages that implements a convenient registry to - * load and cache templates on the fly concurrently. - * - * It was created to assist the JSVM plugin HTML rendering, but could be used in other Go code. - * - * Example: - * - * ``` - * registry := template.NewRegistry() - * - * html1, err := registry.LoadFiles( - * // the files set wil be parsed only once and then cached - * "layout.html", - * "content.html", - * ).Render(map[string]any{"name": "John"}) - * - * html2, err := registry.LoadFiles( - * // reuse the already parsed and cached files set - * "layout.html", - * "content.html", - * ).Render(map[string]any{"name": "Jane"}) - * ``` - */ -namespace template { - interface newRegistry { - /** - * NewRegistry creates and initializes a new templates registry with - * some defaults (eg. global "raw" template function for unescaped HTML). - * - * Use the Registry.Load* methods to load templates into the registry. - */ - (): (Registry) - } - /** - * Registry defines a templates registry that is safe to be used by multiple goroutines. - * - * Use the Registry.Load* methods to load templates into the registry. - */ - interface Registry { - } - interface Registry { - /** - * AddFuncs registers new global template functions. - * - * The key of each map entry is the function name that will be used in the templates. - * If a function with the map entry name already exists it will be replaced with the new one. - * - * The value of each map entry is a function that must have either a - * single return value, or two return values of which the second has type error. - * - * Example: - * - * ``` - * r.AddFuncs(map[string]any{ - * "toUpper": func(str string) string { - * return strings.ToUppser(str) - * }, - * ... - * }) - * ``` - */ - addFuncs(funcs: _TygojaDict): (Registry) - } - interface Registry { - /** - * LoadFiles caches (if not already) the specified filenames set as a - * single template and returns a ready to use Renderer instance. - * - * There must be at least 1 filename specified. - */ - loadFiles(...filenames: string[]): (Renderer) - } - interface Registry { - /** - * LoadString caches (if not already) the specified inline string as a - * single template and returns a ready to use Renderer instance. - */ - loadString(text: string): (Renderer) - } - interface Registry { - /** - * LoadFS caches (if not already) the specified fs and globPatterns - * pair as single template and returns a ready to use Renderer instance. - * - * There must be at least 1 file matching the provided globPattern(s) - * (note that most file names serves as glob patterns matching themselves). - */ - loadFS(fsys: fs.FS, ...globPatterns: string[]): (Renderer) - } - /** - * Renderer defines a single parsed template. - */ - interface Renderer { - } - interface Renderer { - /** - * Render executes the template with the specified data as the dot object - * and returns the result as plain string. - */ - render(data: any): string - } -} - /** * Package dbx provides a set of DB-agnostic and easy-to-use query building methods for relational databases. */ @@ -3774,14 +3693,14 @@ namespace dbx { /** * MssqlBuilder is the builder for SQL Server databases. */ - type _sCtYmpp = BaseBuilder - interface MssqlBuilder extends _sCtYmpp { + type _sXwSmWC = BaseBuilder + interface MssqlBuilder extends _sXwSmWC { } /** * MssqlQueryBuilder is the query builder for SQL Server databases. */ - type _sYCAmRE = BaseQueryBuilder - interface MssqlQueryBuilder extends _sYCAmRE { + type _sltTHWd = BaseQueryBuilder + interface MssqlQueryBuilder extends _sltTHWd { } interface newMssqlBuilder { /** @@ -3852,8 +3771,8 @@ namespace dbx { /** * MysqlBuilder is the builder for MySQL databases. */ - type _skqSaQF = BaseBuilder - interface MysqlBuilder extends _skqSaQF { + type _skPZlIc = BaseBuilder + interface MysqlBuilder extends _skPZlIc { } interface newMysqlBuilder { /** @@ -3928,14 +3847,14 @@ namespace dbx { /** * OciBuilder is the builder for Oracle databases. */ - type _sApSsqS = BaseBuilder - interface OciBuilder extends _sApSsqS { + type _sTLFyQP = BaseBuilder + interface OciBuilder extends _sTLFyQP { } /** * OciQueryBuilder is the query builder for Oracle databases. */ - type _sJYIjNA = BaseQueryBuilder - interface OciQueryBuilder extends _sJYIjNA { + type _stWQXLm = BaseQueryBuilder + interface OciQueryBuilder extends _stWQXLm { } interface newOciBuilder { /** @@ -3998,8 +3917,8 @@ namespace dbx { /** * PgsqlBuilder is the builder for PostgreSQL databases. */ - type _sIdTpCz = BaseBuilder - interface PgsqlBuilder extends _sIdTpCz { + type _scvFVsb = BaseBuilder + interface PgsqlBuilder extends _scvFVsb { } interface newPgsqlBuilder { /** @@ -4066,8 +3985,8 @@ namespace dbx { /** * SqliteQueryBuilder is the query builder for SQLite databases. */ - type _sJNatTO = BaseQueryBuilder - interface SqliteQueryBuilder extends _sJNatTO { + type _sYalOHr = BaseQueryBuilder + interface SqliteQueryBuilder extends _sYalOHr { } interface SqliteQueryBuilder { /** @@ -4088,8 +4007,8 @@ namespace dbx { /** * SqliteBuilder is the builder for SQLite databases. */ - type _scleXId = BaseBuilder - interface SqliteBuilder extends _scleXId { + type _swwjifK = BaseBuilder + interface SqliteBuilder extends _swwjifK { } interface newSqliteBuilder { /** @@ -4188,8 +4107,8 @@ namespace dbx { /** * StandardBuilder is the builder that is used by DB for an unknown driver. */ - type _stvUWpF = BaseBuilder - interface StandardBuilder extends _stvUWpF { + type _sLUImTv = BaseBuilder + interface StandardBuilder extends _sLUImTv { } interface newStandardBuilder { /** @@ -4255,8 +4174,8 @@ namespace dbx { * DB enhances sql.DB by providing a set of DB-agnostic query building methods. * DB allows easier query building and population of data into Go variables. */ - type _sYxbNJq = Builder - interface DB extends _sYxbNJq { + type _sHuPhRS = Builder + interface DB extends _sHuPhRS { /** * FieldMapper maps struct fields to DB columns. Defaults to DefaultFieldMapFunc. */ @@ -5086,8 +5005,8 @@ namespace dbx { * Rows enhances sql.Rows by providing additional data query methods. * Rows can be obtained by calling Query.Rows(). It is mainly used to populate data row by row. */ - type _sBQwGsU = sql.Rows - interface Rows extends _sBQwGsU { + type _sWHLwki = sql.Rows + interface Rows extends _sWHLwki { } interface Rows { /** @@ -5462,8 +5381,8 @@ namespace dbx { }): string } interface structInfo { } - type _sZNoxEO = structInfo - interface structValue extends _sZNoxEO { + type _sFoqRNs = structInfo + interface structValue extends _sFoqRNs { } interface fieldInfo { } @@ -5502,8 +5421,8 @@ namespace dbx { /** * Tx enhances sql.Tx with additional querying methods. */ - type _sQEoADI = Builder - interface Tx extends _sQEoADI { + type _szKxncP = Builder + interface Tx extends _szKxncP { } interface Tx { /** @@ -5776,8 +5695,8 @@ namespace filesystem { */ open(): io.ReadSeekCloser } - type _sjpHkBo = bytes.Reader - interface bytesReadSeekCloser extends _sjpHkBo { + type _sFoniCa = bytes.Reader + interface bytesReadSeekCloser extends _sFoniCa { } interface bytesReadSeekCloser { /** @@ -5795,6 +5714,18 @@ namespace filesystem { */ open(): io.ReadSeekCloser } + type _swuCLgb = hook.Event + interface DeleteEvent extends _swuCLgb { + filesystem?: System + fileKey: string + } + type _sqpDfOW = hook.Event + interface NewWriterEvent extends _sqpDfOW { + filesystem?: System + fileKey: string + options?: blob.WriterOptions + writer?: blob.Writer // filled only after e.Next() + } interface System { } interface newS3 { @@ -5825,6 +5756,24 @@ namespace filesystem { */ close(): void } + interface System { + /** + * OnNewWriter is a low level hook that is triggered on every new writer initialization + * (aka. when attempting to create a new file with [system.NewWriter] or [system.Upload]). + * + * Note that currently it doesn't trigger on [System.Copy] but this may change in future releases. + */ + onNewWriter(): (hook.Hook) + } + interface System { + /** + * OnDelete is a low level hook that is triggered on every [System.Delete] call. + * + * Note that the hook doesn't fire when a file is being overwritten + * by a new one, because in that case [System.Delete] is not invoked. + */ + onDelete(): (hook.Hook) + } interface System { /** * Exists checks if file with fileKey path exists or not. @@ -5905,6 +5854,33 @@ namespace filesystem { */ uploadMultipart(fh: multipart.FileHeader, fileKey: string): void } + interface System { + /** + * NewWriter returns a new blob.Writer instance allowing direct file + * create from an io.Reader value. + * + * If a file with the specified fileKey already exists, it will be replaced. + * + * NB! Make sure to call `Close()` on the resulting writer after you are done working with it. + * + * Note: If you have a bytes slice, [filesystem.File], or a multipart header value, + * you can check also the Upload* related methods as they are more user-friendly. + * + * Example: + * + * ``` + * content := strings.NewReader("Lorem ipsum dolor sit amet...") + * + * fsys, _ := filesystem.NewLocal("dir") + * defer fsys.Close() + * + * w, _ := fsys.NewWriter("example/file/key", nil) + * w.ReadFrom(content) + * w.Close() + * ``` + */ + newWriter(fileKey: string, opts: blob.WriterOptions): (blob.Writer) + } interface System { /** * Delete deletes stored file at fileKey location. @@ -5928,7 +5904,7 @@ namespace filesystem { * A trailing slash will be appended to a non-empty dir string argument * to ensure that the checked prefix is a "directory". * - * Returns "false" in case the has at least one file, otherwise - "true". + * Returns "false" in case it has at least one file, otherwise - "true". */ isEmptyDir(dir: string): boolean } @@ -7460,6 +7436,15 @@ namespace core { * triggered and called only if their event data origin matches the tags. */ onMailerRecordOTPSend(...tags: string[]): (hook.TaggedHook) + /** + * onFilesystemNewWriter is an internal hook for app.NewFilesystem() + * instances that is triggered on every storage filesystem writer initialization + * (aka. whenever attempting to create a new file). + */ + /** + * onFilesystemDelete is an internal hook for app.NewFilesystem() + * instances that is triggered for every storage file delete call. + */ /** * OnRealtimeConnectRequest hook is triggered when establishing the SSE client connection. * @@ -7761,8 +7746,8 @@ namespace core { /** * AuthOrigin defines a Record proxy for working with the authOrigins collection. */ - type _sfIinnU = Record - interface AuthOrigin extends _sfIinnU { + type _svSfNCE = Record + interface AuthOrigin extends _svSfNCE { } interface newAuthOrigin { /** @@ -7880,6 +7865,97 @@ namespace core { */ deleteAllAuthOriginsByRecord(authRecord: Record): void } + interface BaseApp { + /** + * CreateBackup creates a new backup of the current app pb_data directory. + * + * If name is empty, it will be autogenerated. + * If backup with the same name exists, the new backup file will replace it. + * + * To safely perform the backup, it is recommended to have free disk space + * for at least 2x the size of the pb_data directory. + * + * By default backups are stored in pb_data/backups + * (the backups directory itself is excluded from the generated backup). + * + * Backups can be stored on S3 if it is configured in app.Settings().Backups. + * When using S3 storage for the uploaded collection files, you have to + * take care manually to backup those since they are not part of the pb_data. + * + * DB write locks are minimal and isolated only for the duration of the + * VACUUM INTO statement that creates a live copy of the app database. + * + * The backup works as follow: + * + * 1. Start listening for DELETED storage files. + * ``` + * In case a file is being deleted while the backup is still ongoing, + * we directly copy it in the zip before the deletion and mark it as "excluded". + * ``` + * + * 2. Copy the main database with VACUUM INTO, write it in the zip and mark it as "excluded". + * + * 3. Stop listening for DELETED files. + * + * 4. Start listening for NEW storage files and mark all new files from this point as "excluded". + * + * 5. Copy the logs database with VACUUM INTO, write it in the zip and mark it as "excluded". + * + * 6. Copy the rest of the pb_data files in the zip while ignoring the "excluded" list (it should be concurrent safe). + * + * 7. Stop listening for NEW storage files. + * + * While there is a risk for a race condition between steps 1, 2 and 3, it is an + * acceptable trade-off between performance and correctness because in + * the worst case there will be some unused storage files in the backup that don't do any harm. + */ + createBackup(ctx: context.Context, name: string): void + } + interface zipWriter { + } + interface BaseApp { + /** + * RestoreBackup restores the backup with the specified name and restarts + * the current running application process. + * + * NB! This feature is experimental and currently is expected to work only on UNIX based systems. + * + * To safely perform the restore it is recommended to have free disk space + * for at least 2x the size of the restored pb_data backup. + * + * The performed steps are: + * + * 1. Download the backup with the specified name in a temp location + * ``` + * (this is in case of S3; otherwise it creates a temp copy of the zip) + * ``` + * + * 2. Extract the backup in a temp directory inside the app "pb_data" + * ``` + * (eg. "pb_data/.pb_temp_to_delete/pb_restore"). + * ``` + * + * 3. Move the current app "pb_data" content (excluding the local backups and the special temp dir) + * ``` + * under another temp sub dir that will be deleted on the next app start up + * (eg. "pb_data/.pb_temp_to_delete/old_pb_data"). + * This is because on some environments it may not be allowed + * to delete the currently open "pb_data" files. + * ``` + * + * 4. Move the extracted dir content to the app "pb_data". + * + * 5. Restart the app (on successful app bootstrap it will also remove the old pb_data). + * + * If a failure occur during the restore process the dir changes are reverted. + * If for whatever reason the revert is not possible, it panics. + * + * Note that if your pb_data has custom network mounts as subdirectories, then + * it is possible the restore to fail during the `os.Rename` operations + * (see https://github.com/pocketbase/pocketbase/issues/4647). + */ + restoreBackup(ctx: context.Context, name: string): void + } /** * FilesManager defines an interface with common methods that files manager models should implement. */ @@ -8421,82 +8497,16 @@ namespace core { } interface BaseApp { /** - * CreateBackup creates a new backup of the current app pb_data directory. - * - * If name is empty, it will be autogenerated. - * If backup with the same name exists, the new backup file will replace it. - * - * The backup is executed within a transaction, meaning that new writes - * will be temporary "blocked" until the backup file is generated. - * - * To safely perform the backup, it is recommended to have free disk space - * for at least 2x the size of the pb_data directory. - * - * By default backups are stored in pb_data/backups - * (the backups directory itself is excluded from the generated backup). - * - * When using S3 storage for the uploaded collection files, you have to - * take care manually to backup those since they are not part of the pb_data. - * - * Backups can be stored on S3 if it is configured in app.Settings().Backups. + * ImportCollectionsByMarshaledJSON is the same as [ImportCollections] + * but accept marshaled json array as import data (usually used for the autogenerated snapshots). */ - createBackup(ctx: context.Context, name: string): void + importCollectionsByMarshaledJSON(rawSliceOfMaps: string|Array, deleteMissing: boolean): void } interface BaseApp { /** - * RestoreBackup restores the backup with the specified name and restarts - * the current running application process. - * - * NB! This feature is experimental and currently is expected to work only on UNIX based systems. - * - * To safely perform the restore it is recommended to have free disk space - * for at least 2x the size of the restored pb_data backup. + * ImportCollections imports the provided collections data in a single transaction. * - * The performed steps are: - * - * 1. Download the backup with the specified name in a temp location - * ``` - * (this is in case of S3; otherwise it creates a temp copy of the zip) - * ``` - * - * 2. Extract the backup in a temp directory inside the app "pb_data" - * ``` - * (eg. "pb_data/.pb_temp_to_delete/pb_restore"). - * ``` - * - * 3. Move the current app "pb_data" content (excluding the local backups and the special temp dir) - * ``` - * under another temp sub dir that will be deleted on the next app start up - * (eg. "pb_data/.pb_temp_to_delete/old_pb_data"). - * This is because on some environments it may not be allowed - * to delete the currently open "pb_data" files. - * ``` - * - * 4. Move the extracted dir content to the app "pb_data". - * - * 5. Restart the app (on successful app bootstrap it will also remove the old pb_data). - * - * If a failure occur during the restore process the dir changes are reverted. - * If for whatever reason the revert is not possible, it panics. - * - * Note that if your pb_data has custom network mounts as subdirectories, then - * it is possible the restore to fail during the `os.Rename` operations - * (see https://github.com/pocketbase/pocketbase/issues/4647). - */ - restoreBackup(ctx: context.Context, name: string): void - } - interface BaseApp { - /** - * ImportCollectionsByMarshaledJSON is the same as [ImportCollections] - * but accept marshaled json array as import data (usually used for the autogenerated snapshots). - */ - importCollectionsByMarshaledJSON(rawSliceOfMaps: string|Array, deleteMissing: boolean): void - } - interface BaseApp { - /** - * ImportCollections imports the provided collections data in a single transaction. - * - * For existing matching collections, the imported data is unmarshaled on top of the existing model. + * For existing matching collections, the imported data is unmarshaled on top of the existing model. * * NB! If deleteMissing is true, ALL NON-SYSTEM COLLECTIONS AND SCHEMA FIELDS, * that are not present in the imported configuration, WILL BE DELETED @@ -8508,8 +8518,8 @@ namespace core { * @todo experiment eventually replacing the rules *string with a struct? * @todo consider changing the Indexes field to a "getter" for the sqlite_master table? */ - type _sizwTsd = BaseModel - interface baseCollection extends _sizwTsd { + type _sOjAtUN = BaseModel + interface baseCollection extends _sOjAtUN { listRule?: string viewRule?: string createRule?: string @@ -8536,8 +8546,8 @@ namespace core { /** * Collection defines the table, fields and various options related to a set of records. */ - type _sQAoSZt = baseCollection&collectionAuthOptions&collectionViewOptions - interface Collection extends _sQAoSZt { + type _sivNidz = baseCollection&collectionAuthOptions&collectionViewOptions + interface Collection extends _sivNidz { } interface newCollection { /** @@ -9547,8 +9557,8 @@ namespace core { /** * RequestEvent defines the PocketBase router handler event. */ - type _szVzPbI = router.Event - interface RequestEvent extends _szVzPbI { + type _sBRmobm = router.Event + interface RequestEvent extends _sBRmobm { app: App auth?: Record } @@ -9608,8 +9618,8 @@ namespace core { */ clone(): (RequestInfo) } - type _seakEUg = hook.Event&RequestEvent - interface BatchRequestEvent extends _seakEUg { + type _svLlqkX = hook.Event&RequestEvent + interface BatchRequestEvent extends _svLlqkX { batch: Array<(InternalRequest | undefined)> } interface InternalRequest { @@ -9651,24 +9661,24 @@ namespace core { */ tags(): Array } - type _smKcRqA = hook.Event - interface BootstrapEvent extends _smKcRqA { + type _swTqVUi = hook.Event + interface BootstrapEvent extends _swTqVUi { app: App } - type _svwsFqW = hook.Event - interface TerminateEvent extends _svwsFqW { + type _stSrbzV = hook.Event + interface TerminateEvent extends _stSrbzV { app: App isRestart: boolean } - type _ssZaKbo = hook.Event - interface BackupEvent extends _ssZaKbo { + type _sfAzbKq = hook.Event + interface BackupEvent extends _sfAzbKq { app: App context: context.Context name: string // the name of the backup to create/restore. exclude: Array // list of dir entries to exclude from the backup create/restore. } - type _sfgoTts = hook.Event - interface ServeEvent extends _sfgoTts { + type _sMxYydy = hook.Event + interface ServeEvent extends _sMxYydy { app: App router?: router.Router server?: http.Server @@ -9715,31 +9725,39 @@ namespace core { */ fs: fs.FS } - type _sVuYBri = hook.Event&RequestEvent - interface SettingsListRequestEvent extends _sVuYBri { + type _sraCMNI = hook.Event&RequestEvent + interface SettingsListRequestEvent extends _sraCMNI { settings?: Settings } - type _sWWCPFA = hook.Event&RequestEvent - interface SettingsUpdateRequestEvent extends _sWWCPFA { + type _sxxoZbT = hook.Event&RequestEvent + interface SettingsUpdateRequestEvent extends _sxxoZbT { oldSettings?: Settings newSettings?: Settings } - type _ssqdEPv = hook.Event - interface SettingsReloadEvent extends _ssqdEPv { + type _shIjfcV = hook.Event + interface SettingsReloadEvent extends _shIjfcV { app: App } - type _sDJmyab = hook.Event - interface MailerEvent extends _sDJmyab { + type _sLSjZRd = hook.Event + interface MailerEvent extends _sLSjZRd { app: App mailer: mailer.Mailer message?: mailer.Message } - type _sQxMSYG = MailerEvent&baseRecordEventData - interface MailerRecordEvent extends _sQxMSYG { + type _suGXYim = MailerEvent&baseRecordEventData + interface MailerRecordEvent extends _suGXYim { meta: _TygojaDict } - type _sqcQlZs = hook.Event&baseModelEventData - interface ModelEvent extends _sqcQlZs { + type _svjtpoT = hook.Event&filesystem.NewWriterEvent + interface FilesystemNewWriterEvent extends _svjtpoT { + app: App + } + type _sRMYDve = hook.Event&filesystem.DeleteEvent + interface FilesystemDeleteEvent extends _sRMYDve { + app: App + } + type _sPheUbX = hook.Event&baseModelEventData + interface ModelEvent extends _sPheUbX { app: App context: context.Context /** @@ -9751,12 +9769,12 @@ namespace core { */ type: string } - type _sXWefEA = ModelEvent - interface ModelErrorEvent extends _sXWefEA { + type _sWRcbhd = ModelEvent + interface ModelErrorEvent extends _sWRcbhd { error: Error } - type _sdozjDH = hook.Event&baseRecordEventData - interface RecordEvent extends _sdozjDH { + type _sjYHcXw = hook.Event&baseRecordEventData + interface RecordEvent extends _sjYHcXw { app: App context: context.Context /** @@ -9768,12 +9786,12 @@ namespace core { */ type: string } - type _sXvClfK = RecordEvent - interface RecordErrorEvent extends _sXvClfK { + type _sLGNhhD = RecordEvent + interface RecordErrorEvent extends _sLGNhhD { error: Error } - type _sGEatec = hook.Event&baseCollectionEventData - interface CollectionEvent extends _sGEatec { + type _srlkPMj = hook.Event&baseCollectionEventData + interface CollectionEvent extends _srlkPMj { app: App context: context.Context /** @@ -9785,16 +9803,16 @@ namespace core { */ type: string } - type _sDpHwEB = CollectionEvent - interface CollectionErrorEvent extends _sDpHwEB { + type _sHXtCfC = CollectionEvent + interface CollectionErrorEvent extends _sHXtCfC { error: Error } - type _sbwGQSw = hook.Event&RequestEvent&baseRecordEventData - interface FileTokenRequestEvent extends _sbwGQSw { + type _sxeJCKn = hook.Event&RequestEvent&baseRecordEventData + interface FileTokenRequestEvent extends _sxeJCKn { token: string } - type _sGCRYlR = hook.Event&RequestEvent&baseCollectionEventData - interface FileDownloadRequestEvent extends _sGCRYlR { + type _szoWBfK = hook.Event&RequestEvent&baseCollectionEventData + interface FileDownloadRequestEvent extends _szoWBfK { record?: Record fileField?: FileField servedPath: string @@ -9808,21 +9826,21 @@ namespace core { */ thumbError: Error } - type _skOkcdS = hook.Event&RequestEvent - interface CollectionsListRequestEvent extends _skOkcdS { + type _sucxmuL = hook.Event&RequestEvent + interface CollectionsListRequestEvent extends _sucxmuL { collections: Array<(Collection | undefined)> result?: search.Result } - type _sscmyEt = hook.Event&RequestEvent - interface CollectionsImportRequestEvent extends _sscmyEt { + type _sXliejM = hook.Event&RequestEvent + interface CollectionsImportRequestEvent extends _sXliejM { collectionsData: Array<_TygojaDict> deleteMissing: boolean } - type _sdKUQUV = hook.Event&RequestEvent&baseCollectionEventData - interface CollectionRequestEvent extends _sdKUQUV { + type _sUmwboL = hook.Event&RequestEvent&baseCollectionEventData + interface CollectionRequestEvent extends _sUmwboL { } - type _skGYOyZ = hook.Event&RequestEvent - interface RealtimeConnectRequestEvent extends _skGYOyZ { + type _smKHKrb = hook.Event&RequestEvent + interface RealtimeConnectRequestEvent extends _smKHKrb { client: subscriptions.Client /** * IdleTimeout specifies the max duration to wait for a new message @@ -9846,59 +9864,59 @@ namespace core { */ maxTimeout: time.Duration } - type _soMcVGl = hook.Event&RequestEvent - interface RealtimeMessageEvent extends _soMcVGl { + type _sfHbXWy = hook.Event&RequestEvent + interface RealtimeMessageEvent extends _sfHbXWy { client: subscriptions.Client message?: subscriptions.Message } - type _skSioRZ = hook.Event&RequestEvent - interface RealtimeSubscribeRequestEvent extends _skSioRZ { + type _shiFSCG = hook.Event&RequestEvent + interface RealtimeSubscribeRequestEvent extends _shiFSCG { client: subscriptions.Client subscriptions: Array } - type _skhQAbo = hook.Event&RequestEvent&baseCollectionEventData - interface RecordsListRequestEvent extends _skhQAbo { + type _swHaxVx = hook.Event&RequestEvent&baseCollectionEventData + interface RecordsListRequestEvent extends _swHaxVx { /** * @todo consider removing and maybe add as generic to the search.Result? */ records: Array<(Record | undefined)> result?: search.Result } - type _sypXmWm = hook.Event&RequestEvent&baseCollectionEventData - interface RecordRequestEvent extends _sypXmWm { + type _swYgiiQ = hook.Event&RequestEvent&baseCollectionEventData + interface RecordRequestEvent extends _swYgiiQ { record?: Record } - type _sneASKg = hook.Event&baseRecordEventData - interface RecordEnrichEvent extends _sneASKg { + type _soFmWtI = hook.Event&baseRecordEventData + interface RecordEnrichEvent extends _soFmWtI { app: App requestInfo?: RequestInfo } - type _sEYJvyJ = hook.Event&RequestEvent&baseCollectionEventData - interface RecordCreateOTPRequestEvent extends _sEYJvyJ { + type _sjZroLq = hook.Event&RequestEvent&baseCollectionEventData + interface RecordCreateOTPRequestEvent extends _sjZroLq { record?: Record password: string } - type _sHQZafL = hook.Event&RequestEvent&baseCollectionEventData - interface RecordAuthWithOTPRequestEvent extends _sHQZafL { + type _sSmZJuU = hook.Event&RequestEvent&baseCollectionEventData + interface RecordAuthWithOTPRequestEvent extends _sSmZJuU { record?: Record otp?: OTP } - type _sSXEIhO = hook.Event&RequestEvent&baseCollectionEventData - interface RecordAuthRequestEvent extends _sSXEIhO { + type _snBkwXW = hook.Event&RequestEvent&baseCollectionEventData + interface RecordAuthRequestEvent extends _snBkwXW { record?: Record token: string meta: any authMethod: string } - type _sEqiNyr = hook.Event&RequestEvent&baseCollectionEventData - interface RecordAuthWithPasswordRequestEvent extends _sEqiNyr { + type _smqUpJW = hook.Event&RequestEvent&baseCollectionEventData + interface RecordAuthWithPasswordRequestEvent extends _smqUpJW { record?: Record identity: string identityField: string password: string } - type _sGKDemR = hook.Event&RequestEvent&baseCollectionEventData - interface RecordAuthWithOAuth2RequestEvent extends _sGKDemR { + type _ssdCPLG = hook.Event&RequestEvent&baseCollectionEventData + interface RecordAuthWithOAuth2RequestEvent extends _ssdCPLG { providerName: string providerClient: auth.Provider record?: Record @@ -9906,41 +9924,41 @@ namespace core { createData: _TygojaDict isNewRecord: boolean } - type _sMNQfUv = hook.Event&RequestEvent&baseCollectionEventData - interface RecordAuthRefreshRequestEvent extends _sMNQfUv { + type _swsbrYq = hook.Event&RequestEvent&baseCollectionEventData + interface RecordAuthRefreshRequestEvent extends _swsbrYq { record?: Record } - type _sqjWSJM = hook.Event&RequestEvent&baseCollectionEventData - interface RecordRequestPasswordResetRequestEvent extends _sqjWSJM { + type _smYyvhH = hook.Event&RequestEvent&baseCollectionEventData + interface RecordRequestPasswordResetRequestEvent extends _smYyvhH { record?: Record } - type _srdbtmn = hook.Event&RequestEvent&baseCollectionEventData - interface RecordConfirmPasswordResetRequestEvent extends _srdbtmn { + type _sdyhuxX = hook.Event&RequestEvent&baseCollectionEventData + interface RecordConfirmPasswordResetRequestEvent extends _sdyhuxX { record?: Record } - type _sJdvlvP = hook.Event&RequestEvent&baseCollectionEventData - interface RecordRequestVerificationRequestEvent extends _sJdvlvP { + type _sLTnTCC = hook.Event&RequestEvent&baseCollectionEventData + interface RecordRequestVerificationRequestEvent extends _sLTnTCC { record?: Record } - type _sAaKczp = hook.Event&RequestEvent&baseCollectionEventData - interface RecordConfirmVerificationRequestEvent extends _sAaKczp { + type _sGZFTeE = hook.Event&RequestEvent&baseCollectionEventData + interface RecordConfirmVerificationRequestEvent extends _sGZFTeE { record?: Record } - type _spPLJQW = hook.Event&RequestEvent&baseCollectionEventData - interface RecordRequestEmailChangeRequestEvent extends _spPLJQW { + type _sDVwSKs = hook.Event&RequestEvent&baseCollectionEventData + interface RecordRequestEmailChangeRequestEvent extends _sDVwSKs { record?: Record newEmail: string } - type _sSQGmba = hook.Event&RequestEvent&baseCollectionEventData - interface RecordConfirmEmailChangeRequestEvent extends _sSQGmba { + type _smHVdrR = hook.Event&RequestEvent&baseCollectionEventData + interface RecordConfirmEmailChangeRequestEvent extends _smHVdrR { record?: Record newEmail: string } /** * ExternalAuth defines a Record proxy for working with the externalAuths collection. */ - type _sWvgHUl = Record - interface ExternalAuth extends _sWvgHUl { + type _sXZNnnm = Record + interface ExternalAuth extends _sXZNnnm { } interface newExternalAuth { /** @@ -12483,8 +12501,8 @@ namespace core { interface onlyFieldType { type: string } - type _ssKVGAg = Field - interface fieldWithType extends _ssKVGAg { + type _svyUoAD = Field + interface fieldWithType extends _svyUoAD { type: string } interface fieldWithType { @@ -12516,8 +12534,8 @@ namespace core { */ scan(value: any): void } - type _swoXEdw = BaseModel - interface Log extends _swoXEdw { + type _sfSBKMT = BaseModel + interface Log extends _sfSBKMT { created: types.DateTime data: types.JSONMap message: string @@ -12526,6 +12544,15 @@ namespace core { interface Log { tableName(): string } + interface Log { + /** + * DBExport prepares and exports the current log model for db persistence. + * + * It also truncates the log's message and data to ensure that it is + * under app.Settings().Logs.MaxDataSize. + */ + dbExport(app: App): _TygojaDict + } interface BaseApp { /** * LogQuery returns a new Log select query. @@ -12563,8 +12590,8 @@ namespace core { /** * MFA defines a Record proxy for working with the mfas collection. */ - type _sFEvYyU = Record - interface MFA extends _sFEvYyU { + type _sBsoayf = Record + interface MFA extends _sBsoayf { } interface newMFA { /** @@ -12786,8 +12813,8 @@ namespace core { /** * OTP defines a Record proxy for working with the otps collection. */ - type _srsYrRx = Record - interface OTP extends _srsYrRx { + type _sENHuQK = Record + interface OTP extends _sENHuQK { } interface newOTP { /** @@ -13023,8 +13050,8 @@ namespace core { } interface runner { } - type _sYqnYRG = BaseModel - interface Record extends _sYqnYRG { + type _sUAOsut = BaseModel + interface Record extends _sUAOsut { } interface newRecord { /** @@ -13229,6 +13256,12 @@ namespace core { */ getInt(key: string): number } + interface Record { + /** + * GetInt64 returns the data value for "key" as an int64. + */ + getInt64(key: string): number + } interface Record { /** * GetFloat returns the data value for "key" as a float64. @@ -13499,8 +13532,8 @@ namespace core { * BaseRecordProxy implements the [RecordProxy] interface and it is intended * to be used as embed to custom user provided Record proxy structs. */ - type _sMKXSwM = Record - interface BaseRecordProxy extends _sMKXSwM { + type _sPRRXEF = Record + interface BaseRecordProxy extends _sPRRXEF { } interface BaseRecordProxy { /** @@ -13754,8 +13787,8 @@ namespace core { /** * Settings defines the PocketBase app settings. */ - type _smpJHkh = settings - interface Settings extends _smpJHkh { + type _svieIsW = settings + interface Settings extends _svieIsW { } interface Settings { /** @@ -13958,6 +13991,13 @@ namespace core { validate(): void } interface LogsConfig { + /** + * MaxDataSize specifies the maximum allowed serialized log data + * size before it gets truncated (see [Log.DBExport]). + * + * If zero, fallbacks to ~16kb by default. + */ + maxDataSize: number maxDays: number minLevel: number logIP: boolean @@ -14075,8 +14115,8 @@ namespace core { */ string(): string } - type _szfByFq = BaseModel - interface Param extends _szfByFq { + type _slXAIOb = BaseModel + interface Param extends _slXAIOb { created: types.DateTime updated: types.DateTime value: types.JSONRaw @@ -14617,8 +14657,8 @@ namespace apis { */ (limitBytes: number): (hook.Handler) } - type _sjENuGF = io.ReadCloser - interface limitedReader extends _sjENuGF { + type _sBtDrzx = io.ReadCloser + interface limitedReader extends _sBtDrzx { } interface limitedReader { read(b: string|Array): number @@ -14772,8 +14812,8 @@ namespace apis { */ (config: GzipConfig): (hook.Handler) } - type _sMrEyeL = http.ResponseWriter&io.Writer - interface gzipResponseWriter extends _sMrEyeL { + type _sNTnWWH = http.ResponseWriter&io.Writer + interface gzipResponseWriter extends _sNTnWWH { } interface gzipResponseWriter { writeHeader(code: number): void @@ -14793,16 +14833,16 @@ namespace apis { interface gzipResponseWriter { unwrap(): http.ResponseWriter } - type _sIALAAM = sync.RWMutex - interface rateLimiter extends _sIALAAM { + type _sYUnciu = sync.RWMutex + interface rateLimiter extends _sYUnciu { } /** * @todo evaluate swiching to sliding window with approximation counter similar to Cloudflare. * * rateClient implements fixed window rate limit strategy. */ - type _sIHqKuj = sync.Mutex - interface rateClient extends _sIHqKuj { + type _sAjPyBO = sync.Mutex + interface rateClient extends _sAjPyBO { } interface realtimeSubscribeForm { clientId: string @@ -15062,8 +15102,8 @@ namespace pocketbase { * It implements [CoreApp] via embedding and all of the app interface methods * could be accessed directly through the instance (eg. PocketBase.DataDir()). */ - type _stfZQeT = CoreApp - interface PocketBase extends _stfZQeT { + type _sAFUGpF = CoreApp + interface PocketBase extends _sAFUGpF { /** * RootCmd is the main console command */ @@ -15135,16 +15175,115 @@ namespace pocketbase { */ execute(): void } + interface nopWrite { + } + interface nopWrite { + write(p: string|Array): number + } +} + +/** + * Package template is a thin wrapper around the standard html/template + * and text/template packages that implements a convenient registry to + * load and cache templates on the fly concurrently. + * + * It was created to assist the JSVM plugin HTML rendering, but could be used in other Go code. + * + * Example: + * + * ``` + * registry := template.NewRegistry() + * + * html1, err := registry.LoadFiles( + * // the files set wil be parsed only once and then cached + * "layout.html", + * "content.html", + * ).Render(map[string]any{"name": "John"}) + * + * html2, err := registry.LoadFiles( + * // reuse the already parsed and cached files set + * "layout.html", + * "content.html", + * ).Render(map[string]any{"name": "Jane"}) + * ``` + */ +namespace template { + interface newRegistry { + /** + * NewRegistry creates and initializes a new templates registry with + * some defaults (eg. global "raw" template function for unescaped HTML). + * + * Use the Registry.Load* methods to load templates into the registry. + */ + (): (Registry) + } /** - * coloredWriter is a small wrapper struct to construct a [color.Color] writter. + * Registry defines a templates registry that is safe to be used by multiple goroutines. + * + * Use the Registry.Load* methods to load templates into the registry. */ - interface coloredWriter { + interface Registry { } - interface coloredWriter { + interface Registry { /** - * Write writes the p bytes using the colored writer. + * AddFuncs registers new global template functions. + * + * The key of each map entry is the function name that will be used in the templates. + * If a function with the map entry name already exists it will be replaced with the new one. + * + * The value of each map entry is a function that must have either a + * single return value, or two return values of which the second has type error. + * + * Example: + * + * ``` + * r.AddFuncs(map[string]any{ + * "toUpper": func(str string) string { + * return strings.ToUppser(str) + * }, + * ... + * }) + * ``` */ - write(p: string|Array): number + addFuncs(funcs: _TygojaDict): (Registry) + } + interface Registry { + /** + * LoadFiles caches (if not already) the specified filenames set as a + * single template and returns a ready to use Renderer instance. + * + * There must be at least 1 filename specified. + */ + loadFiles(...filenames: string[]): (Renderer) + } + interface Registry { + /** + * LoadString caches (if not already) the specified inline string as a + * single template and returns a ready to use Renderer instance. + */ + loadString(text: string): (Renderer) + } + interface Registry { + /** + * LoadFS caches (if not already) the specified fs and globPatterns + * pair as single template and returns a ready to use Renderer instance. + * + * There must be at least 1 file matching the provided globPattern(s) + * (note that most file names serves as glob patterns matching themselves). + */ + loadFS(fsys: fs.FS, ...globPatterns: string[]): (Renderer) + } + /** + * Renderer defines a single parsed template. + */ + interface Renderer { + } + interface Renderer { + /** + * Render executes the template with the specified data as the dot object + * and returns the result as plain string. + */ + render(data: any): string } } @@ -15205,6 +15344,8 @@ namespace sync { */ unlock(): void } + // @ts-ignore + import rtatomic = atomic /** * A RWMutex is a reader/writer mutual exclusion lock. * The lock can be held by an arbitrary number of readers or a single writer. @@ -15579,8 +15720,6 @@ namespace syscall { */ write(f: (fd: number) => boolean): void } - // @ts-ignore - import runtimesyscall = syscall /** * An Errno is an unsigned number describing an error condition. * It implements the error interface. The zero Errno is by convention @@ -16218,6 +16357,19 @@ namespace time { * A file system can be provided by the host operating system * but also by other packages. * + * # Path Names + * + * The interfaces in this package all operate on the same + * path name syntax, regardless of the host operating system. + * + * Path names are UTF-8-encoded, + * unrooted, slash-separated sequences of path elements, like “x/y/z”. + * Path names must not contain an element that is “.” or “..” or the empty string, + * except for the special case that the name "." may be used for the root directory. + * Paths must not start or end with a slash: “/x” and “x/” are invalid. + * + * # Testing + * * See the [testing/fstest] package for support with testing * implementations of file systems. */ @@ -16414,603 +16566,331 @@ namespace fs { interface WalkDirFunc {(path: string, d: DirEntry, err: Error): void } } -namespace store { +/** + * Package bufio implements buffered I/O. It wraps an io.Reader or io.Writer + * object, creating another object (Reader or Writer) that also implements + * the interface but provides buffering and some help for textual I/O. + */ +namespace bufio { /** - * Store defines a concurrent safe in memory key-value data store. + * ReadWriter stores pointers to a [Reader] and a [Writer]. + * It implements [io.ReadWriter]. */ - interface Store { - } - interface Store { - /** - * Reset clears the store and replaces the store data with a - * shallow copy of the provided newData. - */ - reset(newData: _TygojaDict): void + type _siMPrUO = Reader&Writer + interface ReadWriter extends _siMPrUO { } - interface Store { +} + +/** + * Package context defines the Context type, which carries deadlines, + * cancellation signals, and other request-scoped values across API boundaries + * and between processes. + * + * Incoming requests to a server should create a [Context], and outgoing + * calls to servers should accept a Context. The chain of function + * calls between them must propagate the Context, optionally replacing + * it with a derived Context created using [WithCancel], [WithDeadline], + * [WithTimeout], or [WithValue]. + * + * A Context may be canceled to indicate that work done on its behalf should stop. + * A Context with a deadline is canceled after the deadline passes. + * When a Context is canceled, all Contexts derived from it are also canceled. + * + * The [WithCancel], [WithDeadline], and [WithTimeout] functions take a + * Context (the parent) and return a derived Context (the child) and a + * [CancelFunc]. Calling the CancelFunc directly cancels the child and its + * children, removes the parent's reference to the child, and stops + * any associated timers. Failing to call the CancelFunc leaks the + * child and its children until the parent is canceled. The go vet tool + * checks that CancelFuncs are used on all control-flow paths. + * + * The [WithCancelCause] function returns a [CancelCauseFunc], which takes + * an error and records it as the cancellation cause. [WithDeadlineCause] + * and [WithTimeoutCause] take a cause to use when the deadline expires. + * Calling [Cause] on the canceled context or any of its children retrieves + * the cause. If no cause is specified, Cause(ctx) returns the same value + * as ctx.Err(). + * + * Programs that use Contexts should follow these rules to keep interfaces + * consistent across packages and enable static analysis tools to check context + * propagation: + * + * Do not store Contexts inside a struct type; instead, pass a Context + * explicitly to each function that needs it. This is discussed further in + * https://go.dev/blog/context-and-structs. The Context should be the first + * parameter, typically named ctx: + * + * ``` + * func DoSomething(ctx context.Context, arg Arg) error { + * // ... use ctx ... + * } + * ``` + * + * Do not pass a nil [Context], even if a function permits it. Pass [context.TODO] + * if you are unsure about which Context to use. + * + * Use context Values only for request-scoped data that transits processes and + * APIs, not for passing optional parameters to functions. + * + * The same Context may be passed to functions running in different goroutines; + * Contexts are safe for simultaneous use by multiple goroutines. + * + * See https://go.dev/blog/context for example code for a server that uses + * Contexts. + */ +namespace context { + /** + * A Context carries a deadline, a cancellation signal, and other values across + * API boundaries. + * + * Context's methods may be called by multiple goroutines simultaneously. + */ + interface Context { + [key:string]: any; /** - * Length returns the current number of elements in the store. + * Deadline returns the time when work done on behalf of this context + * should be canceled. Deadline returns ok==false when no deadline is + * set. Successive calls to Deadline return the same results. */ - length(): number - } - interface Store { + deadline(): [time.Time, boolean] /** - * RemoveAll removes all the existing store entries. - */ - removeAll(): void - } - interface Store { - /** - * Remove removes a single entry from the store. + * Done returns a channel that's closed when work done on behalf of this + * context should be canceled. Done may return nil if this context can + * never be canceled. Successive calls to Done return the same value. + * The close of the Done channel may happen asynchronously, + * after the cancel function returns. * - * Remove does nothing if key doesn't exist in the store. - */ - remove(key: K): void - } - interface Store { - /** - * Has checks if element with the specified key exist or not. - */ - has(key: K): boolean - } - interface Store { - /** - * Get returns a single element value from the store. + * WithCancel arranges for Done to be closed when cancel is called; + * WithDeadline arranges for Done to be closed when the deadline + * expires; WithTimeout arranges for Done to be closed when the timeout + * elapses. * - * If key is not set, the zero T value is returned. - */ - get(key: K): T - } - interface Store { - /** - * GetOk is similar to Get but returns also a boolean indicating whether the key exists or not. - */ - getOk(key: K): [T, boolean] - } - interface Store { - /** - * GetAll returns a shallow copy of the current store data. - */ - getAll(): _TygojaDict - } - interface Store { - /** - * Values returns a slice with all of the current store values. + * Done is provided for use in select statements: + * + * // Stream generates values with DoSomething and sends them to out + * // until DoSomething returns an error or ctx.Done is closed. + * func Stream(ctx context.Context, out chan<- Value) error { + * for { + * v, err := DoSomething(ctx) + * if err != nil { + * return err + * } + * select { + * case <-ctx.Done(): + * return ctx.Err() + * case out <- v: + * } + * } + * } + * + * See https://go.dev/blog/pipelines for more examples of how to use + * a Done channel for cancellation. */ - values(): Array - } - interface Store { + done(): undefined /** - * Set sets (or overwrite if already exists) a new value for key. + * If Done is not yet closed, Err returns nil. + * If Done is closed, Err returns a non-nil error explaining why: + * DeadlineExceeded if the context's deadline passed, + * or Canceled if the context was canceled for some other reason. + * After Err returns a non-nil error, successive calls to Err return the same error. */ - set(key: K, value: T): void - } - interface Store { + err(): void /** - * SetFunc sets (or overwrite if already exists) a new value resolved - * from the function callback for the provided key. + * Value returns the value associated with this context for key, or nil + * if no value is associated with key. Successive calls to Value with + * the same key returns the same result. * - * The function callback receives as argument the old store element value (if exists). - * If there is no old store element, the argument will be the T zero value. + * Use context values only for request-scoped data that transits + * processes and API boundaries, not for passing optional parameters to + * functions. * - * Example: + * A key identifies a specific value in a Context. Functions that wish + * to store values in Context typically allocate a key in a global + * variable then use that key as the argument to context.WithValue and + * Context.Value. A key can be any type that supports equality; + * packages should define keys as an unexported type to avoid + * collisions. + * + * Packages that define a Context key should provide type-safe accessors + * for the values stored using that key: * * ``` - * s := store.New[string, int](nil) - * s.SetFunc("count", func(old int) int { - * return old + 1 - * }) - * ``` - */ - setFunc(key: K, fn: (old: T) => T): void - } - interface Store { - /** - * GetOrSet retrieves a single existing value for the provided key - * or stores a new one if it doesn't exist. - */ - getOrSet(key: K, setFunc: () => T): T - } - interface Store { - /** - * SetIfLessThanLimit sets (or overwrite if already exist) a new value for key. + * // Package user defines a User type that's stored in Contexts. + * package user * - * This method is similar to Set() but **it will skip adding new elements** - * to the store if the store length has reached the specified limit. - * false is returned if maxAllowedElements limit is reached. - */ - setIfLessThanLimit(key: K, value: T, maxAllowedElements: number): boolean - } - interface Store { - /** - * UnmarshalJSON implements [json.Unmarshaler] and imports the - * provided JSON data into the store. + * import "context" * - * The store entries that match with the ones from the data will be overwritten with the new value. - */ - unmarshalJSON(data: string|Array): void - } - interface Store { - /** - * MarshalJSON implements [json.Marshaler] and export the current - * store data into valid JSON. + * // User is the type of value stored in the Contexts. + * type User struct {...} + * + * // key is an unexported type for keys defined in this package. + * // This prevents collisions with keys defined in other packages. + * type key int + * + * // userKey is the key for user.User values in Contexts. It is + * // unexported; clients use user.NewContext and user.FromContext + * // instead of using this key directly. + * var userKey key + * + * // NewContext returns a new Context that carries value u. + * func NewContext(ctx context.Context, u *User) context.Context { + * return context.WithValue(ctx, userKey, u) + * } + * + * // FromContext returns the User value stored in ctx, if any. + * func FromContext(ctx context.Context) (*User, bool) { + * u, ok := ctx.Value(userKey).(*User) + * return u, ok + * } + * ``` */ - marshalJSON(): string|Array + value(key: any): any } } /** - * Package syntax parses regular expressions into parse trees and compiles - * parse trees into programs. Most clients of regular expressions will use the - * facilities of package [regexp] (such as [regexp.Compile] and [regexp.Match]) instead of this package. - * - * # Syntax + * Package net provides a portable interface for network I/O, including + * TCP/IP, UDP, domain name resolution, and Unix domain sockets. * - * The regular expression syntax understood by this package when parsing with the [Perl] flag is as follows. - * Parts of the syntax can be disabled by passing alternate flags to [Parse]. + * Although the package provides access to low-level networking + * primitives, most clients will need only the basic interface provided + * by the [Dial], [Listen], and Accept functions and the associated + * [Conn] and [Listener] interfaces. The crypto/tls package uses + * the same interfaces and similar Dial and Listen functions. * - * Single characters: + * The Dial function connects to a server: * * ``` - * . any character, possibly including newline (flag s=true) - * [xyz] character class - * [^xyz] negated character class - * \d Perl character class - * \D negated Perl character class - * [[:alpha:]] ASCII character class - * [[:^alpha:]] negated ASCII character class - * \pN Unicode character class (one-letter name) - * \p{Greek} Unicode character class - * \PN negated Unicode character class (one-letter name) - * \P{Greek} negated Unicode character class + * conn, err := net.Dial("tcp", "golang.org:80") + * if err != nil { + * // handle error + * } + * fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n") + * status, err := bufio.NewReader(conn).ReadString('\n') + * // ... * ``` * - * Composites: + * The Listen function creates servers: * * ``` - * xy x followed by y - * x|y x or y (prefer x) + * ln, err := net.Listen("tcp", ":8080") + * if err != nil { + * // handle error + * } + * for { + * conn, err := ln.Accept() + * if err != nil { + * // handle error + * } + * go handleConnection(conn) + * } * ``` * - * Repetitions: + * # Name Resolution * - * ``` - * x* zero or more x, prefer more - * x+ one or more x, prefer more - * x? zero or one x, prefer one - * x{n,m} n or n+1 or ... or m x, prefer more - * x{n,} n or more x, prefer more - * x{n} exactly n x - * x*? zero or more x, prefer fewer - * x+? one or more x, prefer fewer - * x?? zero or one x, prefer zero - * x{n,m}? n or n+1 or ... or m x, prefer fewer - * x{n,}? n or more x, prefer fewer - * x{n}? exactly n x - * ``` + * The method for resolving domain names, whether indirectly with functions like Dial + * or directly with functions like [LookupHost] and [LookupAddr], varies by operating system. * - * Implementation restriction: The counting forms x{n,m}, x{n,}, and x{n} - * reject forms that create a minimum or maximum repetition count above 1000. - * Unlimited repetitions are not subject to this restriction. + * On Unix systems, the resolver has two options for resolving names. + * It can use a pure Go resolver that sends DNS requests directly to the servers + * listed in /etc/resolv.conf, or it can use a cgo-based resolver that calls C + * library routines such as getaddrinfo and getnameinfo. * - * Grouping: + * On Unix the pure Go resolver is preferred over the cgo resolver, because a blocked DNS + * request consumes only a goroutine, while a blocked C call consumes an operating system thread. + * When cgo is available, the cgo-based resolver is used instead under a variety of + * conditions: on systems that do not let programs make direct DNS requests (OS X), + * when the LOCALDOMAIN environment variable is present (even if empty), + * when the RES_OPTIONS or HOSTALIASES environment variable is non-empty, + * when the ASR_CONFIG environment variable is non-empty (OpenBSD only), + * when /etc/resolv.conf or /etc/nsswitch.conf specify the use of features that the + * Go resolver does not implement. + * + * On all systems (except Plan 9), when the cgo resolver is being used + * this package applies a concurrent cgo lookup limit to prevent the system + * from running out of system threads. Currently, it is limited to 500 concurrent lookups. + * + * The resolver decision can be overridden by setting the netdns value of the + * GODEBUG environment variable (see package runtime) to go or cgo, as in: * * ``` - * (re) numbered capturing group (submatch) - * (?Pre) named & numbered capturing group (submatch) - * (?re) named & numbered capturing group (submatch) - * (?:re) non-capturing group - * (?flags) set flags within current group; non-capturing - * (?flags:re) set flags during re; non-capturing - * - * Flag syntax is xyz (set) or -xyz (clear) or xy-z (set xy, clear z). The flags are: - * - * i case-insensitive (default false) - * m multi-line mode: ^ and $ match begin/end line in addition to begin/end text (default false) - * s let . match \n (default false) - * U ungreedy: swap meaning of x* and x*?, x+ and x+?, etc (default false) - * ``` - * - * Empty strings: - * - * ``` - * ^ at beginning of text or line (flag m=true) - * $ at end of text (like \z not \Z) or line (flag m=true) - * \A at beginning of text - * \b at ASCII word boundary (\w on one side and \W, \A, or \z on the other) - * \B not at ASCII word boundary - * \z at end of text - * ``` - * - * Escape sequences: - * - * ``` - * \a bell (== \007) - * \f form feed (== \014) - * \t horizontal tab (== \011) - * \n newline (== \012) - * \r carriage return (== \015) - * \v vertical tab character (== \013) - * \* literal *, for any punctuation character * - * \123 octal character code (up to three digits) - * \x7F hex character code (exactly two digits) - * \x{10FFFF} hex character code - * \Q...\E literal text ... even if ... has punctuation - * ``` - * - * Character class elements: - * - * ``` - * x single character - * A-Z character range (inclusive) - * \d Perl character class - * [:foo:] ASCII character class foo - * \p{Foo} Unicode character class Foo - * \pF Unicode character class F (one-letter name) - * ``` - * - * Named character classes as character class elements: - * - * ``` - * [\d] digits (== \d) - * [^\d] not digits (== \D) - * [\D] not digits (== \D) - * [^\D] not not digits (== \d) - * [[:name:]] named ASCII class inside character class (== [:name:]) - * [^[:name:]] named ASCII class inside negated character class (== [:^name:]) - * [\p{Name}] named Unicode property inside character class (== \p{Name}) - * [^\p{Name}] named Unicode property inside negated character class (== \P{Name}) - * ``` - * - * Perl character classes (all ASCII-only): - * - * ``` - * \d digits (== [0-9]) - * \D not digits (== [^0-9]) - * \s whitespace (== [\t\n\f\r ]) - * \S not whitespace (== [^\t\n\f\r ]) - * \w word characters (== [0-9A-Za-z_]) - * \W not word characters (== [^0-9A-Za-z_]) - * ``` - * - * ASCII character classes: - * - * ``` - * [[:alnum:]] alphanumeric (== [0-9A-Za-z]) - * [[:alpha:]] alphabetic (== [A-Za-z]) - * [[:ascii:]] ASCII (== [\x00-\x7F]) - * [[:blank:]] blank (== [\t ]) - * [[:cntrl:]] control (== [\x00-\x1F\x7F]) - * [[:digit:]] digits (== [0-9]) - * [[:graph:]] graphical (== [!-~] == [A-Za-z0-9!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]) - * [[:lower:]] lower case (== [a-z]) - * [[:print:]] printable (== [ -~] == [ [:graph:]]) - * [[:punct:]] punctuation (== [!-/:-@[-`{-~]) - * [[:space:]] whitespace (== [\t\n\v\f\r ]) - * [[:upper:]] upper case (== [A-Z]) - * [[:word:]] word characters (== [0-9A-Za-z_]) - * [[:xdigit:]] hex digit (== [0-9A-Fa-f]) + * export GODEBUG=netdns=go # force pure Go resolver + * export GODEBUG=netdns=cgo # force native resolver (cgo, win32) * ``` * - * Unicode character classes are those in [unicode.Categories], - * [unicode.CategoryAliases], and [unicode.Scripts]. - */ -namespace syntax { - /** - * Flags control the behavior of the parser and record information about regexp context. - */ - interface Flags extends Number{} -} - -/** - * Package context defines the Context type, which carries deadlines, - * cancellation signals, and other request-scoped values across API boundaries - * and between processes. - * - * Incoming requests to a server should create a [Context], and outgoing - * calls to servers should accept a Context. The chain of function - * calls between them must propagate the Context, optionally replacing - * it with a derived Context created using [WithCancel], [WithDeadline], - * [WithTimeout], or [WithValue]. - * - * A Context may be canceled to indicate that work done on its behalf should stop. - * A Context with a deadline is canceled after the deadline passes. - * When a Context is canceled, all Contexts derived from it are also canceled. - * - * The [WithCancel], [WithDeadline], and [WithTimeout] functions take a - * Context (the parent) and return a derived Context (the child) and a - * [CancelFunc]. Calling the CancelFunc directly cancels the child and its - * children, removes the parent's reference to the child, and stops - * any associated timers. Failing to call the CancelFunc leaks the - * child and its children until the parent is canceled. The go vet tool - * checks that CancelFuncs are used on all control-flow paths. - * - * The [WithCancelCause], [WithDeadlineCause], and [WithTimeoutCause] functions - * return a [CancelCauseFunc], which takes an error and records it as - * the cancellation cause. Calling [Cause] on the canceled context - * or any of its children retrieves the cause. If no cause is specified, - * Cause(ctx) returns the same value as ctx.Err(). - * - * Programs that use Contexts should follow these rules to keep interfaces - * consistent across packages and enable static analysis tools to check context - * propagation: - * - * Do not store Contexts inside a struct type; instead, pass a Context - * explicitly to each function that needs it. This is discussed further in - * https://go.dev/blog/context-and-structs. The Context should be the first - * parameter, typically named ctx: + * The decision can also be forced while building the Go source tree + * by setting the netgo or netcgo build tag. + * The netgo build tag disables entirely the use of the native (CGO) resolver, + * meaning the Go resolver is the only one that can be used. + * With the netcgo build tag the native and the pure Go resolver are compiled into the binary, + * but the native (CGO) resolver is preferred over the Go resolver. + * With netcgo, the Go resolver can still be forced at runtime with GODEBUG=netdns=go. * - * ``` - * func DoSomething(ctx context.Context, arg Arg) error { - * // ... use ctx ... - * } - * ``` + * A numeric netdns setting, as in GODEBUG=netdns=1, causes the resolver + * to print debugging information about its decisions. + * To force a particular resolver while also printing debugging information, + * join the two settings by a plus sign, as in GODEBUG=netdns=go+1. * - * Do not pass a nil [Context], even if a function permits it. Pass [context.TODO] - * if you are unsure about which Context to use. + * The Go resolver will send an EDNS0 additional header with a DNS request, + * to signal a willingness to accept a larger DNS packet size. + * This can reportedly cause sporadic failures with the DNS server run + * by some modems and routers. Setting GODEBUG=netedns0=0 will disable + * sending the additional header. * - * Use context Values only for request-scoped data that transits processes and - * APIs, not for passing optional parameters to functions. + * On macOS, if Go code that uses the net package is built with + * -buildmode=c-archive, linking the resulting archive into a C program + * requires passing -lresolv when linking the C code. * - * The same Context may be passed to functions running in different goroutines; - * Contexts are safe for simultaneous use by multiple goroutines. + * On Plan 9, the resolver always accesses /net/cs and /net/dns. * - * See https://go.dev/blog/context for example code for a server that uses - * Contexts. + * On Windows, in Go 1.18.x and earlier, the resolver always used C + * library functions, such as GetAddrInfo and DnsQuery. */ -namespace context { +namespace net { /** - * A Context carries a deadline, a cancellation signal, and other values across - * API boundaries. + * Conn is a generic stream-oriented network connection. * - * Context's methods may be called by multiple goroutines simultaneously. + * Multiple goroutines may invoke methods on a Conn simultaneously. */ - interface Context { + interface Conn { [key:string]: any; /** - * Deadline returns the time when work done on behalf of this context - * should be canceled. Deadline returns ok==false when no deadline is - * set. Successive calls to Deadline return the same results. + * Read reads data from the connection. + * Read can be made to time out and return an error after a fixed + * time limit; see SetDeadline and SetReadDeadline. */ - deadline(): [time.Time, boolean] + read(b: string|Array): number /** - * Done returns a channel that's closed when work done on behalf of this - * context should be canceled. Done may return nil if this context can - * never be canceled. Successive calls to Done return the same value. - * The close of the Done channel may happen asynchronously, - * after the cancel function returns. - * - * WithCancel arranges for Done to be closed when cancel is called; - * WithDeadline arranges for Done to be closed when the deadline - * expires; WithTimeout arranges for Done to be closed when the timeout - * elapses. - * - * Done is provided for use in select statements: - * - * // Stream generates values with DoSomething and sends them to out - * // until DoSomething returns an error or ctx.Done is closed. - * func Stream(ctx context.Context, out chan<- Value) error { - * for { - * v, err := DoSomething(ctx) - * if err != nil { - * return err - * } - * select { - * case <-ctx.Done(): - * return ctx.Err() - * case out <- v: - * } - * } - * } - * - * See https://blog.golang.org/pipelines for more examples of how to use - * a Done channel for cancellation. + * Write writes data to the connection. + * Write can be made to time out and return an error after a fixed + * time limit; see SetDeadline and SetWriteDeadline. */ - done(): undefined + write(b: string|Array): number /** - * If Done is not yet closed, Err returns nil. - * If Done is closed, Err returns a non-nil error explaining why: - * DeadlineExceeded if the context's deadline passed, - * or Canceled if the context was canceled for some other reason. - * After Err returns a non-nil error, successive calls to Err return the same error. + * Close closes the connection. + * Any blocked Read or Write operations will be unblocked and return errors. + * Close may or may not block until any buffered data is sent; + * for TCP connections see [*TCPConn.SetLinger]. */ - err(): void + close(): void /** - * Value returns the value associated with this context for key, or nil - * if no value is associated with key. Successive calls to Value with - * the same key returns the same result. + * LocalAddr returns the local network address, if known. + */ + localAddr(): Addr + /** + * RemoteAddr returns the remote network address, if known. + */ + remoteAddr(): Addr + /** + * SetDeadline sets the read and write deadlines associated + * with the connection. It is equivalent to calling both + * SetReadDeadline and SetWriteDeadline. * - * Use context values only for request-scoped data that transits - * processes and API boundaries, not for passing optional parameters to - * functions. - * - * A key identifies a specific value in a Context. Functions that wish - * to store values in Context typically allocate a key in a global - * variable then use that key as the argument to context.WithValue and - * Context.Value. A key can be any type that supports equality; - * packages should define keys as an unexported type to avoid - * collisions. - * - * Packages that define a Context key should provide type-safe accessors - * for the values stored using that key: - * - * ``` - * // Package user defines a User type that's stored in Contexts. - * package user - * - * import "context" - * - * // User is the type of value stored in the Contexts. - * type User struct {...} - * - * // key is an unexported type for keys defined in this package. - * // This prevents collisions with keys defined in other packages. - * type key int - * - * // userKey is the key for user.User values in Contexts. It is - * // unexported; clients use user.NewContext and user.FromContext - * // instead of using this key directly. - * var userKey key - * - * // NewContext returns a new Context that carries value u. - * func NewContext(ctx context.Context, u *User) context.Context { - * return context.WithValue(ctx, userKey, u) - * } - * - * // FromContext returns the User value stored in ctx, if any. - * func FromContext(ctx context.Context) (*User, bool) { - * u, ok := ctx.Value(userKey).(*User) - * return u, ok - * } - * ``` - */ - value(key: any): any - } -} - -/** - * Package net provides a portable interface for network I/O, including - * TCP/IP, UDP, domain name resolution, and Unix domain sockets. - * - * Although the package provides access to low-level networking - * primitives, most clients will need only the basic interface provided - * by the [Dial], [Listen], and Accept functions and the associated - * [Conn] and [Listener] interfaces. The crypto/tls package uses - * the same interfaces and similar Dial and Listen functions. - * - * The Dial function connects to a server: - * - * ``` - * conn, err := net.Dial("tcp", "golang.org:80") - * if err != nil { - * // handle error - * } - * fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n") - * status, err := bufio.NewReader(conn).ReadString('\n') - * // ... - * ``` - * - * The Listen function creates servers: - * - * ``` - * ln, err := net.Listen("tcp", ":8080") - * if err != nil { - * // handle error - * } - * for { - * conn, err := ln.Accept() - * if err != nil { - * // handle error - * } - * go handleConnection(conn) - * } - * ``` - * - * # Name Resolution - * - * The method for resolving domain names, whether indirectly with functions like Dial - * or directly with functions like [LookupHost] and [LookupAddr], varies by operating system. - * - * On Unix systems, the resolver has two options for resolving names. - * It can use a pure Go resolver that sends DNS requests directly to the servers - * listed in /etc/resolv.conf, or it can use a cgo-based resolver that calls C - * library routines such as getaddrinfo and getnameinfo. - * - * On Unix the pure Go resolver is preferred over the cgo resolver, because a blocked DNS - * request consumes only a goroutine, while a blocked C call consumes an operating system thread. - * When cgo is available, the cgo-based resolver is used instead under a variety of - * conditions: on systems that do not let programs make direct DNS requests (OS X), - * when the LOCALDOMAIN environment variable is present (even if empty), - * when the RES_OPTIONS or HOSTALIASES environment variable is non-empty, - * when the ASR_CONFIG environment variable is non-empty (OpenBSD only), - * when /etc/resolv.conf or /etc/nsswitch.conf specify the use of features that the - * Go resolver does not implement. - * - * On all systems (except Plan 9), when the cgo resolver is being used - * this package applies a concurrent cgo lookup limit to prevent the system - * from running out of system threads. Currently, it is limited to 500 concurrent lookups. - * - * The resolver decision can be overridden by setting the netdns value of the - * GODEBUG environment variable (see package runtime) to go or cgo, as in: - * - * ``` - * export GODEBUG=netdns=go # force pure Go resolver - * export GODEBUG=netdns=cgo # force native resolver (cgo, win32) - * ``` - * - * The decision can also be forced while building the Go source tree - * by setting the netgo or netcgo build tag. - * The netgo build tag disables entirely the use of the native (CGO) resolver, - * meaning the Go resolver is the only one that can be used. - * With the netcgo build tag the native and the pure Go resolver are compiled into the binary, - * but the native (CGO) resolver is preferred over the Go resolver. - * With netcgo, the Go resolver can still be forced at runtime with GODEBUG=netdns=go. - * - * A numeric netdns setting, as in GODEBUG=netdns=1, causes the resolver - * to print debugging information about its decisions. - * To force a particular resolver while also printing debugging information, - * join the two settings by a plus sign, as in GODEBUG=netdns=go+1. - * - * The Go resolver will send an EDNS0 additional header with a DNS request, - * to signal a willingness to accept a larger DNS packet size. - * This can reportedly cause sporadic failures with the DNS server run - * by some modems and routers. Setting GODEBUG=netedns0=0 will disable - * sending the additional header. - * - * On macOS, if Go code that uses the net package is built with - * -buildmode=c-archive, linking the resulting archive into a C program - * requires passing -lresolv when linking the C code. - * - * On Plan 9, the resolver always accesses /net/cs and /net/dns. - * - * On Windows, in Go 1.18.x and earlier, the resolver always used C - * library functions, such as GetAddrInfo and DnsQuery. - */ -namespace net { - /** - * Conn is a generic stream-oriented network connection. - * - * Multiple goroutines may invoke methods on a Conn simultaneously. - */ - interface Conn { - [key:string]: any; - /** - * Read reads data from the connection. - * Read can be made to time out and return an error after a fixed - * time limit; see SetDeadline and SetReadDeadline. - */ - read(b: string|Array): number - /** - * Write writes data to the connection. - * Write can be made to time out and return an error after a fixed - * time limit; see SetDeadline and SetWriteDeadline. - */ - write(b: string|Array): number - /** - * Close closes the connection. - * Any blocked Read or Write operations will be unblocked and return errors. - * Close may or may not block until any buffered data is sent; - * for TCP connections see [*TCPConn.SetLinger]. - */ - close(): void - /** - * LocalAddr returns the local network address, if known. - */ - localAddr(): Addr - /** - * RemoteAddr returns the remote network address, if known. - */ - remoteAddr(): Addr - /** - * SetDeadline sets the read and write deadlines associated - * with the connection. It is equivalent to calling both - * SetReadDeadline and SetWriteDeadline. - * - * A deadline is an absolute time after which I/O operations - * fail instead of blocking. The deadline applies to all future - * and pending I/O, not just the immediately following call to - * Read or Write. After a deadline has been exceeded, the - * connection can be refreshed by setting a deadline in the future. + * A deadline is an absolute time after which I/O operations + * fail instead of blocking. The deadline applies to all future + * and pending I/O, not just the immediately following call to + * Read or Write. After a deadline has been exceeded, the + * connection can be refreshed by setting a deadline in the future. * * If the deadline is exceeded a call to Read or Write or to other * I/O methods will return an error that wraps os.ErrDeadlineExceeded. @@ -17066,2348 +16946,1924 @@ namespace net { } /** - * Package jwt is a Go implementation of JSON Web Tokens: http://self-issued.info/docs/draft-jones-json-web-token.html + * Package multipart implements MIME multipart parsing, as defined in RFC + * 2046. * - * See README.md for more info. + * The implementation is sufficient for HTTP (RFC 2388) and the multipart + * bodies generated by popular browsers. + * + * # Limits + * + * To protect against malicious inputs, this package sets limits on the size + * of the MIME data it processes. + * + * [Reader.NextPart] and [Reader.NextRawPart] limit the number of headers in a + * part to 10000 and [Reader.ReadForm] limits the total number of headers in all + * FileHeaders to 10000. + * These limits may be adjusted with the GODEBUG=multipartmaxheaders= + * setting. + * + * Reader.ReadForm further limits the number of parts in a form to 1000. + * This limit may be adjusted with the GODEBUG=multipartmaxparts= + * setting. */ -namespace jwt { +namespace multipart { /** - * MapClaims is a claims type that uses the map[string]any for JSON - * decoding. This is the default claims type if you don't supply one + * A FileHeader describes a file part of a multipart request. */ - interface MapClaims extends _TygojaDict{} - interface MapClaims { - /** - * GetExpirationTime implements the Claims interface. - */ - getExpirationTime(): (NumericDate) - } - interface MapClaims { - /** - * GetNotBefore implements the Claims interface. - */ - getNotBefore(): (NumericDate) - } - interface MapClaims { - /** - * GetIssuedAt implements the Claims interface. - */ - getIssuedAt(): (NumericDate) - } - interface MapClaims { - /** - * GetAudience implements the Claims interface. - */ - getAudience(): ClaimStrings - } - interface MapClaims { - /** - * GetIssuer implements the Claims interface. - */ - getIssuer(): string + interface FileHeader { + filename: string + header: textproto.MIMEHeader + size: number } - interface MapClaims { + interface FileHeader { /** - * GetSubject implements the Claims interface. + * Open opens and returns the [FileHeader]'s associated File. */ - getSubject(): string - } -} - -namespace subscriptions { - /** - * Broker defines a struct for managing subscriptions clients. - */ - interface Broker { - } - interface Broker { - /** - * Clients returns a shallow copy of all registered clients indexed - * with their connection id. - */ - clients(): _TygojaDict - } - interface Broker { - /** - * ChunkedClients splits the current clients into a chunked slice. - */ - chunkedClients(chunkSize: number): Array> - } - interface Broker { - /** - * TotalClients returns the total number of registered clients. - */ - totalClients(): number - } - interface Broker { - /** - * ClientById finds a registered client by its id. - * - * Returns non-nil error when client with clientId is not registered. - */ - clientById(clientId: string): Client - } - interface Broker { - /** - * Register adds a new client to the broker instance. - */ - register(client: Client): void - } - interface Broker { - /** - * Unregister removes a single client by its id and marks it as discarded. - * - * If client with clientId doesn't exist, this method does nothing. - */ - unregister(clientId: string): void - } - /** - * Client is an interface for a generic subscription client. - */ - interface Client { - [key:string]: any; - /** - * Id Returns the unique id of the client. - */ - id(): string - /** - * Channel returns the client's communication channel. - * - * NB! The channel shouldn't be used after calling Discard(). - */ - channel(): undefined - /** - * Subscriptions returns a shallow copy of the client subscriptions matching the prefixes. - * If no prefix is specified, returns all subscriptions. - */ - subscriptions(...prefixes: string[]): _TygojaDict - /** - * Subscribe subscribes the client to the provided subscriptions list. - * - * Each subscription can also have "options" (json serialized SubscriptionOptions) as query parameter. - * - * Example: - * - * ``` - * Subscribe( - * "subscriptionA", - * `subscriptionB?options={"query":{"a":1},"headers":{"x_token":"abc"}}`, - * ) - * ``` - */ - subscribe(...subs: string[]): void - /** - * Unsubscribe unsubscribes the client from the provided subscriptions list. - */ - unsubscribe(...subs: string[]): void - /** - * HasSubscription checks if the client is subscribed to `sub`. - */ - hasSubscription(sub: string): boolean - /** - * Set stores any value to the client's context. - */ - set(key: string, value: any): void - /** - * Unset removes a single value from the client's context. - */ - unset(key: string): void - /** - * Get retrieves the key value from the client's context. - */ - get(key: string): any - /** - * Discard marks the client as "discarded" (and closes its channel), - * meaning that it shouldn't be used anymore for sending new messages. - * - * It is safe to call Discard() multiple times. - */ - discard(): void - /** - * IsDiscarded indicates whether the client has been "discarded" - * and should no longer be used. - */ - isDiscarded(): boolean - /** - * Send sends the specified message to the client's channel (if not discarded). - */ - send(m: Message): void - } - /** - * Message defines a client's channel data. - */ - interface Message { - name: string - data: string|Array - } - interface Message { - /** - * WriteSSE writes the current message in a SSE format into the provided writer. - * - * For example, writing to a router.Event: - * - * ``` - * m := Message{Name: "users/create", Data: []byte{...}} - * m.WriteSSE(e.Response, "yourEventId") - * e.Flush() - * ``` - */ - writeSSE(w: io.Writer, eventId: string): void + open(): File } } /** - * Package slog provides structured logging, - * in which log records include a message, - * a severity level, and various other attributes - * expressed as key-value pairs. + * Package http provides HTTP client and server implementations. * - * It defines a type, [Logger], - * which provides several methods (such as [Logger.Info] and [Logger.Error]) - * for reporting events of interest. + * [Get], [Head], [Post], and [PostForm] make HTTP (or HTTPS) requests: * - * Each Logger is associated with a [Handler]. - * A Logger output method creates a [Record] from the method arguments - * and passes it to the Handler, which decides how to handle it. - * There is a default Logger accessible through top-level functions - * (such as [Info] and [Error]) that call the corresponding Logger methods. + * ``` + * resp, err := http.Get("http://example.com/") + * ... + * resp, err := http.Post("http://example.com/upload", "image/jpeg", &buf) + * ... + * resp, err := http.PostForm("http://example.com/form", + * url.Values{"key": {"Value"}, "id": {"123"}}) + * ``` * - * A log record consists of a time, a level, a message, and a set of key-value - * pairs, where the keys are strings and the values may be of any type. - * As an example, + * The caller must close the response body when finished with it: * * ``` - * slog.Info("hello", "count", 3) + * resp, err := http.Get("http://example.com/") + * if err != nil { + * // handle error + * } + * defer resp.Body.Close() + * body, err := io.ReadAll(resp.Body) + * // ... * ``` * - * creates a record containing the time of the call, - * a level of Info, the message "hello", and a single - * pair with key "count" and value 3. - * - * The [Info] top-level function calls the [Logger.Info] method on the default Logger. - * In addition to [Logger.Info], there are methods for Debug, Warn and Error levels. - * Besides these convenience methods for common levels, - * there is also a [Logger.Log] method which takes the level as an argument. - * Each of these methods has a corresponding top-level function that uses the - * default logger. + * # Clients and Transports * - * The default handler formats the log record's message, time, level, and attributes - * as a string and passes it to the [log] package. + * For control over HTTP client headers, redirect policy, and other + * settings, create a [Client]: * * ``` - * 2022/11/08 15:28:26 INFO hello count=3 - * ``` + * client := &http.Client{ + * CheckRedirect: redirectPolicyFunc, + * } * - * For more control over the output format, create a logger with a different handler. - * This statement uses [New] to create a new logger with a [TextHandler] - * that writes structured records in text form to standard error: + * resp, err := client.Get("http://example.com") + * // ... * - * ``` - * logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) + * req, err := http.NewRequest("GET", "http://example.com", nil) + * // ... + * req.Header.Add("If-None-Match", `W/"wyzzy"`) + * resp, err := client.Do(req) + * // ... * ``` * - * [TextHandler] output is a sequence of key=value pairs, easily and unambiguously - * parsed by machine. This statement: + * For control over proxies, TLS configuration, keep-alives, + * compression, and other settings, create a [Transport]: * * ``` - * logger.Info("hello", "count", 3) + * tr := &http.Transport{ + * MaxIdleConns: 10, + * IdleConnTimeout: 30 * time.Second, + * DisableCompression: true, + * } + * client := &http.Client{Transport: tr} + * resp, err := client.Get("https://example.com") * ``` * - * produces this output: + * Clients and Transports are safe for concurrent use by multiple + * goroutines and for efficiency should only be created once and re-used. * - * ``` - * time=2022-11-08T15:28:26.000-05:00 level=INFO msg=hello count=3 - * ``` + * # Servers * - * The package also provides [JSONHandler], whose output is line-delimited JSON: + * ListenAndServe starts an HTTP server with a given address and handler. + * The handler is usually nil, which means to use [DefaultServeMux]. + * [Handle] and [HandleFunc] add handlers to [DefaultServeMux]: * * ``` - * logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) - * logger.Info("hello", "count", 3) + * http.Handle("/foo", fooHandler) + * + * http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) { + * fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) + * }) + * + * log.Fatal(http.ListenAndServe(":8080", nil)) * ``` * - * produces this output: + * More control over the server's behavior is available by creating a + * custom Server: * * ``` - * {"time":"2022-11-08T15:28:26.000000000-05:00","level":"INFO","msg":"hello","count":3} + * s := &http.Server{ + * Addr: ":8080", + * Handler: myHandler, + * ReadTimeout: 10 * time.Second, + * WriteTimeout: 10 * time.Second, + * MaxHeaderBytes: 1 << 20, + * } + * log.Fatal(s.ListenAndServe()) * ``` * - * Both [TextHandler] and [JSONHandler] can be configured with [HandlerOptions]. - * There are options for setting the minimum level (see Levels, below), - * displaying the source file and line of the log call, and - * modifying attributes before they are logged. + * # HTTP/2 * - * Setting a logger as the default with + * The http package has transparent support for the HTTP/2 protocol. * - * ``` - * slog.SetDefault(logger) - * ``` - * - * will cause the top-level functions like [Info] to use it. - * [SetDefault] also updates the default logger used by the [log] package, - * so that existing applications that use [log.Printf] and related functions - * will send log records to the logger's handler without needing to be rewritten. - * - * Some attributes are common to many log calls. - * For example, you may wish to include the URL or trace identifier of a server request - * with all log events arising from the request. - * Rather than repeat the attribute with every log call, you can use [Logger.With] - * to construct a new Logger containing the attributes: - * - * ``` - * logger2 := logger.With("url", r.URL) - * ``` - * - * The arguments to With are the same key-value pairs used in [Logger.Info]. - * The result is a new Logger with the same handler as the original, but additional - * attributes that will appear in the output of every call. - * - * # Levels - * - * A [Level] is an integer representing the importance or severity of a log event. - * The higher the level, the more severe the event. - * This package defines constants for the most common levels, - * but any int can be used as a level. - * - * In an application, you may wish to log messages only at a certain level or greater. - * One common configuration is to log messages at Info or higher levels, - * suppressing debug logging until it is needed. - * The built-in handlers can be configured with the minimum level to output by - * setting [HandlerOptions.Level]. - * The program's `main` function typically does this. - * The default value is LevelInfo. - * - * Setting the [HandlerOptions.Level] field to a [Level] value - * fixes the handler's minimum level throughout its lifetime. - * Setting it to a [LevelVar] allows the level to be varied dynamically. - * A LevelVar holds a Level and is safe to read or write from multiple - * goroutines. - * To vary the level dynamically for an entire program, first initialize - * a global LevelVar: - * - * ``` - * var programLevel = new(slog.LevelVar) // Info by default - * ``` - * - * Then use the LevelVar to construct a handler, and make it the default: - * - * ``` - * h := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: programLevel}) - * slog.SetDefault(slog.New(h)) - * ``` - * - * Now the program can change its logging level with a single statement: - * - * ``` - * programLevel.Set(slog.LevelDebug) - * ``` - * - * # Groups - * - * Attributes can be collected into groups. - * A group has a name that is used to qualify the names of its attributes. - * How this qualification is displayed depends on the handler. - * [TextHandler] separates the group and attribute names with a dot. - * [JSONHandler] treats each group as a separate JSON object, with the group name as the key. - * - * Use [Group] to create a Group attribute from a name and a list of key-value pairs: - * - * ``` - * slog.Group("request", - * "method", r.Method, - * "url", r.URL) - * ``` - * - * TextHandler would display this group as - * - * ``` - * request.method=GET request.url=http://example.com - * ``` - * - * JSONHandler would display it as - * - * ``` - * "request":{"method":"GET","url":"http://example.com"} - * ``` - * - * Use [Logger.WithGroup] to qualify all of a Logger's output - * with a group name. Calling WithGroup on a Logger results in a - * new Logger with the same Handler as the original, but with all - * its attributes qualified by the group name. - * - * This can help prevent duplicate attribute keys in large systems, - * where subsystems might use the same keys. - * Pass each subsystem a different Logger with its own group name so that - * potential duplicates are qualified: - * - * ``` - * logger := slog.Default().With("id", systemID) - * parserLogger := logger.WithGroup("parser") - * parseInput(input, parserLogger) - * ``` - * - * When parseInput logs with parserLogger, its keys will be qualified with "parser", - * so even if it uses the common key "id", the log line will have distinct keys. - * - * # Contexts - * - * Some handlers may wish to include information from the [context.Context] that is - * available at the call site. One example of such information - * is the identifier for the current span when tracing is enabled. - * - * The [Logger.Log] and [Logger.LogAttrs] methods take a context as a first - * argument, as do their corresponding top-level functions. - * - * Although the convenience methods on Logger (Info and so on) and the - * corresponding top-level functions do not take a context, the alternatives ending - * in "Context" do. For example, - * - * ``` - * slog.InfoContext(ctx, "message") - * ``` - * - * It is recommended to pass a context to an output method if one is available. + * [Server] and [DefaultTransport] automatically enable HTTP/2 support + * when using HTTPS. [Transport] does not enable HTTP/2 by default. * - * # Attrs and Values - * - * An [Attr] is a key-value pair. The Logger output methods accept Attrs as well as - * alternating keys and values. The statement - * - * ``` - * slog.Info("hello", slog.Int("count", 3)) - * ``` - * - * behaves the same as - * - * ``` - * slog.Info("hello", "count", 3) - * ``` - * - * There are convenience constructors for [Attr] such as [Int], [String], and [Bool] - * for common types, as well as the function [Any] for constructing Attrs of any - * type. - * - * The value part of an Attr is a type called [Value]. - * Like an [any], a Value can hold any Go value, - * but it can represent typical values, including all numbers and strings, - * without an allocation. - * - * For the most efficient log output, use [Logger.LogAttrs]. - * It is similar to [Logger.Log] but accepts only Attrs, not alternating - * keys and values; this allows it, too, to avoid allocation. - * - * The call - * - * ``` - * logger.LogAttrs(ctx, slog.LevelInfo, "hello", slog.Int("count", 3)) - * ``` - * - * is the most efficient way to achieve the same output as - * - * ``` - * slog.InfoContext(ctx, "hello", "count", 3) - * ``` - * - * # Customizing a type's logging behavior - * - * If a type implements the [LogValuer] interface, the [Value] returned from its LogValue - * method is used for logging. You can use this to control how values of the type - * appear in logs. For example, you can redact secret information like passwords, - * or gather a struct's fields in a Group. See the examples under [LogValuer] for - * details. - * - * A LogValue method may return a Value that itself implements [LogValuer]. The [Value.Resolve] - * method handles these cases carefully, avoiding infinite loops and unbounded recursion. - * Handler authors and others may wish to use [Value.Resolve] instead of calling LogValue directly. - * - * # Wrapping output methods - * - * The logger functions use reflection over the call stack to find the file name - * and line number of the logging call within the application. This can produce - * incorrect source information for functions that wrap slog. For instance, if you - * define this function in file mylog.go: - * - * ``` - * func Infof(logger *slog.Logger, format string, args ...any) { - * logger.Info(fmt.Sprintf(format, args...)) - * } - * ``` + * To enable or disable support for HTTP/1, HTTP/2, and/or unencrypted HTTP/2, + * see the [Server.Protocols] and [Transport.Protocols] configuration fields. * - * and you call it like this in main.go: - * - * ``` - * Infof(slog.Default(), "hello, %s", "world") - * ``` - * - * then slog will report the source file as mylog.go, not main.go. - * - * A correct implementation of Infof will obtain the source location - * (pc) and pass it to NewRecord. - * The Infof function in the package-level example called "wrapping" - * demonstrates how to do this. - * - * # Working with Records - * - * Sometimes a Handler will need to modify a Record - * before passing it on to another Handler or backend. - * A Record contains a mixture of simple public fields (e.g. Time, Level, Message) - * and hidden fields that refer to state (such as attributes) indirectly. This - * means that modifying a simple copy of a Record (e.g. by calling - * [Record.Add] or [Record.AddAttrs] to add attributes) - * may have unexpected effects on the original. - * Before modifying a Record, use [Record.Clone] to - * create a copy that shares no state with the original, - * or create a new Record with [NewRecord] - * and build up its Attrs by traversing the old ones with [Record.Attrs]. - * - * # Performance considerations - * - * If profiling your application demonstrates that logging is taking significant time, - * the following suggestions may help. - * - * If many log lines have a common attribute, use [Logger.With] to create a Logger with - * that attribute. The built-in handlers will format that attribute only once, at the - * call to [Logger.With]. The [Handler] interface is designed to allow that optimization, - * and a well-written Handler should take advantage of it. - * - * The arguments to a log call are always evaluated, even if the log event is discarded. - * If possible, defer computation so that it happens only if the value is actually logged. - * For example, consider the call + * To configure advanced HTTP/2 features, see the [Server.HTTP2] and + * [Transport.HTTP2] configuration fields. * - * ``` - * slog.Info("starting request", "url", r.URL.String()) // may compute String unnecessarily - * ``` - * - * The URL.String method will be called even if the logger discards Info-level events. - * Instead, pass the URL directly: + * Alternatively, the following GODEBUG settings are currently supported: * * ``` - * slog.Info("starting request", "url", &r.URL) // calls URL.String only if needed + * GODEBUG=http2client=0 # disable HTTP/2 client support + * GODEBUG=http2server=0 # disable HTTP/2 server support + * GODEBUG=http2debug=1 # enable verbose HTTP/2 debug logs + * GODEBUG=http2debug=2 # ... even more verbose, with frame dumps * ``` * - * The built-in [TextHandler] will call its String method, but only - * if the log event is enabled. - * Avoiding the call to String also preserves the structure of the underlying value. - * For example [JSONHandler] emits the components of the parsed URL as a JSON object. - * If you want to avoid eagerly paying the cost of the String call - * without causing the handler to potentially inspect the structure of the value, - * wrap the value in a fmt.Stringer implementation that hides its Marshal methods. - * - * You can also use the [LogValuer] interface to avoid unnecessary work in disabled log - * calls. Say you need to log some expensive value: - * - * ``` - * slog.Debug("frobbing", "value", computeExpensiveValue(arg)) - * ``` - * - * Even if this line is disabled, computeExpensiveValue will be called. - * To avoid that, define a type implementing LogValuer: - * - * ``` - * type expensive struct { arg int } - * - * func (e expensive) LogValue() slog.Value { - * return slog.AnyValue(computeExpensiveValue(e.arg)) - * } - * ``` - * - * Then use a value of that type in log calls: - * - * ``` - * slog.Debug("frobbing", "value", expensive{arg}) - * ``` - * - * Now computeExpensiveValue will only be called when the line is enabled. - * - * The built-in handlers acquire a lock before calling [io.Writer.Write] - * to ensure that exactly one [Record] is written at a time in its entirety. - * Although each log record has a timestamp, - * the built-in handlers do not use that time to sort the written records. - * User-defined handlers are responsible for their own locking and sorting. - * - * # Writing a handler - * - * For a guide to writing a custom handler, see https://golang.org/s/slog-handler-guide. + * The "omithttp2" build tag may be used to disable the HTTP/2 implementation + * contained in the http package. */ -namespace slog { - // @ts-ignore - import loginternal = internal +namespace http { /** - * A Logger records structured information about each call to its - * Log, Debug, Info, Warn, and Error methods. - * For each call, it creates a [Record] and passes it to a [Handler]. - * - * To create a new Logger, call [New] or a Logger method - * that begins "With". + * PushOptions describes options for [Pusher.Push]. */ - interface Logger { - } - interface Logger { + interface PushOptions { /** - * Handler returns l's Handler. + * Method specifies the HTTP method for the promised request. + * If set, it must be "GET" or "HEAD". Empty means "GET". */ - handler(): Handler - } - interface Logger { + method: string /** - * With returns a Logger that includes the given attributes - * in each output operation. Arguments are converted to - * attributes as if by [Logger.Log]. + * Header specifies additional promised request headers. This cannot + * include HTTP/2 pseudo header fields like ":path" and ":scheme", + * which will be added automatically. */ - with(...args: any[]): (Logger) + header: Header } - interface Logger { + // @ts-ignore + import urlpkg = url + /** + * A Request represents an HTTP request received by a server + * or to be sent by a client. + * + * The field semantics differ slightly between client and server + * usage. In addition to the notes on the fields below, see the + * documentation for [Request.Write] and [RoundTripper]. + */ + interface Request { /** - * WithGroup returns a Logger that starts a group, if name is non-empty. - * The keys of all attributes added to the Logger will be qualified by the given - * name. (How that qualification happens depends on the [Handler.WithGroup] - * method of the Logger's Handler.) + * Method specifies the HTTP method (GET, POST, PUT, etc.). + * For client requests, an empty string means GET. + */ + method: string + /** + * URL specifies either the URI being requested (for server + * requests) or the URL to access (for client requests). * - * If name is empty, WithGroup returns the receiver. + * For server requests, the URL is parsed from the URI + * supplied on the Request-Line as stored in RequestURI. For + * most requests, fields other than Path and RawQuery will be + * empty. (See RFC 7230, Section 5.3) + * + * For client requests, the URL's Host specifies the server to + * connect to, while the Request's Host field optionally + * specifies the Host header value to send in the HTTP + * request. */ - withGroup(name: string): (Logger) - } - interface Logger { + url?: url.URL /** - * Enabled reports whether l emits log records at the given context and level. + * The protocol version for incoming server requests. + * + * For client requests, these fields are ignored. The HTTP + * client code always uses either HTTP/1.1 or HTTP/2. + * See the docs on Transport for details. */ - enabled(ctx: context.Context, level: Level): boolean - } - interface Logger { + proto: string // "HTTP/1.0" + protoMajor: number // 1 + protoMinor: number // 0 /** - * Log emits a log record with the current time and the given level and message. - * The Record's Attrs consist of the Logger's attributes followed by - * the Attrs specified by args. + * Header contains the request header fields either received + * by the server or to be sent by the client. + * + * If a server received a request with header lines, * - * The attribute arguments are processed as follows: * ``` - * - If an argument is an Attr, it is used as is. - * - If an argument is a string and this is not the last argument, - * the following argument is treated as the value and the two are combined - * into an Attr. - * - Otherwise, the argument is treated as a value with key "!BADKEY". + * Host: example.com + * accept-encoding: gzip, deflate + * Accept-Language: en-us + * fOO: Bar + * foo: two + * ``` + * + * then + * + * ``` + * Header = map[string][]string{ + * "Accept-Encoding": {"gzip, deflate"}, + * "Accept-Language": {"en-us"}, + * "Foo": {"Bar", "two"}, + * } * ``` + * + * For incoming requests, the Host header is promoted to the + * Request.Host field and removed from the Header map. + * + * HTTP defines that header names are case-insensitive. The + * request parser implements this by using CanonicalHeaderKey, + * making the first character and any characters following a + * hyphen uppercase and the rest lowercase. + * + * For client requests, certain headers such as Content-Length + * and Connection are automatically written when needed and + * values in Header may be ignored. See the documentation + * for the Request.Write method. */ - log(ctx: context.Context, level: Level, msg: string, ...args: any[]): void - } - interface Logger { + header: Header /** - * LogAttrs is a more efficient version of [Logger.Log] that accepts only Attrs. + * Body is the request's body. + * + * For client requests, a nil body means the request has no + * body, such as a GET request. The HTTP Client's Transport + * is responsible for calling the Close method. + * + * For server requests, the Request Body is always non-nil + * but will return EOF immediately when no body is present. + * The Server will close the request body. The ServeHTTP + * Handler does not need to. + * + * Body must allow Read to be called concurrently with Close. + * In particular, calling Close should unblock a Read waiting + * for input. */ - logAttrs(ctx: context.Context, level: Level, msg: string, ...attrs: Attr[]): void - } - interface Logger { + body: io.ReadCloser /** - * Debug logs at [LevelDebug]. + * GetBody defines an optional func to return a new copy of + * Body. It is used for client requests when a redirect requires + * reading the body more than once. Use of GetBody still + * requires setting Body. + * + * For server requests, it is unused. */ - debug(msg: string, ...args: any[]): void - } - interface Logger { + getBody: () => io.ReadCloser /** - * DebugContext logs at [LevelDebug] with the given context. + * ContentLength records the length of the associated content. + * The value -1 indicates that the length is unknown. + * Values >= 0 indicate that the given number of bytes may + * be read from Body. + * + * For client requests, a value of 0 with a non-nil Body is + * also treated as unknown. */ - debugContext(ctx: context.Context, msg: string, ...args: any[]): void - } - interface Logger { + contentLength: number /** - * Info logs at [LevelInfo]. + * TransferEncoding lists the transfer encodings from outermost to + * innermost. An empty list denotes the "identity" encoding. + * TransferEncoding can usually be ignored; chunked encoding is + * automatically added and removed as necessary when sending and + * receiving requests. */ - info(msg: string, ...args: any[]): void - } - interface Logger { + transferEncoding: Array /** - * InfoContext logs at [LevelInfo] with the given context. + * Close indicates whether to close the connection after + * replying to this request (for servers) or after sending this + * request and reading its response (for clients). + * + * For server requests, the HTTP server handles this automatically + * and this field is not needed by Handlers. + * + * For client requests, setting this field prevents re-use of + * TCP connections between requests to the same hosts, as if + * Transport.DisableKeepAlives were set. */ - infoContext(ctx: context.Context, msg: string, ...args: any[]): void - } - interface Logger { + close: boolean /** - * Warn logs at [LevelWarn]. + * For server requests, Host specifies the host on which the + * URL is sought. For HTTP/1 (per RFC 7230, section 5.4), this + * is either the value of the "Host" header or the host name + * given in the URL itself. For HTTP/2, it is the value of the + * ":authority" pseudo-header field. + * It may be of the form "host:port". For international domain + * names, Host may be in Punycode or Unicode form. Use + * golang.org/x/net/idna to convert it to either format if + * needed. + * To prevent DNS rebinding attacks, server Handlers should + * validate that the Host header has a value for which the + * Handler considers itself authoritative. The included + * ServeMux supports patterns registered to particular host + * names and thus protects its registered Handlers. + * + * For client requests, Host optionally overrides the Host + * header to send. If empty, the Request.Write method uses + * the value of URL.Host. Host may contain an international + * domain name. */ - warn(msg: string, ...args: any[]): void - } - interface Logger { + host: string /** - * WarnContext logs at [LevelWarn] with the given context. + * Form contains the parsed form data, including both the URL + * field's query parameters and the PATCH, POST, or PUT form data. + * This field is only available after ParseForm is called. + * The HTTP client ignores Form and uses Body instead. */ - warnContext(ctx: context.Context, msg: string, ...args: any[]): void - } - interface Logger { + form: url.Values /** - * Error logs at [LevelError]. - */ - error(msg: string, ...args: any[]): void - } - interface Logger { - /** - * ErrorContext logs at [LevelError] with the given context. + * PostForm contains the parsed form data from PATCH, POST + * or PUT body parameters. + * + * This field is only available after ParseForm is called. + * The HTTP client ignores PostForm and uses Body instead. */ - errorContext(ctx: context.Context, msg: string, ...args: any[]): void - } -} - -namespace hook { - /** - * Event implements [Resolver] and it is intended to be used as a base - * Hook event that you can embed in your custom typed event structs. - * - * Example: - * - * ``` - * type CustomEvent struct { - * hook.Event - * - * SomeField int - * } - * ``` - */ - interface Event { - } - interface Event { + postForm: url.Values /** - * Next calls the next hook handler. + * MultipartForm is the parsed multipart form, including file uploads. + * This field is only available after ParseMultipartForm is called. + * The HTTP client ignores MultipartForm and uses Body instead. */ - next(): void - } - /** - * Handler defines a single Hook handler. - * Multiple handlers can share the same id. - * If Id is not explicitly set it will be autogenerated by Hook.Add and Hook.AddHandler. - */ - interface Handler { + multipartForm?: multipart.Form /** - * Func defines the handler function to execute. + * Trailer specifies additional headers that are sent after the request + * body. * - * Note that users need to call e.Next() in order to proceed with - * the execution of the hook chain. + * For server requests, the Trailer map initially contains only the + * trailer keys, with nil values. (The client declares which trailers it + * will later send.) While the handler is reading from Body, it must + * not reference Trailer. After reading from Body returns EOF, Trailer + * can be read again and will contain non-nil values, if they were sent + * by the client. + * + * For client requests, Trailer must be initialized to a map containing + * the trailer keys to later send. The values may be nil or their final + * values. The ContentLength must be 0 or -1, to send a chunked request. + * After the HTTP request is sent the map values can be updated while + * the request body is read. Once the body returns EOF, the caller must + * not mutate Trailer. + * + * Writing a request whose Trailer contains a key with invalid bytes + * (such as CR or LF), or such a value present when Write begins, + * returns an error. + * + * Few HTTP clients, servers, or proxies support HTTP trailers. */ - func: (_arg0: T) => void + trailer: Header /** - * Id is the unique identifier of the handler. + * RemoteAddr allows HTTP servers and other software to record + * the network address that sent the request, usually for + * logging. This field is not filled in by ReadRequest and + * has no defined format. The HTTP server in this package + * sets RemoteAddr to an "IP:port" address before invoking a + * handler. + * This field is ignored by the HTTP client. + */ + remoteAddr: string + /** + * RequestURI is the unmodified request-target of the + * Request-Line (RFC 7230, Section 3.1.1) as sent by the client + * to a server. Usually the URL field should be used instead. + * It is an error to set this field in an HTTP client request. + */ + requestURI: string + /** + * TLS allows HTTP servers and other software to record + * information about the TLS connection on which the request + * was received. This field is not filled in by ReadRequest. + * The HTTP server in this package sets the field for + * TLS-enabled connections before invoking a handler; + * otherwise it leaves the field nil. + * This field is ignored by the HTTP client. + */ + tls?: any + /** + * Cancel is an optional channel whose closure indicates that the client + * request should be regarded as canceled. Not all implementations of + * RoundTripper may support Cancel. * - * It could be used later to remove the handler from a hook via [Hook.Remove]. + * For server requests, this field is not applicable. * - * If missing, an autogenerated value will be assigned when adding - * the handler to a hook. + * Deprecated: Set the Request's context with NewRequestWithContext + * instead. If a Request's Cancel field and context are both + * set, it is undefined whether Cancel is respected. */ - id: string + cancel: undefined /** - * Priority allows changing the default exec priority of the handler within a hook. - * - * If 0, the handler will be executed in the same order it was registered. + * Response is the redirect response which caused this request + * to be created. This field is only populated during client + * redirects. */ - priority: number - } - /** - * Hook defines a generic concurrent safe structure for managing event hooks. - * - * When using custom event it must embed the base [hook.Event]. - * - * Example: - * - * ``` - * type CustomEvent struct { - * hook.Event - * SomeField int - * } - * - * h := Hook[*CustomEvent]{} - * - * h.BindFunc(func(e *CustomEvent) error { - * println(e.SomeField) - * - * return e.Next() - * }) - * - * h.Trigger(&CustomEvent{ SomeField: 123 }) - * ``` - */ - interface Hook { + response?: Response + /** + * Pattern is the [ServeMux] pattern that matched the request. + * It is empty if the request was not matched against a pattern. + */ + pattern: string } - interface Hook { + interface Request { /** - * Bind registers the provided handler to the current hooks queue. + * Context returns the request's context. To change the context, use + * [Request.Clone] or [Request.WithContext]. * - * If handler.Id is empty it is updated with autogenerated value. + * The returned context is always non-nil; it defaults to the + * background context. * - * If a handler from the current hook list has Id matching handler.Id - * then the old handler is replaced with the new one. + * For outgoing client requests, the context controls cancellation. + * + * For incoming server requests, the context is canceled when the + * client's connection closes, the request is canceled (with HTTP/2), + * or when the ServeHTTP method returns. */ - bind(handler: Handler): string + context(): context.Context } - interface Hook { + interface Request { /** - * BindFunc is similar to Bind but registers a new handler from just the provided function. + * WithContext returns a shallow copy of r with its context changed + * to ctx. The provided ctx must be non-nil. * - * The registered handler is added with a default 0 priority and the id will be autogenerated. + * For outgoing client request, the context controls the entire + * lifetime of a request and its response: obtaining a connection, + * sending the request, and reading the response headers and body. * - * If you want to register a handler with custom priority or id use the [Hook.Bind] method. + * To create a new request with a context, use [NewRequestWithContext]. + * To make a deep copy of a request with a new context, use [Request.Clone]. */ - bindFunc(fn: (e: T) => void): string + withContext(ctx: context.Context): (Request) } - interface Hook { + interface Request { /** - * Unbind removes one or many hook handler by their id. + * Clone returns a deep copy of r with its context changed to ctx. + * The provided ctx must be non-nil. + * + * Clone only makes a shallow copy of the Body field. + * + * For an outgoing client request, the context controls the entire + * lifetime of a request and its response: obtaining a connection, + * sending the request, and reading the response headers and body. */ - unbind(...idsToRemove: string[]): void + clone(ctx: context.Context): (Request) } - interface Hook { + interface Request { /** - * UnbindAll removes all registered handlers. + * ProtoAtLeast reports whether the HTTP protocol used + * in the request is at least major.minor. */ - unbindAll(): void + protoAtLeast(major: number, minor: number): boolean } - interface Hook { + interface Request { /** - * Length returns to total number of registered hook handlers. + * UserAgent returns the client's User-Agent, if sent in the request. */ - length(): number + userAgent(): string } - interface Hook { + interface Request { /** - * Trigger executes all registered hook handlers one by one - * with the specified event as an argument. - * - * Optionally, this method allows also to register additional one off - * handler funcs that will be temporary appended to the handlers queue. - * - * NB! Each hook handler must call event.Next() in order the hook chain to proceed. + * Cookies parses and returns the HTTP cookies sent with the request. */ - trigger(event: T, ...oneOffHandlerFuncs: ((_arg0: T) => void)[]): void - } - /** - * TaggedHook defines a proxy hook which register handlers that are triggered only - * if the TaggedHook.tags are empty or includes at least one of the event data tag(s). - */ - type _skDDNSt = mainHook - interface TaggedHook extends _skDDNSt { + cookies(): Array<(Cookie | undefined)> } - interface TaggedHook { + interface Request { /** - * CanTriggerOn checks if the current TaggedHook can be triggered with - * the provided event data tags. - * - * It returns always true if the hook doesn't have any tags. + * CookiesNamed parses and returns the named HTTP cookies sent with the request + * or an empty slice if none matched. */ - canTriggerOn(tagsToCheck: Array): boolean + cookiesNamed(name: string): Array<(Cookie | undefined)> } - interface TaggedHook { + interface Request { /** - * Bind registers the provided handler to the current hooks queue. - * - * It is similar to [Hook.Bind] with the difference that the handler - * function is invoked only if the event data tags satisfy h.CanTriggerOn. + * Cookie returns the named cookie provided in the request or + * [ErrNoCookie] if not found. + * If multiple cookies match the given name, only one cookie will + * be returned. */ - bind(handler: Handler): string + cookie(name: string): (Cookie) } - interface TaggedHook { + interface Request { /** - * BindFunc registers a new handler with the specified function. - * - * It is similar to [Hook.Bind] with the difference that the handler - * function is invoked only if the event data tags satisfy h.CanTriggerOn. + * AddCookie adds a cookie to the request. Per RFC 6265 section 5.4, + * AddCookie does not attach more than one [Cookie] header field. That + * means all cookies, if any, are written into the same line, + * separated by semicolon. + * AddCookie only sanitizes c's name and value, and does not sanitize + * a Cookie header already present in the request. */ - bindFunc(fn: (e: T) => void): string - } -} - -/** - * Package bufio implements buffered I/O. It wraps an io.Reader or io.Writer - * object, creating another object (Reader or Writer) that also implements - * the interface but provides buffering and some help for textual I/O. - */ -namespace bufio { - /** - * ReadWriter stores pointers to a [Reader] and a [Writer]. - * It implements [io.ReadWriter]. - */ - type _sgpcBrt = Reader&Writer - interface ReadWriter extends _sgpcBrt { + addCookie(c: Cookie): void } -} - -/** - * Package multipart implements MIME multipart parsing, as defined in RFC - * 2046. - * - * The implementation is sufficient for HTTP (RFC 2388) and the multipart - * bodies generated by popular browsers. - * - * # Limits - * - * To protect against malicious inputs, this package sets limits on the size - * of the MIME data it processes. - * - * [Reader.NextPart] and [Reader.NextRawPart] limit the number of headers in a - * part to 10000 and [Reader.ReadForm] limits the total number of headers in all - * FileHeaders to 10000. - * These limits may be adjusted with the GODEBUG=multipartmaxheaders= - * setting. - * - * Reader.ReadForm further limits the number of parts in a form to 1000. - * This limit may be adjusted with the GODEBUG=multipartmaxparts= - * setting. - */ -namespace multipart { - /** - * A FileHeader describes a file part of a multipart request. - */ - interface FileHeader { - filename: string - header: textproto.MIMEHeader - size: number + interface Request { + /** + * Referer returns the referring URL, if sent in the request. + * + * Referer is misspelled as in the request itself, a mistake from the + * earliest days of HTTP. This value can also be fetched from the + * [Header] map as Header["Referer"]; the benefit of making it available + * as a method is that the compiler can diagnose programs that use the + * alternate (correct English) spelling req.Referrer() but cannot + * diagnose programs that use Header["Referrer"]. + */ + referer(): string } - interface FileHeader { + interface Request { /** - * Open opens and returns the [FileHeader]'s associated File. + * MultipartReader returns a MIME multipart reader if this is a + * multipart/form-data or a multipart/mixed POST request, else returns nil and an error. + * Use this function instead of [Request.ParseMultipartForm] to + * process the request body as a stream. */ - open(): File + multipartReader(): (multipart.Reader) } -} - -/** - * Package http provides HTTP client and server implementations. - * - * [Get], [Head], [Post], and [PostForm] make HTTP (or HTTPS) requests: - * - * ``` - * resp, err := http.Get("http://example.com/") - * ... - * resp, err := http.Post("http://example.com/upload", "image/jpeg", &buf) - * ... - * resp, err := http.PostForm("http://example.com/form", - * url.Values{"key": {"Value"}, "id": {"123"}}) - * ``` - * - * The caller must close the response body when finished with it: - * - * ``` - * resp, err := http.Get("http://example.com/") - * if err != nil { - * // handle error - * } - * defer resp.Body.Close() - * body, err := io.ReadAll(resp.Body) - * // ... - * ``` - * - * # Clients and Transports - * - * For control over HTTP client headers, redirect policy, and other - * settings, create a [Client]: - * - * ``` - * client := &http.Client{ - * CheckRedirect: redirectPolicyFunc, - * } - * - * resp, err := client.Get("http://example.com") - * // ... - * - * req, err := http.NewRequest("GET", "http://example.com", nil) - * // ... - * req.Header.Add("If-None-Match", `W/"wyzzy"`) - * resp, err := client.Do(req) - * // ... - * ``` - * - * For control over proxies, TLS configuration, keep-alives, - * compression, and other settings, create a [Transport]: - * - * ``` - * tr := &http.Transport{ - * MaxIdleConns: 10, - * IdleConnTimeout: 30 * time.Second, - * DisableCompression: true, - * } - * client := &http.Client{Transport: tr} - * resp, err := client.Get("https://example.com") - * ``` - * - * Clients and Transports are safe for concurrent use by multiple - * goroutines and for efficiency should only be created once and re-used. - * - * # Servers - * - * ListenAndServe starts an HTTP server with a given address and handler. - * The handler is usually nil, which means to use [DefaultServeMux]. - * [Handle] and [HandleFunc] add handlers to [DefaultServeMux]: - * - * ``` - * http.Handle("/foo", fooHandler) - * - * http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) { - * fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) - * }) - * - * log.Fatal(http.ListenAndServe(":8080", nil)) - * ``` - * - * More control over the server's behavior is available by creating a - * custom Server: - * - * ``` - * s := &http.Server{ - * Addr: ":8080", - * Handler: myHandler, - * ReadTimeout: 10 * time.Second, - * WriteTimeout: 10 * time.Second, - * MaxHeaderBytes: 1 << 20, - * } - * log.Fatal(s.ListenAndServe()) - * ``` - * - * # HTTP/2 - * - * Starting with Go 1.6, the http package has transparent support for the - * HTTP/2 protocol when using HTTPS. Programs that must disable HTTP/2 - * can do so by setting [Transport.TLSNextProto] (for clients) or - * [Server.TLSNextProto] (for servers) to a non-nil, empty - * map. Alternatively, the following GODEBUG settings are - * currently supported: - * - * ``` - * GODEBUG=http2client=0 # disable HTTP/2 client support - * GODEBUG=http2server=0 # disable HTTP/2 server support - * GODEBUG=http2debug=1 # enable verbose HTTP/2 debug logs - * GODEBUG=http2debug=2 # ... even more verbose, with frame dumps - * ``` - * - * Please report any issues before disabling HTTP/2 support: https://golang.org/s/http2bug - * - * The http package's [Transport] and [Server] both automatically enable - * HTTP/2 support for simple configurations. To enable HTTP/2 for more - * complex configurations, to use lower-level HTTP/2 features, or to use - * a newer version of Go's http2 package, import "golang.org/x/net/http2" - * directly and use its ConfigureTransport and/or ConfigureServer - * functions. Manually configuring HTTP/2 via the golang.org/x/net/http2 - * package takes precedence over the net/http package's built-in HTTP/2 - * support. - */ -namespace http { - // @ts-ignore - import mathrand = rand - /** - * PushOptions describes options for [Pusher.Push]. - */ - interface PushOptions { + interface Request { /** - * Method specifies the HTTP method for the promised request. - * If set, it must be "GET" or "HEAD". Empty means "GET". + * Write writes an HTTP/1.1 request, which is the header and body, in wire format. + * This method consults the following fields of the request: + * + * ``` + * Host + * URL + * Method (defaults to "GET") + * Header + * ContentLength + * TransferEncoding + * Body + * ``` + * + * If Body is present, Content-Length is <= 0 and [Request.TransferEncoding] + * hasn't been set to "identity", Write adds "Transfer-Encoding: + * chunked" to the header. Body is closed after it is sent. + * + * Header values for Host, Content-Length, Transfer-Encoding, + * and Trailer are not used; these are derived from other Request fields. + * If the Header does not contain a User-Agent value, Write uses + * "Go-http-client/1.1". */ - method: string + write(w: io.Writer): void + } + interface Request { /** - * Header specifies additional promised request headers. This cannot - * include HTTP/2 pseudo header fields like ":path" and ":scheme", - * which will be added automatically. + * WriteProxy is like [Request.Write] but writes the request in the form + * expected by an HTTP proxy. In particular, [Request.WriteProxy] writes the + * initial Request-URI line of the request with an absolute URI, per + * section 5.3 of RFC 7230, including the scheme and host. + * In either case, WriteProxy also writes a Host header, using + * either r.Host or r.URL.Host. */ - header: Header + writeProxy(w: io.Writer): void } - // @ts-ignore - import urlpkg = url - /** - * A Request represents an HTTP request received by a server - * or to be sent by a client. - * - * The field semantics differ slightly between client and server - * usage. In addition to the notes on the fields below, see the - * documentation for [Request.Write] and [RoundTripper]. - */ interface Request { /** - * Method specifies the HTTP method (GET, POST, PUT, etc.). - * For client requests, an empty string means GET. + * BasicAuth returns the username and password provided in the request's + * Authorization header, if the request uses HTTP Basic Authentication. + * See RFC 2617, Section 2. */ - method: string + basicAuth(): [string, string, boolean] + } + interface Request { /** - * URL specifies either the URI being requested (for server - * requests) or the URL to access (for client requests). - * - * For server requests, the URL is parsed from the URI - * supplied on the Request-Line as stored in RequestURI. For - * most requests, fields other than Path and RawQuery will be - * empty. (See RFC 7230, Section 5.3) + * SetBasicAuth sets the request's Authorization header to use HTTP + * Basic Authentication with the provided username and password. * - * For client requests, the URL's Host specifies the server to - * connect to, while the Request's Host field optionally - * specifies the Host header value to send in the HTTP + * With HTTP Basic Authentication the provided username and password + * are not encrypted. It should generally only be used in an HTTPS * request. - */ - url?: url.URL - /** - * The protocol version for incoming server requests. * - * For client requests, these fields are ignored. The HTTP - * client code always uses either HTTP/1.1 or HTTP/2. - * See the docs on Transport for details. - */ - proto: string // "HTTP/1.0" - protoMajor: number // 1 - protoMinor: number // 0 + * The username may not contain a colon. Some protocols may impose + * additional requirements on pre-escaping the username and + * password. For instance, when used with OAuth2, both arguments must + * be URL encoded first with [url.QueryEscape]. + */ + setBasicAuth(username: string, password: string): void + } + interface Request { /** - * Header contains the request header fields either received - * by the server or to be sent by the client. - * - * If a server received a request with header lines, - * - * ``` - * Host: example.com - * accept-encoding: gzip, deflate - * Accept-Language: en-us - * fOO: Bar - * foo: two - * ``` - * - * then - * - * ``` - * Header = map[string][]string{ - * "Accept-Encoding": {"gzip, deflate"}, - * "Accept-Language": {"en-us"}, - * "Foo": {"Bar", "two"}, - * } - * ``` - * - * For incoming requests, the Host header is promoted to the - * Request.Host field and removed from the Header map. + * ParseForm populates r.Form and r.PostForm. * - * HTTP defines that header names are case-insensitive. The - * request parser implements this by using CanonicalHeaderKey, - * making the first character and any characters following a - * hyphen uppercase and the rest lowercase. + * For all requests, ParseForm parses the raw query from the URL and updates + * r.Form. * - * For client requests, certain headers such as Content-Length - * and Connection are automatically written when needed and - * values in Header may be ignored. See the documentation - * for the Request.Write method. - */ - header: Header - /** - * Body is the request's body. + * For POST, PUT, and PATCH requests, it also reads the request body, parses it + * as a form and puts the results into both r.PostForm and r.Form. Request body + * parameters take precedence over URL query string values in r.Form. * - * For client requests, a nil body means the request has no - * body, such as a GET request. The HTTP Client's Transport - * is responsible for calling the Close method. + * If the request Body's size has not already been limited by [MaxBytesReader], + * the size is capped at 10MB. * - * For server requests, the Request Body is always non-nil - * but will return EOF immediately when no body is present. - * The Server will close the request body. The ServeHTTP - * Handler does not need to. + * For other HTTP methods, or when the Content-Type is not + * application/x-www-form-urlencoded, the request Body is not read, and + * r.PostForm is initialized to a non-nil, empty value. * - * Body must allow Read to be called concurrently with Close. - * In particular, calling Close should unblock a Read waiting - * for input. + * [Request.ParseMultipartForm] calls ParseForm automatically. + * ParseForm is idempotent. */ - body: io.ReadCloser + parseForm(): void + } + interface Request { /** - * GetBody defines an optional func to return a new copy of - * Body. It is used for client requests when a redirect requires - * reading the body more than once. Use of GetBody still - * requires setting Body. - * - * For server requests, it is unused. + * ParseMultipartForm parses a request body as multipart/form-data. + * The whole request body is parsed and up to a total of maxMemory bytes of + * its file parts are stored in memory, with the remainder stored on + * disk in temporary files. + * ParseMultipartForm calls [Request.ParseForm] if necessary. + * If ParseForm returns an error, ParseMultipartForm returns it but also + * continues parsing the request body. + * After one call to ParseMultipartForm, subsequent calls have no effect. */ - getBody: () => io.ReadCloser + parseMultipartForm(maxMemory: number): void + } + interface Request { /** - * ContentLength records the length of the associated content. - * The value -1 indicates that the length is unknown. - * Values >= 0 indicate that the given number of bytes may - * be read from Body. + * FormValue returns the first value for the named component of the query. + * The precedence order: + * 1. application/x-www-form-urlencoded form body (POST, PUT, PATCH only) + * 2. query parameters (always) + * 3. multipart/form-data form body (always) * - * For client requests, a value of 0 with a non-nil Body is - * also treated as unknown. + * FormValue calls [Request.ParseMultipartForm] and [Request.ParseForm] + * if necessary and ignores any errors returned by these functions. + * If key is not present, FormValue returns the empty string. + * To access multiple values of the same key, call ParseForm and + * then inspect [Request.Form] directly. */ - contentLength: number + formValue(key: string): string + } + interface Request { /** - * TransferEncoding lists the transfer encodings from outermost to - * innermost. An empty list denotes the "identity" encoding. - * TransferEncoding can usually be ignored; chunked encoding is - * automatically added and removed as necessary when sending and - * receiving requests. + * PostFormValue returns the first value for the named component of the POST, + * PUT, or PATCH request body. URL query parameters are ignored. + * PostFormValue calls [Request.ParseMultipartForm] and [Request.ParseForm] if necessary and ignores + * any errors returned by these functions. + * If key is not present, PostFormValue returns the empty string. */ - transferEncoding: Array + postFormValue(key: string): string + } + interface Request { /** - * Close indicates whether to close the connection after - * replying to this request (for servers) or after sending this - * request and reading its response (for clients). - * - * For server requests, the HTTP server handles this automatically - * and this field is not needed by Handlers. - * - * For client requests, setting this field prevents re-use of - * TCP connections between requests to the same hosts, as if - * Transport.DisableKeepAlives were set. + * FormFile returns the first file for the provided form key. + * FormFile calls [Request.ParseMultipartForm] and [Request.ParseForm] if necessary. */ - close: boolean + formFile(key: string): [multipart.File, (multipart.FileHeader)] + } + interface Request { /** - * For server requests, Host specifies the host on which the - * URL is sought. For HTTP/1 (per RFC 7230, section 5.4), this - * is either the value of the "Host" header or the host name - * given in the URL itself. For HTTP/2, it is the value of the - * ":authority" pseudo-header field. - * It may be of the form "host:port". For international domain - * names, Host may be in Punycode or Unicode form. Use - * golang.org/x/net/idna to convert it to either format if - * needed. - * To prevent DNS rebinding attacks, server Handlers should - * validate that the Host header has a value for which the - * Handler considers itself authoritative. The included - * ServeMux supports patterns registered to particular host - * names and thus protects its registered Handlers. + * PathValue returns the value for the named path wildcard in the [ServeMux] pattern + * that matched the request. + * It returns the empty string if the request was not matched against a pattern + * or there is no such wildcard in the pattern. * - * For client requests, Host optionally overrides the Host - * header to send. If empty, the Request.Write method uses - * the value of URL.Host. Host may contain an international - * domain name. + * The value is unescaped. For example, if the pattern "/b/{bucket}" matches + * the path "/b/a%2fb", PathValue("bucket") returns "a/b". */ - host: string + pathValue(name: string): string + } + interface Request { /** - * Form contains the parsed form data, including both the URL - * field's query parameters and the PATCH, POST, or PUT form data. - * This field is only available after ParseForm is called. - * The HTTP client ignores Form and uses Body instead. + * SetPathValue sets name to value, so that subsequent calls to r.PathValue(name) + * return value. + * It does not unescape value. */ - form: url.Values + setPathValue(name: string, value: string): void + } + /** + * A Handler responds to an HTTP request. + * + * [Handler.ServeHTTP] should write reply headers and data to the [ResponseWriter] + * and then return. Returning signals that the request is finished; it + * is not valid to use the [ResponseWriter] or read from the + * [Request.Body] after or concurrently with the completion of the + * ServeHTTP call. + * + * Depending on the HTTP client software, HTTP protocol version, and + * any intermediaries between the client and the Go server, it may not + * be possible to read from the [Request.Body] after writing to the + * [ResponseWriter]. Cautious handlers should read the [Request.Body] + * first, and then reply. + * + * Except for reading the body, handlers should not modify the + * provided Request. + * + * If ServeHTTP panics, the server (the caller of ServeHTTP) assumes + * that the effect of the panic was isolated to the active request. + * It recovers the panic, logs a stack trace to the server error log, + * and either closes the network connection or sends an HTTP/2 + * RST_STREAM, depending on the HTTP protocol. To abort a handler so + * the client sees an interrupted response but the server doesn't log + * an error, panic with the value [ErrAbortHandler]. + */ + interface Handler { + [key:string]: any; + serveHTTP(_arg0: ResponseWriter, _arg1: Request): void + } + /** + * A ResponseWriter interface is used by an HTTP handler to + * construct an HTTP response. + * + * A ResponseWriter may not be used after [Handler.ServeHTTP] has returned. + */ + interface ResponseWriter { + [key:string]: any; /** - * PostForm contains the parsed form data from PATCH, POST - * or PUT body parameters. + * Header returns the header map that will be sent by + * [ResponseWriter.WriteHeader]. The [Header] map also is the mechanism with which + * [Handler] implementations can set HTTP trailers. * - * This field is only available after ParseForm is called. - * The HTTP client ignores PostForm and uses Body instead. - */ - postForm: url.Values - /** - * MultipartForm is the parsed multipart form, including file uploads. - * This field is only available after ParseMultipartForm is called. - * The HTTP client ignores MultipartForm and uses Body instead. + * Changing the header map after a call to [ResponseWriter.WriteHeader] (or + * [ResponseWriter.Write]) has no effect unless the HTTP status code was of the + * 1xx class or the modified headers are trailers. + * + * There are two ways to set Trailers. The preferred way is to + * predeclare in the headers which trailers you will later + * send by setting the "Trailer" header to the names of the + * trailer keys which will come later. In this case, those + * keys of the Header map are treated as if they were + * trailers. See the example. The second way, for trailer + * keys not known to the [Handler] until after the first [ResponseWriter.Write], + * is to prefix the [Header] map keys with the [TrailerPrefix] + * constant value. + * + * To suppress automatic response headers (such as "Date"), set + * their value to nil. */ - multipartForm?: multipart.Form + header(): Header /** - * Trailer specifies additional headers that are sent after the request - * body. + * Write writes the data to the connection as part of an HTTP reply. * - * For server requests, the Trailer map initially contains only the - * trailer keys, with nil values. (The client declares which trailers it - * will later send.) While the handler is reading from Body, it must - * not reference Trailer. After reading from Body returns EOF, Trailer - * can be read again and will contain non-nil values, if they were sent - * by the client. + * If [ResponseWriter.WriteHeader] has not yet been called, Write calls + * WriteHeader(http.StatusOK) before writing the data. If the Header + * does not contain a Content-Type line, Write adds a Content-Type set + * to the result of passing the initial 512 bytes of written data to + * [DetectContentType]. Additionally, if the total size of all written + * data is under a few KB and there are no Flush calls, the + * Content-Length header is added automatically. * - * For client requests, Trailer must be initialized to a map containing - * the trailer keys to later send. The values may be nil or their final - * values. The ContentLength must be 0 or -1, to send a chunked request. - * After the HTTP request is sent the map values can be updated while - * the request body is read. Once the body returns EOF, the caller must - * not mutate Trailer. + * Depending on the HTTP protocol version and the client, calling + * Write or WriteHeader may prevent future reads on the + * Request.Body. For HTTP/1.x requests, handlers should read any + * needed request body data before writing the response. Once the + * headers have been flushed (due to either an explicit Flusher.Flush + * call or writing enough data to trigger a flush), the request body + * may be unavailable. For HTTP/2 requests, the Go HTTP server permits + * handlers to continue to read the request body while concurrently + * writing the response. However, such behavior may not be supported + * by all HTTP/2 clients. Handlers should read before writing if + * possible to maximize compatibility. + */ + write(_arg0: string|Array): number + /** + * WriteHeader sends an HTTP response header with the provided + * status code. * - * Few HTTP clients, servers, or proxies support HTTP trailers. + * If WriteHeader is not called explicitly, the first call to Write + * will trigger an implicit WriteHeader(http.StatusOK). + * Thus explicit calls to WriteHeader are mainly used to + * send error codes or 1xx informational responses. + * + * The provided code must be a valid HTTP 1xx-5xx status code. + * Any number of 1xx headers may be written, followed by at most + * one 2xx-5xx header. 1xx headers are sent immediately, but 2xx-5xx + * headers may be buffered. Use the Flusher interface to send + * buffered data. The header map is cleared when 2xx-5xx headers are + * sent, but not with 1xx headers. + * + * The server will automatically send a 100 (Continue) header + * on the first read from the request body if the request has + * an "Expect: 100-continue" header. */ - trailer: Header + writeHeader(statusCode: number): void + } + /** + * A Server defines parameters for running an HTTP server. + * The zero value for Server is a valid configuration. + */ + interface Server { /** - * RemoteAddr allows HTTP servers and other software to record - * the network address that sent the request, usually for - * logging. This field is not filled in by ReadRequest and - * has no defined format. The HTTP server in this package - * sets RemoteAddr to an "IP:port" address before invoking a - * handler. - * This field is ignored by the HTTP client. + * Addr optionally specifies the TCP address for the server to listen on, + * in the form "host:port". If empty, ":http" (port 80) is used. + * The service names are defined in RFC 6335 and assigned by IANA. + * See net.Dial for details of the address format. */ - remoteAddr: string + addr: string + handler: Handler // handler to invoke, http.DefaultServeMux if nil /** - * RequestURI is the unmodified request-target of the - * Request-Line (RFC 7230, Section 3.1.1) as sent by the client - * to a server. Usually the URL field should be used instead. - * It is an error to set this field in an HTTP client request. + * DisableGeneralOptionsHandler, if true, passes "OPTIONS *" requests to the Handler, + * otherwise responds with 200 OK and Content-Length: 0. */ - requestURI: string + disableGeneralOptionsHandler: boolean /** - * TLS allows HTTP servers and other software to record - * information about the TLS connection on which the request - * was received. This field is not filled in by ReadRequest. - * The HTTP server in this package sets the field for - * TLS-enabled connections before invoking a handler; - * otherwise it leaves the field nil. - * This field is ignored by the HTTP client. + * TLSConfig optionally provides a TLS configuration for use + * by ServeTLS and ListenAndServeTLS. Note that this value is + * cloned by ServeTLS and ListenAndServeTLS, so it's not + * possible to modify the configuration with methods like + * tls.Config.SetSessionTicketKeys. To use + * SetSessionTicketKeys, use Server.Serve with a TLS Listener + * instead. */ - tls?: any + tlsConfig?: any /** - * Cancel is an optional channel whose closure indicates that the client - * request should be regarded as canceled. Not all implementations of - * RoundTripper may support Cancel. - * - * For server requests, this field is not applicable. + * ReadTimeout is the maximum duration for reading the entire + * request, including the body. A zero or negative value means + * there will be no timeout. * - * Deprecated: Set the Request's context with NewRequestWithContext - * instead. If a Request's Cancel field and context are both - * set, it is undefined whether Cancel is respected. + * Because ReadTimeout does not let Handlers make per-request + * decisions on each request body's acceptable deadline or + * upload rate, most users will prefer to use + * ReadHeaderTimeout. It is valid to use them both. */ - cancel: undefined + readTimeout: time.Duration /** - * Response is the redirect response which caused this request - * to be created. This field is only populated during client - * redirects. + * ReadHeaderTimeout is the amount of time allowed to read + * request headers. The connection's read deadline is reset + * after reading the headers and the Handler can decide what + * is considered too slow for the body. If zero, the value of + * ReadTimeout is used. If negative, or if zero and ReadTimeout + * is zero or negative, there is no timeout. */ - response?: Response + readHeaderTimeout: time.Duration /** - * Pattern is the [ServeMux] pattern that matched the request. - * It is empty if the request was not matched against a pattern. + * WriteTimeout is the maximum duration before timing out + * writes of the response. It is reset whenever a new + * request's header is read. Like ReadTimeout, it does not + * let Handlers make decisions on a per-request basis. + * A zero or negative value means there will be no timeout. */ - pattern: string - } - interface Request { + writeTimeout: time.Duration /** - * Context returns the request's context. To change the context, use - * [Request.Clone] or [Request.WithContext]. - * - * The returned context is always non-nil; it defaults to the - * background context. - * - * For outgoing client requests, the context controls cancellation. - * - * For incoming server requests, the context is canceled when the - * client's connection closes, the request is canceled (with HTTP/2), - * or when the ServeHTTP method returns. + * IdleTimeout is the maximum amount of time to wait for the + * next request when keep-alives are enabled. If zero, the value + * of ReadTimeout is used. If negative, or if zero and ReadTimeout + * is zero or negative, there is no timeout. */ - context(): context.Context - } - interface Request { + idleTimeout: time.Duration /** - * WithContext returns a shallow copy of r with its context changed - * to ctx. The provided ctx must be non-nil. - * - * For outgoing client request, the context controls the entire - * lifetime of a request and its response: obtaining a connection, - * sending the request, and reading the response headers and body. - * - * To create a new request with a context, use [NewRequestWithContext]. - * To make a deep copy of a request with a new context, use [Request.Clone]. + * MaxHeaderBytes controls the maximum number of bytes the + * server will read parsing the request header's keys and + * values, including the request line. It does not limit the + * size of the request body. + * If zero, DefaultMaxHeaderBytes is used. */ - withContext(ctx: context.Context): (Request) - } - interface Request { + maxHeaderBytes: number /** - * Clone returns a deep copy of r with its context changed to ctx. - * The provided ctx must be non-nil. - * - * Clone only makes a shallow copy of the Body field. - * - * For an outgoing client request, the context controls the entire - * lifetime of a request and its response: obtaining a connection, - * sending the request, and reading the response headers and body. + * MaxHeaderValueCount controls the maximum number of header + * values that the server is willing to parse from a request. + * If zero, DefaultMaxHeaderValueCount is used. + * Note that comma-separated values in a single header line are + * counted once, while values sent as multiple header lines are + * counted multiple times. */ - clone(ctx: context.Context): (Request) - } - interface Request { + maxHeaderValueCount: number /** - * ProtoAtLeast reports whether the HTTP protocol used - * in the request is at least major.minor. + * TLSNextProto optionally specifies a function to take over + * ownership of the provided TLS connection when an ALPN + * protocol upgrade has occurred. The map key is the protocol + * name negotiated. The Handler argument should be used to + * handle HTTP requests and will initialize the Request's TLS + * and RemoteAddr if not already set. The connection is + * automatically closed when the function returns. + * If TLSNextProto is not nil, HTTP/2 support is not enabled + * automatically. + * + * Historically, TLSNextProto was used to disable HTTP/2 support. + * The Server.Protocols field now provides a simpler way to do this. */ - protoAtLeast(major: number, minor: number): boolean - } - interface Request { + tlsNextProto: _TygojaDict /** - * UserAgent returns the client's User-Agent, if sent in the request. + * ConnState specifies an optional callback function that is + * called when a client connection changes state. See the + * ConnState type and associated constants for details. */ - userAgent(): string - } - interface Request { + connState: (_arg0: net.Conn, _arg1: ConnState) => void /** - * Cookies parses and returns the HTTP cookies sent with the request. + * ErrorLog specifies an optional logger for errors accepting + * connections, unexpected behavior from handlers, and + * underlying FileSystem errors. + * If nil, logging is done via the log package's standard logger. */ - cookies(): Array<(Cookie | undefined)> - } - interface Request { + errorLog?: any /** - * CookiesNamed parses and returns the named HTTP cookies sent with the request - * or an empty slice if none matched. + * BaseContext optionally specifies a function that returns + * the base context for incoming requests on this server. + * The provided Listener is the specific Listener that's + * about to start accepting requests. + * If BaseContext is nil, the default is context.Background(). + * If non-nil, it must return a non-nil context. */ - cookiesNamed(name: string): Array<(Cookie | undefined)> - } - interface Request { + baseContext: (_arg0: net.Listener) => context.Context /** - * Cookie returns the named cookie provided in the request or - * [ErrNoCookie] if not found. - * If multiple cookies match the given name, only one cookie will - * be returned. + * ConnContext optionally specifies a function that modifies + * the context used for a new connection c. The provided ctx + * is derived from the base context and has a ServerContextKey + * value. */ - cookie(name: string): (Cookie) - } - interface Request { + connContext: (ctx: context.Context, c: net.Conn) => context.Context /** - * AddCookie adds a cookie to the request. Per RFC 6265 section 5.4, - * AddCookie does not attach more than one [Cookie] header field. That - * means all cookies, if any, are written into the same line, - * separated by semicolon. - * AddCookie only sanitizes c's name and value, and does not sanitize - * a Cookie header already present in the request. + * HTTP2 configures HTTP/2 connections. */ - addCookie(c: Cookie): void - } - interface Request { + http2?: HTTP2Config /** - * Referer returns the referring URL, if sent in the request. + * Protocols is the set of protocols accepted by the server. * - * Referer is misspelled as in the request itself, a mistake from the - * earliest days of HTTP. This value can also be fetched from the - * [Header] map as Header["Referer"]; the benefit of making it available - * as a method is that the compiler can diagnose programs that use the - * alternate (correct English) spelling req.Referrer() but cannot - * diagnose programs that use Header["Referrer"]. - */ - referer(): string - } - interface Request { - /** - * MultipartReader returns a MIME multipart reader if this is a - * multipart/form-data or a multipart/mixed POST request, else returns nil and an error. - * Use this function instead of [Request.ParseMultipartForm] to - * process the request body as a stream. + * If Protocols includes UnencryptedHTTP2, the server will accept + * unencrypted HTTP/2 connections. The server can serve both + * HTTP/1 and unencrypted HTTP/2 on the same address and port. + * + * If Protocols is nil, the default is usually HTTP/1 and HTTP/2. + * If TLSNextProto is non-nil and does not contain an "h2" entry, + * the default is HTTP/1 only. */ - multipartReader(): (multipart.Reader) - } - interface Request { + protocols?: Protocols /** - * Write writes an HTTP/1.1 request, which is the header and body, in wire format. - * This method consults the following fields of the request: + * DisableClientPriority specifies whether client-specified priority, as + * specified in RFC 9218, should be respected or not. * - * ``` - * Host - * URL - * Method (defaults to "GET") - * Header - * ContentLength - * TransferEncoding - * Body - * ``` + * This field only takes effect if using HTTP/2, and if no custom write + * scheduler is defined for the HTTP/2 server. Otherwise, this field is a + * no-op. * - * If Body is present, Content-Length is <= 0 and [Request.TransferEncoding] - * hasn't been set to "identity", Write adds "Transfer-Encoding: - * chunked" to the header. Body is closed after it is sent. + * If set to true, requests will be served in a round-robin manner, without + * prioritization. */ - write(w: io.Writer): void - } - interface Request { - /** - * WriteProxy is like [Request.Write] but writes the request in the form - * expected by an HTTP proxy. In particular, [Request.WriteProxy] writes the - * initial Request-URI line of the request with an absolute URI, per - * section 5.3 of RFC 7230, including the scheme and host. - * In either case, WriteProxy also writes a Host header, using - * either r.Host or r.URL.Host. - */ - writeProxy(w: io.Writer): void - } - interface Request { - /** - * BasicAuth returns the username and password provided in the request's - * Authorization header, if the request uses HTTP Basic Authentication. - * See RFC 2617, Section 2. - */ - basicAuth(): [string, string, boolean] + disableClientPriority: boolean } - interface Request { + interface Server { /** - * SetBasicAuth sets the request's Authorization header to use HTTP - * Basic Authentication with the provided username and password. + * Close immediately closes all active net.Listeners and any + * connections in state [StateNew], [StateActive], or [StateIdle]. For a + * graceful shutdown, use [Server.Shutdown]. * - * With HTTP Basic Authentication the provided username and password - * are not encrypted. It should generally only be used in an HTTPS - * request. + * Close does not attempt to close (and does not even know about) + * any hijacked connections, such as WebSockets. * - * The username may not contain a colon. Some protocols may impose - * additional requirements on pre-escaping the username and - * password. For instance, when used with OAuth2, both arguments must - * be URL encoded first with [url.QueryEscape]. + * Close returns any error returned from closing the [Server]'s + * underlying Listener(s). */ - setBasicAuth(username: string, password: string): void + close(): void } - interface Request { + interface Server { /** - * ParseForm populates r.Form and r.PostForm. - * - * For all requests, ParseForm parses the raw query from the URL and updates - * r.Form. - * - * For POST, PUT, and PATCH requests, it also reads the request body, parses it - * as a form and puts the results into both r.PostForm and r.Form. Request body - * parameters take precedence over URL query string values in r.Form. + * Shutdown gracefully shuts down the server without interrupting any + * active connections. Shutdown works by first closing all open + * listeners, then closing all idle connections, and then waiting + * indefinitely for connections to return to idle and then shut down. + * If the provided context expires before the shutdown is complete, + * Shutdown returns the context's error, otherwise it returns any + * error returned from closing the [Server]'s underlying Listener(s). * - * If the request Body's size has not already been limited by [MaxBytesReader], - * the size is capped at 10MB. + * When Shutdown is called, [Serve], [ServeTLS], [ListenAndServe], and + * [ListenAndServeTLS] immediately return [ErrServerClosed]. Make sure the + * program doesn't exit and waits instead for Shutdown to return. * - * For other HTTP methods, or when the Content-Type is not - * application/x-www-form-urlencoded, the request Body is not read, and - * r.PostForm is initialized to a non-nil, empty value. + * Shutdown does not attempt to close nor wait for hijacked + * connections such as WebSockets. The caller of Shutdown should + * separately notify such long-lived connections of shutdown and wait + * for them to close, if desired. See [Server.RegisterOnShutdown] for a way to + * register shutdown notification functions. * - * [Request.ParseMultipartForm] calls ParseForm automatically. - * ParseForm is idempotent. + * Once Shutdown has been called on a server, it may not be reused; + * future calls to methods such as Serve will return ErrServerClosed. */ - parseForm(): void + shutdown(ctx: context.Context): void } - interface Request { + interface Server { /** - * ParseMultipartForm parses a request body as multipart/form-data. - * The whole request body is parsed and up to a total of maxMemory bytes of - * its file parts are stored in memory, with the remainder stored on - * disk in temporary files. - * ParseMultipartForm calls [Request.ParseForm] if necessary. - * If ParseForm returns an error, ParseMultipartForm returns it but also - * continues parsing the request body. - * After one call to ParseMultipartForm, subsequent calls have no effect. + * RegisterOnShutdown registers a function to call on [Server.Shutdown]. + * This can be used to gracefully shutdown connections that have + * undergone ALPN protocol upgrade or that have been hijacked. + * This function should start protocol-specific graceful shutdown, + * but should not wait for shutdown to complete. */ - parseMultipartForm(maxMemory: number): void + registerOnShutdown(f: () => void): void } - interface Request { + interface Server { /** - * FormValue returns the first value for the named component of the query. - * The precedence order: - * 1. application/x-www-form-urlencoded form body (POST, PUT, PATCH only) - * 2. query parameters (always) - * 3. multipart/form-data form body (always) + * ListenAndServe listens on the TCP network address s.Addr and then + * calls [Serve] to handle requests on incoming connections. + * Accepted connections are configured to enable TCP keep-alives. * - * FormValue calls [Request.ParseMultipartForm] and [Request.ParseForm] - * if necessary and ignores any errors returned by these functions. - * If key is not present, FormValue returns the empty string. - * To access multiple values of the same key, call ParseForm and - * then inspect [Request.Form] directly. + * If s.Addr is blank, ":http" is used. + * + * ListenAndServe always returns a non-nil error. After [Server.Shutdown] or [Server.Close], + * the returned error is [ErrServerClosed]. */ - formValue(key: string): string + listenAndServe(): void } - interface Request { + interface Server { /** - * PostFormValue returns the first value for the named component of the POST, - * PUT, or PATCH request body. URL query parameters are ignored. - * PostFormValue calls [Request.ParseMultipartForm] and [Request.ParseForm] if necessary and ignores - * any errors returned by these functions. - * If key is not present, PostFormValue returns the empty string. + * Serve accepts incoming connections on the Listener l, creating a + * new service goroutine for each. The service goroutines read requests and + * then call s.Handler to reply to them. + * + * HTTP/2 support is only enabled if the Listener returns [*tls.Conn] + * connections and they were configured with "h2" in the TLS + * Config.NextProtos. + * + * Serve always returns a non-nil error and closes l. + * After [Server.Shutdown] or [Server.Close], the returned error is [ErrServerClosed]. */ - postFormValue(key: string): string + serve(l: net.Listener): void } - interface Request { + interface Server { /** - * FormFile returns the first file for the provided form key. - * FormFile calls [Request.ParseMultipartForm] and [Request.ParseForm] if necessary. + * ServeTLS accepts incoming connections on the Listener l, creating a + * new service goroutine for each. The service goroutines perform TLS + * setup and then read requests, calling s.Handler to reply to them. + * + * Files containing a certificate and matching private key for the + * server must be provided if neither the [Server]'s + * TLSConfig.Certificates, TLSConfig.GetCertificate nor + * config.GetConfigForClient are populated. + * If the certificate is signed by a certificate authority, the + * certFile should be the concatenation of the server's certificate, + * any intermediates, and the CA's certificate. + * + * ServeTLS always returns a non-nil error. After [Server.Shutdown] or [Server.Close], the + * returned error is [ErrServerClosed]. */ - formFile(key: string): [multipart.File, (multipart.FileHeader)] + serveTLS(l: net.Listener, certFile: string, keyFile: string): void } - interface Request { + interface Server { /** - * PathValue returns the value for the named path wildcard in the [ServeMux] pattern - * that matched the request. - * It returns the empty string if the request was not matched against a pattern - * or there is no such wildcard in the pattern. + * SetKeepAlivesEnabled controls whether HTTP keep-alives are enabled. + * By default, keep-alives are always enabled. Only very + * resource-constrained environments or servers in the process of + * shutting down should disable them. */ - pathValue(name: string): string + setKeepAlivesEnabled(v: boolean): void } - interface Request { + interface Server { /** - * SetPathValue sets name to value, so that subsequent calls to r.PathValue(name) - * return value. + * ListenAndServeTLS listens on the TCP network address s.Addr and + * then calls [ServeTLS] to handle requests on incoming TLS connections. + * Accepted connections are configured to enable TCP keep-alives. + * + * Filenames containing a certificate and matching private key for the + * server must be provided if neither the [Server]'s TLSConfig.Certificates + * nor TLSConfig.GetCertificate are populated. If the certificate is + * signed by a certificate authority, the certFile should be the + * concatenation of the server's certificate, any intermediates, and + * the CA's certificate. + * + * If s.Addr is blank, ":https" is used. + * + * ListenAndServeTLS always returns a non-nil error. After [Server.Shutdown] or + * [Server.Close], the returned error is [ErrServerClosed]. */ - setPathValue(name: string, value: string): void - } - /** - * A Handler responds to an HTTP request. - * - * [Handler.ServeHTTP] should write reply headers and data to the [ResponseWriter] - * and then return. Returning signals that the request is finished; it - * is not valid to use the [ResponseWriter] or read from the - * [Request.Body] after or concurrently with the completion of the - * ServeHTTP call. - * - * Depending on the HTTP client software, HTTP protocol version, and - * any intermediaries between the client and the Go server, it may not - * be possible to read from the [Request.Body] after writing to the - * [ResponseWriter]. Cautious handlers should read the [Request.Body] - * first, and then reply. - * - * Except for reading the body, handlers should not modify the - * provided Request. - * - * If ServeHTTP panics, the server (the caller of ServeHTTP) assumes - * that the effect of the panic was isolated to the active request. - * It recovers the panic, logs a stack trace to the server error log, - * and either closes the network connection or sends an HTTP/2 - * RST_STREAM, depending on the HTTP protocol. To abort a handler so - * the client sees an interrupted response but the server doesn't log - * an error, panic with the value [ErrAbortHandler]. - */ - interface Handler { - [key:string]: any; - serveHTTP(_arg0: ResponseWriter, _arg1: Request): void + listenAndServeTLS(certFile: string, keyFile: string): void } - /** - * A ResponseWriter interface is used by an HTTP handler to - * construct an HTTP response. - * - * A ResponseWriter may not be used after [Handler.ServeHTTP] has returned. - */ - interface ResponseWriter { - [key:string]: any; +} + +/** + * Package syntax parses regular expressions into parse trees and compiles + * parse trees into programs. Most clients of regular expressions will use the + * facilities of package [regexp] (such as [regexp.Compile] and [regexp.Match]) instead of this package. + * + * # Syntax + * + * The regular expression syntax understood by this package when parsing with the [Perl] flag is as follows. + * Parts of the syntax can be disabled by passing alternate flags to [Parse]. + * + * Single characters: + * + * ``` + * . any character, possibly including newline (flag s=true) + * [xyz] character class + * [^xyz] negated character class + * \d Perl character class + * \D negated Perl character class + * [[:alpha:]] ASCII character class + * [[:^alpha:]] negated ASCII character class + * \pN Unicode character class (one-letter name) + * \p{Greek} Unicode character class + * \PN negated Unicode character class (one-letter name) + * \P{Greek} negated Unicode character class + * ``` + * + * Composites: + * + * ``` + * xy x followed by y + * x|y x or y (prefer x) + * ``` + * + * Repetitions: + * + * ``` + * x* zero or more x, prefer more + * x+ one or more x, prefer more + * x? zero or one x, prefer one + * x{n,m} n or n+1 or ... or m x, prefer more + * x{n,} n or more x, prefer more + * x{n} exactly n x + * x*? zero or more x, prefer fewer + * x+? one or more x, prefer fewer + * x?? zero or one x, prefer zero + * x{n,m}? n or n+1 or ... or m x, prefer fewer + * x{n,}? n or more x, prefer fewer + * x{n}? exactly n x + * ``` + * + * Implementation restriction: The counting forms x{n,m}, x{n,}, and x{n} + * reject forms that create a minimum or maximum repetition count above 1000. + * Unlimited repetitions are not subject to this restriction. + * + * Grouping: + * + * ``` + * (re) numbered capturing group (submatch) + * (?Pre) named & numbered capturing group (submatch) + * (?re) named & numbered capturing group (submatch) + * (?:re) non-capturing group + * (?flags) set flags within current group; non-capturing + * (?flags:re) set flags during re; non-capturing + * + * Flag syntax is xyz (set) or -xyz (clear) or xy-z (set xy, clear z). The flags are: + * + * i case-insensitive (default false) + * m multi-line mode: ^ and $ match begin/end line in addition to begin/end text (default false) + * s let . match \n (default false) + * U ungreedy: swap meaning of x* and x*?, x+ and x+?, etc (default false) + * ``` + * + * Empty strings: + * + * ``` + * ^ at beginning of text or line (flag m=true) + * $ at end of text (like \z not \Z) or line (flag m=true) + * \A at beginning of text + * \b at ASCII word boundary (\w on one side and \W, \A, or \z on the other) + * \B not at ASCII word boundary + * \z at end of text + * ``` + * + * Escape sequences: + * + * ``` + * \a bell (== \007) + * \f form feed (== \014) + * \t horizontal tab (== \011) + * \n newline (== \012) + * \r carriage return (== \015) + * \v vertical tab character (== \013) + * \* literal *, for any punctuation character * + * \123 octal character code (up to three digits) + * \x7F hex character code (exactly two digits) + * \x{10FFFF} hex character code + * \Q...\E literal text ... even if ... has punctuation + * ``` + * + * Character class elements: + * + * ``` + * x single character + * A-Z character range (inclusive) + * \d Perl character class + * [:foo:] ASCII character class foo + * \p{Foo} Unicode character class Foo + * \pF Unicode character class F (one-letter name) + * ``` + * + * Named character classes as character class elements: + * + * ``` + * [\d] digits (== \d) + * [^\d] not digits (== \D) + * [\D] not digits (== \D) + * [^\D] not not digits (== \d) + * [[:name:]] named ASCII class inside character class (== [:name:]) + * [^[:name:]] named ASCII class inside negated character class (== [:^name:]) + * [\p{Name}] named Unicode property inside character class (== \p{Name}) + * [^\p{Name}] named Unicode property inside negated character class (== \P{Name}) + * ``` + * + * Perl character classes (all ASCII-only): + * + * ``` + * \d digits (== [0-9]) + * \D not digits (== [^0-9]) + * \s whitespace (== [\t\n\f\r ]) + * \S not whitespace (== [^\t\n\f\r ]) + * \w word characters (== [0-9A-Za-z_]) + * \W not word characters (== [^0-9A-Za-z_]) + * ``` + * + * ASCII character classes: + * + * ``` + * [[:alnum:]] alphanumeric (== [0-9A-Za-z]) + * [[:alpha:]] alphabetic (== [A-Za-z]) + * [[:ascii:]] ASCII (== [\x00-\x7F]) + * [[:blank:]] blank (== [\t ]) + * [[:cntrl:]] control (== [\x00-\x1F\x7F]) + * [[:digit:]] digits (== [0-9]) + * [[:graph:]] graphical (== [!-~] == [A-Za-z0-9!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]) + * [[:lower:]] lower case (== [a-z]) + * [[:print:]] printable (== [ -~] == [ [:graph:]]) + * [[:punct:]] punctuation (== [!-/:-@[-`{-~]) + * [[:space:]] whitespace (== [\t\n\v\f\r ]) + * [[:upper:]] upper case (== [A-Z]) + * [[:word:]] word characters (== [0-9A-Za-z_]) + * [[:xdigit:]] hex digit (== [0-9A-Fa-f]) + * ``` + * + * Unicode character classes are those in [unicode.Categories], + * [unicode.CategoryAliases], and [unicode.Scripts]. + */ +namespace syntax { + /** + * Flags control the behavior of the parser and record information about regexp context. + */ + interface Flags extends Number{} +} + +namespace store { + /** + * Store defines a concurrent safe in memory key-value data store. + */ + interface Store { + } + interface Store { /** - * Header returns the header map that will be sent by - * [ResponseWriter.WriteHeader]. The [Header] map also is the mechanism with which - * [Handler] implementations can set HTTP trailers. - * - * Changing the header map after a call to [ResponseWriter.WriteHeader] (or - * [ResponseWriter.Write]) has no effect unless the HTTP status code was of the - * 1xx class or the modified headers are trailers. - * - * There are two ways to set Trailers. The preferred way is to - * predeclare in the headers which trailers you will later - * send by setting the "Trailer" header to the names of the - * trailer keys which will come later. In this case, those - * keys of the Header map are treated as if they were - * trailers. See the example. The second way, for trailer - * keys not known to the [Handler] until after the first [ResponseWriter.Write], - * is to prefix the [Header] map keys with the [TrailerPrefix] - * constant value. - * - * To suppress automatic response headers (such as "Date"), set - * their value to nil. + * Reset clears the store and replaces the store data with a + * shallow copy of the provided newData. */ - header(): Header + reset(newData: _TygojaDict): void + } + interface Store { /** - * Write writes the data to the connection as part of an HTTP reply. - * - * If [ResponseWriter.WriteHeader] has not yet been called, Write calls - * WriteHeader(http.StatusOK) before writing the data. If the Header - * does not contain a Content-Type line, Write adds a Content-Type set - * to the result of passing the initial 512 bytes of written data to - * [DetectContentType]. Additionally, if the total size of all written - * data is under a few KB and there are no Flush calls, the - * Content-Length header is added automatically. - * - * Depending on the HTTP protocol version and the client, calling - * Write or WriteHeader may prevent future reads on the - * Request.Body. For HTTP/1.x requests, handlers should read any - * needed request body data before writing the response. Once the - * headers have been flushed (due to either an explicit Flusher.Flush - * call or writing enough data to trigger a flush), the request body - * may be unavailable. For HTTP/2 requests, the Go HTTP server permits - * handlers to continue to read the request body while concurrently - * writing the response. However, such behavior may not be supported - * by all HTTP/2 clients. Handlers should read before writing if - * possible to maximize compatibility. + * Length returns the current number of elements in the store. */ - write(_arg0: string|Array): number + length(): number + } + interface Store { /** - * WriteHeader sends an HTTP response header with the provided - * status code. - * - * If WriteHeader is not called explicitly, the first call to Write - * will trigger an implicit WriteHeader(http.StatusOK). - * Thus explicit calls to WriteHeader are mainly used to - * send error codes or 1xx informational responses. - * - * The provided code must be a valid HTTP 1xx-5xx status code. - * Any number of 1xx headers may be written, followed by at most - * one 2xx-5xx header. 1xx headers are sent immediately, but 2xx-5xx - * headers may be buffered. Use the Flusher interface to send - * buffered data. The header map is cleared when 2xx-5xx headers are - * sent, but not with 1xx headers. - * - * The server will automatically send a 100 (Continue) header - * on the first read from the request body if the request has - * an "Expect: 100-continue" header. + * RemoveAll removes all the existing store entries. */ - writeHeader(statusCode: number): void + removeAll(): void } - /** - * A Server defines parameters for running an HTTP server. - * The zero value for Server is a valid configuration. - */ - interface Server { + interface Store { /** - * Addr optionally specifies the TCP address for the server to listen on, - * in the form "host:port". If empty, ":http" (port 80) is used. - * The service names are defined in RFC 6335 and assigned by IANA. - * See net.Dial for details of the address format. - */ - addr: string - handler: Handler // handler to invoke, http.DefaultServeMux if nil - /** - * DisableGeneralOptionsHandler, if true, passes "OPTIONS *" requests to the Handler, - * otherwise responds with 200 OK and Content-Length: 0. + * Remove removes a single entry from the store. + * + * Remove does nothing if key doesn't exist in the store. */ - disableGeneralOptionsHandler: boolean + remove(key: K): void + } + interface Store { /** - * TLSConfig optionally provides a TLS configuration for use - * by ServeTLS and ListenAndServeTLS. Note that this value is - * cloned by ServeTLS and ListenAndServeTLS, so it's not - * possible to modify the configuration with methods like - * tls.Config.SetSessionTicketKeys. To use - * SetSessionTicketKeys, use Server.Serve with a TLS Listener - * instead. + * Has checks if element with the specified key exist or not. */ - tlsConfig?: any + has(key: K): boolean + } + interface Store { /** - * ReadTimeout is the maximum duration for reading the entire - * request, including the body. A zero or negative value means - * there will be no timeout. + * Get returns a single element value from the store. * - * Because ReadTimeout does not let Handlers make per-request - * decisions on each request body's acceptable deadline or - * upload rate, most users will prefer to use - * ReadHeaderTimeout. It is valid to use them both. - */ - readTimeout: time.Duration - /** - * ReadHeaderTimeout is the amount of time allowed to read - * request headers. The connection's read deadline is reset - * after reading the headers and the Handler can decide what - * is considered too slow for the body. If zero, the value of - * ReadTimeout is used. If negative, or if zero and ReadTimeout - * is zero or negative, there is no timeout. + * If key is not set, the zero T value is returned. */ - readHeaderTimeout: time.Duration + get(key: K): T + } + interface Store { /** - * WriteTimeout is the maximum duration before timing out - * writes of the response. It is reset whenever a new - * request's header is read. Like ReadTimeout, it does not - * let Handlers make decisions on a per-request basis. - * A zero or negative value means there will be no timeout. + * GetOk is similar to Get but returns also a boolean indicating whether the key exists or not. */ - writeTimeout: time.Duration + getOk(key: K): [T, boolean] + } + interface Store { /** - * IdleTimeout is the maximum amount of time to wait for the - * next request when keep-alives are enabled. If zero, the value - * of ReadTimeout is used. If negative, or if zero and ReadTimeout - * is zero or negative, there is no timeout. + * GetAll returns a shallow copy of the current store data. */ - idleTimeout: time.Duration + getAll(): _TygojaDict + } + interface Store { /** - * MaxHeaderBytes controls the maximum number of bytes the - * server will read parsing the request header's keys and - * values, including the request line. It does not limit the - * size of the request body. - * If zero, DefaultMaxHeaderBytes is used. + * Keys returns a slice with all of the store keys. */ - maxHeaderBytes: number + keys(): Array + } + interface Store { /** - * TLSNextProto optionally specifies a function to take over - * ownership of the provided TLS connection when an ALPN - * protocol upgrade has occurred. The map key is the protocol - * name negotiated. The Handler argument should be used to - * handle HTTP requests and will initialize the Request's TLS - * and RemoteAddr if not already set. The connection is - * automatically closed when the function returns. - * If TLSNextProto is not nil, HTTP/2 support is not enabled - * automatically. + * Values returns a slice with all of the store values. */ - tlsNextProto: _TygojaDict + values(): Array + } + interface Store { /** - * ConnState specifies an optional callback function that is - * called when a client connection changes state. See the - * ConnState type and associated constants for details. + * Set sets (or overwrite if already exists) a new value for key. */ - connState: (_arg0: net.Conn, _arg1: ConnState) => void + set(key: K, value: T): void + } + interface Store { /** - * ErrorLog specifies an optional logger for errors accepting - * connections, unexpected behavior from handlers, and - * underlying FileSystem errors. - * If nil, logging is done via the log package's standard logger. + * SetFunc sets (or overwrite if already exists) a new value resolved + * from the function callback for the provided key. + * + * The function callback receives as argument the old store element value (if exists). + * If there is no old store element, the argument will be the T zero value. + * + * Example: + * + * ``` + * s := store.New[string, int](nil) + * s.SetFunc("count", func(old int) int { + * return old + 1 + * }) + * ``` */ - errorLog?: any + setFunc(key: K, fn: (old: T) => T): void + } + interface Store { /** - * BaseContext optionally specifies a function that returns - * the base context for incoming requests on this server. - * The provided Listener is the specific Listener that's - * about to start accepting requests. - * If BaseContext is nil, the default is context.Background(). - * If non-nil, it must return a non-nil context. + * GetOrSet retrieves a single existing value for the provided key + * or stores a new one if it doesn't exist. */ - baseContext: (_arg0: net.Listener) => context.Context + getOrSet(key: K, setFunc: () => T): T + } + interface Store { /** - * ConnContext optionally specifies a function that modifies - * the context used for a new connection c. The provided ctx - * is derived from the base context and has a ServerContextKey - * value. + * SetIfLessThanLimit sets (or overwrite if already exist) a new value for key. + * + * This method is similar to Set() but **it will skip adding new elements** + * to the store if the store length has reached the specified limit. + * false is returned if maxAllowedElements limit is reached. */ - connContext: (ctx: context.Context, c: net.Conn) => context.Context + setIfLessThanLimit(key: K, value: T, maxAllowedElements: number): boolean + } + interface Store { /** - * HTTP2 configures HTTP/2 connections. + * UnmarshalJSON implements [json.Unmarshaler] and imports the + * provided JSON data into the store. * - * This field does not yet have any effect. - * See https://go.dev/issue/67813. + * The store entries that match with the ones from the data will be overwritten with the new value. */ - http2?: HTTP2Config + unmarshalJSON(data: string|Array): void + } + interface Store { /** - * Protocols is the set of protocols accepted by the server. - * - * If Protocols includes UnencryptedHTTP2, the server will accept - * unencrypted HTTP/2 connections. The server can serve both - * HTTP/1 and unencrypted HTTP/2 on the same address and port. - * - * If Protocols is nil, the default is usually HTTP/1 and HTTP/2. - * If TLSNextProto is non-nil and does not contain an "h2" entry, - * the default is HTTP/1 only. + * MarshalJSON implements [json.Marshaler] and export the current + * store data into valid JSON. */ - protocols?: Protocols + marshalJSON(): string|Array } - interface Server { +} + +/** + * Package jwt is a Go implementation of JSON Web Tokens: http://self-issued.info/docs/draft-jones-json-web-token.html + * + * See README.md for more info. + */ +namespace jwt { + /** + * MapClaims is a claims type that uses the map[string]any for JSON + * decoding. This is the default claims type if you don't supply one + */ + interface MapClaims extends _TygojaDict{} + interface MapClaims { /** - * Close immediately closes all active net.Listeners and any - * connections in state [StateNew], [StateActive], or [StateIdle]. For a - * graceful shutdown, use [Server.Shutdown]. - * - * Close does not attempt to close (and does not even know about) - * any hijacked connections, such as WebSockets. - * - * Close returns any error returned from closing the [Server]'s - * underlying Listener(s). + * GetExpirationTime implements the Claims interface. */ - close(): void + getExpirationTime(): (NumericDate) } - interface Server { + interface MapClaims { /** - * Shutdown gracefully shuts down the server without interrupting any - * active connections. Shutdown works by first closing all open - * listeners, then closing all idle connections, and then waiting - * indefinitely for connections to return to idle and then shut down. - * If the provided context expires before the shutdown is complete, - * Shutdown returns the context's error, otherwise it returns any - * error returned from closing the [Server]'s underlying Listener(s). - * - * When Shutdown is called, [Serve], [ServeTLS], [ListenAndServe], and - * [ListenAndServeTLS] immediately return [ErrServerClosed]. Make sure the - * program doesn't exit and waits instead for Shutdown to return. - * - * Shutdown does not attempt to close nor wait for hijacked - * connections such as WebSockets. The caller of Shutdown should - * separately notify such long-lived connections of shutdown and wait - * for them to close, if desired. See [Server.RegisterOnShutdown] for a way to - * register shutdown notification functions. - * - * Once Shutdown has been called on a server, it may not be reused; - * future calls to methods such as Serve will return ErrServerClosed. + * GetNotBefore implements the Claims interface. */ - shutdown(ctx: context.Context): void + getNotBefore(): (NumericDate) } - interface Server { + interface MapClaims { /** - * RegisterOnShutdown registers a function to call on [Server.Shutdown]. - * This can be used to gracefully shutdown connections that have - * undergone ALPN protocol upgrade or that have been hijacked. - * This function should start protocol-specific graceful shutdown, - * but should not wait for shutdown to complete. + * GetIssuedAt implements the Claims interface. */ - registerOnShutdown(f: () => void): void + getIssuedAt(): (NumericDate) } - interface Server { + interface MapClaims { /** - * ListenAndServe listens on the TCP network address s.Addr and then - * calls [Serve] to handle requests on incoming connections. - * Accepted connections are configured to enable TCP keep-alives. - * - * If s.Addr is blank, ":http" is used. - * - * ListenAndServe always returns a non-nil error. After [Server.Shutdown] or [Server.Close], - * the returned error is [ErrServerClosed]. + * GetAudience implements the Claims interface. */ - listenAndServe(): void + getAudience(): ClaimStrings } - interface Server { + interface MapClaims { /** - * Serve accepts incoming connections on the Listener l, creating a - * new service goroutine for each. The service goroutines read requests and - * then call s.Handler to reply to them. - * - * HTTP/2 support is only enabled if the Listener returns [*tls.Conn] - * connections and they were configured with "h2" in the TLS - * Config.NextProtos. - * - * Serve always returns a non-nil error and closes l. - * After [Server.Shutdown] or [Server.Close], the returned error is [ErrServerClosed]. + * GetIssuer implements the Claims interface. */ - serve(l: net.Listener): void + getIssuer(): string } - interface Server { + interface MapClaims { /** - * ServeTLS accepts incoming connections on the Listener l, creating a - * new service goroutine for each. The service goroutines perform TLS - * setup and then read requests, calling s.Handler to reply to them. - * - * Files containing a certificate and matching private key for the - * server must be provided if neither the [Server]'s - * TLSConfig.Certificates, TLSConfig.GetCertificate nor - * config.GetConfigForClient are populated. - * If the certificate is signed by a certificate authority, the - * certFile should be the concatenation of the server's certificate, - * any intermediates, and the CA's certificate. - * - * ServeTLS always returns a non-nil error. After [Server.Shutdown] or [Server.Close], the - * returned error is [ErrServerClosed]. - */ - serveTLS(l: net.Listener, certFile: string, keyFile: string): void - } - interface Server { - /** - * SetKeepAlivesEnabled controls whether HTTP keep-alives are enabled. - * By default, keep-alives are always enabled. Only very - * resource-constrained environments or servers in the process of - * shutting down should disable them. - */ - setKeepAlivesEnabled(v: boolean): void - } - interface Server { - /** - * ListenAndServeTLS listens on the TCP network address s.Addr and - * then calls [ServeTLS] to handle requests on incoming TLS connections. - * Accepted connections are configured to enable TCP keep-alives. - * - * Filenames containing a certificate and matching private key for the - * server must be provided if neither the [Server]'s TLSConfig.Certificates - * nor TLSConfig.GetCertificate are populated. If the certificate is - * signed by a certificate authority, the certFile should be the - * concatenation of the server's certificate, any intermediates, and - * the CA's certificate. - * - * If s.Addr is blank, ":https" is used. - * - * ListenAndServeTLS always returns a non-nil error. After [Server.Shutdown] or - * [Server.Close], the returned error is [ErrServerClosed]. + * GetSubject implements the Claims interface. */ - listenAndServeTLS(certFile: string, keyFile: string): void + getSubject(): string } } -/** - * Package types implements some commonly used db serializable types - * like datetime, json, etc. - */ -namespace types { +namespace hook { /** - * DateTime represents a [time.Time] instance in UTC that is wrapped - * and serialized using the app default date layout. + * Event implements [Resolver] and it is intended to be used as a base + * Hook event that you can embed in your custom typed event structs. + * + * Example: + * + * ``` + * type CustomEvent struct { + * hook.Event + * + * SomeField int + * } + * ``` */ - interface DateTime { + interface Event { } - interface DateTime { + interface Event { /** - * Time returns the internal [time.Time] instance. + * Next calls the next hook handler. */ - time(): time.Time + next(): void } - interface DateTime { + /** + * Handler defines a single Hook handler. + * Multiple handlers can share the same id. + * If Id is not explicitly set it will be autogenerated by Hook.Add and Hook.AddHandler. + */ + interface Handler { /** - * Add returns a new DateTime based on the current DateTime + the specified duration. + * Func defines the handler function to execute. + * + * Note that users need to call e.Next() in order to proceed with + * the execution of the hook chain. */ - add(duration: time.Duration): DateTime - } - interface DateTime { + func: (_arg0: T) => void /** - * Sub returns a [time.Duration] by subtracting the specified DateTime from the current one. + * Id is the unique identifier of the handler. * - * If the result exceeds the maximum (or minimum) value that can be stored in a [time.Duration], - * the maximum (or minimum) duration will be returned. + * It could be used later to remove the handler from a hook via [Hook.Remove]. + * + * If missing, an autogenerated value will be assigned when adding + * the handler to a hook. */ - sub(u: DateTime): time.Duration - } - interface DateTime { + id: string /** - * AddDate returns a new DateTime based on the current one + duration. + * Priority allows changing the default exec priority of the handler within a hook. * - * It follows the same rules as [time.AddDate]. + * If 0, the handler will be executed in the same order it was registered. */ - addDate(years: number, months: number, days: number): DateTime + priority: number } - interface DateTime { - /** - * After reports whether the current DateTime instance is after u. - */ - after(u: DateTime): boolean + /** + * Hook defines a generic concurrent safe structure for managing event hooks. + * + * When using custom event it must embed the base [hook.Event]. + * + * Example: + * + * ``` + * type CustomEvent struct { + * hook.Event + * SomeField int + * } + * + * h := Hook[*CustomEvent]{} + * + * h.BindFunc(func(e *CustomEvent) error { + * println(e.SomeField) + * + * return e.Next() + * }) + * + * h.Trigger(&CustomEvent{ SomeField: 123 }) + * ``` + */ + interface Hook { } - interface DateTime { + interface Hook { /** - * Before reports whether the current DateTime instance is before u. + * Bind registers the provided handler to the current hooks queue. + * + * If handler.Id is empty it is updated with autogenerated value. + * + * If a handler from the current hook list has Id matching handler.Id + * then the old handler is replaced with the new one. */ - before(u: DateTime): boolean + bind(handler: Handler): string } - interface DateTime { + interface Hook { /** - * Compare compares the current DateTime instance with u. - * If the current instance is before u, it returns -1. - * If the current instance is after u, it returns +1. - * If they're the same, it returns 0. + * BindFunc is similar to Bind but registers a new handler from just the provided function. + * + * The registered handler is added with a default 0 priority and the id will be autogenerated. + * + * If you want to register a handler with custom priority or id use the [Hook.Bind] method. */ - compare(u: DateTime): number + bindFunc(fn: (e: T) => void): string } - interface DateTime { + interface Hook { /** - * Equal reports whether the current DateTime and u represent the same time instant. - * Two DateTime can be equal even if they are in different locations. - * For example, 6:00 +0200 and 4:00 UTC are Equal. + * Unbind removes one or many hook handler by their id. */ - equal(u: DateTime): boolean + unbind(...idsToRemove: string[]): void } - interface DateTime { + interface Hook { /** - * Unix returns the current DateTime as a Unix time, aka. - * the number of seconds elapsed since January 1, 1970 UTC. + * UnbindAll removes all registered handlers. */ - unix(): number + unbindAll(): void } - interface DateTime { + interface Hook { /** - * IsZero checks whether the current DateTime instance has zero time value. + * Length returns to total number of registered hook handlers. */ - isZero(): boolean + length(): number } - interface DateTime { + interface Hook { /** - * String serializes the current DateTime instance into a formatted - * UTC date string. + * Trigger executes all registered hook handlers one by one + * with the specified event as an argument. * - * The zero value is serialized to an empty string. + * Optionally, this method allows also to register additional one off + * handler funcs that will be temporary appended to the handlers queue. + * + * NB! Each hook handler must call event.Next() in order the hook chain to proceed. */ - string(): string + trigger(event: T, ...oneOffHandlerFuncs: ((_arg0: T) => void)[]): void } - interface DateTime { - /** - * MarshalJSON implements the [json.Marshaler] interface. - */ - marshalJSON(): string|Array + /** + * TaggedHook defines a proxy hook which register handlers that are triggered only + * if the TaggedHook.tags are empty or includes at least one of the event data tag(s). + */ + type _sHecNFO = mainHook + interface TaggedHook extends _sHecNFO { } - interface DateTime { + interface TaggedHook { /** - * UnmarshalJSON implements the [json.Unmarshaler] interface. + * CanTriggerOn checks if the current TaggedHook can be triggered with + * the provided event data tags. + * + * It returns always true if the hook doesn't have any tags. */ - unmarshalJSON(b: string|Array): void + canTriggerOn(tagsToCheck: Array): boolean } - interface DateTime { + interface TaggedHook { /** - * Value implements the [driver.Valuer] interface. + * Bind registers the provided handler to the current hooks queue. + * + * It is similar to [Hook.Bind] with the difference that the handler + * function is invoked only if the event data tags satisfy h.CanTriggerOn. */ - value(): any + bind(handler: Handler): string } - interface DateTime { + interface TaggedHook { /** - * Scan implements [sql.Scanner] interface to scan the provided value - * into the current DateTime instance. + * BindFunc registers a new handler with the specified function. + * + * It is similar to [Hook.Bind] with the difference that the handler + * function is invoked only if the event data tags satisfy h.CanTriggerOn. */ - scan(value: any): void + bindFunc(fn: (e: T) => void): string } +} + +namespace exec { /** - * GeoPoint defines a struct for storing geo coordinates as serialized json object - * (e.g. {lon:0,lat:0}). + * Cmd represents an external command being prepared or run. * - * Note: using object notation and not a plain array to avoid the confusion - * as there doesn't seem to be a fixed standard for the coordinates order. + * A Cmd cannot be reused after calling its [Cmd.Start], [Cmd.Run], + * [Cmd.Output], or [Cmd.CombinedOutput] methods. */ - interface GeoPoint { - lon: number - lat: number - } - interface GeoPoint { + interface Cmd { /** - * String returns the string representation of the current GeoPoint instance. + * Path is the path of the command to run. + * + * This is the only field that must be set to a non-zero + * value. If Path is relative, it is evaluated relative + * to Dir. */ - string(): string - } - interface GeoPoint { + path: string /** - * AsMap implements [core.mapExtractor] and returns a value suitable - * to be used in an API rule expression. + * Args holds command line arguments, including the command as Args[0]. + * If the Args field is empty or nil, Run uses {Path}. + * + * In typical use, both Path and Args are set by calling Command. */ - asMap(): _TygojaDict - } - interface GeoPoint { + args: Array /** - * Value implements the [driver.Valuer] interface. + * Env specifies the environment of the process. + * Each entry is of the form "key=value". + * If Env is nil, the new process uses the current process's + * environment. + * If Env contains duplicate environment keys, only the last + * value in the slice for each duplicate key is used. + * As a special case on Windows, SYSTEMROOT is always added if + * missing and not explicitly set to the empty string. + * + * See also the Dir field, which may set PWD in the environment. */ - value(): any - } - interface GeoPoint { + env: Array /** - * Scan implements [sql.Scanner] interface to scan the provided value - * into the current GeoPoint instance. + * Dir specifies the working directory of the command. + * If Dir is the empty string, Run runs the command in the + * calling process's current directory. * - * The value argument could be nil (no-op), another GeoPoint instance, - * map or serialized json object with lat-lon props. + * On Unix systems, the value of Dir also determines the + * child process's PWD environment variable if not otherwise + * specified. A Unix process represents its working directory + * not by name but as an implicit reference to a node in the + * file tree. So, if the child process obtains its working + * directory by calling a function such as C's getcwd, which + * computes the canonical name by walking up the file tree, it + * will not recover the original value of Dir if that value + * was an alias involving symbolic links. However, if the + * child process calls Go's [os.Getwd] or GNU C's + * get_current_dir_name, and the value of PWD is an alias for + * the current directory, those functions will return the + * value of PWD, which matches the value of Dir. */ - scan(value: any): void - } - /** - * JSONArray defines a slice that is safe for json and db read/write. - */ - interface JSONArray extends Array{} - interface JSONArray { + dir: string /** - * MarshalJSON implements the [json.Marshaler] interface. + * Stdin specifies the process's standard input. + * + * If Stdin is nil, the process reads from the null device (os.DevNull). + * + * If Stdin is an *os.File, the process's standard input is connected + * directly to that file. + * + * Otherwise, during the execution of the command a separate + * goroutine reads from Stdin and delivers that data to the command + * over a pipe. In this case, Wait does not complete until the goroutine + * stops copying, either because it has reached the end of Stdin + * (EOF or a read error), or because writing to the pipe returned an error, + * or because a nonzero WaitDelay was set and expired. + * + * Regardless of WaitDelay, Wait can block until a Read from + * Stdin completes. If you need to use a blocking io.Reader, + * use the StdinPipe method to get a pipe, copy from the Reader + * to the pipe, and arrange to close the Reader after Wait returns. */ - marshalJSON(): string|Array - } - interface JSONArray { + stdin: io.Reader /** - * String returns the string representation of the current json array. + * Stdout and Stderr specify the process's standard output and error. + * + * If either is nil, Run connects the corresponding file descriptor + * to the null device (os.DevNull). + * + * If either is an *os.File, the corresponding output from the process + * is connected directly to that file. + * + * Otherwise, during the execution of the command a separate goroutine + * reads from the process over a pipe and delivers that data to the + * corresponding Writer. In this case, Wait does not complete until the + * goroutine reaches EOF or encounters an error or a nonzero WaitDelay + * expires. + * + * Regardless of WaitDelay, Wait can block until a Write to + * Stdout or Stderr completes. If you need to use a blocking io.Writer, + * use the StdoutPipe or StderrPipe method to get a pipe, + * copy from the pipe to the Writer, and arrange to close the + * Writer after Wait returns. + * + * If Stdout and Stderr are the same writer, and have a type that can + * be compared with ==, at most one goroutine at a time will call Write. */ - string(): string - } - interface JSONArray { + stdout: io.Writer + stderr: io.Writer /** - * Value implements the [driver.Valuer] interface. + * ExtraFiles specifies additional open files to be inherited by the + * new process. It does not include standard input, standard output, or + * standard error. If non-nil, entry i becomes file descriptor 3+i. + * + * ExtraFiles is not supported on Windows. */ - value(): any - } - interface JSONArray { + extraFiles: Array<(os.File | undefined)> /** - * Scan implements [sql.Scanner] interface to scan the provided value - * into the current JSONArray[T] instance. + * SysProcAttr holds optional, operating system-specific attributes. + * Run passes it to os.StartProcess as the os.ProcAttr's Sys field. */ - scan(value: any): void - } - /** - * JSONMap defines a map that is safe for json and db read/write. - */ - interface JSONMap extends _TygojaDict{} - interface JSONMap { + sysProcAttr?: syscall.SysProcAttr /** - * MarshalJSON implements the [json.Marshaler] interface. + * Process is the underlying process, once started. */ - marshalJSON(): string|Array - } - interface JSONMap { + process?: os.Process /** - * String returns the string representation of the current json map. + * ProcessState contains information about an exited process. + * If the process was started successfully, Wait or Run will + * populate its ProcessState when the command completes. */ - string(): string - } - interface JSONMap { + processState?: os.ProcessState + err: Error // LookPath error, if any. /** - * Get retrieves a single value from the current JSONMap[T]. + * If Cancel is non-nil, the command must have been created with + * CommandContext and Cancel will be called when the command's + * Context is done. By default, CommandContext sets Cancel to + * call the Kill method on the command's Process. * - * This helper was added primarily to assist the goja integration since custom map types - * don't have direct access to the map keys (https://pkg.go.dev/github.com/dop251/goja#hdr-Maps_with_methods). - */ - get(key: string): T - } - interface JSONMap { - /** - * Set sets a single value in the current JSONMap[T]. + * Typically a custom Cancel will send a signal to the command's + * Process, but it may instead take other actions to initiate cancellation, + * such as closing a stdin or stdout pipe or sending a shutdown request on a + * network socket. * - * This helper was added primarily to assist the goja integration since custom map types - * don't have direct access to the map keys (https://pkg.go.dev/github.com/dop251/goja#hdr-Maps_with_methods). - */ - set(key: string, value: T): void - } - interface JSONMap { - /** - * Value implements the [driver.Valuer] interface. + * If the command exits with a success status after Cancel is + * called, and Cancel does not return an error equivalent to + * os.ErrProcessDone, then Wait and similar methods will return a non-nil + * error: either an error wrapping the one returned by Cancel, + * or the error from the Context. + * (If the command exits with a non-success status, or Cancel + * returns an error that wraps os.ErrProcessDone, Wait and similar methods + * continue to return the command's usual exit status.) + * + * If Cancel is set to nil, nothing will happen immediately when the command's + * Context is done, but a nonzero WaitDelay will still take effect. That may + * be useful, for example, to work around deadlocks in commands that do not + * support shutdown signals but are expected to always finish quickly. + * + * Cancel will not be called if Start returns a non-nil error. */ - value(): any - } - interface JSONMap { + cancel: () => void /** - * Scan implements [sql.Scanner] interface to scan the provided value - * into the current JSONMap[T] instance. + * If WaitDelay is non-zero, it bounds the time spent waiting on two sources + * of unexpected delay in Wait: a child process that fails to exit after the + * associated Context is canceled, and a child process that exits but leaves + * its I/O pipes unclosed. + * + * The WaitDelay timer starts when either the associated Context is done or a + * call to Wait observes that the child process has exited, whichever occurs + * first. When the delay has elapsed, the command shuts down the child process + * and/or its I/O pipes. + * + * If the child process has failed to exit — perhaps because it ignored or + * failed to receive a shutdown signal from a Cancel function, or because no + * Cancel function was set — then it will be terminated using os.Process.Kill. + * + * Then, if the I/O pipes communicating with the child process are still open, + * those pipes are closed in order to unblock any goroutines currently blocked + * on Read or Write calls. + * + * If pipes are closed due to WaitDelay, no Cancel call has occurred, + * and the command has otherwise exited with a successful status, Wait and + * similar methods will return ErrWaitDelay instead of nil. + * + * If WaitDelay is zero (the default), I/O pipes will be read until EOF, + * which might not occur until orphaned subprocesses of the command have + * also closed their descriptors for the pipes. */ - scan(value: any): void + waitDelay: time.Duration } - /** - * JSONRaw defines a json value type that is safe for db read/write. - */ - interface JSONRaw extends Array{} - interface JSONRaw { + interface Cmd { /** - * String returns the current JSONRaw instance as a json encoded string. + * String returns a human-readable description of c. + * It is intended only for debugging. + * In particular, it is not suitable for use as input to a shell. + * The output of String may vary across Go releases. */ string(): string } - interface JSONRaw { - /** - * MarshalJSON implements the [json.Marshaler] interface. - */ - marshalJSON(): string|Array - } - interface JSONRaw { + interface Cmd { /** - * UnmarshalJSON implements the [json.Unmarshaler] interface. + * Run starts the specified command and waits for it to complete. + * + * The returned error is nil if the command runs, has no problems + * copying stdin, stdout, and stderr, and exits with a zero exit + * status. + * + * If the command starts but does not complete successfully, the error is of + * type [*ExitError]. Other error types may be returned for other situations. + * + * If the calling goroutine has locked the operating system thread + * with [runtime.LockOSThread] and modified any inheritable OS-level + * thread state (for example, Linux or Plan 9 name spaces), the new + * process will inherit the caller's thread state. */ - unmarshalJSON(b: string|Array): void + run(): void } - interface JSONRaw { + interface Cmd { /** - * Value implements the [driver.Valuer] interface. + * Start starts the specified command but does not wait for it to complete. + * + * If Start returns successfully, the c.Process field will be set. + * + * After a successful call to Start the [Cmd.Wait] method must be called in + * order to release associated system resources. */ - value(): any + start(): void } - interface JSONRaw { + interface Cmd { /** - * Scan implements [sql.Scanner] interface to scan the provided value - * into the current JSONRaw instance. - */ - scan(value: any): void - } -} - -namespace auth { - /** - * @todo refactor and consider replace with a plain struct - * - * Provider defines a common interface for an OAuth2 client. - */ - interface Provider { - [key:string]: any; - /** - * @todo temp backport + * Wait waits for the command to exit and waits for any copying to + * stdin or copying from stdout or stderr to complete. * - * Order returns the sorting order of the provider usually used in the auth methods list response. - */ - logo(): string - /** - * @todo temp backport + * The command must have been started by [Cmd.Start]. * - * Order returns the sorting order of the provider usually used in the auth methods list response. - */ - order(): number - /** - * Context returns the context associated with the provider (if any). - */ - context(): context.Context - /** - * SetContext assigns the specified context to the current provider. - */ - setContext(ctx: context.Context): void - /** - * PKCE indicates whether the provider can use the PKCE flow. - */ - pkce(): boolean - /** - * SetPKCE toggles the state whether the provider can use the PKCE flow or not. - */ - setPKCE(enable: boolean): void - /** - * DisplayName usually returns provider name as it is officially written - * and it could be used directly in the UI. - */ - displayName(): string - /** - * SetDisplayName sets the provider's display name. - */ - setDisplayName(displayName: string): void - /** - * Scopes returns the provider access permissions that will be requested. - */ - scopes(): Array - /** - * SetScopes sets the provider access permissions that will be requested later. - */ - setScopes(scopes: Array): void - /** - * ClientId returns the provider client's app ID. - */ - clientId(): string - /** - * SetClientId sets the provider client's ID. - */ - setClientId(clientId: string): void - /** - * ClientSecret returns the provider client's app secret. - */ - clientSecret(): string - /** - * SetClientSecret sets the provider client's app secret. - */ - setClientSecret(secret: string): void - /** - * RedirectURL returns the end address to redirect the user - * going through the OAuth flow. - */ - redirectURL(): string - /** - * SetRedirectURL sets the provider's RedirectURL. - */ - setRedirectURL(url: string): void - /** - * AuthURL returns the provider's authorization service url. - */ - authURL(): string - /** - * SetAuthURL sets the provider's AuthURL. - */ - setAuthURL(url: string): void - /** - * TokenURL returns the provider's token exchange service url. - */ - tokenURL(): string - /** - * SetTokenURL sets the provider's TokenURL. - */ - setTokenURL(url: string): void - /** - * UserInfoURL returns the provider's user info api url. - */ - userInfoURL(): string - /** - * SetUserInfoURL sets the provider's UserInfoURL. - */ - setUserInfoURL(url: string): void - /** - * Extra returns a shallow copy of any custom config data - * that the provider may need. + * The returned error is nil if the command runs, has no problems + * copying stdin, stdout, and stderr, and exits with a zero exit + * status. + * + * If the command fails to run or doesn't complete successfully, the + * error is of type [*ExitError]. Other error types may be + * returned for I/O problems. + * + * If any of c.Stdin, c.Stdout or c.Stderr are not an [*os.File], Wait also waits + * for the respective I/O loop copying to or from the process to complete. + * + * Wait must not be called concurrently from multiple goroutines. + * A custom Cmd.Cancel function should not call Wait. + * + * Wait releases any resources associated with the [Cmd]. */ - extra(): _TygojaDict + wait(): void + } + interface Cmd { /** - * SetExtra updates the provider's custom config data. + * Output runs the command and returns its standard output. + * Any returned error will usually be of type [*ExitError]. + * If c.Stderr was nil and the returned error is of type + * [*ExitError], Output populates the Stderr field of the + * returned error. */ - setExtra(data: _TygojaDict): void + output(): string|Array + } + interface Cmd { /** - * Client returns an http client using the provided token. + * CombinedOutput runs the command and returns its combined standard + * output and standard error. */ - client(token: oauth2.Token): (any) + combinedOutput(): string|Array + } + interface Cmd { /** - * BuildAuthURL returns a URL to the provider's consent page - * that asks for permissions for the required scopes explicitly. + * StdinPipe returns a pipe that will be connected to the command's + * standard input when the command starts. + * The pipe will be closed automatically after [Cmd.Wait] sees the command exit. + * A caller need only call Close to force the pipe to close sooner. + * For example, if the command being run will not exit until standard input + * is closed, the caller must close the pipe. */ - buildAuthURL(state: string, ...opts: oauth2.AuthCodeOption[]): string + stdinPipe(): io.WriteCloser + } + interface Cmd { /** - * FetchToken converts an authorization code to token. + * StdoutPipe returns a pipe that will be connected to the command's + * standard output when the command starts. + * + * [Cmd.Wait] will close the pipe after seeing the command exit, so most callers + * need not close the pipe themselves. It is thus incorrect to call Wait + * before all reads from the pipe have completed. + * For the same reason, it is incorrect to call [Cmd.Run] when using StdoutPipe. + * See the example for idiomatic usage. */ - fetchToken(code: string, ...opts: oauth2.AuthCodeOption[]): (oauth2.Token) + stdoutPipe(): io.ReadCloser + } + interface Cmd { /** - * FetchRawUserInfo requests and marshalizes into `result` the - * the OAuth user api response. + * StderrPipe returns a pipe that will be connected to the command's + * standard error when the command starts. + * + * [Cmd.Wait] will close the pipe after seeing the command exit, so most callers + * need not close the pipe themselves. It is thus incorrect to call Wait + * before all reads from the pipe have completed. + * For the same reason, it is incorrect to use [Cmd.Run] when using StderrPipe. + * See the StdoutPipe example for idiomatic usage. */ - fetchRawUserInfo(token: oauth2.Token): string|Array + stderrPipe(): io.ReadCloser + } + interface Cmd { /** - * FetchAuthUser is similar to FetchRawUserInfo, but normalizes and - * marshalizes the user api response into a standardized AuthUser struct. + * Environ returns a copy of the environment in which the command would be run + * as it is currently configured. */ - fetchAuthUser(token: oauth2.Token): (AuthUser) + environ(): Array } +} + +namespace mailer { /** - * AuthUser defines a standardized OAuth2 user data structure. + * Message defines a generic email message struct. */ - interface AuthUser { - expiry: types.DateTime - rawUser: _TygojaDict - id: string - name: string - username: string - avatarURL: string - accessToken: string - refreshToken: string - /** - * The VERIFIED OAuth2 account email. - * - * It must be empty if the provider is not able to verify the email ownership. - */ - email: string - /** - * @todo - * deprecated: use AvatarURL instead - * AvatarUrl will be removed after dropping v0.22 support - */ - avatarUrl: string + interface Message { + from: { address: string; name?: string; } + to: Array<{ address: string; name?: string; }> + bcc: Array<{ address: string; name?: string; }> + cc: Array<{ address: string; name?: string; }> + subject: string + html: string + text: string + headers: _TygojaDict + attachments: _TygojaDict + inlineAttachments: _TygojaDict } - interface AuthUser { + /** + * Mailer defines a base mail client interface. + */ + interface Mailer { + [key:string]: any; /** - * MarshalJSON implements the [json.Marshaler] interface. - * - * @todo remove after dropping v0.22 support + * Send sends an email with the provided Message. */ - marshalJSON(): string|Array + send(message: Message): void } } @@ -20086,378 +19542,137 @@ namespace sql { } } -namespace exec { +/** + * Package blob defines a lightweight abstration for interacting with + * various storage services (local filesystem, S3, etc.). + * + * NB! + * For compatibility with earlier PocketBase versions and to prevent + * unnecessary breaking changes, this package is based and implemented + * as a minimal, stripped down version of the previously used gocloud.dev/blob. + * While there is no promise that it won't diverge in the future to accommodate + * better some PocketBase specific use cases, currently it copies and + * tries to follow as close as possible the same implementations, + * conventions and rules for the key escaping/unescaping, blob read/write + * interfaces and struct options as gocloud.dev/blob, therefore the + * credits goes to the original Go Cloud Development Kit Authors. + */ +namespace blob { /** - * Cmd represents an external command being prepared or run. - * - * A Cmd cannot be reused after calling its [Cmd.Run], [Cmd.Output] or [Cmd.CombinedOutput] - * methods. + * ListObject represents a single blob returned from List. */ - interface Cmd { + interface ListObject { /** - * Path is the path of the command to run. - * - * This is the only field that must be set to a non-zero - * value. If Path is relative, it is evaluated relative - * to Dir. + * Key is the key for this blob. */ - path: string + key: string /** - * Args holds command line arguments, including the command as Args[0]. - * If the Args field is empty or nil, Run uses {Path}. - * - * In typical use, both Path and Args are set by calling Command. + * ModTime is the time the blob was last modified. */ - args: Array + modTime: time.Time /** - * Env specifies the environment of the process. - * Each entry is of the form "key=value". - * If Env is nil, the new process uses the current process's - * environment. - * If Env contains duplicate environment keys, only the last - * value in the slice for each duplicate key is used. - * As a special case on Windows, SYSTEMROOT is always added if - * missing and not explicitly set to the empty string. - * - * See also the Dir field, which may set PWD in the environment. + * Size is the size of the blob's content in bytes. */ - env: Array + size: number /** - * Dir specifies the working directory of the command. - * If Dir is the empty string, Run runs the command in the - * calling process's current directory. - * - * On Unix systems, the value of Dir also determines the - * child process's PWD environment variable if not otherwise - * specified. A Unix process represents its working directory - * not by name but as an implicit reference to a node in the - * file tree. So, if the child process obtains its working - * directory by calling a function such as C's getcwd, which - * computes the canonical name by walking up the file tree, it - * will not recover the original value of Dir if that value - * was an alias involving symbolic links. However, if the - * child process calls Go's [os.Getwd] or GNU C's - * get_current_dir_name, and the value of PWD is an alias for - * the current directory, those functions will return the - * value of PWD, which matches the value of Dir. + * MD5 is an MD5 hash of the blob contents or nil if not available. */ - dir: string + md5: string|Array /** - * Stdin specifies the process's standard input. - * - * If Stdin is nil, the process reads from the null device (os.DevNull). - * - * If Stdin is an *os.File, the process's standard input is connected - * directly to that file. - * - * Otherwise, during the execution of the command a separate - * goroutine reads from Stdin and delivers that data to the command - * over a pipe. In this case, Wait does not complete until the goroutine - * stops copying, either because it has reached the end of Stdin - * (EOF or a read error), or because writing to the pipe returned an error, - * or because a nonzero WaitDelay was set and expired. + * IsDir indicates that this result represents a "directory" in the + * hierarchical namespace, ending in ListOptions.Delimiter. Key can be + * passed as ListOptions.Prefix to list items in the "directory". + * Fields other than Key and IsDir will not be set if IsDir is true. */ - stdin: io.Reader + isDir: boolean + } + /** + * Attributes contains attributes about a blob. + */ + interface Attributes { /** - * Stdout and Stderr specify the process's standard output and error. - * - * If either is nil, Run connects the corresponding file descriptor - * to the null device (os.DevNull). - * - * If either is an *os.File, the corresponding output from the process - * is connected directly to that file. - * - * Otherwise, during the execution of the command a separate goroutine - * reads from the process over a pipe and delivers that data to the - * corresponding Writer. In this case, Wait does not complete until the - * goroutine reaches EOF or encounters an error or a nonzero WaitDelay - * expires. - * - * If Stdout and Stderr are the same writer, and have a type that can - * be compared with ==, at most one goroutine at a time will call Write. + * CacheControl specifies caching attributes that services may use + * when serving the blob. + * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control */ - stdout: io.Writer - stderr: io.Writer + cacheControl: string /** - * ExtraFiles specifies additional open files to be inherited by the - * new process. It does not include standard input, standard output, or - * standard error. If non-nil, entry i becomes file descriptor 3+i. - * - * ExtraFiles is not supported on Windows. + * ContentDisposition specifies whether the blob content is expected to be + * displayed inline or as an attachment. + * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition */ - extraFiles: Array<(os.File | undefined)> + contentDisposition: string /** - * SysProcAttr holds optional, operating system-specific attributes. - * Run passes it to os.StartProcess as the os.ProcAttr's Sys field. + * ContentEncoding specifies the encoding used for the blob's content, if any. + * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding */ - sysProcAttr?: syscall.SysProcAttr + contentEncoding: string /** - * Process is the underlying process, once started. + * ContentLanguage specifies the language used in the blob's content, if any. + * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Language */ - process?: os.Process + contentLanguage: string /** - * ProcessState contains information about an exited process. - * If the process was started successfully, Wait or Run will - * populate its ProcessState when the command completes. + * ContentType is the MIME type of the blob. It will not be empty. + * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type */ - processState?: os.ProcessState - err: Error // LookPath error, if any. + contentType: string /** - * If Cancel is non-nil, the command must have been created with - * CommandContext and Cancel will be called when the command's - * Context is done. By default, CommandContext sets Cancel to - * call the Kill method on the command's Process. - * - * Typically a custom Cancel will send a signal to the command's - * Process, but it may instead take other actions to initiate cancellation, - * such as closing a stdin or stdout pipe or sending a shutdown request on a - * network socket. - * - * If the command exits with a success status after Cancel is - * called, and Cancel does not return an error equivalent to - * os.ErrProcessDone, then Wait and similar methods will return a non-nil - * error: either an error wrapping the one returned by Cancel, - * or the error from the Context. - * (If the command exits with a non-success status, or Cancel - * returns an error that wraps os.ErrProcessDone, Wait and similar methods - * continue to return the command's usual exit status.) - * - * If Cancel is set to nil, nothing will happen immediately when the command's - * Context is done, but a nonzero WaitDelay will still take effect. That may - * be useful, for example, to work around deadlocks in commands that do not - * support shutdown signals but are expected to always finish quickly. - * - * Cancel will not be called if Start returns a non-nil error. + * Metadata holds key/value pairs associated with the blob. + * Keys are guaranteed to be in lowercase, even if the backend service + * has case-sensitive keys (although note that Metadata written via + * this package will always be lowercased). If there are duplicate + * case-insensitive keys (e.g., "foo" and "FOO"), only one value + * will be kept, and it is undefined which one. */ - cancel: () => void + metadata: _TygojaDict /** - * If WaitDelay is non-zero, it bounds the time spent waiting on two sources - * of unexpected delay in Wait: a child process that fails to exit after the - * associated Context is canceled, and a child process that exits but leaves - * its I/O pipes unclosed. - * - * The WaitDelay timer starts when either the associated Context is done or a - * call to Wait observes that the child process has exited, whichever occurs - * first. When the delay has elapsed, the command shuts down the child process - * and/or its I/O pipes. - * - * If the child process has failed to exit — perhaps because it ignored or - * failed to receive a shutdown signal from a Cancel function, or because no - * Cancel function was set — then it will be terminated using os.Process.Kill. - * - * Then, if the I/O pipes communicating with the child process are still open, - * those pipes are closed in order to unblock any goroutines currently blocked - * on Read or Write calls. - * - * If pipes are closed due to WaitDelay, no Cancel call has occurred, - * and the command has otherwise exited with a successful status, Wait and - * similar methods will return ErrWaitDelay instead of nil. - * - * If WaitDelay is zero (the default), I/O pipes will be read until EOF, - * which might not occur until orphaned subprocesses of the command have - * also closed their descriptors for the pipes. + * CreateTime is the time the blob was created, if available. If not available, + * CreateTime will be the zero time. */ - waitDelay: time.Duration - } - interface Cmd { + createTime: time.Time /** - * String returns a human-readable description of c. - * It is intended only for debugging. - * In particular, it is not suitable for use as input to a shell. - * The output of String may vary across Go releases. + * ModTime is the time the blob was last modified. */ - string(): string - } - interface Cmd { + modTime: time.Time /** - * Run starts the specified command and waits for it to complete. - * - * The returned error is nil if the command runs, has no problems - * copying stdin, stdout, and stderr, and exits with a zero exit - * status. - * - * If the command starts but does not complete successfully, the error is of - * type [*ExitError]. Other error types may be returned for other situations. - * - * If the calling goroutine has locked the operating system thread - * with [runtime.LockOSThread] and modified any inheritable OS-level - * thread state (for example, Linux or Plan 9 name spaces), the new - * process will inherit the caller's thread state. + * Size is the size of the blob's content in bytes. */ - run(): void - } - interface Cmd { + size: number /** - * Start starts the specified command but does not wait for it to complete. - * - * If Start returns successfully, the c.Process field will be set. - * - * After a successful call to Start the [Cmd.Wait] method must be called in - * order to release associated system resources. + * MD5 is an MD5 hash of the blob contents or nil if not available. */ - start(): void + md5: string|Array + /** + * ETag for the blob; see https://en.wikipedia.org/wiki/HTTP_ETag. + */ + eTag: string } - interface Cmd { + /** + * WriterOptions sets options for NewWriter. + */ + interface WriterOptions { /** - * Wait waits for the command to exit and waits for any copying to - * stdin or copying from stdout or stderr to complete. + * BufferSize changes the default size in bytes of the chunks that + * Writer will upload in a single request; larger blobs will be split into + * multiple requests. * - * The command must have been started by [Cmd.Start]. + * This option may be ignored by some drivers. * - * The returned error is nil if the command runs, has no problems - * copying stdin, stdout, and stderr, and exits with a zero exit - * status. + * If 0, the driver will choose a reasonable default. * - * If the command fails to run or doesn't complete successfully, the - * error is of type [*ExitError]. Other error types may be - * returned for I/O problems. + * If the Writer is used to do many small writes concurrently, using a + * smaller BufferSize may reduce memory usage. + */ + bufferSize: number + /** + * MaxConcurrency changes the default concurrency for parts of an upload. * - * If any of c.Stdin, c.Stdout or c.Stderr are not an [*os.File], Wait also waits - * for the respective I/O loop copying to or from the process to complete. + * This option may be ignored by some drivers. * - * Wait releases any resources associated with the [Cmd]. + * If 0, the driver will choose a reasonable default. */ - wait(): void - } - interface Cmd { - /** - * Output runs the command and returns its standard output. - * Any returned error will usually be of type [*ExitError]. - * If c.Stderr was nil and the returned error is of type - * [*ExitError], Output populates the Stderr field of the - * returned error. - */ - output(): string|Array - } - interface Cmd { - /** - * CombinedOutput runs the command and returns its combined standard - * output and standard error. - */ - combinedOutput(): string|Array - } - interface Cmd { - /** - * StdinPipe returns a pipe that will be connected to the command's - * standard input when the command starts. - * The pipe will be closed automatically after [Cmd.Wait] sees the command exit. - * A caller need only call Close to force the pipe to close sooner. - * For example, if the command being run will not exit until standard input - * is closed, the caller must close the pipe. - */ - stdinPipe(): io.WriteCloser - } - interface Cmd { - /** - * StdoutPipe returns a pipe that will be connected to the command's - * standard output when the command starts. - * - * [Cmd.Wait] will close the pipe after seeing the command exit, so most callers - * need not close the pipe themselves. It is thus incorrect to call Wait - * before all reads from the pipe have completed. - * For the same reason, it is incorrect to call [Cmd.Run] when using StdoutPipe. - * See the example for idiomatic usage. - */ - stdoutPipe(): io.ReadCloser - } - interface Cmd { - /** - * StderrPipe returns a pipe that will be connected to the command's - * standard error when the command starts. - * - * [Cmd.Wait] will close the pipe after seeing the command exit, so most callers - * need not close the pipe themselves. It is thus incorrect to call Wait - * before all reads from the pipe have completed. - * For the same reason, it is incorrect to use [Cmd.Run] when using StderrPipe. - * See the StdoutPipe example for idiomatic usage. - */ - stderrPipe(): io.ReadCloser - } - interface Cmd { - /** - * Environ returns a copy of the environment in which the command would be run - * as it is currently configured. - */ - environ(): Array - } -} - -namespace mailer { - /** - * Message defines a generic email message struct. - */ - interface Message { - from: { address: string; name?: string; } - to: Array<{ address: string; name?: string; }> - bcc: Array<{ address: string; name?: string; }> - cc: Array<{ address: string; name?: string; }> - subject: string - html: string - text: string - headers: _TygojaDict - attachments: _TygojaDict - inlineAttachments: _TygojaDict - } - /** - * Mailer defines a base mail client interface. - */ - interface Mailer { - [key:string]: any; - /** - * Send sends an email with the provided Message. - */ - send(message: Message): void - } -} - -/** - * Package blob defines a lightweight abstration for interacting with - * various storage services (local filesystem, S3, etc.). - * - * NB! - * For compatibility with earlier PocketBase versions and to prevent - * unnecessary breaking changes, this package is based and implemented - * as a minimal, stripped down version of the previously used gocloud.dev/blob. - * While there is no promise that it won't diverge in the future to accommodate - * better some PocketBase specific use cases, currently it copies and - * tries to follow as close as possible the same implementations, - * conventions and rules for the key escaping/unescaping, blob read/write - * interfaces and struct options as gocloud.dev/blob, therefore the - * credits goes to the original Go Cloud Development Kit Authors. - */ -namespace blob { - /** - * ListObject represents a single blob returned from List. - */ - interface ListObject { - /** - * Key is the key for this blob. - */ - key: string - /** - * ModTime is the time the blob was last modified. - */ - modTime: time.Time - /** - * Size is the size of the blob's content in bytes. - */ - size: number - /** - * MD5 is an MD5 hash of the blob contents or nil if not available. - */ - md5: string|Array - /** - * IsDir indicates that this result represents a "directory" in the - * hierarchical namespace, ending in ListOptions.Delimiter. Key can be - * passed as ListOptions.Prefix to list items in the "directory". - * Fields other than Key and IsDir will not be set if IsDir is true. - */ - isDir: boolean - } - /** - * Attributes contains attributes about a blob. - */ - interface Attributes { + maxConcurrency: number /** * CacheControl specifies caching attributes that services may use * when serving the blob. @@ -20481,40 +19696,36 @@ namespace blob { */ contentLanguage: string /** - * ContentType is the MIME type of the blob. It will not be empty. + * ContentType specifies the MIME type of the blob being written. If not set, + * it will be inferred from the content using the algorithm described at + * http://mimesniff.spec.whatwg.org/. + * Set DisableContentTypeDetection to true to disable the above and force + * the ContentType to stay empty. * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type */ contentType: string /** - * Metadata holds key/value pairs associated with the blob. - * Keys are guaranteed to be in lowercase, even if the backend service - * has case-sensitive keys (although note that Metadata written via - * this package will always be lowercased). If there are duplicate - * case-insensitive keys (e.g., "foo" and "FOO"), only one value - * will be kept, and it is undefined which one. - */ - metadata: _TygojaDict - /** - * CreateTime is the time the blob was created, if available. If not available, - * CreateTime will be the zero time. - */ - createTime: time.Time - /** - * ModTime is the time the blob was last modified. - */ - modTime: time.Time - /** - * Size is the size of the blob's content in bytes. + * When true, if ContentType is the empty string, it will stay the empty + * string rather than being inferred from the content. + * Note that while the blob will be written with an empty string ContentType, + * most providers will fill one in during reads, so don't expect an empty + * ContentType if you read the blob back. */ - size: number + disableContentTypeDetection: boolean /** - * MD5 is an MD5 hash of the blob contents or nil if not available. + * ContentMD5 is used as a message integrity check. + * If len(ContentMD5) > 0, the MD5 hash of the bytes written must match + * ContentMD5, or Close will return an error without completing the write. + * https://tools.ietf.org/html/rfc1864 */ - md5: string|Array + contentMD5: string|Array /** - * ETag for the blob; see https://en.wikipedia.org/wiki/HTTP_ETag. + * Metadata holds key/value strings to be associated with the blob, or nil. + * Keys may not be empty, and are lowercased before being written. + * Duplicate case-insensitive keys (e.g., "foo" and "FOO") will result in + * an error. */ - eTag: string + metadata: _TygojaDict } /** * Reader reads bytes from a blob. @@ -20568,334 +19779,1280 @@ namespace blob { */ writeTo(w: io.Writer): number } -} - -namespace search { /** - * Result defines the returned search result structure. + * Writer writes bytes to a blob. + * + * It implements io.WriteCloser (https://golang.org/pkg/io/#Closer), and must be + * closed after all writes are done. */ - interface Result { - items: any - page: number - perPage: number - totalItems: number - totalPages: number + interface Writer { + } + interface Writer { + /** + * Write implements the io.Writer interface (https://golang.org/pkg/io/#Writer). + * + * Writes may happen asynchronously, so the returned error can be nil + * even if the actual write eventually fails. The write is only guaranteed to + * have succeeded if Close returns no error. + */ + write(p: string|Array): number + } + interface Writer { + /** + * Close closes the blob writer. The write operation is not guaranteed + * to have succeeded until Close returns with no error. + * + * Close may return an error if the context provided to create the + * Writer is canceled or reaches its deadline. + */ + close(): void } + interface Writer { + /** + * ReadFrom reads from r and writes to w until EOF or error. + * The return value is the number of bytes read from r. + * + * It implements the io.ReaderFrom interface. + */ + readFrom(r: io.Reader): number + } +} + +/** + * Package types implements some commonly used db serializable types + * like datetime, json, etc. + */ +namespace types { /** - * ResolverResult defines a single FieldResolver.Resolve() successfully parsed result. + * DateTime represents a [time.Time] instance in UTC that is wrapped + * and serialized using the app default date layout. */ - interface ResolverResult { + interface DateTime { + } + interface DateTime { /** - * Identifier is the plain SQL identifier/column that will be used - * in the final db expression as left or right operand. + * Time returns the internal [time.Time] instance. */ - identifier: string + time(): time.Time + } + interface DateTime { /** - * NullFallback specify the preference for how NULL or empty values - * should be resolved (default to "auto"). + * Add returns a new DateTime based on the current DateTime + the specified duration. + */ + add(duration: time.Duration): DateTime + } + interface DateTime { + /** + * Sub returns a [time.Duration] by subtracting the specified DateTime from the current one. * - * Set to NullFallbackDisabled to prevent any COALESCE or NULL fallbacks. - * Set to NullFallbackEnforced to prefer COALESCE or NULL fallbacks when needed. + * If the result exceeds the maximum (or minimum) value that can be stored in a [time.Duration], + * the maximum (or minimum) duration will be returned. */ - nullFallback: NullFallbackPreference + sub(u: DateTime): time.Duration + } + interface DateTime { /** - * Params is a map with db placeholder->value pairs that will be added - * to the query when building both resolved operands/sides in a single expression. + * AddDate returns a new DateTime based on the current one + duration. + * + * It follows the same rules as [time.AddDate]. */ - params: dbx.Params + addDate(years: number, months: number, days: number): DateTime + } + interface DateTime { /** - * MultiMatchSubQuery is an optional sub query expression that will be added - * in addition to the combined ResolverResult expression during build. + * After reports whether the current DateTime instance is after u. */ - multiMatchSubQuery?: MultiMatchSubquery + after(u: DateTime): boolean + } + interface DateTime { /** - * AfterBuild is an optional function that will be called after building - * and combining the result of both resolved operands/sides in a single expression. + * Before reports whether the current DateTime instance is before u. */ - afterBuild: (expr: dbx.Expression) => dbx.Expression + before(u: DateTime): boolean } -} - -namespace router { + interface DateTime { + /** + * Compare compares the current DateTime instance with u. + * If the current instance is before u, it returns -1. + * If the current instance is after u, it returns +1. + * If they're the same, it returns 0. + */ + compare(u: DateTime): number + } + interface DateTime { + /** + * Equal reports whether the current DateTime and u represent the same time instant. + * Two DateTime can be equal even if they are in different locations. + * For example, 6:00 +0200 and 4:00 UTC are Equal. + */ + equal(u: DateTime): boolean + } + interface DateTime { + /** + * Unix returns the current DateTime as a Unix time, aka. + * the number of seconds elapsed since January 1, 1970 UTC. + */ + unix(): number + } + interface DateTime { + /** + * IsZero checks whether the current DateTime instance has zero time value. + */ + isZero(): boolean + } + interface DateTime { + /** + * String serializes the current DateTime instance into a formatted + * UTC date string. + * + * The zero value is serialized to an empty string. + */ + string(): string + } + interface DateTime { + /** + * MarshalJSON implements the [json.Marshaler] interface. + */ + marshalJSON(): string|Array + } + interface DateTime { + /** + * UnmarshalJSON implements the [json.Unmarshaler] interface. + */ + unmarshalJSON(b: string|Array): void + } + interface DateTime { + /** + * Value implements the [driver.Valuer] interface. + */ + value(): any + } + interface DateTime { + /** + * Scan implements [sql.Scanner] interface to scan the provided value + * into the current DateTime instance. + */ + scan(value: any): void + } + /** + * GeoPoint defines a struct for storing geo coordinates as serialized json object + * (e.g. {lon:0,lat:0}). + * + * Note: using object notation and not a plain array to avoid the confusion + * as there doesn't seem to be a fixed standard for the coordinates order. + */ + interface GeoPoint { + lon: number + lat: number + } + interface GeoPoint { + /** + * String returns the string representation of the current GeoPoint instance. + */ + string(): string + } + interface GeoPoint { + /** + * AsMap implements [core.mapExtractor] and returns a value suitable + * to be used in an API rule expression. + */ + asMap(): _TygojaDict + } + interface GeoPoint { + /** + * Value implements the [driver.Valuer] interface. + */ + value(): any + } + interface GeoPoint { + /** + * Scan implements [sql.Scanner] interface to scan the provided value + * into the current GeoPoint instance. + * + * The value argument could be nil (no-op), another GeoPoint instance, + * map or serialized json object with lat-lon props. + */ + scan(value: any): void + } + /** + * JSONArray defines a slice that is safe for json and db read/write. + */ + interface JSONArray extends Array{} + interface JSONArray { + /** + * MarshalJSON implements the [json.Marshaler] interface. + */ + marshalJSON(): string|Array + } + interface JSONArray { + /** + * String returns the string representation of the current json array. + */ + string(): string + } + interface JSONArray { + /** + * Value implements the [driver.Valuer] interface. + */ + value(): any + } + interface JSONArray { + /** + * Scan implements [sql.Scanner] interface to scan the provided value + * into the current JSONArray[T] instance. + */ + scan(value: any): void + } + /** + * JSONMap defines a map that is safe for json and db read/write. + */ + interface JSONMap extends _TygojaDict{} + interface JSONMap { + /** + * Get retrieves a single value from the current JSONMap[T]. + * + * This helper was added primarily to assist the goja integration since custom map types + * don't have direct access to the map keys (https://pkg.go.dev/github.com/dop251/goja#hdr-Maps_with_methods). + */ + get(key: string): T + } + interface JSONMap { + /** + * Set sets a single value in the current JSONMap[T]. + * + * This helper was added primarily to assist the goja integration since custom map types + * don't have direct access to the map keys (https://pkg.go.dev/github.com/dop251/goja#hdr-Maps_with_methods). + */ + set(key: string, value: T): void + } + interface JSONMap { + /** + * MarshalJSON implements the [json.Marshaler] interface. + */ + marshalJSON(): string|Array + } + interface JSONMap { + /** + * String returns the string representation of the current json map. + */ + string(): string + } + interface JSONMap { + /** + * Value implements the [driver.Valuer] interface. + */ + value(): any + } + interface JSONMap { + /** + * Scan implements [sql.Scanner] interface to scan the provided value + * into the current JSONMap[T] instance. + */ + scan(value: any): void + } + /** + * JSONRaw defines a json value type that is safe for db read/write. + */ + interface JSONRaw extends Array{} + interface JSONRaw { + /** + * String returns the current JSONRaw instance as a json encoded string. + */ + string(): string + } + interface JSONRaw { + /** + * MarshalJSON implements the [json.Marshaler] interface. + */ + marshalJSON(): string|Array + } + interface JSONRaw { + /** + * UnmarshalJSON implements the [json.Unmarshaler] interface. + */ + unmarshalJSON(b: string|Array): void + } + interface JSONRaw { + /** + * Value implements the [driver.Valuer] interface. + */ + value(): any + } + interface JSONRaw { + /** + * Scan implements [sql.Scanner] interface to scan the provided value + * into the current JSONRaw instance. + */ + scan(value: any): void + } +} + +namespace search { + /** + * Result defines the returned search result structure. + */ + interface Result { + items: any + page: number + perPage: number + totalItems: number + totalPages: number + } + /** + * ResolverResult defines a single FieldResolver.Resolve() successfully parsed result. + */ + interface ResolverResult { + /** + * Identifier is the plain SQL identifier/column that will be used + * in the final db expression as left or right operand. + */ + identifier: string + /** + * NullFallback specify the preference for how NULL or empty values + * should be resolved (default to "auto"). + * + * Set to NullFallbackDisabled to prevent any COALESCE or NULL fallbacks. + * Set to NullFallbackEnforced to prefer COALESCE or NULL fallbacks when needed. + */ + nullFallback: NullFallbackPreference + /** + * Params is a map with db placeholder->value pairs that will be added + * to the query when building both resolved operands/sides in a single expression. + */ + params: dbx.Params + /** + * MultiMatchSubQuery is an optional sub query expression that will be added + * in addition to the combined ResolverResult expression during build. + */ + multiMatchSubQuery?: MultiMatchSubquery + /** + * AfterBuild is an optional function that will be called after building + * and combining the result of both resolved operands/sides in a single expression. + */ + afterBuild: (expr: dbx.Expression) => dbx.Expression + } +} + +namespace router { + // @ts-ignore + import validation = ozzo_validation + /** + * ApiError defines the struct for a basic api error response. + */ + interface ApiError { + data: _TygojaDict + message: string + status: number + } + interface ApiError { + /** + * Error makes it compatible with the `error` interface. + */ + error(): string + } + interface ApiError { + /** + * RawData returns the unformatted error data (could be an internal error, text, etc.) + */ + rawData(): any + } + interface ApiError { + /** + * Is reports whether the current ApiError wraps the target. + */ + is(target: Error): boolean + } + /** + * Event specifies based Route handler event that is usually intended + * to be embedded as part of a custom event struct. + * + * NB! It is expected that the Response and Request fields are always set. + */ + type _sfsEYyF = hook.Event + interface Event extends _sfsEYyF { + response: http.ResponseWriter + request?: http.Request + } + interface Event { + /** + * Written reports whether the current response has already been written. + * + * This method always returns false if e.ResponseWritter doesn't implement the WriteTracker interface + * (all router package handlers receives a ResponseWritter that implements it unless explicitly replaced with a custom one). + */ + written(): boolean + } + interface Event { + /** + * Status reports the status code of the current response. + * + * This method always returns 0 if e.Response doesn't implement the StatusTracker interface + * (all router package handlers receives a ResponseWritter that implements it unless explicitly replaced with a custom one). + */ + status(): number + } + interface Event { + /** + * Flush flushes buffered data to the current response. + * + * Returns [http.ErrNotSupported] if e.Response doesn't implement the [http.Flusher] interface + * (all router package handlers receives a ResponseWritter that implements it unless explicitly replaced with a custom one). + */ + flush(): void + } + interface Event { + /** + * IsTLS reports whether the connection on which the request was received is TLS. + */ + isTLS(): boolean + } + interface Event { + /** + * SetCookie is an alias for [http.SetCookie]. + * + * SetCookie adds a Set-Cookie header to the current response's headers. + * The provided cookie must have a valid Name. + * Invalid cookies may be silently dropped. + */ + setCookie(cookie: http.Cookie): void + } + interface Event { + /** + * RemoteIP returns the IP address of the client that sent the request. + * + * IPv6 addresses are returned expanded. + * For example, "2001:db8::1" becomes "2001:0db8:0000:0000:0000:0000:0000:0001". + * + * Note that if you are behind reverse proxy(ies), this method returns + * the IP of the last connecting proxy. + */ + remoteIP(): string + } + interface Event { + /** + * FindUploadedFiles extracts all form files of "key" from a http request + * and returns a slice with filesystem.File instances (if any). + */ + findUploadedFiles(key: string): Array<(filesystem.File | undefined)> + } + interface Event { + /** + * Get retrieves single value from the current event data store. + */ + get(key: string): any + } + interface Event { + /** + * GetAll returns a copy of the current event data store. + */ + getAll(): _TygojaDict + } + interface Event { + /** + * Set saves single value into the current event data store. + */ + set(key: string, value: any): void + } + interface Event { + /** + * SetAll saves all items from m into the current event data store. + */ + setAll(m: _TygojaDict): void + } + interface Event { + /** + * String writes a plain string response. + */ + string(status: number, data: string): void + } + interface Event { + /** + * HTML writes an HTML response. + */ + html(status: number, data: string): void + } + interface Event { + /** + * JSON writes a JSON response. + * + * It also provides a generic response data fields picker if the "fields" query parameter is set. + * For example, if you are requesting `?fields=a,b` for `e.JSON(200, map[string]int{ "a":1, "b":2, "c":3 })`, + * it should result in a JSON response like: `{"a":1, "b": 2}`. + */ + json(status: number, data: any): void + } + interface Event { + /** + * XML writes an XML response. + * It automatically prepends the generic [xml.Header] string to the response. + */ + xml(status: number, data: any): void + } + interface Event { + /** + * Stream streams the specified reader into the response. + */ + stream(status: number, contentType: string, reader: io.Reader): void + } + interface Event { + /** + * Blob writes a blob (bytes slice) response. + */ + blob(status: number, contentType: string, b: string|Array): void + } + interface Event { + /** + * FileFS serves the specified filename from fsys. + * + * It is similar to [echo.FileFS] for consistency with earlier versions. + */ + fileFS(fsys: fs.FS, filename: string): void + } + interface Event { + /** + * NoContent writes a response with no body (ex. 204). + */ + noContent(status: number): void + } + interface Event { + /** + * Redirect writes a redirect response to the specified url. + * The status code must be in between 300 – 399 range. + */ + redirect(status: number, url: string): void + } + interface Event { + error(status: number, message: string, errData: any): (ApiError) + } + interface Event { + badRequestError(message: string, errData: any): (ApiError) + } + interface Event { + notFoundError(message: string, errData: any): (ApiError) + } + interface Event { + forbiddenError(message: string, errData: any): (ApiError) + } + interface Event { + unauthorizedError(message: string, errData: any): (ApiError) + } + interface Event { + tooManyRequestsError(message: string, errData: any): (ApiError) + } + interface Event { + internalServerError(message: string, errData: any): (ApiError) + } + interface Event { + /** + * BindBody unmarshal the request body into the provided dst. + * + * dst must be either a struct pointer or map[string]any. + * + * The rules how the body will be scanned depends on the request Content-Type. + * + * Currently the following Content-Types are supported: + * ``` + * - application/json + * - text/xml, application/xml + * - multipart/form-data, application/x-www-form-urlencoded + * ``` + * + * Respectively the following struct tags are supported (again, which one will be used depends on the Content-Type): + * ``` + * - "json" (json body)- uses the builtin Go json package for unmarshaling. + * - "xml" (xml body) - uses the builtin Go xml package for unmarshaling. + * - "form" (form data) - utilizes the custom [router.UnmarshalRequestData] method. + * ``` + * + * NB! When dst is a struct make sure that it doesn't have public fields + * that shouldn't be bindable and it is advisible such fields to be unexported + * or have a separate struct just for the binding. For example: + * + * ``` + * data := struct{ + * somethingPrivate string + * + * Title string `json:"title" form:"title"` + * Total int `json:"total" form:"total"` + * }{} + * err := e.BindBody(&data) + * ``` + */ + bindBody(dst: any): void + } + /** + * Router defines a thin wrapper around the standard Go [http.ServeMux] by + * adding support for routing sub-groups, middlewares and other common utils. + * + * Example: + * + * ``` + * r := NewRouter[*MyEvent](eventFactory) + * + * // middlewares + * r.BindFunc(m1, m2) + * + * // routes + * r.GET("/test", handler1) + * + * // sub-routers/groups + * api := r.Group("/api") + * api.GET("/admins", handler2) + * + * // generate a http.ServeMux instance based on the router configurations + * mux, _ := r.BuildMux() + * + * http.ListenAndServe("localhost:8090", mux) + * ``` + */ + type _sWlrNIi = RouterGroup + interface Router extends _sWlrNIi { + } + interface Router { + /** + * BuildMux constructs a new mux [http.Handler] instance from the current router configurations. + */ + buildMux(): http.Handler + } +} + +/** + * Package slog provides structured logging, + * in which log records include a message, + * a severity level, and various other attributes + * expressed as key-value pairs. + * + * It defines a type, [Logger], + * which provides several methods (such as [Logger.Info] and [Logger.Error]) + * for reporting events of interest. + * + * Each Logger is associated with a [Handler]. + * A Logger output method creates a [Record] from the method arguments + * and passes it to the Handler, which decides how to handle it. + * There is a default Logger accessible through top-level functions + * (such as [Info] and [Error]) that call the corresponding Logger methods. + * + * A log record consists of a time, a level, a message, and a set of key-value + * pairs, where the keys are strings and the values may be of any type. + * As an example, + * + * ``` + * slog.Info("hello", "count", 3) + * ``` + * + * creates a record containing the time of the call, + * a level of Info, the message "hello", and a single + * pair with key "count" and value 3. + * + * The [Info] top-level function calls the [Logger.Info] method on the default Logger. + * In addition to [Logger.Info], there are methods for Debug, Warn and Error levels. + * Besides these convenience methods for common levels, + * there is also a [Logger.Log] method which takes the level as an argument. + * Each of these methods has a corresponding top-level function that uses the + * default logger. + * + * The default handler formats the log record's message, time, level, and attributes + * as a string and passes it to the [log] package. + * + * ``` + * 2022/11/08 15:28:26 INFO hello count=3 + * ``` + * + * For more control over the output format, create a logger with a different handler. + * This statement uses [New] to create a new logger with a [TextHandler] + * that writes structured records in text form to standard error: + * + * ``` + * logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) + * ``` + * + * [TextHandler] output is a sequence of key=value pairs, easily and unambiguously + * parsed by machine. This statement: + * + * ``` + * logger.Info("hello", "count", 3) + * ``` + * + * produces this output: + * + * ``` + * time=2022-11-08T15:28:26.000-05:00 level=INFO msg=hello count=3 + * ``` + * + * The package also provides [JSONHandler], whose output is line-delimited JSON: + * + * ``` + * logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) + * logger.Info("hello", "count", 3) + * ``` + * + * produces this output: + * + * ``` + * {"time":"2022-11-08T15:28:26.000000000-05:00","level":"INFO","msg":"hello","count":3} + * ``` + * + * Both [TextHandler] and [JSONHandler] can be configured with [HandlerOptions]. + * There are options for setting the minimum level (see Levels, below), + * displaying the source file and line of the log call, and + * modifying attributes before they are logged. + * + * Setting a logger as the default with + * + * ``` + * slog.SetDefault(logger) + * ``` + * + * will cause the top-level functions like [Info] to use it. + * [SetDefault] also updates the default logger used by the [log] package, + * so that existing applications that use [log.Printf] and related functions + * will send log records to the logger's handler without needing to be rewritten. + * + * Some attributes are common to many log calls. + * For example, you may wish to include the URL or trace identifier of a server request + * with all log events arising from the request. + * Rather than repeat the attribute with every log call, you can use [Logger.With] + * to construct a new Logger containing the attributes: + * + * ``` + * logger2 := logger.With("url", r.URL) + * ``` + * + * The arguments to With are the same key-value pairs used in [Logger.Info]. + * The result is a new Logger with the same handler as the original, but additional + * attributes that will appear in the output of every call. + * + * # Levels + * + * A [Level] is an integer representing the importance or severity of a log event. + * The higher the level, the more severe the event. + * This package defines constants for the most common levels, + * but any int can be used as a level. + * + * In an application, you may wish to log messages only at a certain level or greater. + * One common configuration is to log messages at Info or higher levels, + * suppressing debug logging until it is needed. + * The built-in handlers can be configured with the minimum level to output by + * setting [HandlerOptions.Level]. + * The program's `main` function typically does this. + * The default value is LevelInfo. + * + * Setting the [HandlerOptions.Level] field to a [Level] value + * fixes the handler's minimum level throughout its lifetime. + * Setting it to a [LevelVar] allows the level to be varied dynamically. + * A LevelVar holds a Level and is safe to read or write from multiple + * goroutines. + * To vary the level dynamically for an entire program, first initialize + * a global LevelVar: + * + * ``` + * var programLevel = new(slog.LevelVar) // Info by default + * ``` + * + * Then use the LevelVar to construct a handler, and make it the default: + * + * ``` + * h := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: programLevel}) + * slog.SetDefault(slog.New(h)) + * ``` + * + * Now the program can change its logging level with a single statement: + * + * ``` + * programLevel.Set(slog.LevelDebug) + * ``` + * + * # Groups + * + * Attributes can be collected into groups. + * A group has a name that is used to qualify the names of its attributes. + * How this qualification is displayed depends on the handler. + * [TextHandler] separates the group and attribute names with a dot. + * [JSONHandler] treats each group as a separate JSON object, with the group name as the key. + * + * Use [Group] to create a Group attribute from a name and a list of key-value pairs: + * + * ``` + * slog.Group("request", + * "method", r.Method, + * "url", r.URL) + * ``` + * + * TextHandler would display this group as + * + * ``` + * request.method=GET request.url=http://example.com + * ``` + * + * JSONHandler would display it as + * + * ``` + * "request":{"method":"GET","url":"http://example.com"} + * ``` + * + * Use [Logger.WithGroup] to qualify all of a Logger's output + * with a group name. Calling WithGroup on a Logger results in a + * new Logger with the same Handler as the original, but with all + * its attributes qualified by the group name. + * + * This can help prevent duplicate attribute keys in large systems, + * where subsystems might use the same keys. + * Pass each subsystem a different Logger with its own group name so that + * potential duplicates are qualified: + * + * ``` + * logger := slog.Default().With("id", systemID) + * parserLogger := logger.WithGroup("parser") + * parseInput(input, parserLogger) + * ``` + * + * When parseInput logs with parserLogger, its keys will be qualified with "parser", + * so even if it uses the common key "id", the log line will have distinct keys. + * + * # Contexts + * + * Some handlers may wish to include information from the [context.Context] that is + * available at the call site. One example of such information + * is the identifier for the current span when tracing is enabled. + * + * The [Logger.Log] and [Logger.LogAttrs] methods take a context as a first + * argument, as do their corresponding top-level functions. + * + * Although the convenience methods on Logger (Info and so on) and the + * corresponding top-level functions do not take a context, the alternatives ending + * in "Context" do. For example, + * + * ``` + * slog.InfoContext(ctx, "message") + * ``` + * + * It is recommended to pass a context to an output method if one is available. + * + * # Attrs and Values + * + * An [Attr] is a key-value pair. The Logger output methods accept Attrs as well as + * alternating keys and values. The statement + * + * ``` + * slog.Info("hello", slog.Int("count", 3)) + * ``` + * + * behaves the same as + * + * ``` + * slog.Info("hello", "count", 3) + * ``` + * + * There are convenience constructors for [Attr] such as [Int], [String], and [Bool] + * for common types, as well as the function [Any] for constructing Attrs of any + * type. + * + * The value part of an Attr is a type called [Value]. + * Like an [any], a Value can hold any Go value, + * but it can represent typical values, including all numbers and strings, + * without an allocation. + * + * For the most efficient log output, use [Logger.LogAttrs]. + * It is similar to [Logger.Log] but accepts only Attrs, not alternating + * keys and values; this allows it, too, to avoid allocation. + * + * The call + * + * ``` + * logger.LogAttrs(ctx, slog.LevelInfo, "hello", slog.Int("count", 3)) + * ``` + * + * is the most efficient way to achieve the same output as + * + * ``` + * slog.InfoContext(ctx, "hello", "count", 3) + * ``` + * + * # Customizing a type's logging behavior + * + * If a type implements the [LogValuer] interface, the [Value] returned from its LogValue + * method is used for logging. You can use this to control how values of the type + * appear in logs. For example, you can redact secret information like passwords, + * or gather a struct's fields in a Group. See the examples under [LogValuer] for + * details. + * + * A LogValue method may return a Value that itself implements [LogValuer]. The [Value.Resolve] + * method handles these cases carefully, avoiding infinite loops and unbounded recursion. + * Handler authors and others may wish to use [Value.Resolve] instead of calling LogValue directly. + * + * # Wrapping output methods + * + * The logger functions use reflection over the call stack to find the file name + * and line number of the logging call within the application. This can produce + * incorrect source information for functions that wrap slog. For instance, if you + * define this function in file mylog.go: + * + * ``` + * func Infof(logger *slog.Logger, format string, args ...any) { + * logger.Info(fmt.Sprintf(format, args...)) + * } + * ``` + * + * and you call it like this in main.go: + * + * ``` + * Infof(slog.Default(), "hello, %s", "world") + * ``` + * + * then slog will report the source file as mylog.go, not main.go. + * + * A correct implementation of Infof will obtain the source location + * (pc) and pass it to NewRecord. + * The Infof function in the package-level example called "wrapping" + * demonstrates how to do this. + * + * # Working with Records + * + * Sometimes a Handler will need to modify a Record + * before passing it on to another Handler or backend. + * A Record contains a mixture of simple public fields (e.g. Time, Level, Message) + * and hidden fields that refer to state (such as attributes) indirectly. This + * means that modifying a simple copy of a Record (e.g. by calling + * [Record.Add] or [Record.AddAttrs] to add attributes) + * may have unexpected effects on the original. + * Before modifying a Record, use [Record.Clone] to + * create a copy that shares no state with the original, + * or create a new Record with [NewRecord] + * and build up its Attrs by traversing the old ones with [Record.Attrs]. + * + * # Performance considerations + * + * If profiling your application demonstrates that logging is taking significant time, + * the following suggestions may help. + * + * If many log lines have a common attribute, use [Logger.With] to create a Logger with + * that attribute. The built-in handlers will format that attribute only once, at the + * call to [Logger.With]. The [Handler] interface is designed to allow that optimization, + * and a well-written Handler should take advantage of it. + * + * The arguments to a log call are always evaluated, even if the log event is discarded. + * If possible, defer computation so that it happens only if the value is actually logged. + * For example, consider the call + * + * ``` + * slog.Info("starting request", "url", r.URL.String()) // may compute String unnecessarily + * ``` + * + * The URL.String method will be called even if the logger discards Info-level events. + * Instead, pass the URL directly: + * + * ``` + * slog.Info("starting request", "url", &r.URL) // calls URL.String only if needed + * ``` + * + * The built-in [TextHandler] will call its String method, but only + * if the log event is enabled. + * Avoiding the call to String also preserves the structure of the underlying value. + * For example [JSONHandler] emits the components of the parsed URL as a JSON object. + * If you want to avoid eagerly paying the cost of the String call + * without causing the handler to potentially inspect the structure of the value, + * wrap the value in a fmt.Stringer implementation that hides its Marshal methods. + * + * You can also use the [LogValuer] interface to avoid unnecessary work in disabled log + * calls. Say you need to log some expensive value: + * + * ``` + * slog.Debug("frobbing", "value", computeExpensiveValue(arg)) + * ``` + * + * Even if this line is disabled, computeExpensiveValue will be called. + * To avoid that, define a type implementing LogValuer: + * + * ``` + * type expensive struct { arg int } + * + * func (e expensive) LogValue() slog.Value { + * return slog.AnyValue(computeExpensiveValue(e.arg)) + * } + * ``` + * + * Then use a value of that type in log calls: + * + * ``` + * slog.Debug("frobbing", "value", expensive{arg}) + * ``` + * + * Now computeExpensiveValue will only be called when the line is enabled. + * + * The built-in handlers acquire a lock before calling [io.Writer.Write] + * to ensure that exactly one [Record] is written at a time in its entirety. + * Although each log record has a timestamp, + * the built-in handlers do not use that time to sort the written records. + * User-defined handlers are responsible for their own locking and sorting. + * + * # Writing a handler + * + * For a guide to writing a custom handler, see https://golang.org/s/slog-handler-guide. + */ +namespace slog { // @ts-ignore - import validation = ozzo_validation + import loginternal = internal /** - * ApiError defines the struct for a basic api error response. + * A Logger records structured information about each call to its + * Log, Debug, Info, Warn, and Error methods. + * For each call, it creates a [Record] and passes it to a [Handler]. + * + * To create a new Logger, call [New] or a Logger method + * that begins "With". */ - interface ApiError { - data: _TygojaDict - message: string - status: number + interface Logger { } - interface ApiError { + interface Logger { /** - * Error makes it compatible with the `error` interface. + * Handler returns l's Handler. */ - error(): string + handler(): Handler } - interface ApiError { + interface Logger { /** - * RawData returns the unformatted error data (could be an internal error, text, etc.) + * With returns a Logger that includes the given attributes + * in each output operation. Arguments are converted to + * attributes as if by [Logger.Log]. */ - rawData(): any + with(...args: any[]): (Logger) } - interface ApiError { + interface Logger { /** - * Is reports whether the current ApiError wraps the target. + * WithGroup returns a Logger that starts a group, if name is non-empty. + * The keys of all attributes added to the Logger will be qualified by the given + * name. (How that qualification happens depends on the [Handler.WithGroup] + * method of the Logger's Handler.) + * + * If name is empty, WithGroup returns the receiver. */ - is(target: Error): boolean + withGroup(name: string): (Logger) } - /** - * Event specifies based Route handler event that is usually intended - * to be embedded as part of a custom event struct. - * - * NB! It is expected that the Response and Request fields are always set. - */ - type _sdpOauu = hook.Event - interface Event extends _sdpOauu { - response: http.ResponseWriter - request?: http.Request + interface Logger { + /** + * Enabled reports whether l emits log records at the given context and level. + */ + enabled(ctx: context.Context, level: Level): boolean } - interface Event { + interface Logger { /** - * Written reports whether the current response has already been written. + * Log emits a log record with the current time and the given level and message. + * The Record's Attrs consist of the Logger's attributes followed by + * the Attrs specified by args. * - * This method always returns false if e.ResponseWritter doesn't implement the WriteTracker interface - * (all router package handlers receives a ResponseWritter that implements it unless explicitly replaced with a custom one). + * The attribute arguments are processed as follows: + * ``` + * - If an argument is an Attr, it is used as is. + * - If an argument is a string and this is not the last argument, + * the following argument is treated as the value and the two are combined + * into an Attr. + * - Otherwise, the argument is treated as a value with key "!BADKEY". + * ``` */ - written(): boolean + log(ctx: context.Context, level: Level, msg: string, ...args: any[]): void } - interface Event { + interface Logger { /** - * Status reports the status code of the current response. - * - * This method always returns 0 if e.Response doesn't implement the StatusTracker interface - * (all router package handlers receives a ResponseWritter that implements it unless explicitly replaced with a custom one). + * LogAttrs is a more efficient version of [Logger.Log] that accepts only Attrs. */ - status(): number + logAttrs(ctx: context.Context, level: Level, msg: string, ...attrs: Attr[]): void } - interface Event { + interface Logger { /** - * Flush flushes buffered data to the current response. - * - * Returns [http.ErrNotSupported] if e.Response doesn't implement the [http.Flusher] interface - * (all router package handlers receives a ResponseWritter that implements it unless explicitly replaced with a custom one). + * Debug logs at [LevelDebug]. + * It uses [context.Background] internally; to specify the context, use + * [Logger.DebugContext]. */ - flush(): void + debug(msg: string, ...args: any[]): void } - interface Event { + interface Logger { /** - * IsTLS reports whether the connection on which the request was received is TLS. + * DebugContext logs at [LevelDebug] with the given context. */ - isTLS(): boolean + debugContext(ctx: context.Context, msg: string, ...args: any[]): void } - interface Event { + interface Logger { /** - * SetCookie is an alias for [http.SetCookie]. - * - * SetCookie adds a Set-Cookie header to the current response's headers. - * The provided cookie must have a valid Name. - * Invalid cookies may be silently dropped. + * Info logs at [LevelInfo]. + * It uses [context.Background] internally; to specify the context, use + * [Logger.InfoContext]. */ - setCookie(cookie: http.Cookie): void + info(msg: string, ...args: any[]): void } - interface Event { + interface Logger { /** - * RemoteIP returns the IP address of the client that sent the request. - * - * IPv6 addresses are returned expanded. - * For example, "2001:db8::1" becomes "2001:0db8:0000:0000:0000:0000:0000:0001". - * - * Note that if you are behind reverse proxy(ies), this method returns - * the IP of the last connecting proxy. + * InfoContext logs at [LevelInfo] with the given context. */ - remoteIP(): string + infoContext(ctx: context.Context, msg: string, ...args: any[]): void } - interface Event { + interface Logger { /** - * FindUploadedFiles extracts all form files of "key" from a http request - * and returns a slice with filesystem.File instances (if any). + * Warn logs at [LevelWarn]. + * It uses [context.Background] internally; to specify the context, use + * [Logger.WarnContext]. */ - findUploadedFiles(key: string): Array<(filesystem.File | undefined)> + warn(msg: string, ...args: any[]): void } - interface Event { + interface Logger { /** - * Get retrieves single value from the current event data store. + * WarnContext logs at [LevelWarn] with the given context. */ - get(key: string): any + warnContext(ctx: context.Context, msg: string, ...args: any[]): void } - interface Event { + interface Logger { /** - * GetAll returns a copy of the current event data store. + * Error logs at [LevelError]. + * It uses [context.Background] internally; to specify the context, use + * [Logger.ErrorContext]. */ - getAll(): _TygojaDict + error(msg: string, ...args: any[]): void } - interface Event { + interface Logger { /** - * Set saves single value into the current event data store. + * ErrorContext logs at [LevelError] with the given context. */ - set(key: string, value: any): void + errorContext(ctx: context.Context, msg: string, ...args: any[]): void } - interface Event { +} + +namespace subscriptions { + /** + * Broker defines a struct for managing subscriptions clients. + */ + interface Broker { + } + interface Broker { /** - * SetAll saves all items from m into the current event data store. + * Clients returns a shallow copy of all registered clients indexed + * with their connection id. */ - setAll(m: _TygojaDict): void + clients(): _TygojaDict } - interface Event { + interface Broker { /** - * String writes a plain string response. + * ChunkedClients splits the current clients into a chunked slice. */ - string(status: number, data: string): void + chunkedClients(chunkSize: number): Array> } - interface Event { + interface Broker { /** - * HTML writes an HTML response. + * TotalClients returns the total number of registered clients. */ - html(status: number, data: string): void + totalClients(): number } - interface Event { + interface Broker { /** - * JSON writes a JSON response. + * ClientById finds a registered client by its id. * - * It also provides a generic response data fields picker if the "fields" query parameter is set. - * For example, if you are requesting `?fields=a,b` for `e.JSON(200, map[string]int{ "a":1, "b":2, "c":3 })`, - * it should result in a JSON response like: `{"a":1, "b": 2}`. + * Returns non-nil error when client with clientId is not registered. */ - json(status: number, data: any): void + clientById(clientId: string): Client } - interface Event { + interface Broker { /** - * XML writes an XML response. - * It automatically prepends the generic [xml.Header] string to the response. + * Register adds a new client to the broker instance. */ - xml(status: number, data: any): void + register(client: Client): void } - interface Event { + interface Broker { /** - * Stream streams the specified reader into the response. + * Unregister removes a single client by its id and marks it as discarded. + * + * If client with clientId doesn't exist, this method does nothing. */ - stream(status: number, contentType: string, reader: io.Reader): void + unregister(clientId: string): void } - interface Event { + /** + * Client is an interface for a generic subscription client. + */ + interface Client { + [key:string]: any; /** - * Blob writes a blob (bytes slice) response. + * Id Returns the unique id of the client. */ - blob(status: number, contentType: string, b: string|Array): void - } - interface Event { + id(): string + /** + * Channel returns the client's communication channel. + * + * NB! The channel shouldn't be used after calling Discard(). + */ + channel(): undefined + /** + * Subscriptions returns a shallow copy of the client subscriptions matching the prefixes. + * If no prefix is specified, returns all subscriptions. + */ + subscriptions(...prefixes: string[]): _TygojaDict + /** + * Subscribe subscribes the client to the provided subscriptions list. + * + * Each subscription can also have "options" (json serialized SubscriptionOptions) as query parameter. + * + * Example: + * + * ``` + * Subscribe( + * "subscriptionA", + * `subscriptionB?options={"query":{"a":1},"headers":{"x_token":"abc"}}`, + * ) + * ``` + */ + subscribe(...subs: string[]): void + /** + * Unsubscribe unsubscribes the client from the provided subscriptions list. + */ + unsubscribe(...subs: string[]): void + /** + * HasSubscription checks if the client is subscribed to `sub`. + */ + hasSubscription(sub: string): boolean + /** + * Set stores any value to the client's context. + */ + set(key: string, value: any): void + /** + * Unset removes a single value from the client's context. + */ + unset(key: string): void + /** + * Get retrieves the key value from the client's context. + */ + get(key: string): any /** - * FileFS serves the specified filename from fsys. + * Discard marks the client as "discarded" (and closes its channel), + * meaning that it shouldn't be used anymore for sending new messages. * - * It is similar to [echo.FileFS] for consistency with earlier versions. + * It is safe to call Discard() multiple times. */ - fileFS(fsys: fs.FS, filename: string): void - } - interface Event { + discard(): void /** - * NoContent writes a response with no body (ex. 204). + * IsDiscarded indicates whether the client has been "discarded" + * and should no longer be used. */ - noContent(status: number): void - } - interface Event { + isDiscarded(): boolean /** - * Redirect writes a redirect response to the specified url. - * The status code must be in between 300 – 399 range. + * Send sends the specified message to the client's channel (if not discarded). */ - redirect(status: number, url: string): void - } - interface Event { - error(status: number, message: string, errData: any): (ApiError) - } - interface Event { - badRequestError(message: string, errData: any): (ApiError) - } - interface Event { - notFoundError(message: string, errData: any): (ApiError) - } - interface Event { - forbiddenError(message: string, errData: any): (ApiError) - } - interface Event { - unauthorizedError(message: string, errData: any): (ApiError) - } - interface Event { - tooManyRequestsError(message: string, errData: any): (ApiError) + send(m: Message): void } - interface Event { - internalServerError(message: string, errData: any): (ApiError) + /** + * Message defines a client's channel data. + */ + interface Message { + name: string + data: string|Array } - interface Event { + interface Message { /** - * BindBody unmarshal the request body into the provided dst. - * - * dst must be either a struct pointer or map[string]any. - * - * The rules how the body will be scanned depends on the request Content-Type. - * - * Currently the following Content-Types are supported: - * ``` - * - application/json - * - text/xml, application/xml - * - multipart/form-data, application/x-www-form-urlencoded - * ``` - * - * Respectively the following struct tags are supported (again, which one will be used depends on the Content-Type): - * ``` - * - "json" (json body)- uses the builtin Go json package for unmarshaling. - * - "xml" (xml body) - uses the builtin Go xml package for unmarshaling. - * - "form" (form data) - utilizes the custom [router.UnmarshalRequestData] method. - * ``` + * WriteSSE writes the current message in a SSE format into the provided writer. * - * NB! When dst is a struct make sure that it doesn't have public fields - * that shouldn't be bindable and it is advisible such fields to be unexported - * or have a separate struct just for the binding. For example: + * For example, writing to a router.Event: * * ``` - * data := struct{ - * somethingPrivate string - * - * Title string `json:"title" form:"title"` - * Total int `json:"total" form:"total"` - * }{} - * err := e.BindBody(&data) + * m := Message{Name: "users/create", Data: []byte{...}} + * m.WriteSSE(e.Response, "yourEventId") + * e.Flush() * ``` */ - bindBody(dst: any): void - } - /** - * Router defines a thin wrapper around the standard Go [http.ServeMux] by - * adding support for routing sub-groups, middlewares and other common utils. - * - * Example: - * - * ``` - * r := NewRouter[*MyEvent](eventFactory) - * - * // middlewares - * r.BindFunc(m1, m2) - * - * // routes - * r.GET("/test", handler1) - * - * // sub-routers/groups - * api := r.Group("/api") - * api.GET("/admins", handler2) - * - * // generate a http.ServeMux instance based on the router configurations - * mux, _ := r.BuildMux() - * - * http.ListenAndServe("localhost:8090", mux) - * ``` - */ - type _sToAjYb = RouterGroup - interface Router extends _sToAjYb { - } - interface Router { - /** - * BuildMux constructs a new mux [http.Handler] instance from the current router configurations. - */ - buildMux(): http.Handler + writeSSE(w: io.Writer, eventId: string): void } } @@ -22078,302 +22235,205 @@ namespace cron { } } -namespace sync { - // @ts-ignore - import isync = sync - /** - * A Locker represents an object that can be locked and unlocked. - */ - interface Locker { - [key:string]: any; - lock(): void - unlock(): void - } -} - -namespace io { - /** - * WriteCloser is the interface that groups the basic Write and Close methods. - */ - interface WriteCloser { - [key:string]: any; - } -} - -namespace syscall { - // @ts-ignore - import errpkg = errors +namespace auth { /** - * SysProcIDMap holds Container ID to Host ID mappings used for User Namespaces in Linux. - * See user_namespaces(7). + * @todo refactor and consider replace with a plain struct * - * Note that User Namespaces are not available on a number of popular Linux - * versions (due to security issues), or are available but subject to AppArmor - * restrictions like in Ubuntu 24.04. - */ - interface SysProcIDMap { - containerID: number // Container ID. - hostID: number // Host ID. - size: number // Size. - } - // @ts-ignore - import errorspkg = errors - /** - * Credential holds user and group identities to be assumed - * by a child process started by [StartProcess]. - */ - interface Credential { - uid: number // User ID. - gid: number // Group ID. - groups: Array // Supplementary group IDs. - noSetGroups: boolean // If true, don't set supplementary groups - } - // @ts-ignore - import runtimesyscall = syscall - /** - * A Signal is a number describing a process signal. - * It implements the [os.Signal] interface. - */ - interface Signal extends Number{} - interface Signal { - signal(): void - } - interface Signal { - string(): string - } -} - -namespace time { - /** - * A Month specifies a month of the year (January = 1, ...). + * Provider defines a common interface for an OAuth2 client. */ - interface Month extends Number{} - interface Month { + interface Provider { + [key:string]: any; + /** + * @todo temp backport + * + * Order returns the sorting order of the provider usually used in the auth methods list response. + */ + logo(): string + /** + * @todo temp backport + * + * Order returns the sorting order of the provider usually used in the auth methods list response. + */ + order(): number + /** + * Context returns the context associated with the provider (if any). + */ + context(): context.Context + /** + * SetContext assigns the specified context to the current provider. + */ + setContext(ctx: context.Context): void + /** + * PKCE indicates whether the provider can use the PKCE flow. + */ + pkce(): boolean + /** + * SetPKCE toggles the state whether the provider can use the PKCE flow or not. + */ + setPKCE(enable: boolean): void + /** + * DisplayName usually returns provider name as it is officially written + * and it could be used directly in the UI. + */ + displayName(): string + /** + * SetDisplayName sets the provider's display name. + */ + setDisplayName(displayName: string): void + /** + * Scopes returns the provider access permissions that will be requested. + */ + scopes(): Array + /** + * SetScopes sets the provider access permissions that will be requested later. + */ + setScopes(scopes: Array): void + /** + * ClientId returns the provider client's app ID. + */ + clientId(): string + /** + * SetClientId sets the provider client's ID. + */ + setClientId(clientId: string): void + /** + * ClientSecret returns the provider client's app secret. + */ + clientSecret(): string + /** + * SetClientSecret sets the provider client's app secret. + */ + setClientSecret(secret: string): void + /** + * RedirectURL returns the end address to redirect the user + * going through the OAuth flow. + */ + redirectURL(): string + /** + * SetRedirectURL sets the provider's RedirectURL. + */ + setRedirectURL(url: string): void /** - * String returns the English name of the month ("January", "February", ...). + * AuthURL returns the provider's authorization service url. */ - string(): string - } - /** - * A Weekday specifies a day of the week (Sunday = 0, ...). - */ - interface Weekday extends Number{} - interface Weekday { + authURL(): string /** - * String returns the English name of the day ("Sunday", "Monday", ...). + * SetAuthURL sets the provider's AuthURL. */ - string(): string - } - /** - * A Location maps time instants to the zone in use at that time. - * Typically, the Location represents the collection of time offsets - * in use in a geographical area. For many Locations the time offset varies - * depending on whether daylight savings time is in use at the time instant. - * - * Location is used to provide a time zone in a printed Time value and for - * calculations involving intervals that may cross daylight savings time - * boundaries. - */ - interface Location { - } - interface Location { + setAuthURL(url: string): void /** - * String returns a descriptive name for the time zone information, - * corresponding to the name argument to [LoadLocation] or [FixedZone]. + * TokenURL returns the provider's token exchange service url. */ - string(): string - } -} - -namespace context { -} - -namespace fs { -} - -namespace slog { - /** - * An Attr is a key-value pair. - */ - interface Attr { - key: string - value: Value - } - interface Attr { + tokenURL(): string /** - * Equal reports whether a and b have equal keys and values. + * SetTokenURL sets the provider's TokenURL. */ - equal(b: Attr): boolean - } - interface Attr { - string(): string - } - /** - * A Handler handles log records produced by a Logger. - * - * A typical handler may print log records to standard error, - * or write them to a file or database, or perhaps augment them - * with additional attributes and pass them on to another handler. - * - * Any of the Handler's methods may be called concurrently with itself - * or with other methods. It is the responsibility of the Handler to - * manage this concurrency. - * - * Users of the slog package should not invoke Handler methods directly. - * They should use the methods of [Logger] instead. - * - * Before implementing your own handler, consult https://go.dev/s/slog-handler-guide. - */ - interface Handler { - [key:string]: any; + setTokenURL(url: string): void /** - * Enabled reports whether the handler handles records at the given level. - * The handler ignores records whose level is lower. - * It is called early, before any arguments are processed, - * to save effort if the log event should be discarded. - * If called from a Logger method, the first argument is the context - * passed to that method, or context.Background() if nil was passed - * or the method does not take a context. - * The context is passed so Enabled can use its values - * to make a decision. + * UserInfoURL returns the provider's user info api url. */ - enabled(_arg0: context.Context, _arg1: Level): boolean + userInfoURL(): string /** - * Handle handles the Record. - * It will only be called when Enabled returns true. - * The Context argument is as for Enabled. - * It is present solely to provide Handlers access to the context's values. - * Canceling the context should not affect record processing. - * (Among other things, log messages may be necessary to debug a - * cancellation-related problem.) - * - * Handle methods that produce output should observe the following rules: - * ``` - * - If r.Time is the zero time, ignore the time. - * - If r.PC is zero, ignore it. - * - Attr's values should be resolved. - * - If an Attr's key and value are both the zero value, ignore the Attr. - * This can be tested with attr.Equal(Attr{}). - * - If a group's key is empty, inline the group's Attrs. - * - If a group has no Attrs (even if it has a non-empty key), - * ignore it. - * ``` - * - * [Logger] discards any errors from Handle. Wrap the Handle method to - * process any errors from Handlers. + * SetUserInfoURL sets the provider's UserInfoURL. */ - handle(_arg0: context.Context, _arg1: Record): void + setUserInfoURL(url: string): void /** - * WithAttrs returns a new Handler whose attributes consist of - * both the receiver's attributes and the arguments. - * The Handler owns the slice: it may retain, modify or discard it. + * Extra returns a shallow copy of any custom config data + * that the provider may need. */ - withAttrs(attrs: Array): Handler + extra(): _TygojaDict /** - * WithGroup returns a new Handler with the given group appended to - * the receiver's existing groups. - * The keys of all subsequent attributes, whether added by With or in a - * Record, should be qualified by the sequence of group names. - * - * How this qualification happens is up to the Handler, so long as - * this Handler's attribute keys differ from those of another Handler - * with a different sequence of group names. - * - * A Handler should treat WithGroup as starting a Group of Attrs that ends - * at the end of the log event. That is, - * - * ``` - * logger.WithGroup("s").LogAttrs(ctx, level, msg, slog.Int("a", 1), slog.Int("b", 2)) - * ``` - * - * should behave like - * - * ``` - * logger.LogAttrs(ctx, level, msg, slog.Group("s", slog.Int("a", 1), slog.Int("b", 2))) - * ``` - * - * If the name is empty, WithGroup returns the receiver. + * SetExtra updates the provider's custom config data. */ - withGroup(name: string): Handler - } - /** - * A Level is the importance or severity of a log event. - * The higher the level, the more important or severe the event. - */ - interface Level extends Number{} - interface Level { + setExtra(data: _TygojaDict): void /** - * String returns a name for the level. - * If the level has a name, then that name - * in uppercase is returned. - * If the level is between named values, then - * an integer is appended to the uppercased name. - * Examples: - * - * ``` - * LevelWarn.String() => "WARN" - * (LevelInfo+2).String() => "INFO+2" - * ``` + * Client returns an http client using the provided token. */ - string(): string - } - interface Level { + client(token: oauth2.Token): (any) /** - * MarshalJSON implements [encoding/json.Marshaler] - * by quoting the output of [Level.String]. + * BuildAuthURL returns a URL to the provider's consent page + * that asks for permissions for the required scopes explicitly. */ - marshalJSON(): string|Array - } - interface Level { + buildAuthURL(state: string, ...opts: oauth2.AuthCodeOption[]): string /** - * UnmarshalJSON implements [encoding/json.Unmarshaler] - * It accepts any string produced by [Level.MarshalJSON], - * ignoring case. - * It also accepts numeric offsets that would result in a different string on - * output. For example, "Error-8" would marshal as "INFO". + * FetchToken converts an authorization code to token. */ - unmarshalJSON(data: string|Array): void - } - interface Level { + fetchToken(code: string, ...opts: oauth2.AuthCodeOption[]): (oauth2.Token) /** - * AppendText implements [encoding.TextAppender] - * by calling [Level.String]. + * FetchRawUserInfo requests and marshalizes into `result` the + * the OAuth user api response. */ - appendText(b: string|Array): string|Array - } - interface Level { + fetchRawUserInfo(token: oauth2.Token): string|Array /** - * MarshalText implements [encoding.TextMarshaler] - * by calling [Level.AppendText]. + * FetchAuthUser is similar to FetchRawUserInfo, but normalizes and + * marshalizes the user api response into a standardized AuthUser struct. */ - marshalText(): string|Array + fetchAuthUser(token: oauth2.Token): (AuthUser) } - interface Level { + /** + * AuthUser defines a standardized OAuth2 user data structure. + */ + interface AuthUser { + expiry: types.DateTime + rawUser: _TygojaDict + id: string + name: string + username: string + avatarURL: string + accessToken: string + refreshToken: string /** - * UnmarshalText implements [encoding.TextUnmarshaler]. - * It accepts any string produced by [Level.MarshalText], - * ignoring case. - * It also accepts numeric offsets that would result in a different string on - * output. For example, "Error-8" would marshal as "INFO". + * The VERIFIED OAuth2 account email. + * + * It must be empty if the provider is not able to verify the email ownership. */ - unmarshalText(data: string|Array): void + email: string + /** + * @todo + * deprecated: use AvatarURL instead + * AvatarUrl will be removed after dropping v0.22 support + */ + avatarUrl: string } - interface Level { + interface AuthUser { /** - * Level returns the receiver. - * It implements [Leveler]. + * MarshalJSON implements the [json.Marshaler] interface. + * + * @todo remove after dropping v0.22 support */ - level(): Level + marshalJSON(): string|Array + } +} + +namespace sync { + // @ts-ignore + import isync = sync + /** + * A Locker represents an object that can be locked and unlocked. + */ + interface Locker { + [key:string]: any; + lock(): void + unlock(): void } // @ts-ignore - import loginternal = internal + import rtatomic = atomic +} + +namespace io { + /** + * WriteCloser is the interface that groups the basic Write and Close methods. + */ + interface WriteCloser { + [key:string]: any; + } } namespace bufio { /** * Reader implements buffering for an io.Reader object. * A new Reader is created by calling [NewReader] or [NewReaderSize]; - * alternatively the zero value of a Reader may be used after calling [Reset] + * alternatively the zero value of a Reader may be used after calling [Reader.Reset] * on it. */ interface Reader { @@ -22633,6 +22693,95 @@ namespace bufio { } } +namespace syscall { + // @ts-ignore + import errpkg = errors + /** + * SysProcIDMap holds Container ID to Host ID mappings used for User Namespaces in Linux. + * See user_namespaces(7). + * + * Note that User Namespaces are not available on a number of popular Linux + * versions (due to security issues), or are available but subject to AppArmor + * restrictions like in Ubuntu 24.04. + */ + interface SysProcIDMap { + containerID: number // Container ID. + hostID: number // Host ID. + size: number // Size. + } + // @ts-ignore + import errorspkg = errors + /** + * Credential holds user and group identities to be assumed + * by a child process started by [StartProcess]. + */ + interface Credential { + uid: number // User ID. + gid: number // Group ID. + groups: Array // Supplementary group IDs. + noSetGroups: boolean // If true, don't set supplementary groups + } + /** + * A Signal is a number describing a process signal. + * It implements the [os.Signal] interface. + */ + interface Signal extends Number{} + interface Signal { + signal(): void + } + interface Signal { + string(): string + } +} + +namespace time { + /** + * A Month specifies a month of the year (January = 1, ...). + */ + interface Month extends Number{} + interface Month { + /** + * String returns the English name of the month ("January", "February", ...). + */ + string(): string + } + /** + * A Weekday specifies a day of the week (Sunday = 0, ...). + */ + interface Weekday extends Number{} + interface Weekday { + /** + * String returns the English name of the day ("Sunday", "Monday", ...). + */ + string(): string + } + /** + * A Location maps time instants to the zone in use at that time. + * Typically, the Location represents the collection of time offsets + * in use in a geographical area. For many Locations the time offset varies + * depending on whether daylight savings time is in use at the time instant. + * + * Location is used to provide a time zone in a printed Time value and for + * calculations involving intervals that may cross daylight savings time + * boundaries. + */ + interface Location { + } + interface Location { + /** + * String returns a descriptive name for the time zone information, + * corresponding to the name argument to [LoadLocation] or [FixedZone]. + */ + string(): string + } +} + +namespace fs { +} + +namespace context { +} + namespace net { /** * Addr represents a network end point address. @@ -22792,39 +22941,6 @@ namespace multipart { } } -namespace cron { - /** - * Job defines a single registered cron job. - */ - interface Job { - } - interface Job { - /** - * Id returns the cron job id. - */ - id(): string - } - interface Job { - /** - * Expression returns the plain cron job schedule expression. - */ - expression(): string - } - interface Job { - /** - * Run runs the cron job function. - */ - run(): void - } - interface Job { - /** - * MarshalJSON implements [json.Marshaler] and export the current - * jobs data into valid JSON. - */ - marshalJSON(): string|Array - } -} - /** * Package url parses URLs and implements query escaping. * @@ -22859,26 +22975,47 @@ namespace url { * A consequence is that it is impossible to tell which slashes in the Path were * slashes in the raw URL and which were %2f. This distinction is rarely important, * but when it is, the code should use the [URL.EscapedPath] method, which preserves - * the original encoding of Path. + * the original encoding of Path. The Fragment field is also stored in decoded form, + * use [URL.EscapedFragment] to retrieve the original encoding. * - * The RawPath field is an optional field which is only set when the default - * encoding of Path is different from the escaped path. See the EscapedPath method - * for more details. - * - * URL's String method uses the EscapedPath method to obtain the path. + * The [URL.String] method uses the [URL.EscapedPath] method to obtain the path. */ interface URL { scheme: string opaque: string // encoded opaque data user?: Userinfo // username and password information - host: string // host or host:port (see Hostname and Port methods) + host: string // "host" or "host:port" (see Hostname and Port methods) path: string // path (relative paths may omit leading slash) - rawPath: string // encoded path hint (see EscapedPath method) - omitHost: boolean // do not emit empty host (authority) - forceQuery: boolean // append a query ('?') even if RawQuery is empty - rawQuery: string // encoded query values, without '?' - fragment: string // fragment for references, without '#' - rawFragment: string // encoded fragment hint (see EscapedFragment method) + fragment: string // fragment for references (without '#') + /** + * RawQuery contains the encoded query values, without the initial '?'. + * Use URL.Query to decode the query. + */ + rawQuery: string + /** + * RawPath is an optional field containing an encoded path hint. + * See the EscapedPath method for more details. + * + * In general, code should call EscapedPath instead of reading RawPath. + */ + rawPath: string + /** + * RawFragment is an optional field containing an encoded fragment hint. + * See the EscapedFragment method for more details. + * + * In general, code should call EscapedFragment instead of reading RawFragment. + */ + rawFragment: string + /** + * ForceQuery indicates whether the original URL contained a query ('?') character. + * When set, the String method will include a trailing '?', even when RawQuery is empty. + */ + forceQuery: boolean + /** + * OmitHost indicates the URL has an empty host (authority). + * When set, the String method will not include the host when it is empty. + */ + omitHost: boolean } interface URL { /** @@ -22986,6 +23123,12 @@ namespace url { */ has(key: string): boolean } + interface Values { + /** + * Clone creates a deep copy of the subject [Values]. + */ + clone(): Values + } interface Values { /** * Encode encodes the values into “URL encoded” form @@ -23065,9 +23208,16 @@ namespace url { * JoinPath returns a new [URL] with the provided path elements joined to * any existing path and the resulting path cleaned of any ./ or ../ elements. * Any sequences of multiple / characters will be reduced to a single /. + * Path elements must already be in escaped form, as produced by [PathEscape]. */ joinPath(...elem: string[]): (URL) } + interface URL { + /** + * Clone creates a deep copy of the fields of the subject [URL]. + */ + clone(): (URL) + } } namespace http { @@ -23113,8 +23263,6 @@ namespace http { */ valid(): void } - // @ts-ignore - import mathrand = rand /** * A Header represents the key-value pairs in an HTTP header. * @@ -23200,7 +23348,7 @@ namespace http { * - HTTP1 is the HTTP/1.0 and HTTP/1.1 protocols. * HTTP1 is supported on both unsecured TCP and secured TLS connections. * - * - HTTP2 is the HTTP/2 protcol over a TLS connection. + * - HTTP2 is the HTTP/2 protocol over a TLS connection. * * - UnencryptedHTTP2 is the HTTP/2 protocol over an unsecured TCP connection. * ``` @@ -23253,10 +23401,24 @@ namespace http { interface HTTP2Config { /** * MaxConcurrentStreams optionally specifies the number of - * concurrent streams that a peer may have open at a time. + * concurrent streams that a client may have open at a time. * If zero, MaxConcurrentStreams defaults to at least 100. + * + * This parameter only applies to Servers. */ maxConcurrentStreams: number + /** + * StrictMaxConcurrentRequests controls whether an HTTP/2 server's + * concurrency limit should be respected across all connections + * to that server. + * If true, new requests sent when a connection's concurrency limit + * has been exceeded will block until an existing request completes. + * If false, an additional connection will be opened if all + * existing connections are at their limit. + * + * This parameter only applies to Transports. + */ + strictMaxConcurrentRequests: boolean /** * MaxDecoderHeaderTableSize optionally specifies an upper limit for the * size of the header compression table used for decoding headers sent @@ -23363,7 +23525,10 @@ namespace http { * a zero-length body. It is the caller's responsibility to * close Body. The default HTTP client's Transport may not * reuse HTTP/1.x "keep-alive" TCP connections if the Body is - * not read to completion and closed. + * not read to completion and closed; however, manually reading + * the body to completion should not be needed in most cases, + * as closing the body will also cause the body to be read to + * completion asynchronously, up to a conservative limit. * * The Body is automatically dechunked if the server replied * with a "chunked" Transfer-Encoding. @@ -23480,114 +23645,21 @@ namespace http { * It's used by the optional [Server.ConnState] hook. */ interface ConnState extends Number{} - interface ConnState { - string(): string - } -} - -/** - * Package oauth2 provides support for making - * OAuth2 authorized and authenticated HTTP requests, - * as specified in RFC 6749. - * It can additionally grant authorization with Bearer JWT. - */ -namespace oauth2 { - /** - * An AuthCodeOption is passed to Config.AuthCodeURL. - */ - interface AuthCodeOption { - [key:string]: any; - } - /** - * Token represents the credentials used to authorize - * the requests to access protected resources on the OAuth 2.0 - * provider's backend. - * - * Most users of this package should not access fields of Token - * directly. They're exported mostly for use by related packages - * implementing derivative OAuth2 flows. - */ - interface Token { - /** - * AccessToken is the token that authorizes and authenticates - * the requests. - */ - accessToken: string - /** - * TokenType is the type of token. - * The Type method returns either this or "Bearer", the default. - */ - tokenType: string - /** - * RefreshToken is a token that's used by the application - * (as opposed to the user) to refresh the access token - * if it expires. - */ - refreshToken: string - /** - * Expiry is the optional expiration time of the access token. - * - * If zero, [TokenSource] implementations will reuse the same - * token forever and RefreshToken or equivalent - * mechanisms for that TokenSource will not be used. - */ - expiry: time.Time - /** - * ExpiresIn is the OAuth2 wire format "expires_in" field, - * which specifies how many seconds later the token expires, - * relative to an unknown time base approximately around "now". - * It is the application's responsibility to populate - * `Expiry` from `ExpiresIn` when required. - */ - expiresIn: number - } - interface Token { - /** - * Type returns t.TokenType if non-empty, else "Bearer". - */ - type(): string - } - interface Token { - /** - * SetAuthHeader sets the Authorization header to r using the access - * token in t. - * - * This method is unnecessary when using [Transport] or an HTTP Client - * returned by this package. - */ - setAuthHeader(r: http.Request): void - } - interface Token { - /** - * WithExtra returns a new [Token] that's a clone of t, but using the - * provided raw extra map. This is only intended for use by packages - * implementing derivative OAuth2 flows. - */ - withExtra(extra: any): (Token) - } - interface Token { - /** - * Extra returns an extra field. - * Extra fields are key-value pairs returned by the server as - * part of the token retrieval response. - */ - extra(key: string): any - } - interface Token { - /** - * Valid reports whether t is non-nil, has an AccessToken, and is not expired. - */ - valid(): boolean + interface ConnState { + string(): string } } +namespace store { +} + namespace jwt { /** * NumericDate represents a JSON numeric date value, as referenced at * https://datatracker.ietf.org/doc/html/rfc7519#section-2. */ - type _sGiaGYO = time.Time - interface NumericDate extends _sGiaGYO { + type _sVbLTZR = time.Time + interface NumericDate extends _sVbLTZR { } interface NumericDate { /** @@ -23619,314 +23691,549 @@ namespace jwt { } } -namespace store { -} - namespace hook { /** * wrapped local Hook embedded struct to limit the public API surface. */ - type _sHoxvkd = Hook - interface mainHook extends _sHoxvkd { + type _slsDAlu = Hook + interface mainHook extends _slsDAlu { } } -namespace cobra { - interface PositionalArgs {(cmd: Command, args: Array): void } - // @ts-ignore - import flag = pflag +namespace cron { /** - * FParseErrWhitelist configures Flag parse errors to be ignored + * Job defines a single registered cron job. */ - interface FParseErrWhitelist extends _TygojaAny{} + interface Job { + } + interface Job { + /** + * Id returns the cron job id. + */ + id(): string + } + interface Job { + /** + * Expression returns the plain cron job schedule expression. + */ + expression(): string + } + interface Job { + /** + * Run runs the cron job function. + */ + run(): void + } + interface Job { + /** + * MarshalJSON implements [json.Marshaler] and export the current + * jobs data into valid JSON. + */ + marshalJSON(): string|Array + } +} + +namespace sql { /** - * Group Structure to manage groups for commands + * IsolationLevel is the transaction isolation level used in [TxOptions]. */ - interface Group { - id: string - title: string + interface IsolationLevel extends Number{} + interface IsolationLevel { + /** + * String returns the name of the transaction isolation level. + */ + string(): string } /** - * CompletionOptions are the options to control shell completion + * DBStats contains database statistics. */ - interface CompletionOptions { + interface DBStats { + maxOpenConnections: number // Maximum number of open connections to the database. /** - * DisableDefaultCmd prevents Cobra from creating a default 'completion' command + * Pool Status */ - disableDefaultCmd: boolean + openConnections: number // The number of established connections both in use and idle. + inUse: number // The number of connections currently in use. + idle: number // The number of idle connections. /** - * DisableNoDescFlag prevents Cobra from creating the '--no-descriptions' flag - * for shells that support completion descriptions + * Counters */ - disableNoDescFlag: boolean + waitCount: number // The total number of connections waited for. + waitDuration: time.Duration // The total time blocked waiting for a new connection. + maxIdleClosed: number // The total number of connections closed due to SetMaxIdleConns. + maxIdleTimeClosed: number // The total number of connections closed due to SetConnMaxIdleTime. + maxLifetimeClosed: number // The total number of connections closed due to SetConnMaxLifetime. + } + /** + * Conn represents a single database connection rather than a pool of database + * connections. Prefer running queries from [DB] unless there is a specific + * need for a continuous single database connection. + * + * A Conn must call [Conn.Close] to return the connection to the database pool + * and may do so concurrently with a running query. + * + * After a call to [Conn.Close], all operations on the + * connection fail with [ErrConnDone]. + */ + interface Conn { + } + interface Conn { /** - * DisableDescriptions turns off all completion descriptions for shells - * that support them + * PingContext verifies the connection to the database is still alive. */ - disableDescriptions: boolean + pingContext(ctx: context.Context): void + } + interface Conn { /** - * HiddenDefaultCmd makes the default 'completion' command hidden + * ExecContext executes a query without returning any rows. + * The args are for any placeholder parameters in the query. */ - hiddenDefaultCmd: boolean + execContext(ctx: context.Context, query: string, ...args: any[]): Result + } + interface Conn { /** - * DefaultShellCompDirective sets the ShellCompDirective that is returned - * if no special directive can be determined + * QueryContext executes a query that returns rows, typically a SELECT. + * The args are for any placeholder parameters in the query. */ - defaultShellCompDirective?: ShellCompDirective + queryContext(ctx: context.Context, query: string, ...args: any[]): (Rows) } - interface CompletionOptions { - setDefaultShellCompDirective(directive: ShellCompDirective): void + interface Conn { + /** + * QueryRowContext executes a query that is expected to return at most one row. + * QueryRowContext always returns a non-nil value. Errors are deferred until + * the [*Row.Scan] method is called. + * If the query selects no rows, the [*Row.Scan] will return [ErrNoRows]. + * Otherwise, the [*Row.Scan] scans the first selected row and discards + * the rest. + */ + queryRowContext(ctx: context.Context, query: string, ...args: any[]): (Row) + } + interface Conn { + /** + * PrepareContext creates a prepared statement for later queries or executions. + * Multiple queries or executions may be run concurrently from the + * returned statement. + * The caller must call the statement's [*Stmt.Close] method + * when the statement is no longer needed. + * + * The provided context is used for the preparation of the statement, not for the + * execution of the statement. + */ + prepareContext(ctx: context.Context, query: string): (Stmt) + } + interface Conn { + /** + * Raw executes f exposing the underlying driver connection for the + * duration of f. The driverConn must not be used outside of f. + * + * Once f returns and err is not [driver.ErrBadConn], the [Conn] will continue to be usable + * until [Conn.Close] is called. + */ + raw(f: (driverConn: any) => void): void + } + interface Conn { + /** + * BeginTx starts a transaction. + * + * The provided context is used until the transaction is committed or rolled back. + * If the context is canceled, the sql package will roll back + * the transaction. [Tx.Commit] will return an error if the context provided to + * BeginTx is canceled. + * + * The provided [TxOptions] is optional and may be nil if defaults should be used. + * If a non-default isolation level is used that the driver doesn't support, + * an error will be returned. + */ + beginTx(ctx: context.Context, opts: TxOptions): (Tx) + } + interface Conn { + /** + * Close returns the connection to the connection pool. + * All operations after a Close will return with [ErrConnDone]. + * Close is safe to call concurrently with other operations and will + * block until all other operations finish. It may be useful to first + * cancel any used context and then call close directly after. + */ + close(): void } /** - * Completion is a string that can be used for completions - * - * two formats are supported: - * ``` - * - the completion choice - * - the completion choice with a textual description (separated by a TAB). - * ``` - * - * [CompletionWithDesc] can be used to create a completion string with a textual description. - * - * Note: Go type alias is used to provide a more descriptive name in the documentation, but any string can be used. + * ColumnType contains the name and type of a column. */ - interface Completion extends String{} + interface ColumnType { + } + interface ColumnType { + /** + * Name returns the name or alias of the column. + */ + name(): string + } + interface ColumnType { + /** + * Length returns the column type length for variable length column types such + * as text and binary field types. If the type length is unbounded the value will + * be [math.MaxInt64] (any database limits will still apply). + * If the column type is not variable length, such as an int, or if not supported + * by the driver ok is false. + */ + length(): [number, boolean] + } + interface ColumnType { + /** + * DecimalSize returns the scale and precision of a decimal type. + * If not applicable or if not supported ok is false. + */ + decimalSize(): [number, number, boolean] + } + interface ColumnType { + /** + * ScanType returns a Go type suitable for scanning into using [Rows.Scan]. + * If a driver does not support this property ScanType will return + * the type of an empty interface. + */ + scanType(): any + } + interface ColumnType { + /** + * Nullable reports whether the column may be null. + * If a driver does not support this property ok will be false. + */ + nullable(): [boolean, boolean] + } + interface ColumnType { + /** + * DatabaseTypeName returns the database system name of the column type. If an empty + * string is returned, then the driver type name is not supported. + * Consult your driver documentation for a list of driver data types. [ColumnType.Length] specifiers + * are not included. + * Common type names include "VARCHAR", "TEXT", "NVARCHAR", "DECIMAL", "BOOL", + * "INT", and "BIGINT". + */ + databaseTypeName(): string + } /** - * CompletionFunc is a function that provides completion results. + * Row is the result of calling [DB.QueryRow] to select a single row. */ - interface CompletionFunc {(cmd: Command, args: Array, toComplete: string): [Array, ShellCompDirective] } + interface Row { + } + interface Row { + /** + * Scan copies the columns from the matched row into the values + * pointed at by dest. See the documentation on [Rows.Scan] for details. + * If more than one row matches the query, + * Scan uses the first row and discards the rest. If no row matches + * the query, Scan returns [ErrNoRows]. + */ + scan(...dest: any[]): void + } + interface Row { + /** + * Err provides a way for wrapping packages to check for + * query errors without calling [Row.Scan]. + * Err returns the error, if any, that was encountered while running the query. + * If this error is not nil, this error will also be returned from [Row.Scan]. + */ + err(): void + } } -namespace sql { +namespace types { +} + +namespace search { /** - * IsolationLevel is the transaction isolation level used in [TxOptions]. + * MultiMatchSubquery defines a multi-match record subquery expression. */ - interface IsolationLevel extends Number{} - interface IsolationLevel { + interface MultiMatchSubquery { + targetTableAlias: string + fromTableName: string + fromTableAlias: string + valueIdentifier: string + joins: Array<(Join | undefined)> + params: dbx.Params + } + interface MultiMatchSubquery { /** - * String returns the name of the transaction isolation level. + * Build converts the expression into a SQL fragment. + * + * Implements [dbx.Expression] interface. */ - string(): string + build(db: dbx.DB, params: dbx.Params): string } + interface NullFallbackPreference extends Number{} +} + +namespace slog { /** - * DBStats contains database statistics. + * An Attr is a key-value pair. */ - interface DBStats { - maxOpenConnections: number // Maximum number of open connections to the database. - /** - * Pool Status - */ - openConnections: number // The number of established connections both in use and idle. - inUse: number // The number of connections currently in use. - idle: number // The number of idle connections. + interface Attr { + key: string + value: Value + } + interface Attr { /** - * Counters + * Equal reports whether a and b have equal keys and values. */ - waitCount: number // The total number of connections waited for. - waitDuration: time.Duration // The total time blocked waiting for a new connection. - maxIdleClosed: number // The total number of connections closed due to SetMaxIdleConns. - maxIdleTimeClosed: number // The total number of connections closed due to SetConnMaxIdleTime. - maxLifetimeClosed: number // The total number of connections closed due to SetConnMaxLifetime. + equal(b: Attr): boolean + } + interface Attr { + string(): string } /** - * Conn represents a single database connection rather than a pool of database - * connections. Prefer running queries from [DB] unless there is a specific - * need for a continuous single database connection. + * A Handler handles log records produced by a Logger. * - * A Conn must call [Conn.Close] to return the connection to the database pool - * and may do so concurrently with a running query. + * A typical handler may print log records to standard error, + * or write them to a file or database, or perhaps augment them + * with additional attributes and pass them on to another handler. * - * After a call to [Conn.Close], all operations on the - * connection fail with [ErrConnDone]. + * Any of the Handler's methods may be called concurrently with itself + * or with other methods. It is the responsibility of the Handler to + * manage this concurrency. + * + * Users of the slog package should not invoke Handler methods directly. + * They should use the methods of [Logger] instead. + * + * Before implementing your own handler, consult https://go.dev/s/slog-handler-guide. */ - interface Conn { - } - interface Conn { + interface Handler { + [key:string]: any; /** - * PingContext verifies the connection to the database is still alive. + * Enabled reports whether the handler handles records at the given level. + * The handler ignores records whose level is lower. + * It is called early, before any arguments are processed, + * to save effort if the log event should be discarded. + * If called from a Logger method, the first argument is the context + * passed to that method, or context.Background() if nil was passed + * or the method does not take a context. + * The context is passed so Enabled can use its values + * to make a decision. */ - pingContext(ctx: context.Context): void + enabled(_arg0: context.Context, _arg1: Level): boolean + /** + * Handle handles the Record. + * It will only be called when Enabled returns true. + * The Context argument is as for Enabled. + * It is present solely to provide Handlers access to the context's values. + * Canceling the context should not affect record processing. + * (Among other things, log messages may be necessary to debug a + * cancellation-related problem.) + * + * Handle methods that produce output should observe the following rules: + * ``` + * - If r.Time is the zero time, ignore the time. + * - If r.PC is zero, ignore it. + * - Attr's values should be resolved. + * - If an Attr's key and value are both the zero value, ignore the Attr. + * This can be tested with attr.Equal(Attr{}). + * - If a group's key is empty, inline the group's Attrs. + * - If a group has no Attrs (even if it has a non-empty key), + * ignore it. + * ``` + * + * [Logger] discards any errors from Handle. Wrap the Handle method to + * process any errors from Handlers. + */ + handle(_arg0: context.Context, _arg1: Record): void + /** + * WithAttrs returns a new Handler whose attributes consist of + * both the receiver's attributes and the arguments. + * The Handler owns the slice: it may retain, modify or discard it. + */ + withAttrs(attrs: Array): Handler + /** + * WithGroup returns a new Handler with the given group appended to + * the receiver's existing groups. + * The keys of all subsequent attributes, whether added by With or in a + * Record, should be qualified by the sequence of group names. + * + * How this qualification happens is up to the Handler, so long as + * this Handler's attribute keys differ from those of another Handler + * with a different sequence of group names. + * + * A Handler should treat WithGroup as starting a Group of Attrs that ends + * at the end of the log event. That is, + * + * ``` + * logger.WithGroup("s").LogAttrs(ctx, level, msg, slog.Int("a", 1), slog.Int("b", 2)) + * ``` + * + * should behave like + * + * ``` + * logger.LogAttrs(ctx, level, msg, slog.Group("s", slog.Int("a", 1), slog.Int("b", 2))) + * ``` + * + * If the name is empty, WithGroup returns the receiver. + */ + withGroup(name: string): Handler } - interface Conn { + /** + * A Level is the importance or severity of a log event. + * The higher the level, the more important or severe the event. + */ + interface Level extends Number{} + interface Level { /** - * ExecContext executes a query without returning any rows. - * The args are for any placeholder parameters in the query. + * String returns a name for the level. + * If the level has a name, then that name + * in uppercase is returned. + * If the level is between named values, then + * an integer is appended to the uppercased name. + * Examples: + * + * ``` + * LevelWarn.String() => "WARN" + * (LevelInfo+2).String() => "INFO+2" + * ``` */ - execContext(ctx: context.Context, query: string, ...args: any[]): Result + string(): string } - interface Conn { + interface Level { /** - * QueryContext executes a query that returns rows, typically a SELECT. - * The args are for any placeholder parameters in the query. + * MarshalJSON implements [encoding/json.Marshaler] + * by quoting the output of [Level.String]. */ - queryContext(ctx: context.Context, query: string, ...args: any[]): (Rows) + marshalJSON(): string|Array } - interface Conn { + interface Level { /** - * QueryRowContext executes a query that is expected to return at most one row. - * QueryRowContext always returns a non-nil value. Errors are deferred until - * the [*Row.Scan] method is called. - * If the query selects no rows, the [*Row.Scan] will return [ErrNoRows]. - * Otherwise, the [*Row.Scan] scans the first selected row and discards - * the rest. + * UnmarshalJSON implements [encoding/json.Unmarshaler] + * It accepts any string produced by [Level.MarshalJSON], + * ignoring case. + * It also accepts numeric offsets that would result in a different string on + * output. For example, "Error-8" would marshal as "INFO". */ - queryRowContext(ctx: context.Context, query: string, ...args: any[]): (Row) + unmarshalJSON(data: string|Array): void } - interface Conn { + interface Level { /** - * PrepareContext creates a prepared statement for later queries or executions. - * Multiple queries or executions may be run concurrently from the - * returned statement. - * The caller must call the statement's [*Stmt.Close] method - * when the statement is no longer needed. - * - * The provided context is used for the preparation of the statement, not for the - * execution of the statement. + * AppendText implements [encoding.TextAppender] + * by calling [Level.String]. */ - prepareContext(ctx: context.Context, query: string): (Stmt) + appendText(b: string|Array): string|Array } - interface Conn { + interface Level { /** - * Raw executes f exposing the underlying driver connection for the - * duration of f. The driverConn must not be used outside of f. - * - * Once f returns and err is not [driver.ErrBadConn], the [Conn] will continue to be usable - * until [Conn.Close] is called. + * MarshalText implements [encoding.TextMarshaler] + * by calling [Level.AppendText]. */ - raw(f: (driverConn: any) => void): void + marshalText(): string|Array } - interface Conn { + interface Level { /** - * BeginTx starts a transaction. - * - * The provided context is used until the transaction is committed or rolled back. - * If the context is canceled, the sql package will roll back - * the transaction. [Tx.Commit] will return an error if the context provided to - * BeginTx is canceled. - * - * The provided [TxOptions] is optional and may be nil if defaults should be used. - * If a non-default isolation level is used that the driver doesn't support, - * an error will be returned. + * UnmarshalText implements [encoding.TextUnmarshaler]. + * It accepts any string produced by [Level.MarshalText], + * ignoring case. + * It also accepts numeric offsets that would result in a different string on + * output. For example, "Error-8" would marshal as "INFO". */ - beginTx(ctx: context.Context, opts: TxOptions): (Tx) + unmarshalText(data: string|Array): void } - interface Conn { + interface Level { /** - * Close returns the connection to the connection pool. - * All operations after a Close will return with [ErrConnDone]. - * Close is safe to call concurrently with other operations and will - * block until all other operations finish. It may be useful to first - * cancel any used context and then call close directly after. + * Level returns the receiver. + * It implements [Leveler]. */ - close(): void + level(): Level } + // @ts-ignore + import loginternal = internal +} + +/** + * Package oauth2 provides support for making + * OAuth2 authorized and authenticated HTTP requests, + * as specified in RFC 6749. + * It can additionally grant authorization with Bearer JWT. + */ +namespace oauth2 { /** - * ColumnType contains the name and type of a column. + * An AuthCodeOption is passed to Config.AuthCodeURL. */ - interface ColumnType { + interface AuthCodeOption { + [key:string]: any; } - interface ColumnType { + /** + * Token represents the credentials used to authorize + * the requests to access protected resources on the OAuth 2.0 + * provider's backend. + * + * Most users of this package should not access fields of Token + * directly. They're exported mostly for use by related packages + * implementing derivative OAuth2 flows. + */ + interface Token { /** - * Name returns the name or alias of the column. + * AccessToken is the token that authorizes and authenticates + * the requests. */ - name(): string - } - interface ColumnType { + accessToken: string /** - * Length returns the column type length for variable length column types such - * as text and binary field types. If the type length is unbounded the value will - * be [math.MaxInt64] (any database limits will still apply). - * If the column type is not variable length, such as an int, or if not supported - * by the driver ok is false. + * TokenType is the type of token. + * The Type method returns either this or "Bearer", the default. */ - length(): [number, boolean] - } - interface ColumnType { + tokenType: string /** - * DecimalSize returns the scale and precision of a decimal type. - * If not applicable or if not supported ok is false. + * RefreshToken is a token that's used by the application + * (as opposed to the user) to refresh the access token + * if it expires. */ - decimalSize(): [number, number, boolean] - } - interface ColumnType { + refreshToken: string /** - * ScanType returns a Go type suitable for scanning into using [Rows.Scan]. - * If a driver does not support this property ScanType will return - * the type of an empty interface. + * Expiry is the optional expiration time of the access token. + * + * If zero, [TokenSource] implementations will reuse the same + * token forever and RefreshToken or equivalent + * mechanisms for that TokenSource will not be used. */ - scanType(): any - } - interface ColumnType { + expiry: time.Time /** - * Nullable reports whether the column may be null. - * If a driver does not support this property ok will be false. + * ExpiresIn is the OAuth2 wire format "expires_in" field, + * which specifies how many seconds later the token expires, + * relative to an unknown time base approximately around "now". + * It is the application's responsibility to populate + * `Expiry` from `ExpiresIn` when required. */ - nullable(): [boolean, boolean] + expiresIn: number } - interface ColumnType { + interface Token { /** - * DatabaseTypeName returns the database system name of the column type. If an empty - * string is returned, then the driver type name is not supported. - * Consult your driver documentation for a list of driver data types. [ColumnType.Length] specifiers - * are not included. - * Common type names include "VARCHAR", "TEXT", "NVARCHAR", "DECIMAL", "BOOL", - * "INT", and "BIGINT". + * Type returns t.TokenType if non-empty, else "Bearer". */ - databaseTypeName(): string - } - /** - * Row is the result of calling [DB.QueryRow] to select a single row. - */ - interface Row { + type(): string } - interface Row { + interface Token { /** - * Scan copies the columns from the matched row into the values - * pointed at by dest. See the documentation on [Rows.Scan] for details. - * If more than one row matches the query, - * Scan uses the first row and discards the rest. If no row matches - * the query, Scan returns [ErrNoRows]. + * SetAuthHeader sets the Authorization header to r using the access + * token in t. + * + * This method is unnecessary when using [Transport] or an HTTP Client + * returned by this package. */ - scan(...dest: any[]): void + setAuthHeader(r: http.Request): void } - interface Row { + interface Token { /** - * Err provides a way for wrapping packages to check for - * query errors without calling [Row.Scan]. - * Err returns the error, if any, that was encountered while running the query. - * If this error is not nil, this error will also be returned from [Row.Scan]. + * WithExtra returns a new [Token] that's a clone of t, but using the + * provided raw extra map. This is only intended for use by packages + * implementing derivative OAuth2 flows. */ - err(): void + withExtra(extra: any): (Token) } -} - -namespace types { -} - -namespace search { - /** - * MultiMatchSubquery defines a multi-match record subquery expression. - */ - interface MultiMatchSubquery { - targetTableAlias: string - fromTableName: string - fromTableAlias: string - valueIdentifier: string - joins: Array<(Join | undefined)> - params: dbx.Params + interface Token { + /** + * Extra returns an extra field. + * Extra fields are key-value pairs returned by the server as + * part of the token retrieval response. + */ + extra(key: string): any } - interface MultiMatchSubquery { + interface Token { /** - * Build converts the expression into a SQL fragment. - * - * Implements [dbx.Expression] interface. + * Valid reports whether t is non-nil, has an AccessToken, and is not expired. */ - build(db: dbx.DB, params: dbx.Params): string + valid(): boolean } - interface NullFallbackPreference extends Number{} -} - -namespace subscriptions { } namespace router { @@ -24063,6 +24370,75 @@ namespace router { } } +namespace cobra { + interface PositionalArgs {(cmd: Command, args: Array): void } + // @ts-ignore + import flag = pflag + /** + * FParseErrWhitelist configures Flag parse errors to be ignored + */ + interface FParseErrWhitelist extends _TygojaAny{} + /** + * Group Structure to manage groups for commands + */ + interface Group { + id: string + title: string + } + /** + * CompletionOptions are the options to control shell completion + */ + interface CompletionOptions { + /** + * DisableDefaultCmd prevents Cobra from creating a default 'completion' command + */ + disableDefaultCmd: boolean + /** + * DisableNoDescFlag prevents Cobra from creating the '--no-descriptions' flag + * for shells that support completion descriptions + */ + disableNoDescFlag: boolean + /** + * DisableDescriptions turns off all completion descriptions for shells + * that support them + */ + disableDescriptions: boolean + /** + * HiddenDefaultCmd makes the default 'completion' command hidden + */ + hiddenDefaultCmd: boolean + /** + * DefaultShellCompDirective sets the ShellCompDirective that is returned + * if no special directive can be determined + */ + defaultShellCompDirective?: ShellCompDirective + } + interface CompletionOptions { + setDefaultShellCompDirective(directive: ShellCompDirective): void + } + /** + * Completion is a string that can be used for completions + * + * two formats are supported: + * ``` + * - the completion choice + * - the completion choice with a textual description (separated by a TAB). + * ``` + * + * [CompletionWithDesc] can be used to create a completion string with a textual description. + * + * Note: Go type alias is used to provide a more descriptive name in the documentation, but any string can be used. + */ + interface Completion extends String{} + /** + * CompletionFunc is a function that provides completion results. + */ + interface CompletionFunc {(cmd: Command, args: Array, toComplete: string): [Array, ShellCompDirective] } +} + +namespace subscriptions { +} + namespace url { /** * The Userinfo type is an immutable encapsulation of username and @@ -24093,6 +24469,16 @@ namespace url { } } +namespace cobra { + // @ts-ignore + import flag = pflag + /** + * ShellCompDirective is a bit map representing the different behaviors the shell + * can be instructed to have once completions have been provided. + */ + interface ShellCompDirective extends Number{} +} + namespace multipart { /** * A Part represents a single part in a multipart body. @@ -24143,12 +24529,18 @@ namespace http { */ interface SameSite extends Number{} // @ts-ignore - import mathrand = rand - // @ts-ignore import urlpkg = url } -namespace oauth2 { +namespace search { + /** + * Join defines common fields required for a single SQL JOIN clause. + */ + interface Join { + tableName: string + tableAlias: string + on: dbx.Expression + } } namespace slog { @@ -24331,25 +24723,7 @@ namespace slog { } } -namespace cobra { - // @ts-ignore - import flag = pflag - /** - * ShellCompDirective is a bit map representing the different behaviors the shell - * can be instructed to have once completions have been provided. - */ - interface ShellCompDirective extends Number{} -} - -namespace search { - /** - * Join defines common fields required for a single SQL JOIN clause. - */ - interface Join { - tableName: string - tableAlias: string - on: dbx.Expression - } +namespace oauth2 { } namespace router { diff --git a/plugins/migratecmd/migratecmd.go b/plugins/migratecmd/migratecmd.go index 38485ff121..6d426ea8d4 100644 --- a/plugins/migratecmd/migratecmd.go +++ b/plugins/migratecmd/migratecmd.go @@ -194,7 +194,8 @@ func (p *plugin) migrateCreateHandler(template string, args []string, interactiv } func (p *plugin) migrateCollectionsHandler(args []string, interactive bool) (string, error) { - createArgs := []string{"collections_snapshot"} + createArgs := make([]string, 0, len(args)+1) + createArgs = append(createArgs, "collections_snapshot") createArgs = append(createArgs, args...) collections := []*core.Collection{} diff --git a/plugins/migratecmd/migratecmd_test.go b/plugins/migratecmd/migratecmd_test.go index fab857b36d..2c0d95e961 100644 --- a/plugins/migratecmd/migratecmd_test.go +++ b/plugins/migratecmd/migratecmd_test.go @@ -93,12 +93,12 @@ migrate((app) => { "type": "text" }, { - "exceptDomains": null, + "exceptDomains": [], "help": "", "hidden": false, "id": "email@TEST_RANDOM", "name": "email", - "onlyDomains": null, + "onlyDomains": [], "presentable": false, "required": true, "system": true, @@ -200,7 +200,7 @@ migrate((app) => { package _test_migrations import ( - "encoding/json" + "encoding/json/v2" "github.com/pocketbase/pocketbase/core" m "github.com/pocketbase/pocketbase/migrations" @@ -275,12 +275,12 @@ func init() { "type": "text" }, { - "exceptDomains": null, + "exceptDomains": [], "help": "", "hidden": false, "id": "email@TEST_RANDOM", "name": "email", - "onlyDomains": null, + "onlyDomains": [], "presentable": false, "required": true, "system": true, @@ -546,12 +546,12 @@ migrate((app) => { "type": "text" }, { - "exceptDomains": null, + "exceptDomains": [], "help": "", "hidden": false, "id": "email3885137012", "name": "email", - "onlyDomains": null, + "onlyDomains": [], "presentable": false, "required": true, "system": true, @@ -649,7 +649,7 @@ migrate((app) => { package _test_migrations import ( - "encoding/json" + "encoding/json/v2" "github.com/pocketbase/pocketbase/core" m "github.com/pocketbase/pocketbase/migrations" @@ -731,12 +731,12 @@ func init() { "type": "text" }, { - "exceptDomains": null, + "exceptDomains": [], "help": "", "hidden": false, "id": "email3885137012", "name": "email", - "onlyDomains": null, + "onlyDomains": [], "presentable": false, "required": true, "system": true, @@ -1041,7 +1041,7 @@ migrate((app) => { package _test_migrations import ( - "encoding/json" + "encoding/json/v2" "github.com/pocketbase/pocketbase/core" m "github.com/pocketbase/pocketbase/migrations" diff --git a/plugins/migratecmd/templates.go b/plugins/migratecmd/templates.go index 3667deb7b3..b135529b70 100644 --- a/plugins/migratecmd/templates.go +++ b/plugins/migratecmd/templates.go @@ -2,7 +2,8 @@ package migratecmd import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "path/filepath" @@ -383,7 +384,7 @@ func (p *plugin) goCreateTemplate(collection *core.Collection) (string, error) { const template = `package %s import ( - "encoding/json" + "encoding/json/v2" "github.com/pocketbase/pocketbase/core" m "github.com/pocketbase/pocketbase/migrations" @@ -436,7 +437,7 @@ func (p *plugin) goDeleteTemplate(collection *core.Collection) (string, error) { const template = `package %s import ( - "encoding/json" + "encoding/json/v2" "github.com/pocketbase/pocketbase/core" m "github.com/pocketbase/pocketbase/migrations" @@ -621,7 +622,7 @@ func (p *plugin) goDiffTemplate(new *core.Collection, old *core.Collection) (str if strings.Contains(combined, "json.Unmarshal(") || strings.Contains(combined, "json.Marshal(") { - imports += "\n\t\"encoding/json\"\n" + imports += "\n\t\"encoding/json/v2\"\n" } imports += "\n\t\"github.com/pocketbase/pocketbase/core\"" @@ -666,7 +667,11 @@ func init() { } func marhshalWithoutEscape(v any, prefix string, indent string) ([]byte, error) { - raw, err := json.MarshalIndent(v, prefix, indent) + raw, err := json.Marshal(v, + json.Deterministic(true), + jsontext.WithIndentPrefix(prefix), + jsontext.WithIndent(indent), + ) if err != nil { return nil, err } @@ -720,8 +725,8 @@ func diffMaps(old, new map[string]any, excludeKeys ...string) map[string]any { } // compare the serialized version of the values in case of slice or other custom type - rawOld, _ := json.Marshal(vOld) - rawNew, _ := json.Marshal(vNew) + rawOld, _ := json.Marshal(vOld, json.Deterministic(true)) + rawNew, _ := json.Marshal(vNew, json.Deterministic(true)) if !bytes.Equal(rawOld, rawNew) { // if both are maps add recursively only the changed fields diff --git a/pocketbase.go b/pocketbase.go index 91b92c8105..509364c390 100644 --- a/pocketbase.go +++ b/pocketbase.go @@ -1,6 +1,7 @@ package pocketbase import ( + "errors" "io" "os" "os/signal" @@ -9,7 +10,6 @@ import ( "syscall" "time" - "github.com/fatih/color" "github.com/pocketbase/pocketbase/cmd" "github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/tools/hook" @@ -117,8 +117,9 @@ func NewWithConfig(config Config) *PocketBase { hideStartBanner: config.HideStartBanner, } - // replace with a colored stderr writer - pb.RootCmd.SetErr(newErrWriter()) + // don't write command errors to the stderr because the error is + // propagated back to the app.Start() and could result in duplication + pb.RootCmd.SetErr(&nopWrite{}) // parse base flags // (errors are ignored, since the full flags parsing happens on Execute()) @@ -183,26 +184,25 @@ func (pb *PocketBase) Execute() error { } } - done := make(chan bool, 1) + execCh := make(chan error, 1) + sigCh := make(chan os.Signal, 1) // listen for interrupt signal to gracefully shutdown the application - go func() { - sigch := make(chan os.Signal, 1) - signal.Notify(sigch, os.Interrupt, syscall.SIGTERM) - <-sigch - - done <- true - }() + signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM) // execute the root command go func() { - // note: leave to the commands to decide whether to print their error - pb.RootCmd.Execute() - - done <- true + execCh <- routine.SafeWrap(pb.RootCmd.Execute)() }() - <-done + // wait for either an OS signal or the command to complete + var execErr error + select { + case <-sigCh: + case execErr = <-execCh: + } + + signal.Stop(sigCh) // trigger cleanups // @@ -210,7 +210,7 @@ func (pb *PocketBase) Execute() error { event := new(core.TerminateEvent) event.App = pb return pb.OnTerminate().Trigger(event, func(e *core.TerminateEvent) error { - return e.App.ResetBootstrapState() + return errors.Join(e.App.ResetBootstrapState(), execErr) }) } @@ -308,24 +308,10 @@ func inspectRuntime() (baseDir string, withGoRun bool) { return } -// newErrWriter returns a red colored stderr writter. -func newErrWriter() *coloredWriter { - return &coloredWriter{ - w: os.Stderr, - c: color.New(color.FgRed), - } -} +var _ io.Writer = (*nopWrite)(nil) -// coloredWriter is a small wrapper struct to construct a [color.Color] writter. -type coloredWriter struct { - w io.Writer - c *color.Color -} +type nopWrite struct{} -// Write writes the p bytes using the colored writer. -func (colored *coloredWriter) Write(p []byte) (n int, err error) { - colored.c.SetWriter(colored.w) - defer colored.c.UnsetWriter(colored.w) - - return colored.c.Print(string(p)) +func (w *nopWrite) Write(p []byte) (n int, err error) { + return } diff --git a/tests/api.go b/tests/api.go index 43b820a294..f979fc1712 100644 --- a/tests/api.go +++ b/tests/api.go @@ -3,7 +3,7 @@ package tests import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "io" "maps" @@ -249,6 +249,7 @@ func (scenario *ApiScenario) test(t testing.TB) { t.Errorf("Expected status code %d, got %d", scenario.ExpectedStatus, res.StatusCode) } + // @todo consider removing in favour of synctest.Wait() if scenario.Delay > 0 { time.Sleep(scenario.Delay) } @@ -259,14 +260,16 @@ func (scenario *ApiScenario) test(t testing.TB) { } } else { // normalize json response format - buffer := new(bytes.Buffer) - err := json.Compact(buffer, recorder.Body.Bytes()) var normalizedBody string + + buf := new(bytes.Buffer) + enc := jsontext.NewEncoder(buf) + err := enc.WriteValue(recorder.Body.Bytes()) if err != nil { // not a json... normalizedBody = recorder.Body.String() } else { - normalizedBody = buffer.String() + normalizedBody = buf.String() } for _, item := range scenario.ExpectedContent { diff --git a/tests/app.go b/tests/app.go index 5299e80679..c22b9d2e6b 100644 --- a/tests/app.go +++ b/tests/app.go @@ -549,6 +549,21 @@ func NewTestAppWithConfig(config core.BaseAppConfig) (*TestApp, error) { Priority: -99999, }) + // t.OnFilesystemDelete().Bind(&hook.Handler[*core.FilesystemDeleteEvent]{ + // Func: func(e *core.FilesystemDeleteEvent) error { + // t.registerEventCall("OnFilesystemDelete") + // return e.Next() + // }, + // Priority: -99999, + // }) + // t.OnFilesystemNewWriter().Bind(&hook.Handler[*core.FilesystemNewWriterEvent]{ + // Func: func(e *core.FilesystemNewWriterEvent) error { + // t.registerEventCall("OnFilesystemNewWriter") + // return e.Next() + // }, + // Priority: -99999, + // }) + t.OnRealtimeConnectRequest().Bind(&hook.Handler[*core.RealtimeConnectRequestEvent]{ Func: func(e *core.RealtimeConnectRequestEvent) error { t.registerEventCall("OnRealtimeConnectRequest") diff --git a/tools/archive/create_test.go b/tools/archive/create_test.go index f75eb8efe3..178a01c76a 100644 --- a/tools/archive/create_test.go +++ b/tools/archive/create_test.go @@ -48,7 +48,7 @@ func TestCreateSuccess(t *testing.T) { t.Fatalf("Expected zip with name %q, got %q", zipName, name) } - expectedSize := int64(544) + expectedSize := int64(532) if size := info.Size(); size != expectedSize { t.Fatalf("Expected zip with size %d, got %d", expectedSize, size) } diff --git a/tools/auth/apple.go b/tools/auth/apple.go index 2a7da9308b..3a9cbb1830 100644 --- a/tools/auth/apple.go +++ b/tools/auth/apple.go @@ -2,7 +2,7 @@ package auth import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" diff --git a/tools/auth/auth.go b/tools/auth/auth.go index d0a9fbe8e8..fcac40e517 100644 --- a/tools/auth/auth.go +++ b/tools/auth/auth.go @@ -2,7 +2,7 @@ package auth import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "net/http" diff --git a/tools/auth/base_provider_test.go b/tools/auth/base_provider_test.go index 1ec37012ba..b5bd7af42b 100644 --- a/tools/auth/base_provider_test.go +++ b/tools/auth/base_provider_test.go @@ -3,7 +3,7 @@ package auth import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "testing" "golang.org/x/oauth2" @@ -215,12 +215,12 @@ func TestExtra(t *testing.T) { after := b.Extra() - rawExtra, err := json.Marshal(extra) + rawExtra, err := json.Marshal(extra, json.Deterministic(true)) if err != nil { t.Fatal(err) } - rawAfter, err := json.Marshal(after) + rawAfter, err := json.Marshal(after, json.Deterministic(true)) if err != nil { t.Fatal(err) } diff --git a/tools/auth/bitbucket.go b/tools/auth/bitbucket.go index 05ffa29c65..fb1f39aa87 100644 --- a/tools/auth/bitbucket.go +++ b/tools/auth/bitbucket.go @@ -2,7 +2,7 @@ package auth import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "io" diff --git a/tools/auth/box.go b/tools/auth/box.go index b93394c5e0..d139ceb36b 100644 --- a/tools/auth/box.go +++ b/tools/auth/box.go @@ -2,7 +2,7 @@ package auth import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "github.com/pocketbase/pocketbase/tools/types" diff --git a/tools/auth/discord.go b/tools/auth/discord.go index b35887193b..acd65aaa03 100644 --- a/tools/auth/discord.go +++ b/tools/auth/discord.go @@ -2,7 +2,7 @@ package auth import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "github.com/pocketbase/pocketbase/tools/types" diff --git a/tools/auth/facebook.go b/tools/auth/facebook.go index 07f6198882..dd92f980ba 100644 --- a/tools/auth/facebook.go +++ b/tools/auth/facebook.go @@ -2,7 +2,7 @@ package auth import ( "context" - "encoding/json" + "encoding/json/v2" "github.com/pocketbase/pocketbase/tools/types" "golang.org/x/oauth2" @@ -53,12 +53,14 @@ func (p *Facebook) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) { } extracted := struct { - Id string - Name string - Email string + Id string `json:"id"` + Name string `json:"name"` + Email string `json:"email"` Picture struct { - Data struct{ Url string } - } + Data struct { + Url string `json:"url"` + } `json:"data"` + } `json:"picture"` }{} if err := json.Unmarshal(data, &extracted); err != nil { return nil, err diff --git a/tools/auth/gitea.go b/tools/auth/gitea.go index 12090d82b5..9d25f232a6 100644 --- a/tools/auth/gitea.go +++ b/tools/auth/gitea.go @@ -2,7 +2,7 @@ package auth import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -119,9 +119,9 @@ func (p *Gitea) fetchVerifiedPrimaryEmail(token *oauth2.Token) (string, error) { } emails := []struct { - Email string - Verified bool - Primary bool + Email string `json:"email"` + Verified bool `json:"verified"` + Primary bool `json:"primary"` }{} if err := json.Unmarshal(content, &emails); err != nil { return "", err diff --git a/tools/auth/gitee.go b/tools/auth/gitee.go index 9bcfbeeb50..45234fedf2 100644 --- a/tools/auth/gitee.go +++ b/tools/auth/gitee.go @@ -2,7 +2,7 @@ package auth import ( "context" - "encoding/json" + "encoding/json/v2" "io" "strconv" @@ -120,9 +120,9 @@ func (p *Gitee) fetchPrimaryEmail(token *oauth2.Token) (string, error) { } emails := []struct { - Email string - State string - Scope []string + Email string `json:"email"` + State string `json:"state"` + Scope []string `json:"scope"` }{} if err := json.Unmarshal(content, &emails); err != nil { // ignore unmarshal error in case "Keep my email address private" diff --git a/tools/auth/github.go b/tools/auth/github.go index b8d858ef62..8606cc893e 100644 --- a/tools/auth/github.go +++ b/tools/auth/github.go @@ -2,7 +2,7 @@ package auth import ( "context" - "encoding/json" + "encoding/json/v2" "io" "strconv" @@ -116,9 +116,9 @@ func (p *Github) fetchVerifiedPrimaryEmail(token *oauth2.Token) (string, error) } emails := []struct { - Email string - Verified bool - Primary bool + Email string `json:"email"` + Verified bool `json:"verified"` + Primary bool `json:"primary"` }{} if err := json.Unmarshal(content, &emails); err != nil { return "", err diff --git a/tools/auth/gitlab.go b/tools/auth/gitlab.go index c879743c98..79eac0fdff 100644 --- a/tools/auth/gitlab.go +++ b/tools/auth/gitlab.go @@ -2,7 +2,7 @@ package auth import ( "context" - "encoding/json" + "encoding/json/v2" "strconv" "time" diff --git a/tools/auth/google.go b/tools/auth/google.go index 4c6fe142fe..f989080bef 100644 --- a/tools/auth/google.go +++ b/tools/auth/google.go @@ -2,7 +2,7 @@ package auth import ( "context" - "encoding/json" + "encoding/json/v2" "github.com/pocketbase/pocketbase/tools/types" "golang.org/x/oauth2" diff --git a/tools/auth/instagram.go b/tools/auth/instagram.go index 580149b7bd..43884e99f5 100644 --- a/tools/auth/instagram.go +++ b/tools/auth/instagram.go @@ -2,7 +2,7 @@ package auth import ( "context" - "encoding/json" + "encoding/json/v2" "github.com/pocketbase/pocketbase/tools/types" "golang.org/x/oauth2" diff --git a/tools/auth/internal/jwk/jwk.go b/tools/auth/internal/jwk/jwk.go index 27a1adec42..0535b039f3 100644 --- a/tools/auth/internal/jwk/jwk.go +++ b/tools/auth/internal/jwk/jwk.go @@ -7,7 +7,7 @@ import ( "crypto/ed25519" "crypto/rsa" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -105,7 +105,7 @@ func Fetch(ctx context.Context, jwksURL string, kid string) (*JWK, error) { } jwks := struct { - Keys []*JWK + Keys []*JWK `json:"keys"` }{} err = json.Unmarshal(rawBody, &jwks) diff --git a/tools/auth/internal/jwk/jwk_test.go b/tools/auth/internal/jwk/jwk_test.go index 189efcc210..4dc43f3b45 100644 --- a/tools/auth/internal/jwk/jwk_test.go +++ b/tools/auth/internal/jwk/jwk_test.go @@ -7,7 +7,7 @@ import ( "crypto/rand" "crypto/rsa" "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "math/big" "net/http" @@ -252,7 +252,7 @@ func TestValidateTokenSignature(t *testing.T) { } server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { - _ = json.NewEncoder(res).Encode(map[string]any{"keys": []*jwk.JWK{ + _ = json.MarshalWrite(res, map[string]any{"keys": []*jwk.JWK{ { Kid: "key1", Kty: "OKP", diff --git a/tools/auth/kakao.go b/tools/auth/kakao.go index 68fd5f54ce..d8c143c9bc 100644 --- a/tools/auth/kakao.go +++ b/tools/auth/kakao.go @@ -2,7 +2,7 @@ package auth import ( "context" - "encoding/json" + "encoding/json/v2" "strconv" "github.com/pocketbase/pocketbase/tools/types" diff --git a/tools/auth/lark.go b/tools/auth/lark.go index d4d93ef9e4..b8bf437aeb 100644 --- a/tools/auth/lark.go +++ b/tools/auth/lark.go @@ -2,7 +2,7 @@ package auth import ( "context" - "encoding/json" + "encoding/json/v2" "github.com/pocketbase/pocketbase/tools/types" "golang.org/x/oauth2" diff --git a/tools/auth/linear.go b/tools/auth/linear.go index bf256b1adb..708c2adeef 100644 --- a/tools/auth/linear.go +++ b/tools/auth/linear.go @@ -3,7 +3,7 @@ package auth import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "errors" "net/http" @@ -68,7 +68,6 @@ func (p *Linear) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) { } `json:"viewer"` } `json:"data"` }{} - if err := json.Unmarshal(data, &extracted); err != nil { return nil, err } diff --git a/tools/auth/livechat.go b/tools/auth/livechat.go index 0d5d480cef..ffa0be71a0 100644 --- a/tools/auth/livechat.go +++ b/tools/auth/livechat.go @@ -2,7 +2,7 @@ package auth import ( "context" - "encoding/json" + "encoding/json/v2" "github.com/pocketbase/pocketbase/tools/types" "golang.org/x/oauth2" diff --git a/tools/auth/mailcow.go b/tools/auth/mailcow.go index b7c37b9bc8..9f39dcb0d8 100644 --- a/tools/auth/mailcow.go +++ b/tools/auth/mailcow.go @@ -2,7 +2,7 @@ package auth import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "strings" diff --git a/tools/auth/microsoft.go b/tools/auth/microsoft.go index 2913467307..47d42186d8 100644 --- a/tools/auth/microsoft.go +++ b/tools/auth/microsoft.go @@ -2,7 +2,7 @@ package auth import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "slices" diff --git a/tools/auth/monday.go b/tools/auth/monday.go index 416f1822ec..3dd933702c 100644 --- a/tools/auth/monday.go +++ b/tools/auth/monday.go @@ -3,7 +3,7 @@ package auth import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "errors" "net/http" diff --git a/tools/auth/notion.go b/tools/auth/notion.go index 2a0d09dd77..685d08007d 100644 --- a/tools/auth/notion.go +++ b/tools/auth/notion.go @@ -2,7 +2,7 @@ package auth import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/pocketbase/pocketbase/tools/types" diff --git a/tools/auth/oidc.go b/tools/auth/oidc.go index 9390eb9352..e9a30a8186 100644 --- a/tools/auth/oidc.go +++ b/tools/auth/oidc.go @@ -2,7 +2,7 @@ package auth import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "os" diff --git a/tools/auth/patreon.go b/tools/auth/patreon.go index 3a85fc646e..e3b4ca31cf 100644 --- a/tools/auth/patreon.go +++ b/tools/auth/patreon.go @@ -2,7 +2,7 @@ package auth import ( "context" - "encoding/json" + "encoding/json/v2" "github.com/pocketbase/pocketbase/tools/types" "golang.org/x/oauth2" diff --git a/tools/auth/planningcenter.go b/tools/auth/planningcenter.go index 1658f234da..3ba5136946 100644 --- a/tools/auth/planningcenter.go +++ b/tools/auth/planningcenter.go @@ -2,7 +2,7 @@ package auth import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "github.com/pocketbase/pocketbase/tools/types" @@ -62,7 +62,7 @@ func (p *Planningcenter) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) { // don't map the email because users can have multiple assigned // and it's not clear if they are verified } - } + } `json:"data"` }{} if err := json.Unmarshal(data, &extracted); err != nil { return nil, err diff --git a/tools/auth/spotify.go b/tools/auth/spotify.go index 5f4d44c502..1401cf4bdd 100644 --- a/tools/auth/spotify.go +++ b/tools/auth/spotify.go @@ -2,7 +2,7 @@ package auth import ( "context" - "encoding/json" + "encoding/json/v2" "github.com/pocketbase/pocketbase/tools/types" "golang.org/x/oauth2" diff --git a/tools/auth/strava.go b/tools/auth/strava.go index 510fb03d61..3efd907468 100644 --- a/tools/auth/strava.go +++ b/tools/auth/strava.go @@ -2,7 +2,7 @@ package auth import ( "context" - "encoding/json" + "encoding/json/v2" "strconv" "github.com/pocketbase/pocketbase/tools/types" diff --git a/tools/auth/trakt.go b/tools/auth/trakt.go index 740f31bded..affa4e430b 100644 --- a/tools/auth/trakt.go +++ b/tools/auth/trakt.go @@ -2,7 +2,7 @@ package auth import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/pocketbase/pocketbase/tools/types" diff --git a/tools/auth/twitch.go b/tools/auth/twitch.go index 2b336820ec..028ce0ae26 100644 --- a/tools/auth/twitch.go +++ b/tools/auth/twitch.go @@ -2,7 +2,7 @@ package auth import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "net/http" diff --git a/tools/auth/twitter.go b/tools/auth/twitter.go index b57125c72e..26de646dfb 100644 --- a/tools/auth/twitter.go +++ b/tools/auth/twitter.go @@ -2,7 +2,7 @@ package auth import ( "context" - "encoding/json" + "encoding/json/v2" "github.com/pocketbase/pocketbase/tools/types" "golang.org/x/oauth2" diff --git a/tools/auth/vk.go b/tools/auth/vk.go index fc75d20021..1f91483ea4 100644 --- a/tools/auth/vk.go +++ b/tools/auth/vk.go @@ -2,7 +2,7 @@ package auth import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "strconv" @@ -69,7 +69,6 @@ func (p *VK) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) { AvatarURL string `json:"photo_max"` } `json:"response"` }{} - if err := json.Unmarshal(data, &extracted); err != nil { return nil, err } diff --git a/tools/auth/wakatime.go b/tools/auth/wakatime.go index d2fede3b52..b67f66ff4e 100644 --- a/tools/auth/wakatime.go +++ b/tools/auth/wakatime.go @@ -2,7 +2,7 @@ package auth import ( "context" - "encoding/json" + "encoding/json/v2" "github.com/pocketbase/pocketbase/tools/types" "golang.org/x/oauth2" @@ -62,7 +62,6 @@ func (p *Wakatime) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) { IsEmailConfirmed bool `json:"is_email_confirmed"` } `json:"data"` }{} - if err := json.Unmarshal(data, &extracted); err != nil { return nil, err } diff --git a/tools/auth/yandex.go b/tools/auth/yandex.go index 024b890da2..fb02e0afa5 100644 --- a/tools/auth/yandex.go +++ b/tools/auth/yandex.go @@ -2,7 +2,7 @@ package auth import ( "context" - "encoding/json" + "encoding/json/v2" "github.com/pocketbase/pocketbase/tools/types" "golang.org/x/oauth2" diff --git a/tools/cron/cron_test.go b/tools/cron/cron_test.go index 9b6dc33f28..018e468dc6 100644 --- a/tools/cron/cron_test.go +++ b/tools/cron/cron_test.go @@ -1,10 +1,11 @@ package cron import ( - "encoding/json" + "encoding/json/v2" "slices" "sync" "testing" + "testing/synctest" "time" ) @@ -128,7 +129,7 @@ func TestCronAddAndRemove(t *testing.T) { "test5": `{"minutes":{"1":{}},"hours":{"2":{}},"days":{"3":{}},"months":{"4":{}},"daysOfWeek":{"5":{}}}`, } for k, v := range expectedSchedules { - raw, err := json.Marshal(indexedJobs[k].schedule) + raw, err := json.Marshal(indexedJobs[k].schedule, json.Deterministic(true)) if err != nil { t.Fatal(err) } @@ -254,63 +255,65 @@ func TestCronJobs(t *testing.T) { func TestCronStartStop(t *testing.T) { t.Parallel() - var mu sync.Mutex + synctest.Test(t, func(t *testing.T) { + var mu sync.Mutex - test1 := 0 - test2 := 0 + test1 := 0 + test2 := 0 - c := New() + c := New() - c.SetInterval(250 * time.Millisecond) + c.SetInterval(250 * time.Millisecond) - c.Add("test1", "* * * * *", func() { - mu.Lock() - defer mu.Unlock() - test1++ - }) + c.Add("test1", "* * * * *", func() { + mu.Lock() + defer mu.Unlock() + test1++ + }) - c.Add("test2", "* * * * *", func() { - mu.Lock() - defer mu.Unlock() - test2++ - }) + c.Add("test2", "* * * * *", func() { + mu.Lock() + defer mu.Unlock() + test2++ + }) - // call twice Start to check if the previous ticker will be reseted - c.Start() - c.Start() + // call twice Start to check if the previous ticker will be reseted + c.Start() + c.Start() - time.Sleep(505 * time.Millisecond) // slightly larger to minimize flakiness + synctest.Sleep(500 * time.Millisecond) - // call twice Stop to ensure that the second stop is no-op - c.Stop() - c.Stop() + // call twice Stop to ensure that the second stop is no-op + c.Stop() + c.Stop() - expectedCalls := 2 + expectedCalls := 2 - mu.Lock() - if test1 != expectedCalls { - t.Fatalf("Expected %d test1, got %d", expectedCalls, test1) - } - if test2 != expectedCalls { - t.Fatalf("Expected %d test2, got %d", expectedCalls, test2) - } - mu.Unlock() + mu.Lock() + if test1 != expectedCalls { + t.Fatalf("Expected %d test1, got %d", expectedCalls, test1) + } + if test2 != expectedCalls { + t.Fatalf("Expected %d test2, got %d", expectedCalls, test2) + } + mu.Unlock() - // resume for 1 seconds - c.Start() + // resume for 1 seconds + c.Start() - time.Sleep(1005 * time.Millisecond) // slightly larger to minimize flakiness + synctest.Sleep(1000 * time.Millisecond) - c.Stop() + c.Stop() - expectedCalls += 4 + expectedCalls += 4 - mu.Lock() - if test1 != expectedCalls { - t.Fatalf("Expected %d test1, got %d", expectedCalls, test1) - } - if test2 != expectedCalls { - t.Fatalf("Expected %d test2, got %d", expectedCalls, test2) - } - mu.Unlock() + mu.Lock() + if test1 != expectedCalls { + t.Fatalf("Expected %d test1, got %d", expectedCalls, test1) + } + if test2 != expectedCalls { + t.Fatalf("Expected %d test2, got %d", expectedCalls, test2) + } + mu.Unlock() + }) } diff --git a/tools/cron/job.go b/tools/cron/job.go index 8fcabd9c7d..7827da52bc 100644 --- a/tools/cron/job.go +++ b/tools/cron/job.go @@ -1,6 +1,6 @@ package cron -import "encoding/json" +import "encoding/json/v2" // Job defines a single registered cron job. type Job struct { diff --git a/tools/cron/job_test.go b/tools/cron/job_test.go index 9ce2e8efa5..04fcd98eea 100644 --- a/tools/cron/job_test.go +++ b/tools/cron/job_test.go @@ -1,7 +1,7 @@ package cron import ( - "encoding/json" + "encoding/json/v2" "testing" ) @@ -59,7 +59,7 @@ func TestJobMarshalJSON(t *testing.T) { j := Job{id: "test_id", schedule: s} - raw, err := json.Marshal(j) + raw, err := json.Marshal(j, json.Deterministic(true)) if err != nil { t.Fatal(err) } diff --git a/tools/cron/schedule_test.go b/tools/cron/schedule_test.go index edc38e11be..1e7f4751d5 100644 --- a/tools/cron/schedule_test.go +++ b/tools/cron/schedule_test.go @@ -1,7 +1,7 @@ package cron_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "testing" "time" @@ -265,7 +265,7 @@ func TestNewSchedule(t *testing.T) { return } - encoded, err := json.Marshal(schedule) + encoded, err := json.Marshal(schedule, json.Deterministic(true)) if err != nil { t.Fatalf("Failed to marshalize the result schedule: %v", err) } diff --git a/tools/dbutils/index_test.go b/tools/dbutils/index_test.go index 5346c2c218..895d35c6e8 100644 --- a/tools/dbutils/index_test.go +++ b/tools/dbutils/index_test.go @@ -2,7 +2,7 @@ package dbutils_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "strings" "testing" diff --git a/tools/filesystem/file_test.go b/tools/filesystem/file_test.go index b5bca2ce47..3b46150f32 100644 --- a/tools/filesystem/file_test.go +++ b/tools/filesystem/file_test.go @@ -64,8 +64,8 @@ func TestNewFileFromPath(t *testing.T) { if match, err := regexp.Match(normalizedNamePattern, []byte(f.Name)); !match { t.Fatalf("Expected Name to match %v, got %q (%v)", normalizedNamePattern, f.Name, err) } - if f.Size != 73 { - t.Fatalf("Expected Size %v, got %v", 73, f.Size) + if f.Size != 77 { + t.Fatalf("Expected Size %v, got %v", 77, f.Size) } if _, ok := f.Reader.(*filesystem.PathReader); !ok { t.Fatalf("Expected Reader to be PathReader, got %v", f.Reader) diff --git a/tools/filesystem/filesystem.go b/tools/filesystem/filesystem.go index 319ed8c447..32185e9581 100644 --- a/tools/filesystem/filesystem.go +++ b/tools/filesystem/filesystem.go @@ -22,21 +22,42 @@ import ( "github.com/pocketbase/pocketbase/tools/filesystem/internal/fileblob" "github.com/pocketbase/pocketbase/tools/filesystem/internal/s3blob" "github.com/pocketbase/pocketbase/tools/filesystem/internal/s3blob/s3" + "github.com/pocketbase/pocketbase/tools/hook" "github.com/pocketbase/pocketbase/tools/list" "github.com/pocketbase/pocketbase/tools/routine" - // explicit webp decoder because disintegration/imaging does not support webp + // manually register the webp decoder because disintegration/imaging does not support webp _ "golang.org/x/image/webp" ) // note: the same as blob.ErrNotFound for backward compatibility with earlier versions var ErrNotFound = blob.ErrNotFound -const metadataOriginalName = "original-filename" +const MetadataOriginalName = "original-filename" + +type DeleteEvent struct { + hook.Event + Filesystem *System + FileKey string +} + +type NewWriterEvent struct { + hook.Event + Filesystem *System + FileKey string + Options *blob.WriterOptions + Writer *blob.Writer // filled only after e.Next() +} + +// @todo consider renaming type System struct { ctx context.Context bucket *blob.Bucket + + // @todo consider with the refactoring to bind on driver level + onNewWriter *hook.Hook[*NewWriterEvent] + onDelete *hook.Hook[*DeleteEvent] } // NewS3 initializes a new S3 filesystem instance. @@ -97,9 +118,43 @@ func (s *System) SetContext(ctx context.Context) { // Close releases any resources used for the related filesystem. func (s *System) Close() error { + if s.onNewWriter != nil { + s.onNewWriter.UnbindAll() + s.onNewWriter = nil + } + + if s.onDelete != nil { + s.onDelete.UnbindAll() + s.onDelete = nil + } + return s.bucket.Close() } +// OnNewWriter is a low level hook that is triggered on every new writer initialization +// (aka. when attempting to create a new file with [system.NewWriter] or [system.Upload]). +// +// Note that currently it doesn't trigger on [System.Copy] but this may change in future releases. +func (s *System) OnNewWriter() *hook.Hook[*NewWriterEvent] { + if s.onNewWriter == nil { + s.onNewWriter = &hook.Hook[*NewWriterEvent]{} + } + + return s.onNewWriter +} + +// OnDelete is a low level hook that is triggered on every [System.Delete] call. +// +// Note that the hook doesn't fire when a file is being overwritten +// by a new one, because in that case [System.Delete] is not invoked. +func (s *System) OnDelete() *hook.Hook[*DeleteEvent] { + if s.onDelete == nil { + s.onDelete = &hook.Hook[*DeleteEvent]{} + } + + return s.onDelete +} + // Exists checks if file with fileKey path exists or not. func (s *System) Exists(fileKey string) (bool, error) { return s.bucket.Exists(s.ctx, fileKey) @@ -145,7 +200,7 @@ func (s *System) GetReuploadableFile(fileKey string, preserveName bool) (*File, } name := path.Base(fileKey) - originalName := attrs.Metadata[metadataOriginalName] + originalName := attrs.Metadata[MetadataOriginalName] if originalName == "" { originalName = name } @@ -203,7 +258,7 @@ func (s *System) Upload(content []byte, fileKey string) error { ContentType: mimetype.Detect(content).String(), } - w, writerErr := s.bucket.NewWriter(s.ctx, fileKey, opts) + w, writerErr := s.NewWriter(fileKey, opts) if writerErr != nil { return writerErr } @@ -240,11 +295,11 @@ func (s *System) UploadFile(file *File, fileKey string) error { opts := &blob.WriterOptions{ ContentType: mt.String(), Metadata: map[string]string{ - metadataOriginalName: originalName, + MetadataOriginalName: originalName, }, } - w, err := s.bucket.NewWriter(s.ctx, fileKey, opts) + w, err := s.NewWriter(fileKey, opts) if err != nil { return err } @@ -282,11 +337,11 @@ func (s *System) UploadMultipart(fh *multipart.FileHeader, fileKey string) error opts := &blob.WriterOptions{ ContentType: mt.String(), Metadata: map[string]string{ - metadataOriginalName: originalName, + MetadataOriginalName: originalName, }, } - w, err := s.bucket.NewWriter(s.ctx, fileKey, opts) + w, err := s.NewWriter(fileKey, opts) if err != nil { return err } @@ -300,11 +355,68 @@ func (s *System) UploadMultipart(fh *multipart.FileHeader, fileKey string) error return w.Close() } +// NewWriter returns a new blob.Writer instance allowing direct file +// create from an io.Reader value. +// +// If a file with the specified fileKey already exists, it will be replaced. +// +// NB! Make sure to call `Close()` on the resulting writer after you are done working with it. +// +// Note: If you have a bytes slice, [filesystem.File], or a multipart header value, +// you can check also the Upload* related methods as they are more user-friendly. +// +// Example: +// +// content := strings.NewReader("Lorem ipsum dolor sit amet...") +// +// fsys, _ := filesystem.NewLocal("dir") +// defer fsys.Close() +// +// w, _ := fsys.NewWriter("example/file/key", nil) +// w.ReadFrom(content) +// w.Close() +func (s *System) NewWriter(fileKey string, opts *blob.WriterOptions) (*blob.Writer, error) { + if s.onNewWriter == nil { + return s.bucket.NewWriter(s.ctx, fileKey, opts) + } + + event := new(NewWriterEvent) + event.Filesystem = s + event.FileKey = fileKey + event.Options = opts + + err := s.onNewWriter.Trigger(event, func(e *NewWriterEvent) error { + writer, err := e.Filesystem.bucket.NewWriter(e.Filesystem.ctx, e.FileKey, e.Options) + if err != nil { + return err + } + + e.Writer = writer + + return nil + }) + if err != nil { + return nil, err + } + + return event.Writer, nil +} + // Delete deletes stored file at fileKey location. // // If the file doesn't exist returns ErrNotFound. func (s *System) Delete(fileKey string) error { - return s.bucket.Delete(s.ctx, fileKey) + if s.onDelete == nil { + return s.bucket.Delete(s.ctx, fileKey) + } + + event := new(DeleteEvent) + event.Filesystem = s + event.FileKey = fileKey + + return s.onDelete.Trigger(event, func(e *DeleteEvent) error { + return e.Filesystem.bucket.Delete(e.Filesystem.ctx, e.FileKey) + }) } // DeletePrefix deletes everything starting with the specified prefix. @@ -383,7 +495,7 @@ func (s *System) DeletePrefix(prefix string) []error { // A trailing slash will be appended to a non-empty dir string argument // to ensure that the checked prefix is a "directory". // -// Returns "false" in case the has at least one file, otherwise - "true". +// Returns "false" in case it has at least one file, otherwise - "true". func (s *System) IsEmptyDir(dir string) bool { if dir != "" && !strings.HasSuffix(dir, "/") { dir += "/" @@ -464,7 +576,7 @@ func (s *System) Serve(res http.ResponseWriter, req *http.Request, fileKey strin extContentType = ct } - setHeaderIfMissing(res, "Content-Disposition", disposition+"; filename="+name) + setHeaderIfMissing(res, "Content-Disposition", disposition+"; filename="+strconv.Quote(name)) setHeaderIfMissing(res, "Content-Type", extContentType) setHeaderIfMissing(res, "Content-Security-Policy", "default-src 'none'; media-src 'self'; style-src 'unsafe-inline'; sandbox") @@ -579,7 +691,7 @@ func (s *System) createThumb(originalKey, thumbKey, thumbSize string) error { } // open a thumb storage writer (aka. prepare for upload) - w, err := s.bucket.NewWriter(s.ctx, thumbKey, opts) + w, err := s.NewWriter(thumbKey, opts) if err != nil { return err } diff --git a/tools/filesystem/filesystem_test.go b/tools/filesystem/filesystem_test.go index f3b4d5401e..c470713e88 100644 --- a/tools/filesystem/filesystem_test.go +++ b/tools/filesystem/filesystem_test.go @@ -17,9 +17,10 @@ import ( "github.com/gabriel-vasile/mimetype" "github.com/pocketbase/pocketbase/tools/filesystem" + "github.com/pocketbase/pocketbase/tools/filesystem/blob" ) -func TestFileSystemExists(t *testing.T) { +func TestFilesystemExists(t *testing.T) { dir := createTestDir(t) defer os.RemoveAll(dir) @@ -29,6 +30,8 @@ func TestFileSystemExists(t *testing.T) { } defer fsys.Close() + hookCalls := bindHooks(fsys) + scenarios := []struct { file string exists bool @@ -52,9 +55,11 @@ func TestFileSystemExists(t *testing.T) { } }) } + + checkHooks(t, hookCalls, map[string]int{"OnDelete": 0, "OnNewWriter": 0}) } -func TestFileSystemAttributes(t *testing.T) { +func TestFilesystemAttributes(t *testing.T) { dir := createTestDir(t) defer os.RemoveAll(dir) @@ -64,6 +69,8 @@ func TestFileSystemAttributes(t *testing.T) { } defer fsys.Close() + hookCalls := bindHooks(fsys) + scenarios := []struct { file string expectError bool @@ -94,9 +101,11 @@ func TestFileSystemAttributes(t *testing.T) { } }) } + + checkHooks(t, hookCalls, map[string]int{"OnDelete": 0, "OnNewWriter": 0}) } -func TestFileSystemDelete(t *testing.T) { +func TestFilesystemNewWriter(t *testing.T) { dir := createTestDir(t) defer os.RemoveAll(dir) @@ -106,6 +115,73 @@ func TestFileSystemDelete(t *testing.T) { } defer fsys.Close() + hookCalls := bindHooks(fsys) + + testKey := "test_writer" + + opts := &blob.WriterOptions{ + Metadata: map[string]string{"test": "abc"}, + } + + w, err := fsys.NewWriter("test_writer", opts) + if err != nil { + t.Fatal(err) + } + + expectedContent := "test_content" + + _, err = w.ReadFrom(strings.NewReader(expectedContent)) + if err != nil { + t.Fatal(err) + } + + err = w.Close() + if err != nil { + t.Fatal(err) + } + + // verify that the file attributes and content were properly saved + // --------------------------------------------------------------- + attrs, err := fsys.Attributes(testKey) + if err != nil { + t.Fatal(err) + } + if attrs.Metadata["test"] != "abc" { + t.Fatalf("Expected metadata test:abc, got %v", attrs.Metadata) + } + + r, err := fsys.GetReader(testKey) + if err != nil { + t.Fatal(err) + } + + var buf bytes.Buffer + + _, err = io.Copy(&buf, r) + if err != nil { + t.Fatal(err) + } + + result := buf.String() + if result != expectedContent { + t.Fatalf("Expected file content\n%s\ngot\n%s", expectedContent, result) + } + + checkHooks(t, hookCalls, map[string]int{"OnDelete": 0, "OnNewWriter": 1}) +} + +func TestFilesystemDelete(t *testing.T) { + dir := createTestDir(t) + defer os.RemoveAll(dir) + + fsys, err := filesystem.NewLocal(dir) + if err != nil { + t.Fatal(err) + } + defer fsys.Close() + + hookCalls := bindHooks(fsys) + if err := fsys.Delete("missing.txt"); err == nil || !errors.Is(err, filesystem.ErrNotFound) { t.Fatalf("Expected ErrNotFound error, got %v", err) } @@ -113,9 +189,11 @@ func TestFileSystemDelete(t *testing.T) { if err := fsys.Delete("image.png"); err != nil { t.Fatalf("Expected nil, got error %v", err) } + + checkHooks(t, hookCalls, map[string]int{"OnDelete": 2, "OnNewWriter": 0}) } -func TestFileSystemDeletePrefixWithoutTrailingSlash(t *testing.T) { +func TestFilesystemDeletePrefixWithoutTrailingSlash(t *testing.T) { dir := createTestDir(t) defer os.RemoveAll(dir) @@ -125,6 +203,8 @@ func TestFileSystemDeletePrefixWithoutTrailingSlash(t *testing.T) { } defer fsys.Close() + hookCalls := bindHooks(fsys) + if errs := fsys.DeletePrefix(""); len(errs) == 0 { t.Fatal("Expected error, got nil", errs) } @@ -149,9 +229,11 @@ func TestFileSystemDeletePrefixWithoutTrailingSlash(t *testing.T) { if _, err := os.Stat(filepath.Join(dir, "test")); os.IsNotExist(err) { t.Fatal("Expected the prefix dir to remain") } + + checkHooks(t, hookCalls, map[string]int{"OnDelete": 2, "OnNewWriter": 0}) } -func TestFileSystemDeletePrefixWithTrailingSlash(t *testing.T) { +func TestFilesystemDeletePrefixWithTrailingSlash(t *testing.T) { dir := createTestDir(t) defer os.RemoveAll(dir) @@ -161,6 +243,8 @@ func TestFileSystemDeletePrefixWithTrailingSlash(t *testing.T) { } defer fsys.Close() + hookCalls := bindHooks(fsys) + if errs := fsys.DeletePrefix("missing/"); len(errs) != 0 { t.Fatalf("Not existing prefix shouldn't error, got %v", errs) } @@ -181,9 +265,11 @@ func TestFileSystemDeletePrefixWithTrailingSlash(t *testing.T) { if _, err := os.Stat(filepath.Join(dir, "test")); !os.IsNotExist(err) { t.Fatal("Expected the prefix dir to be deleted") } + + checkHooks(t, hookCalls, map[string]int{"OnDelete": 4, "OnNewWriter": 0}) } -func TestFileSystemIsEmptyDir(t *testing.T) { +func TestFilesystemIsEmptyDir(t *testing.T) { dir := createTestDir(t) defer os.RemoveAll(dir) @@ -193,6 +279,8 @@ func TestFileSystemIsEmptyDir(t *testing.T) { } defer fsys.Close() + hookCalls := bindHooks(fsys) + scenarios := []struct { dir string expected bool @@ -216,9 +304,11 @@ func TestFileSystemIsEmptyDir(t *testing.T) { } }) } + + checkHooks(t, hookCalls, map[string]int{"OnDelete": 0, "OnNewWriter": 0}) } -func TestFileSystemUploadMultipart(t *testing.T) { +func TestFilesystemUploadMultipart(t *testing.T) { dir := createTestDir(t) defer os.RemoveAll(dir) @@ -248,6 +338,8 @@ func TestFileSystemUploadMultipart(t *testing.T) { } defer fsys.Close() + hookCalls := bindHooks(fsys) + fileKey := "newdir/newkey.txt" uploadErr := fsys.UploadMultipart(fh, fileKey) @@ -266,9 +358,11 @@ func TestFileSystemUploadMultipart(t *testing.T) { if name, ok := attrs.Metadata["original-filename"]; !ok || name != "test" { t.Fatalf("Expected original-filename to be %q, got %q", "test", name) } + + checkHooks(t, hookCalls, map[string]int{"OnDelete": 0, "OnNewWriter": 1}) } -func TestFileSystemUploadFile(t *testing.T) { +func TestFilesystemUploadFile(t *testing.T) { dir := createTestDir(t) defer os.RemoveAll(dir) @@ -278,6 +372,8 @@ func TestFileSystemUploadFile(t *testing.T) { } defer fsys.Close() + hookCalls := bindHooks(fsys) + fileKey := "newdir/newkey.txt" file, err := filesystem.NewFileFromPath(filepath.Join(dir, "image.svg")) @@ -303,9 +399,11 @@ func TestFileSystemUploadFile(t *testing.T) { if name, ok := attrs.Metadata["original-filename"]; !ok || name != file.OriginalName { t.Fatalf("Expected original-filename to be %q, got %q", file.OriginalName, name) } + + checkHooks(t, hookCalls, map[string]int{"OnDelete": 0, "OnNewWriter": 1}) } -func TestFileSystemUpload(t *testing.T) { +func TestFilesystemUpload(t *testing.T) { dir := createTestDir(t) defer os.RemoveAll(dir) @@ -315,6 +413,8 @@ func TestFileSystemUpload(t *testing.T) { } defer fsys.Close() + hookCalls := bindHooks(fsys) + fileKey := "newdir/newkey.txt" uploadErr := fsys.Upload([]byte("demo"), fileKey) @@ -325,9 +425,11 @@ func TestFileSystemUpload(t *testing.T) { if exists, _ := fsys.Exists(fileKey); !exists { t.Fatalf("Expected %s to exist", fileKey) } + + checkHooks(t, hookCalls, map[string]int{"OnDelete": 0, "OnNewWriter": 1}) } -func TestFileSystemServe(t *testing.T) { +func TestFilesystemServe(t *testing.T) { dir := createTestDir(t) defer os.RemoveAll(dir) @@ -337,6 +439,8 @@ func TestFileSystemServe(t *testing.T) { } defer fsys.Close() + hookCalls := bindHooks(fsys) + csp := "default-src 'none'; media-src 'self'; style-src 'unsafe-inline'; sandbox" cacheControl := "max-age=2592000, stale-while-revalidate=86400" @@ -365,7 +469,7 @@ func TestFileSystemServe(t *testing.T) { nil, false, map[string]string{ - "Content-Disposition": "attachment; filename=test_name.txt", + "Content-Disposition": `attachment; filename="test_name.txt"`, "Content-Type": "application/octet-stream", "Content-Length": "4", "Content-Security-Policy": csp, @@ -380,9 +484,9 @@ func TestFileSystemServe(t *testing.T) { nil, false, map[string]string{ - "Content-Disposition": "inline; filename=test_name.png", + "Content-Disposition": `inline; filename="test_name.png"`, "Content-Type": "image/png", - "Content-Length": "73", + "Content-Length": "77", "Content-Security-Policy": csp, "Cache-Control": cacheControl, }, @@ -395,9 +499,9 @@ func TestFileSystemServe(t *testing.T) { nil, false, map[string]string{ - "Content-Disposition": "attachment; filename=test_name_download.png", + "Content-Disposition": `attachment; filename="test_name_download.png"`, "Content-Type": "image/png", - "Content-Length": "73", + "Content-Length": "77", "Content-Security-Policy": csp, "Cache-Control": cacheControl, }, @@ -410,7 +514,7 @@ func TestFileSystemServe(t *testing.T) { nil, false, map[string]string{ - "Content-Disposition": "attachment; filename=test_name.abc", + "Content-Disposition": `attachment; filename="test_name.abc"`, "Content-Type": "image/svg+xml", "Content-Length": "0", "Content-Security-Policy": csp, @@ -425,7 +529,7 @@ func TestFileSystemServe(t *testing.T) { nil, false, map[string]string{ - "Content-Disposition": "attachment; filename=test_name", + "Content-Disposition": `attachment; filename="test_name"`, "Content-Type": "text/css", "Content-Length": "0", "Content-Security-Policy": csp, @@ -440,7 +544,7 @@ func TestFileSystemServe(t *testing.T) { nil, false, map[string]string{ - "Content-Disposition": "attachment; filename=test_name.abc", + "Content-Disposition": `attachment; filename="test_name.abc"`, "Content-Type": "text/javascript", "Content-Length": "0", "Content-Security-Policy": csp, @@ -455,7 +559,7 @@ func TestFileSystemServe(t *testing.T) { nil, false, map[string]string{ - "Content-Disposition": "attachment; filename=test_name.abc", + "Content-Disposition": `attachment; filename="test_name.abc"`, "Content-Type": "text/javascript", "Content-Length": "0", "Content-Security-Policy": csp, @@ -470,7 +574,7 @@ func TestFileSystemServe(t *testing.T) { nil, false, map[string]string{ - "Content-Disposition": "attachment; filename=test_name.abc", + "Content-Disposition": `attachment; filename="test_name.abc"`, "Content-Type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "Content-Length": "0", "Content-Security-Policy": csp, @@ -485,7 +589,7 @@ func TestFileSystemServe(t *testing.T) { nil, false, map[string]string{ - "Content-Disposition": "attachment; filename=test_name.abc", + "Content-Disposition": `attachment; filename="test_name.abc"`, "Content-Type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "Content-Length": "0", "Content-Security-Policy": csp, @@ -500,7 +604,7 @@ func TestFileSystemServe(t *testing.T) { nil, false, map[string]string{ - "Content-Disposition": "attachment; filename=test_name.abc", + "Content-Disposition": `attachment; filename="test_name.abc"`, "Content-Type": "application/vnd.openxmlformats-officedocument.presentationml.presentation", "Content-Length": "0", "Content-Security-Policy": csp, @@ -569,9 +673,11 @@ func TestFileSystemServe(t *testing.T) { } }) } + + checkHooks(t, hookCalls, map[string]int{"OnDelete": 0, "OnNewWriter": 0}) } -func TestFileSystemGetReader(t *testing.T) { +func TestFilesystemGetReader(t *testing.T) { dir := createTestDir(t) defer os.RemoveAll(dir) @@ -581,6 +687,8 @@ func TestFileSystemGetReader(t *testing.T) { } defer fsys.Close() + hookCalls := bindHooks(fsys) + scenarios := []struct { file string expectError bool @@ -621,9 +729,11 @@ func TestFileSystemGetReader(t *testing.T) { } }) } + + checkHooks(t, hookCalls, map[string]int{"OnDelete": 0, "OnNewWriter": 0}) } -func TestFileSystemGetReuploadableFile(t *testing.T) { +func TestFilesystemGetReuploadableFile(t *testing.T) { dir := createTestDir(t) defer os.RemoveAll(dir) @@ -633,6 +743,8 @@ func TestFileSystemGetReuploadableFile(t *testing.T) { } defer fsys.Close() + hookCalls := bindHooks(fsys) + t.Run("missing.txt", func(t *testing.T) { _, err := fsys.GetReuploadableFile("missing.txt", false) if err == nil { @@ -699,9 +811,11 @@ func TestFileSystemGetReuploadableFile(t *testing.T) { testReader(t, file, "sub1") }) + + checkHooks(t, hookCalls, map[string]int{"OnDelete": 0, "OnNewWriter": 0}) } -func TestFileSystemCopy(t *testing.T) { +func TestFilesystemCopy(t *testing.T) { dir := createTestDir(t) defer os.RemoveAll(dir) @@ -711,6 +825,8 @@ func TestFileSystemCopy(t *testing.T) { } defer fsys.Close() + hookCalls := bindHooks(fsys) + src := "image.png" dst := "image.png_copy" @@ -730,12 +846,14 @@ func TestFileSystemCopy(t *testing.T) { } defer f.Close() - if f.Size() != 73 { - t.Fatalf("Expected file size %d, got %d", 73, f.Size()) + if f.Size() != 77 { + t.Fatalf("Expected file size %d, got %d", 77, f.Size()) } + + checkHooks(t, hookCalls, map[string]int{"OnDelete": 0, "OnNewWriter": 0}) } -func TestFileSystemList(t *testing.T) { +func TestFilesystemList(t *testing.T) { dir := createTestDir(t) defer os.RemoveAll(dir) @@ -745,6 +863,8 @@ func TestFileSystemList(t *testing.T) { } defer fsys.Close() + hookCalls := bindHooks(fsys) + scenarios := []struct { prefix string expected []string @@ -807,9 +927,11 @@ func TestFileSystemList(t *testing.T) { } }) } + + checkHooks(t, hookCalls, map[string]int{"OnDelete": 0, "OnNewWriter": 0}) } -func TestFileSystemServeSingleRange(t *testing.T) { +func TestFilesystemServeSingleRange(t *testing.T) { dir := createTestDir(t) defer os.RemoveAll(dir) @@ -819,6 +941,8 @@ func TestFileSystemServeSingleRange(t *testing.T) { } defer fsys.Close() + hookCalls := bindHooks(fsys) + res := httptest.NewRecorder() req := httptest.NewRequest("GET", "/", nil) req.Header.Add("Range", "bytes=0-20") @@ -833,7 +957,7 @@ func TestFileSystemServeSingleRange(t *testing.T) { t.Fatalf("Expected StatusCode %d, got %d", http.StatusPartialContent, result.StatusCode) } - expectedRange := "bytes 0-20/73" + expectedRange := "bytes 0-20/77" if cr := result.Header.Get("Content-Range"); cr != expectedRange { t.Fatalf("Expected Content-Range %q, got %q", expectedRange, cr) } @@ -841,9 +965,11 @@ func TestFileSystemServeSingleRange(t *testing.T) { if l := result.Header.Get("Content-Length"); l != "21" { t.Fatalf("Expected Content-Length %v, got %v", 21, l) } + + checkHooks(t, hookCalls, map[string]int{"OnDelete": 0, "OnNewWriter": 0}) } -func TestFileSystemServeMultiRange(t *testing.T) { +func TestFilesystemServeMultiRange(t *testing.T) { dir := createTestDir(t) defer os.RemoveAll(dir) @@ -853,6 +979,8 @@ func TestFileSystemServeMultiRange(t *testing.T) { } defer fsys.Close() + hookCalls := bindHooks(fsys) + res := httptest.NewRecorder() req := httptest.NewRequest("GET", "/", nil) req.Header.Add("Range", "bytes=0-20, 25-30") @@ -870,18 +998,14 @@ func TestFileSystemServeMultiRange(t *testing.T) { if ct := result.Header.Get("Content-Type"); !strings.HasPrefix(ct, "multipart/byteranges; boundary=") { t.Fatalf("Expected Content-Type to be multipart/byteranges, got %v", ct) } + + checkHooks(t, hookCalls, map[string]int{"OnDelete": 0, "OnNewWriter": 0}) } -func TestFileSystemCreateThumb(t *testing.T) { +func TestFilesystemCreateThumb(t *testing.T) { dir := createTestDir(t) defer os.RemoveAll(dir) - fsys, err := filesystem.NewLocal(dir) - if err != nil { - t.Fatal(err) - } - defer fsys.Close() - scenarios := []struct { file string thumb string @@ -918,10 +1042,18 @@ func TestFileSystemCreateThumb(t *testing.T) { for _, s := range scenarios { t.Run(s.file+"_"+s.thumb+"_"+s.size, func(t *testing.T) { - err := fsys.CreateThumb(s.file, s.thumb, s.size) + fsys, err := filesystem.NewLocal(dir) + if err != nil { + t.Fatal(err) + } + defer fsys.Close() + + hookCalls := bindHooks(fsys) expectErr := s.expectedMimeType == "" + err = fsys.CreateThumb(s.file, s.thumb, s.size) + hasErr := err != nil if hasErr != expectErr { t.Fatalf("Expected hasErr to be %v, got %v (%v)", expectErr, hasErr, err) @@ -952,10 +1084,45 @@ func TestFileSystemCreateThumb(t *testing.T) { if attrsMimeType != s.expectedMimeType { t.Fatalf("Expected thumb attrs %s MimeType %q, got %q", s.thumb, s.expectedMimeType, attrsMimeType) } + + checkHooks(t, hookCalls, map[string]int{"OnDelete": 0, "OnNewWriter": 1}) }) } } +func TestFilesystemClose(t *testing.T) { + dir := createTestDir(t) + defer os.RemoveAll(dir) + + fsys, err := filesystem.NewLocal(dir) + if err != nil { + t.Fatal(err) + } + + bindHooks(fsys) + + if v := fsys.OnDelete().Length(); v != 1 { + t.Fatalf("Expected 1 OnDelete listener, got %d", v) + } + + if v := fsys.OnNewWriter().Length(); v != 1 { + t.Fatalf("Expected 1 OnNewWriter listener, got %d", v) + } + + err = fsys.Close() + if err != nil { + t.Fatal(err) + } + + if v := fsys.OnDelete().Length(); v != 0 { + t.Fatalf("Expected 0 OnDelete listeners after close, got %d", v) + } + + if v := fsys.OnNewWriter().Length(); v != 0 { + t.Fatalf("Expected 0 OnNewWriter listeners after close, got %d", v) + } +} + // --- func createTestDir(t *testing.T) string { @@ -1118,3 +1285,28 @@ func createTestDir(t *testing.T) string { return dir } + +func bindHooks(fsys *filesystem.System) map[string]int { + hookCalls := map[string]int{} + + fsys.OnDelete().BindFunc(func(e *filesystem.DeleteEvent) error { + hookCalls["OnDelete"]++ + return e.Next() + }) + + fsys.OnNewWriter().BindFunc(func(e *filesystem.NewWriterEvent) error { + hookCalls["OnNewWriter"]++ + return e.Next() + }) + + return hookCalls +} + +func checkHooks(t *testing.T, hookCalls, expectations map[string]int) { + for event, expected := range expectations { + got, _ := hookCalls[event] + if got != expected { + t.Fatalf("Expected event %q to be called %d, got %d", event, expected, got) + } + } +} diff --git a/tools/filesystem/internal/fileblob/attrs.go b/tools/filesystem/internal/fileblob/attrs.go index 3f30ed3b65..b5bbf8c9c8 100644 --- a/tools/filesystem/internal/fileblob/attrs.go +++ b/tools/filesystem/internal/fileblob/attrs.go @@ -1,7 +1,7 @@ package fileblob import ( - "encoding/json" + "encoding/json/v2" "fmt" "os" ) @@ -50,7 +50,8 @@ func setAttrs(path string, xa xattrs) error { return err } - if err := json.NewEncoder(f).Encode(xa); err != nil { + err = json.MarshalWrite(f, xa) + if err != nil { f.Close() os.Remove(f.Name()) return err @@ -75,7 +76,9 @@ func getAttrs(path string) (xattrs, error) { } xa := new(xattrs) - if err := json.NewDecoder(f).Decode(xa); err != nil { + + err = json.UnmarshalRead(f, xa) + if err != nil { f.Close() return xattrs{}, err } diff --git a/tools/filesystem/internal/s3blob/s3/error_test.go b/tools/filesystem/internal/s3blob/s3/error_test.go index 4728c391eb..3eb77ed089 100644 --- a/tools/filesystem/internal/s3blob/s3/error_test.go +++ b/tools/filesystem/internal/s3blob/s3/error_test.go @@ -1,7 +1,7 @@ package s3_test import ( - "encoding/json" + "encoding/json/v2" "encoding/xml" "testing" @@ -29,7 +29,7 @@ func TestResponseErrorSerialization(t *testing.T) { t.Fatal(err) } - jsonRaw, err := json.Marshal(respErr) + jsonRaw, err := json.Marshal(respErr, json.Deterministic(true)) if err != nil { t.Fatal(err) } diff --git a/tools/filesystem/internal/s3blob/s3/get_object_test.go b/tools/filesystem/internal/s3blob/s3/get_object_test.go index 3dde3b0539..495b7f4166 100644 --- a/tools/filesystem/internal/s3blob/s3/get_object_test.go +++ b/tools/filesystem/internal/s3blob/s3/get_object_test.go @@ -2,7 +2,7 @@ package s3_test import ( "context" - "encoding/json" + "encoding/json/v2" "io" "net/http" "strings" @@ -78,7 +78,7 @@ func TestS3GetObject(t *testing.T) { } // check serialized attributes - raw, err := json.Marshal(resp) + raw, err := json.Marshal(resp, json.Deterministic(true)) if err != nil { t.Fatal(err) } diff --git a/tools/filesystem/internal/s3blob/s3/head_object_test.go b/tools/filesystem/internal/s3blob/s3/head_object_test.go index e2bf97961c..4cac81b569 100644 --- a/tools/filesystem/internal/s3blob/s3/head_object_test.go +++ b/tools/filesystem/internal/s3blob/s3/head_object_test.go @@ -2,7 +2,7 @@ package s3_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -63,7 +63,7 @@ func TestS3HeadObject(t *testing.T) { t.Fatal(err) } - raw, err := json.Marshal(resp) + raw, err := json.Marshal(resp, json.Deterministic(true)) if err != nil { t.Fatal(err) } diff --git a/tools/filesystem/internal/s3blob/s3/list_objects_test.go b/tools/filesystem/internal/s3blob/s3/list_objects_test.go index cd0b7f7cac..81ccd7b773 100644 --- a/tools/filesystem/internal/s3blob/s3/list_objects_test.go +++ b/tools/filesystem/internal/s3blob/s3/list_objects_test.go @@ -2,7 +2,7 @@ package s3_test import ( "context" - "encoding/json" + "encoding/json/v2" "io" "net/http" "strings" @@ -143,7 +143,7 @@ func TestS3ListObjects(t *testing.T) { t.Fatal(err) } - raw, err := json.Marshal(resp) + raw, err := json.Marshal(resp, json.Deterministic(true)) if err != nil { t.Fatal(err) } diff --git a/tools/filesystem/internal/s3blob/s3blob.go b/tools/filesystem/internal/s3blob/s3blob.go index 303b2670e1..106e603f47 100644 --- a/tools/filesystem/internal/s3blob/s3blob.go +++ b/tools/filesystem/internal/s3blob/s3blob.go @@ -254,26 +254,27 @@ func (drv *driver) NewTypedWriter(ctx context.Context, key string, contentType s } u.Metadata = md - var reqOptions []func(*http.Request) - reqOptions = append(reqOptions, func(r *http.Request) { - r.Header.Set("Content-Type", contentType) + reqOptions := []func(*http.Request){ + func(r *http.Request) { + r.Header.Set("Content-Type", contentType) - if opts.CacheControl != "" { - r.Header.Set("Cache-Control", opts.CacheControl) - } - if opts.ContentDisposition != "" { - r.Header.Set("Content-Disposition", opts.ContentDisposition) - } - if opts.ContentEncoding != "" { - r.Header.Set("Content-Encoding", opts.ContentEncoding) - } - if opts.ContentLanguage != "" { - r.Header.Set("Content-Language", opts.ContentLanguage) - } - if len(opts.ContentMD5) > 0 { - r.Header.Set("Content-MD5", base64.StdEncoding.EncodeToString(opts.ContentMD5)) - } - }) + if opts.CacheControl != "" { + r.Header.Set("Cache-Control", opts.CacheControl) + } + if opts.ContentDisposition != "" { + r.Header.Set("Content-Disposition", opts.ContentDisposition) + } + if opts.ContentEncoding != "" { + r.Header.Set("Content-Encoding", opts.ContentEncoding) + } + if opts.ContentLanguage != "" { + r.Header.Set("Content-Language", opts.ContentLanguage) + } + if len(opts.ContentMD5) > 0 { + r.Header.Set("Content-MD5", base64.StdEncoding.EncodeToString(opts.ContentMD5)) + } + }, + } return &writer{ ctx: ctx, diff --git a/tools/filesystem/internal/s3blob/s3blob_test.go b/tools/filesystem/internal/s3blob/s3blob_test.go index 20d0f3b781..c420ab2c26 100644 --- a/tools/filesystem/internal/s3blob/s3blob_test.go +++ b/tools/filesystem/internal/s3blob/s3blob_test.go @@ -2,7 +2,7 @@ package s3blob_test import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -261,7 +261,11 @@ func TestDriverAttributes(t *testing.T) { t.Fatal(err) } - raw, err := json.Marshal(attrs) + raw, err := json.Marshal( + attrs, + json.Deterministic(true), + json.FormatNilSliceAsNull(true), + ) if err != nil { t.Fatal(err) } @@ -362,7 +366,11 @@ func TestDriverListPaged(t *testing.T) { t.Fatal(err) } - raw, err := json.Marshal(page) + raw, err := json.Marshal( + page, + json.Deterministic(true), + json.FormatNilSliceAsNull(true), + ) if err != nil { t.Fatal(err) } @@ -510,7 +518,11 @@ func TestDriverNewRangeReader(t *testing.T) { } } - rawAttrs, err := json.Marshal(r.Attributes()) + rawAttrs, err := json.Marshal( + r.Attributes(), + json.Deterministic(true), + json.FormatNilSliceAsNull(true), + ) if err != nil { t.Fatal(err) } diff --git a/tools/list/list.go b/tools/list/list.go index 76c41eedf8..27c4b9615f 100644 --- a/tools/list/list.go +++ b/tools/list/list.go @@ -1,7 +1,7 @@ package list import ( - "encoding/json" + "encoding/json/v2" "regexp" "strings" diff --git a/tools/list/list_test.go b/tools/list/list_test.go index d169a75fa5..c1cd9b3ab8 100644 --- a/tools/list/list_test.go +++ b/tools/list/list_test.go @@ -1,7 +1,7 @@ package list_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "testing" diff --git a/tools/logger/batch_handler.go b/tools/logger/batch_handler.go index 156215c0f0..08b22a5104 100644 --- a/tools/logger/batch_handler.go +++ b/tools/logger/batch_handler.go @@ -2,7 +2,7 @@ package logger import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "log/slog" "sync" diff --git a/tools/logger/batch_handler_test.go b/tools/logger/batch_handler_test.go index efb309ac5f..4248d03af1 100644 --- a/tools/logger/batch_handler_test.go +++ b/tools/logger/batch_handler_test.go @@ -2,6 +2,7 @@ package logger import ( "context" + "encoding/json/v2" "errors" "fmt" "log/slog" @@ -326,7 +327,7 @@ func TestBatchHandlerAttrsFormat(t *testing.T) { for i, data := range expected { t.Run(fmt.Sprintf("log handler %d", i), func(t *testing.T) { log := beforeLogs[i] - raw, _ := log.Data.MarshalJSON() + raw, _ := json.Marshal(log.Data, json.Deterministic(true)) if string(raw) != data { t.Fatalf("Expected \n%s \ngot \n%s", data, raw) } diff --git a/tools/picker/pick.go b/tools/picker/pick.go index 2cd23aa531..a419a3e0be 100644 --- a/tools/picker/pick.go +++ b/tools/picker/pick.go @@ -1,7 +1,7 @@ package picker import ( - "encoding/json" + "encoding/json/v2" "strings" "github.com/pocketbase/pocketbase/tools/search" diff --git a/tools/picker/pick_test.go b/tools/picker/pick_test.go index 95add5886b..fdd438c191 100644 --- a/tools/picker/pick_test.go +++ b/tools/picker/pick_test.go @@ -1,7 +1,7 @@ package picker_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/pocketbase/pocketbase/tools/picker" @@ -263,7 +263,7 @@ func TestPickFields(t *testing.T) { return } - serialized, err := json.Marshal(result) + serialized, err := json.Marshal(result, json.Deterministic(true)) if err != nil { t.Fatal(err) } diff --git a/tools/router/error_test.go b/tools/router/error_test.go index d6b497434b..a5e397bdc5 100644 --- a/tools/router/error_test.go +++ b/tools/router/error_test.go @@ -2,7 +2,7 @@ package router_test import ( "database/sql" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io/fs" @@ -22,7 +22,7 @@ func TestNewApiErrorWithRawData(t *testing.T) { "rawData_test", ) - result, _ := json.Marshal(e) + result, _ := json.Marshal(e, json.Deterministic(true)) expected := `{"data":{},"message":"Message_test.","status":300}` if string(result) != expected { @@ -61,7 +61,7 @@ func TestNewApiErrorWithValidationData(t *testing.T) { }, ) - result, _ := json.Marshal(e) + result, _ := json.Marshal(e, json.Deterministic(true)) expected := `{"data":{"err1":{"code":"validation_invalid_value","message":"Invalid value."},"err2":{"code":"validation_required","message":"Cannot be blank."},"err3":{"err3.1":{"code":"validation_invalid_value","message":"Invalid value."},"err3.2":{"code":"validation_required","message":"Cannot be blank."},"err3.3":{"err3.3.1":{"code":"validation_required","message":"Cannot be blank."}}},"err4":{"code":"mock_code","message":"Mock_error.","mock_resolve":123},"err5":{"err5.1":{"code":"validation_required","message":"Cannot be blank."}}},"message":"Message_test.","status":300}` if string(result) != expected { @@ -93,7 +93,7 @@ func TestNewNotFoundError(t *testing.T) { for i, s := range scenarios { t.Run(strconv.Itoa(i), func(t *testing.T) { e := router.NewNotFoundError(s.message, s.data) - result, _ := json.Marshal(e) + result, _ := json.Marshal(e, json.Deterministic(true)) if str := string(result); str != s.expected { t.Fatalf("Expected\n%v\ngot\n%v", s.expected, str) @@ -118,7 +118,7 @@ func TestNewBadRequestError(t *testing.T) { for i, s := range scenarios { t.Run(strconv.Itoa(i), func(t *testing.T) { e := router.NewBadRequestError(s.message, s.data) - result, _ := json.Marshal(e) + result, _ := json.Marshal(e, json.Deterministic(true)) if str := string(result); str != s.expected { t.Fatalf("Expected\n%v\ngot\n%v", s.expected, str) @@ -143,7 +143,7 @@ func TestNewForbiddenError(t *testing.T) { for i, s := range scenarios { t.Run(strconv.Itoa(i), func(t *testing.T) { e := router.NewForbiddenError(s.message, s.data) - result, _ := json.Marshal(e) + result, _ := json.Marshal(e, json.Deterministic(true)) if str := string(result); str != s.expected { t.Fatalf("Expected\n%v\ngot\n%v", s.expected, str) @@ -168,7 +168,7 @@ func TestNewUnauthorizedError(t *testing.T) { for i, s := range scenarios { t.Run(strconv.Itoa(i), func(t *testing.T) { e := router.NewUnauthorizedError(s.message, s.data) - result, _ := json.Marshal(e) + result, _ := json.Marshal(e, json.Deterministic(true)) if str := string(result); str != s.expected { t.Fatalf("Expected\n%v\ngot\n%v", s.expected, str) @@ -193,7 +193,7 @@ func TestNewInternalServerError(t *testing.T) { for i, s := range scenarios { t.Run(strconv.Itoa(i), func(t *testing.T) { e := router.NewInternalServerError(s.message, s.data) - result, _ := json.Marshal(e) + result, _ := json.Marshal(e, json.Deterministic(true)) if str := string(result); str != s.expected { t.Fatalf("Expected\n%v\ngot\n%v", s.expected, str) @@ -218,7 +218,7 @@ func TestNewTooManyRequestsError(t *testing.T) { for i, s := range scenarios { t.Run(strconv.Itoa(i), func(t *testing.T) { e := router.NewTooManyRequestsError(s.message, s.data) - result, _ := json.Marshal(e) + result, _ := json.Marshal(e, json.Deterministic(true)) if str := string(result); str != s.expected { t.Fatalf("Expected\n%v\ngot\n%v", s.expected, str) @@ -321,7 +321,7 @@ func TestToApiError(t *testing.T) { for _, s := range scenarios { t.Run(s.name, func(t *testing.T) { - raw, err := json.Marshal(router.ToApiError(s.err)) + raw, err := json.Marshal(router.ToApiError(s.err), json.Deterministic(true)) if err != nil { t.Fatal(err) } diff --git a/tools/router/event.go b/tools/router/event.go index 88b03786ec..d0f30bb5e0 100644 --- a/tools/router/event.go +++ b/tools/router/event.go @@ -1,7 +1,8 @@ package router import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "encoding/xml" "errors" "io" @@ -192,7 +193,7 @@ func (e *Event) JSON(status int, data any) error { // error response or no fields to pick if rawFields == "" || status < 200 || status > 299 { - return json.NewEncoder(e.Response).Encode(data) + return json.MarshalWrite(e.Response, data) } // pick only the requested fields @@ -201,7 +202,7 @@ func (e *Event) JSON(status int, data any) error { return err } - return json.NewEncoder(e.Response).Encode(modified) + return json.MarshalWrite(e.Response, modified) } // XML writes an XML response. @@ -359,10 +360,23 @@ func (e *Event) BindBody(dst any) error { contentType := e.Request.Header.Get(headerContentType) if strings.HasPrefix(contentType, "application/json") { - dec := json.NewDecoder(e.Request.Body) - err := dec.Decode(dst) + // note: don't use json.UnmarshalRead because it perfoms an extra + // whitespace scanning which will trigger the auto reread and + // will start again from the beginning and causing an error + // + // (technically json.UnmarshalRead is the better option here but to make + // it work it will require extra interface or other mechanism to temp disable + // the auto reread functionality and this could be a footgun for middlewares + // that wraps the body so for now it is kept as it is and it should + // be similar to the old json.NewDecoder(e.Request.Body).Decode(dst)) + dec := jsontext.NewDecoder(e.Request.Body) + err := json.UnmarshalDecode(dec, dst, + // minimize breaking changes with earlier version + // @todo remove with the "Stage 2" refactoring + json.MatchCaseInsensitiveNames(true), + ) if err == nil { - // manually call Reread because single call of json.Decoder.Decode() + // manually call Reread because single call of json.UnmarshalDecode // doesn't ensure that the entire body is a valid json string // and it is not guaranteed that it will reach EOF to trigger the reread reset // (ex. in case of trailing spaces or invalid trailing parts like: `{"test":1},something`) diff --git a/tools/router/event_test.go b/tools/router/event_test.go index 8b929efe91..1a76cd93da 100644 --- a/tools/router/event_test.go +++ b/tools/router/event_test.go @@ -3,7 +3,7 @@ package router_test import ( "bytes" "crypto/tls" - "encoding/json" + "encoding/json/v2" "encoding/xml" "errors" "fmt" @@ -328,7 +328,7 @@ func TestEventSetAllGetAll(t *testing.T) { "a": 123, "b": 456, } - rawData, err := json.Marshal(data) + rawData, err := json.Marshal(data, json.Deterministic(true)) if err != nil { t.Fatal(err) } @@ -340,7 +340,7 @@ func TestEventSetAllGetAll(t *testing.T) { data["c"] = 789 result := event.GetAll() - rawResult, err := json.Marshal(result) + rawResult, err := json.Marshal(result, json.Deterministic(true)) if err != nil { t.Fatal(err) } @@ -410,8 +410,8 @@ func TestEventHTML(t *testing.T) { func TestEventJSON(t *testing.T) { body := map[string]any{"a": 123, "b": 456, "c": "test"} - expectedPickedBody := `{"a":123,"c":"test"}` + "\n" - expectedFullBody := `{"a":123,"b":456,"c":"test"}` + "\n" + expectedPickedBody := `{"a":123,"c":"test"}` + expectedFullBody := `{"a":123,"b":456,"c":"test"}` scenarios := []testResponseWriteScenario[any]{ { @@ -673,7 +673,7 @@ func TestEventFileFS(t *testing.T) { func TestEventError(t *testing.T) { err := new(router.Event).Error(123, "message_test", map[string]any{"a": validation.Required, "b": "test"}) - result, _ := json.Marshal(err) + result, _ := json.Marshal(err, json.Deterministic(true)) expected := `{"data":{"a":{"code":"validation_invalid_value","message":"Invalid value."},"b":{"code":"validation_invalid_value","message":"Invalid value."}},"message":"Message_test.","status":123}` if string(result) != expected { @@ -684,7 +684,7 @@ func TestEventError(t *testing.T) { func TestEventBadRequestError(t *testing.T) { err := new(router.Event).BadRequestError("message_test", map[string]any{"a": validation.Required, "b": "test"}) - result, _ := json.Marshal(err) + result, _ := json.Marshal(err, json.Deterministic(true)) expected := `{"data":{"a":{"code":"validation_invalid_value","message":"Invalid value."},"b":{"code":"validation_invalid_value","message":"Invalid value."}},"message":"Message_test.","status":400}` if string(result) != expected { @@ -695,7 +695,7 @@ func TestEventBadRequestError(t *testing.T) { func TestEventNotFoundError(t *testing.T) { err := new(router.Event).NotFoundError("message_test", map[string]any{"a": validation.Required, "b": "test"}) - result, _ := json.Marshal(err) + result, _ := json.Marshal(err, json.Deterministic(true)) expected := `{"data":{"a":{"code":"validation_invalid_value","message":"Invalid value."},"b":{"code":"validation_invalid_value","message":"Invalid value."}},"message":"Message_test.","status":404}` if string(result) != expected { @@ -706,7 +706,7 @@ func TestEventNotFoundError(t *testing.T) { func TestEventForbiddenError(t *testing.T) { err := new(router.Event).ForbiddenError("message_test", map[string]any{"a": validation.Required, "b": "test"}) - result, _ := json.Marshal(err) + result, _ := json.Marshal(err, json.Deterministic(true)) expected := `{"data":{"a":{"code":"validation_invalid_value","message":"Invalid value."},"b":{"code":"validation_invalid_value","message":"Invalid value."}},"message":"Message_test.","status":403}` if string(result) != expected { @@ -717,7 +717,7 @@ func TestEventForbiddenError(t *testing.T) { func TestEventUnauthorizedError(t *testing.T) { err := new(router.Event).UnauthorizedError("message_test", map[string]any{"a": validation.Required, "b": "test"}) - result, _ := json.Marshal(err) + result, _ := json.Marshal(err, json.Deterministic(true)) expected := `{"data":{"a":{"code":"validation_invalid_value","message":"Invalid value."},"b":{"code":"validation_invalid_value","message":"Invalid value."}},"message":"Message_test.","status":401}` if string(result) != expected { @@ -728,7 +728,7 @@ func TestEventUnauthorizedError(t *testing.T) { func TestEventTooManyRequestsError(t *testing.T) { err := new(router.Event).TooManyRequestsError("message_test", map[string]any{"a": validation.Required, "b": "test"}) - result, _ := json.Marshal(err) + result, _ := json.Marshal(err, json.Deterministic(true)) expected := `{"data":{"a":{"code":"validation_invalid_value","message":"Invalid value."},"b":{"code":"validation_invalid_value","message":"Invalid value."}},"message":"Message_test.","status":429}` if string(result) != expected { @@ -739,7 +739,7 @@ func TestEventTooManyRequestsError(t *testing.T) { func TestEventInternalServerError(t *testing.T) { err := new(router.Event).InternalServerError("message_test", map[string]any{"a": validation.Required, "b": "test"}) - result, _ := json.Marshal(err) + result, _ := json.Marshal(err, json.Deterministic(true)) expected := `{"data":{"a":{"code":"validation_invalid_value","message":"Invalid value."},"b":{"code":"validation_invalid_value","message":"Invalid value."}},"message":"Message_test.","status":500}` if string(result) != expected { @@ -873,7 +873,7 @@ func TestEventBindBody(t *testing.T) { t.Fatalf("Expected hasErr %v, got %v (%v)", s.expectError, hasErr, err) } - dstRaw, err := json.Marshal(dst) + dstRaw, err := json.Marshal(dst, json.Deterministic(true)) if err != nil { t.Fatal(err) } @@ -925,29 +925,32 @@ func testEventResponseWrite[T any]( } result := rec.Result() + defer result.Body.Close() if result.StatusCode != scenario.expectedStatus { t.Fatalf("Expected status code %d, got %d", scenario.expectedStatus, result.StatusCode) } - resultBody, err := io.ReadAll(result.Body) - result.Body.Close() + rawBody, err := io.ReadAll(result.Body) if err != nil { t.Fatalf("Failed to read response body: %v", err) } - - resultBody, err = json.Marshal(string(resultBody)) - if err != nil { - t.Fatal(err) - } - - expectedBody, err := json.Marshal(scenario.expectedBody) - if err != nil { - t.Fatal(err) + strBody := string(rawBody) + + // try to deserialize into a map and then serialize again in a + // deterministic manner in case it is json + var jsonBody any + err = json.Unmarshal(rawBody, &jsonBody) + if err == nil { + normalized, err := json.Marshal(jsonBody, json.Deterministic(true)) + if err != nil { + t.Fatalf("Failed to deterministicly serialize json body\n%s\ngot\n%v", jsonBody, err) + } + strBody = string(normalized) } - if !bytes.Equal(resultBody, expectedBody) { - t.Fatalf("Expected body\n%s\ngot\n%s", expectedBody, resultBody) + if scenario.expectedBody != strBody { + t.Fatalf("Expected body\n%s\ngot\n%s", scenario.expectedBody, strBody) } for k, ev := range scenario.expectedHeaders { diff --git a/tools/router/router.go b/tools/router/router.go index c2c35dddfe..77c935e85f 100644 --- a/tools/router/router.go +++ b/tools/router/router.go @@ -2,7 +2,7 @@ package router import ( "bufio" - "encoding/json" + "encoding/json/v2" "errors" "io" "log" @@ -176,7 +176,9 @@ func ErrorHandler(resp http.ResponseWriter, req *http.Request, err error) { resp.WriteHeader(apiErr.Status) if req.Method != http.MethodHead { - if jsonErr := json.NewEncoder(resp).Encode(apiErr); jsonErr != nil { + // note: deterministic because some logs may depend on the exact serialized error + jsonErr := json.MarshalWrite(resp, apiErr, json.Deterministic(true)) + if jsonErr != nil { log.Println(jsonErr) // truly rare case, log to stderr only for dev purposes } } diff --git a/tools/router/unmarshal_request_data.go b/tools/router/unmarshal_request_data.go index 099a915d35..3c10b2cd09 100644 --- a/tools/router/unmarshal_request_data.go +++ b/tools/router/unmarshal_request_data.go @@ -2,7 +2,7 @@ package router import ( "encoding" - "encoding/json" + "encoding/json/v2" "errors" "reflect" "regexp" @@ -139,7 +139,7 @@ func unmarshalInStructValue( fieldValue := dereference(dstStructValue.Field(i)) ft := fieldType.Type - if ft.Kind() == reflect.Ptr { + if ft.Kind() == reflect.Pointer { ft = ft.Elem() } @@ -237,7 +237,7 @@ func unmarshalInStructValue( // dereference returns the underlying value v points to. func dereference(v reflect.Value) reflect.Value { - for v.Kind() == reflect.Ptr { + for v.Kind() == reflect.Pointer { if v.IsNil() { // initialize with a new value and continue searching v.Set(reflect.New(v.Type().Elem())) diff --git a/tools/router/unmarshal_request_data_test.go b/tools/router/unmarshal_request_data_test.go index 797719a3d0..94d77bb6f9 100644 --- a/tools/router/unmarshal_request_data_test.go +++ b/tools/router/unmarshal_request_data_test.go @@ -2,7 +2,7 @@ package router_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "testing" "time" @@ -428,7 +428,7 @@ func TestUnmarshalRequestData(t *testing.T) { return } - raw, err := json.Marshal(s.dst) + raw, err := json.Marshal(s.dst, json.Deterministic(true)) if err != nil { t.Fatal(err) } diff --git a/tools/search/filter.go b/tools/search/filter.go index 8df74a9557..48983bb89a 100644 --- a/tools/search/filter.go +++ b/tools/search/filter.go @@ -1,7 +1,7 @@ package search import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "strconv" @@ -69,7 +69,7 @@ func (f FilterData) BuildExprWithLimit( // try to json serialize as fallback if replacement == "" { - raw, _ := json.Marshal(v) + raw, _ := json.Marshal(v, json.Deterministic(true)) replacement = string(raw) } diff --git a/tools/search/multi_match_subquery_test.go b/tools/search/multi_match_subquery_test.go index 6caae96836..f24564d4f9 100644 --- a/tools/search/multi_match_subquery_test.go +++ b/tools/search/multi_match_subquery_test.go @@ -3,7 +3,7 @@ package search_test import ( "bytes" "database/sql" - "encoding/json" + "encoding/json/v2" "testing" "github.com/pocketbase/dbx" @@ -40,7 +40,7 @@ func TestMultiMatchSubqueryBuild(t *testing.T) { } // the params from all expressions should be merged in the root - rawParams, err := json.Marshal(params) + rawParams, err := json.Marshal(params, json.Deterministic(true)) if err != nil { t.Fatal(err) } diff --git a/tools/search/provider_test.go b/tools/search/provider_test.go index fc73a4dfc7..f654f0fcbe 100644 --- a/tools/search/provider_test.go +++ b/tools/search/provider_test.go @@ -3,7 +3,7 @@ package search import ( "context" "database/sql" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "strconv" @@ -138,7 +138,7 @@ func TestProviderSort(t *testing.T) { Sort(initialSort). AddSort(SortField{"test3", SortDesc}) - encoded, _ := json.Marshal(p.sort) + encoded, _ := json.Marshal(p.sort, json.Deterministic(true)) expected := `[{"name":"test1","direction":"ASC"},{"name":"test2","direction":"ASC"},{"name":"test3","direction":"DESC"}]` if string(encoded) != expected { @@ -153,7 +153,7 @@ func TestProviderFilter(t *testing.T) { Filter(initialFilter). AddFilter("test3") - encoded, _ := json.Marshal(p.filter) + encoded, _ := json.Marshal(p.filter, json.Deterministic(true)) expected := `["test1","test2","test3"]` if string(encoded) != expected { @@ -246,12 +246,12 @@ func TestProviderParse(t *testing.T) { t.Fatalf("Expected perPage %v, got %v", s.expectPerPage, p.perPage) } - encodedSort, _ := json.Marshal(p.sort) + encodedSort, _ := json.Marshal(p.sort, json.Deterministic(true)) if string(encodedSort) != s.expectSort { t.Fatalf("Expected sort %v, got \n%v", s.expectSort, string(encodedSort)) } - encodedFilter, _ := json.Marshal(p.filter) + encodedFilter, _ := json.Marshal(p.filter, json.Deterministic(true)) if string(encodedFilter) != s.expectFilter { t.Fatalf("Expected filter %v, got \n%v", s.expectFilter, string(encodedFilter)) } @@ -453,7 +453,7 @@ func TestProviderExecNonEmptyQuery(t *testing.T) { t.Fatalf("Expected resolver.Update to be called %d, got %d", 1, testResolver.UpdateQueryCalls) } - encoded, _ := json.Marshal(result) + encoded, _ := json.Marshal(result, json.Deterministic(true)) if string(encoded) != s.expectResult { t.Fatalf("Expected result %v, got \n%v", s.expectResult, string(encoded)) } @@ -722,7 +722,7 @@ func TestProviderParseAndExec(t *testing.T) { t.Fatalf("Expected %d db queries, got %d: \n%v", expectedQueries, len(testDB.CalledQueries), testDB.CalledQueries) } - encoded, _ := json.Marshal(result) + encoded, _ := json.Marshal(result, json.Deterministic(true)) if string(encoded) != s.expectResult { t.Fatalf("Expected result \n%v\ngot\n%v", s.expectResult, string(encoded)) } diff --git a/tools/search/sort_test.go b/tools/search/sort_test.go index 1c23cc4526..e47ad6cb0d 100644 --- a/tools/search/sort_test.go +++ b/tools/search/sort_test.go @@ -1,7 +1,7 @@ package search_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "testing" @@ -67,7 +67,7 @@ func TestParseSortFromString(t *testing.T) { for _, s := range scenarios { t.Run(s.value, func(t *testing.T) { result := search.ParseSortFromString(s.value) - encoded, _ := json.Marshal(result) + encoded, _ := json.Marshal(result, json.Deterministic(true)) encodedStr := string(encoded) if encodedStr != s.expected { diff --git a/tools/store/store.go b/tools/store/store.go index bdb5d96a20..1905bc0810 100644 --- a/tools/store/store.go +++ b/tools/store/store.go @@ -1,7 +1,7 @@ package store import ( - "encoding/json" + "encoding/json/v2" "sync" ) @@ -122,7 +122,21 @@ func (s *Store[K, T]) GetAll() map[K]T { return clone } -// Values returns a slice with all of the current store values. +// Keys returns a slice with all of the store keys. +func (s *Store[K, T]) Keys() []K { + s.mu.RLock() + defer s.mu.RUnlock() + + var keys = make([]K, 0, len(s.data)) + + for k := range s.data { + keys = append(keys, k) + } + + return keys +} + +// Values returns a slice with all of the store values. func (s *Store[K, T]) Values() []T { s.mu.RLock() defer s.mu.RUnlock() diff --git a/tools/store/store_test.go b/tools/store/store_test.go index 24f2874189..8a1e2d95b8 100644 --- a/tools/store/store_test.go +++ b/tools/store/store_test.go @@ -2,7 +2,7 @@ package store_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "slices" "strconv" "testing" @@ -12,7 +12,7 @@ import ( func TestNew(t *testing.T) { data := map[string]int{"test1": 1, "test2": 2} - originalRawData, err := json.Marshal(data) + originalRawData, err := json.Marshal(data, json.Deterministic(true)) if err != nil { t.Fatal(err) } @@ -22,7 +22,7 @@ func TestNew(t *testing.T) { s.Remove("test1") // remove 1 item // check if data was shallow copied - rawData, _ := json.Marshal(data) + rawData, _ := json.Marshal(data, json.Deterministic(true)) if !bytes.Equal(originalRawData, rawData) { t.Fatalf("Expected data \n%s, \ngot \n%s", originalRawData, rawData) } @@ -44,7 +44,7 @@ func TestReset(t *testing.T) { s := store.New(map[string]int{"test1": 1}) data := map[string]int{"test2": 2} - originalRawData, err := json.Marshal(data) + originalRawData, err := json.Marshal(data, json.Deterministic(true)) if err != nil { t.Fatal(err) } @@ -53,7 +53,7 @@ func TestReset(t *testing.T) { s.Set("test3", 3) // check if data was shallow copied - rawData, _ := json.Marshal(data) + rawData, _ := json.Marshal(data, json.Deterministic(true)) if !bytes.Equal(originalRawData, rawData) { t.Fatalf("Expected data \n%s, \ngot \n%s", originalRawData, rawData) } @@ -205,6 +205,27 @@ func TestGetAll(t *testing.T) { } } +func TestKeys(t *testing.T) { + data := map[string]int{ + "a": 1, + "b": 2, + } + + keys := store.New(data).Keys() + + expected := []string{"a", "b"} + + if len(keys) != len(expected) { + t.Fatalf("Expected %d keys, got %d", len(expected), len(keys)) + } + + for _, k := range expected { + if !slices.Contains(keys, k) { + t.Fatalf("Missing key %s in\n%v", k, keys) + } + } +} + func TestValues(t *testing.T) { data := map[string]int{ "a": 1, @@ -371,7 +392,7 @@ func TestMarshalJSON(t *testing.T) { expected := []byte(`{"a":"test1", "b":"test2"}`) - result, err := json.Marshal(s) + result, err := json.Marshal(s, json.Deterministic(true)) if err != nil { t.Fatal(err) } diff --git a/tools/subscriptions/client.go b/tools/subscriptions/client.go index 06309d5409..3bff80180b 100644 --- a/tools/subscriptions/client.go +++ b/tools/subscriptions/client.go @@ -1,7 +1,7 @@ package subscriptions import ( - "encoding/json" + "encoding/json/v2" "net/url" "strings" "sync" diff --git a/tools/subscriptions/client_test.go b/tools/subscriptions/client_test.go index ff5b59b713..eec6b81751 100644 --- a/tools/subscriptions/client_test.go +++ b/tools/subscriptions/client_test.go @@ -1,7 +1,7 @@ package subscriptions_test import ( - "encoding/json" + "encoding/json/v2" "strings" "sync" "testing" diff --git a/tools/types/datetime.go b/tools/types/datetime.go index 26820391d9..b302e99433 100644 --- a/tools/types/datetime.go +++ b/tools/types/datetime.go @@ -2,7 +2,7 @@ package types import ( "database/sql/driver" - "encoding/json" + "encoding/json/v2" "time" "github.com/spf13/cast" diff --git a/tools/types/geo_point.go b/tools/types/geo_point.go index 286120d062..bbe3a7b93c 100644 --- a/tools/types/geo_point.go +++ b/tools/types/geo_point.go @@ -2,7 +2,7 @@ package types import ( "database/sql/driver" - "encoding/json" + "encoding/json/v2" "fmt" ) diff --git a/tools/types/json_array.go b/tools/types/json_array.go index 024fd7b0f0..a7749a9f74 100644 --- a/tools/types/json_array.go +++ b/tools/types/json_array.go @@ -2,7 +2,7 @@ package types import ( "database/sql/driver" - "encoding/json" + "encoding/json/v2" "fmt" ) @@ -14,12 +14,9 @@ type jsonArrayAlias[T any] JSONArray[T] // MarshalJSON implements the [json.Marshaler] interface. func (m JSONArray[T]) MarshalJSON() ([]byte, error) { - // initialize an empty map to ensure that `[]` is returned as json - if m == nil { - m = JSONArray[T]{} - } - - return json.Marshal(jsonArrayAlias[T](m)) + // note: forces the Deterministic option to ensure consistent output + // in mixed json v1 and v2 configurations + return json.Marshal(jsonArrayAlias[T](m), json.Deterministic(true)) } // String returns the string representation of the current json array. @@ -30,8 +27,7 @@ func (m JSONArray[T]) String() string { // Value implements the [driver.Valuer] interface. func (m JSONArray[T]) Value() (driver.Value, error) { - data, err := json.Marshal(m) - + data, err := m.MarshalJSON() return string(data), err } @@ -54,5 +50,12 @@ func (m *JSONArray[T]) Scan(value any) error { data = []byte("[]") } - return json.Unmarshal(data, m) + err := json.Unmarshal(data, m) + if err != nil { + // reset because jsonv2 performs streaming decoding and mutates the dst even on error + *m = JSONArray[T]{} + return err + } + + return nil } diff --git a/tools/types/json_array_test.go b/tools/types/json_array_test.go index 27aa16fec9..8527e08378 100644 --- a/tools/types/json_array_test.go +++ b/tools/types/json_array_test.go @@ -2,7 +2,7 @@ package types_test import ( "database/sql/driver" - "encoding/json" + "encoding/json/v2" "fmt" "testing" @@ -115,7 +115,7 @@ func TestJSONArrayScan(t *testing.T) { continue } - result, _ := arr.MarshalJSON() + result, _ := json.Marshal(arr, json.Deterministic(true)) if string(result) != s.expectJSON { t.Errorf("(%d) Expected %s, got %v", i, s.expectJSON, string(result)) diff --git a/tools/types/json_map.go b/tools/types/json_map.go index 87b0fc8441..f51eeeaa51 100644 --- a/tools/types/json_map.go +++ b/tools/types/json_map.go @@ -2,31 +2,13 @@ package types import ( "database/sql/driver" - "encoding/json" + "encoding/json/v2" "fmt" ) // JSONMap defines a map that is safe for json and db read/write. type JSONMap[T any] map[string]T -// MarshalJSON implements the [json.Marshaler] interface. -func (m JSONMap[T]) MarshalJSON() ([]byte, error) { - type alias JSONMap[T] // prevent recursion - - // initialize an empty map to ensure that `{}` is returned as json - if m == nil { - m = JSONMap[T]{} - } - - return json.Marshal(alias(m)) -} - -// String returns the string representation of the current json map. -func (m JSONMap[T]) String() string { - v, _ := m.MarshalJSON() - return string(v) -} - // Get retrieves a single value from the current JSONMap[T]. // // This helper was added primarily to assist the goja integration since custom map types @@ -43,10 +25,24 @@ func (m JSONMap[T]) Set(key string, value T) { m[key] = value } +// MarshalJSON implements the [json.Marshaler] interface. +func (m JSONMap[T]) MarshalJSON() ([]byte, error) { + type alias JSONMap[T] // prevent recursion + + // note: forces the Deterministic option to ensure consistent output + // in mixed json v1 and v2 configurations + return json.Marshal(alias(m), json.Deterministic(true)) +} + +// String returns the string representation of the current json map. +func (m JSONMap[T]) String() string { + v, _ := m.MarshalJSON() + return string(v) +} + // Value implements the [driver.Valuer] interface. func (m JSONMap[T]) Value() (driver.Value, error) { - data, err := json.Marshal(m) - + data, err := m.MarshalJSON() return string(data), err } @@ -69,5 +65,12 @@ func (m *JSONMap[T]) Scan(value any) error { data = []byte("{}") } - return json.Unmarshal(data, m) + err := json.Unmarshal(data, m) + if err != nil { + // reset because jsonv2 performs streaming decoding and mutates the dst even on error + *m = JSONMap[T]{} + return err + } + + return nil } diff --git a/tools/types/json_map_test.go b/tools/types/json_map_test.go index 56ea95aa3f..eab4b94965 100644 --- a/tools/types/json_map_test.go +++ b/tools/types/json_map_test.go @@ -2,6 +2,7 @@ package types_test import ( "database/sql/driver" + "encoding/json/v2" "fmt" "testing" @@ -154,7 +155,7 @@ func TestJSONArrayMapScan(t *testing.T) { t.Fatalf("Expected %v, got %v (%v)", s.expectError, hasErr, scanErr) } - result, _ := arr.MarshalJSON() + result, _ := json.Marshal(arr, json.Deterministic(true)) if string(result) != s.expectJSON { t.Fatalf("Expected %s, got %s", s.expectJSON, result) diff --git a/tools/types/json_raw.go b/tools/types/json_raw.go index 8ed58c2a0c..129251f30a 100644 --- a/tools/types/json_raw.go +++ b/tools/types/json_raw.go @@ -2,7 +2,7 @@ package types import ( "database/sql/driver" - "encoding/json" + "encoding/json/v2" "errors" ) @@ -73,7 +73,7 @@ func (j *JSONRaw) Scan(value any) error { data = []byte(v) } default: - bytes, err := json.Marshal(v) + bytes, err := json.Marshal(v, json.Deterministic(true)) if err != nil { return err } diff --git a/ui/.env b/ui/.env index 7969a99f2c..3cd7ecfe32 100644 --- a/ui/.env +++ b/ui/.env @@ -12,4 +12,4 @@ PB_DOCS_URL = "https://pocketbase.io/docs" PB_JS_SDK_URL = "https://github.com/pocketbase/js-sdk" PB_DART_SDK_URL = "https://github.com/pocketbase/dart-sdk" PB_RELEASES = "https://github.com/pocketbase/pocketbase/releases" -PB_VERSION = "v0.39.11" +PB_VERSION = "v0.40.0" diff --git a/ui/dist/assets/index-aF6iCWTU.css b/ui/dist/assets/index-CZinEv5W.css similarity index 85% rename from ui/dist/assets/index-aF6iCWTU.css rename to ui/dist/assets/index-CZinEv5W.css index a2177c01f5..df55213a29 100644 --- a/ui/dist/assets/index-aF6iCWTU.css +++ b/ui/dist/assets/index-CZinEv5W.css @@ -1 +1 @@ -@font-face{font-display:block;font-family:remixicon;src:url(../fonts/remixicon/remixicon.woff2?t=1734404658139)format("woff2")}@font-face{font-display:block;font-family:IBM Plex Sans;font-style:normal;font-weight:400;src:url(../fonts/ibm-plex-sans/ibm-plex-sans-v23-cyrillic_latin_latin-ext-regular.woff2)format("woff2")}@font-face{font-display:block;font-family:IBM Plex Sans;font-style:italic;font-weight:400;src:url(../fonts/ibm-plex-sans/ibm-plex-sans-v23-cyrillic_latin_latin-ext-italic.woff2)format("woff2")}@font-face{font-display:block;font-family:IBM Plex Sans;font-style:normal;font-weight:600;src:url(../fonts/ibm-plex-sans/ibm-plex-sans-v23-cyrillic_latin_latin-ext-600.woff2)format("woff2")}@font-face{font-display:block;font-family:IBM Plex Sans;font-style:italic;font-weight:600;src:url(../fonts/ibm-plex-sans/ibm-plex-sans-v23-cyrillic_latin_latin-ext-600italic.woff2)format("woff2")}@font-face{font-display:swap;font-family:IBM Plex Mono;font-style:normal;font-weight:400;src:url(../fonts/ibm-plex-mono/ibm-plex-mono-v20-latin-regular.woff2)format("woff2")}@font-face{font-display:swap;font-family:IBM Plex Mono;font-style:normal;font-weight:600;src:url(../fonts/ibm-plex-mono/ibm-plex-mono-v20-latin-600.woff2)format("woff2")}.ri-24-hours-fill:before{content:""}.ri-24-hours-line:before{content:""}.ri-4k-fill:before{content:""}.ri-4k-line:before{content:""}.ri-a-b:before{content:""}.ri-account-box-fill:before{content:""}.ri-account-box-line:before{content:""}.ri-account-circle-fill:before{content:""}.ri-account-circle-line:before{content:""}.ri-account-pin-box-fill:before{content:""}.ri-account-pin-box-line:before{content:""}.ri-account-pin-circle-fill:before{content:""}.ri-account-pin-circle-line:before{content:""}.ri-add-box-fill:before{content:""}.ri-add-box-line:before{content:""}.ri-add-circle-fill:before{content:""}.ri-add-circle-line:before{content:""}.ri-add-fill:before{content:""}.ri-add-line:before{content:""}.ri-admin-fill:before{content:""}.ri-admin-line:before{content:""}.ri-advertisement-fill:before{content:""}.ri-advertisement-line:before{content:""}.ri-airplay-fill:before{content:""}.ri-airplay-line:before{content:""}.ri-alarm-fill:before{content:""}.ri-alarm-line:before{content:""}.ri-alarm-warning-fill:before{content:""}.ri-alarm-warning-line:before{content:""}.ri-album-fill:before{content:""}.ri-album-line:before{content:""}.ri-alert-fill:before{content:""}.ri-alert-line:before{content:""}.ri-aliens-fill:before{content:""}.ri-aliens-line:before{content:""}.ri-align-bottom:before{content:""}.ri-align-center:before{content:""}.ri-align-justify:before{content:""}.ri-align-left:before{content:""}.ri-align-right:before{content:""}.ri-align-top:before{content:""}.ri-align-vertically:before{content:""}.ri-alipay-fill:before{content:""}.ri-alipay-line:before{content:""}.ri-amazon-fill:before{content:""}.ri-amazon-line:before{content:""}.ri-anchor-fill:before{content:""}.ri-anchor-line:before{content:""}.ri-ancient-gate-fill:before{content:""}.ri-ancient-gate-line:before{content:""}.ri-ancient-pavilion-fill:before{content:""}.ri-ancient-pavilion-line:before{content:""}.ri-android-fill:before{content:""}.ri-android-line:before{content:""}.ri-angularjs-fill:before{content:""}.ri-angularjs-line:before{content:""}.ri-anticlockwise-2-fill:before{content:""}.ri-anticlockwise-2-line:before{content:""}.ri-anticlockwise-fill:before{content:""}.ri-anticlockwise-line:before{content:""}.ri-app-store-fill:before{content:""}.ri-app-store-line:before{content:""}.ri-apple-fill:before{content:""}.ri-apple-line:before{content:""}.ri-apps-2-fill:before{content:""}.ri-apps-2-line:before{content:""}.ri-apps-fill:before{content:""}.ri-apps-line:before{content:""}.ri-archive-drawer-fill:before{content:""}.ri-archive-drawer-line:before{content:""}.ri-archive-fill:before{content:""}.ri-archive-line:before{content:""}.ri-arrow-down-circle-fill:before{content:""}.ri-arrow-down-circle-line:before{content:""}.ri-arrow-down-fill:before{content:""}.ri-arrow-down-line:before{content:""}.ri-arrow-down-s-fill:before{content:""}.ri-arrow-down-s-line:before{content:""}.ri-arrow-drop-down-fill:before{content:""}.ri-arrow-drop-down-line:before{content:""}.ri-arrow-drop-left-fill:before{content:""}.ri-arrow-drop-left-line:before{content:""}.ri-arrow-drop-right-fill:before{content:""}.ri-arrow-drop-right-line:before{content:""}.ri-arrow-drop-up-fill:before{content:""}.ri-arrow-drop-up-line:before{content:""}.ri-arrow-go-back-fill:before{content:""}.ri-arrow-go-back-line:before{content:""}.ri-arrow-go-forward-fill:before{content:""}.ri-arrow-go-forward-line:before{content:""}.ri-arrow-left-circle-fill:before{content:""}.ri-arrow-left-circle-line:before{content:""}.ri-arrow-left-down-fill:before{content:""}.ri-arrow-left-down-line:before{content:""}.ri-arrow-left-fill:before{content:""}.ri-arrow-left-line:before{content:""}.ri-arrow-left-right-fill:before{content:""}.ri-arrow-left-right-line:before{content:""}.ri-arrow-left-s-fill:before{content:""}.ri-arrow-left-s-line:before{content:""}.ri-arrow-left-up-fill:before{content:""}.ri-arrow-left-up-line:before{content:""}.ri-arrow-right-circle-fill:before{content:""}.ri-arrow-right-circle-line:before{content:""}.ri-arrow-right-down-fill:before{content:""}.ri-arrow-right-down-line:before{content:""}.ri-arrow-right-fill:before{content:""}.ri-arrow-right-line:before{content:""}.ri-arrow-right-s-fill:before{content:""}.ri-arrow-right-s-line:before{content:""}.ri-arrow-right-up-fill:before{content:""}.ri-arrow-right-up-line:before{content:""}.ri-arrow-up-circle-fill:before{content:""}.ri-arrow-up-circle-line:before{content:""}.ri-arrow-up-down-fill:before{content:""}.ri-arrow-up-down-line:before{content:""}.ri-arrow-up-fill:before{content:""}.ri-arrow-up-line:before{content:""}.ri-arrow-up-s-fill:before{content:""}.ri-arrow-up-s-line:before{content:""}.ri-artboard-2-fill:before{content:""}.ri-artboard-2-line:before{content:""}.ri-artboard-fill:before{content:""}.ri-artboard-line:before{content:""}.ri-article-fill:before{content:""}.ri-article-line:before{content:""}.ri-aspect-ratio-fill:before{content:""}.ri-aspect-ratio-line:before{content:""}.ri-asterisk:before{content:""}.ri-at-fill:before{content:""}.ri-at-line:before{content:""}.ri-attachment-2:before{content:""}.ri-attachment-fill:before{content:""}.ri-attachment-line:before{content:""}.ri-auction-fill:before{content:""}.ri-auction-line:before{content:""}.ri-award-fill:before{content:""}.ri-award-line:before{content:""}.ri-baidu-fill:before{content:""}.ri-baidu-line:before{content:""}.ri-ball-pen-fill:before{content:""}.ri-ball-pen-line:before{content:""}.ri-bank-card-2-fill:before{content:""}.ri-bank-card-2-line:before{content:""}.ri-bank-card-fill:before{content:""}.ri-bank-card-line:before{content:""}.ri-bank-fill:before{content:""}.ri-bank-line:before{content:""}.ri-bar-chart-2-fill:before{content:""}.ri-bar-chart-2-line:before{content:""}.ri-bar-chart-box-fill:before{content:""}.ri-bar-chart-box-line:before{content:""}.ri-bar-chart-fill:before{content:""}.ri-bar-chart-grouped-fill:before{content:""}.ri-bar-chart-grouped-line:before{content:""}.ri-bar-chart-horizontal-fill:before{content:""}.ri-bar-chart-horizontal-line:before{content:""}.ri-bar-chart-line:before{content:""}.ri-barcode-box-fill:before{content:""}.ri-barcode-box-line:before{content:""}.ri-barcode-fill:before{content:""}.ri-barcode-line:before{content:""}.ri-barricade-fill:before{content:""}.ri-barricade-line:before{content:""}.ri-base-station-fill:before{content:""}.ri-base-station-line:before{content:""}.ri-basketball-fill:before{content:""}.ri-basketball-line:before{content:""}.ri-battery-2-charge-fill:before{content:""}.ri-battery-2-charge-line:before{content:""}.ri-battery-2-fill:before{content:""}.ri-battery-2-line:before{content:""}.ri-battery-charge-fill:before{content:""}.ri-battery-charge-line:before{content:""}.ri-battery-fill:before{content:""}.ri-battery-line:before{content:""}.ri-battery-low-fill:before{content:""}.ri-battery-low-line:before{content:""}.ri-battery-saver-fill:before{content:""}.ri-battery-saver-line:before{content:""}.ri-battery-share-fill:before{content:""}.ri-battery-share-line:before{content:""}.ri-bear-smile-fill:before{content:""}.ri-bear-smile-line:before{content:""}.ri-behance-fill:before{content:""}.ri-behance-line:before{content:""}.ri-bell-fill:before{content:""}.ri-bell-line:before{content:""}.ri-bike-fill:before{content:""}.ri-bike-line:before{content:""}.ri-bilibili-fill:before{content:""}.ri-bilibili-line:before{content:""}.ri-bill-fill:before{content:""}.ri-bill-line:before{content:""}.ri-billiards-fill:before{content:""}.ri-billiards-line:before{content:""}.ri-bit-coin-fill:before{content:""}.ri-bit-coin-line:before{content:""}.ri-blaze-fill:before{content:""}.ri-blaze-line:before{content:""}.ri-bluetooth-connect-fill:before{content:""}.ri-bluetooth-connect-line:before{content:""}.ri-bluetooth-fill:before{content:""}.ri-bluetooth-line:before{content:""}.ri-blur-off-fill:before{content:""}.ri-blur-off-line:before{content:""}.ri-body-scan-fill:before{content:""}.ri-body-scan-line:before{content:""}.ri-bold:before{content:""}.ri-book-2-fill:before{content:""}.ri-book-2-line:before{content:""}.ri-book-3-fill:before{content:""}.ri-book-3-line:before{content:""}.ri-book-fill:before{content:""}.ri-book-line:before{content:""}.ri-book-marked-fill:before{content:""}.ri-book-marked-line:before{content:""}.ri-book-open-fill:before{content:""}.ri-book-open-line:before{content:""}.ri-book-read-fill:before{content:""}.ri-book-read-line:before{content:""}.ri-booklet-fill:before{content:""}.ri-booklet-line:before{content:""}.ri-bookmark-2-fill:before{content:""}.ri-bookmark-2-line:before{content:""}.ri-bookmark-3-fill:before{content:""}.ri-bookmark-3-line:before{content:""}.ri-bookmark-fill:before{content:""}.ri-bookmark-line:before{content:""}.ri-boxing-fill:before{content:""}.ri-boxing-line:before{content:""}.ri-braces-fill:before{content:""}.ri-braces-line:before{content:""}.ri-brackets-fill:before{content:""}.ri-brackets-line:before{content:""}.ri-briefcase-2-fill:before{content:""}.ri-briefcase-2-line:before{content:""}.ri-briefcase-3-fill:before{content:""}.ri-briefcase-3-line:before{content:""}.ri-briefcase-4-fill:before{content:""}.ri-briefcase-4-line:before{content:""}.ri-briefcase-5-fill:before{content:""}.ri-briefcase-5-line:before{content:""}.ri-briefcase-fill:before{content:""}.ri-briefcase-line:before{content:""}.ri-bring-forward:before{content:""}.ri-bring-to-front:before{content:""}.ri-broadcast-fill:before{content:""}.ri-broadcast-line:before{content:""}.ri-brush-2-fill:before{content:""}.ri-brush-2-line:before{content:""}.ri-brush-3-fill:before{content:""}.ri-brush-3-line:before{content:""}.ri-brush-4-fill:before{content:""}.ri-brush-4-line:before{content:""}.ri-brush-fill:before{content:""}.ri-brush-line:before{content:""}.ri-bubble-chart-fill:before{content:""}.ri-bubble-chart-line:before{content:""}.ri-bug-2-fill:before{content:""}.ri-bug-2-line:before{content:""}.ri-bug-fill:before{content:""}.ri-bug-line:before{content:""}.ri-building-2-fill:before{content:""}.ri-building-2-line:before{content:""}.ri-building-3-fill:before{content:""}.ri-building-3-line:before{content:""}.ri-building-4-fill:before{content:""}.ri-building-4-line:before{content:""}.ri-building-fill:before{content:""}.ri-building-line:before{content:""}.ri-bus-2-fill:before{content:""}.ri-bus-2-line:before{content:""}.ri-bus-fill:before{content:""}.ri-bus-line:before{content:""}.ri-bus-wifi-fill:before{content:""}.ri-bus-wifi-line:before{content:""}.ri-cactus-fill:before{content:""}.ri-cactus-line:before{content:""}.ri-cake-2-fill:before{content:""}.ri-cake-2-line:before{content:""}.ri-cake-3-fill:before{content:""}.ri-cake-3-line:before{content:""}.ri-cake-fill:before{content:""}.ri-cake-line:before{content:""}.ri-calculator-fill:before{content:""}.ri-calculator-line:before{content:""}.ri-calendar-2-fill:before{content:""}.ri-calendar-2-line:before{content:""}.ri-calendar-check-fill:before{content:""}.ri-calendar-check-line:before{content:""}.ri-calendar-event-fill:before{content:""}.ri-calendar-event-line:before{content:""}.ri-calendar-fill:before{content:""}.ri-calendar-line:before{content:""}.ri-calendar-todo-fill:before{content:""}.ri-calendar-todo-line:before{content:""}.ri-camera-2-fill:before{content:""}.ri-camera-2-line:before{content:""}.ri-camera-3-fill:before{content:""}.ri-camera-3-line:before{content:""}.ri-camera-fill:before{content:""}.ri-camera-lens-fill:before{content:""}.ri-camera-lens-line:before{content:""}.ri-camera-line:before{content:""}.ri-camera-off-fill:before{content:""}.ri-camera-off-line:before{content:""}.ri-camera-switch-fill:before{content:""}.ri-camera-switch-line:before{content:""}.ri-capsule-fill:before{content:""}.ri-capsule-line:before{content:""}.ri-car-fill:before{content:""}.ri-car-line:before{content:""}.ri-car-washing-fill:before{content:""}.ri-car-washing-line:before{content:""}.ri-caravan-fill:before{content:""}.ri-caravan-line:before{content:""}.ri-cast-fill:before{content:""}.ri-cast-line:before{content:""}.ri-cellphone-fill:before{content:""}.ri-cellphone-line:before{content:""}.ri-celsius-fill:before{content:""}.ri-celsius-line:before{content:""}.ri-centos-fill:before{content:""}.ri-centos-line:before{content:""}.ri-character-recognition-fill:before{content:""}.ri-character-recognition-line:before{content:""}.ri-charging-pile-2-fill:before{content:""}.ri-charging-pile-2-line:before{content:""}.ri-charging-pile-fill:before{content:""}.ri-charging-pile-line:before{content:""}.ri-chat-1-fill:before{content:""}.ri-chat-1-line:before{content:""}.ri-chat-2-fill:before{content:""}.ri-chat-2-line:before{content:""}.ri-chat-3-fill:before{content:""}.ri-chat-3-line:before{content:""}.ri-chat-4-fill:before{content:""}.ri-chat-4-line:before{content:""}.ri-chat-check-fill:before{content:""}.ri-chat-check-line:before{content:""}.ri-chat-delete-fill:before{content:""}.ri-chat-delete-line:before{content:""}.ri-chat-download-fill:before{content:""}.ri-chat-download-line:before{content:""}.ri-chat-follow-up-fill:before{content:""}.ri-chat-follow-up-line:before{content:""}.ri-chat-forward-fill:before{content:""}.ri-chat-forward-line:before{content:""}.ri-chat-heart-fill:before{content:""}.ri-chat-heart-line:before{content:""}.ri-chat-history-fill:before{content:""}.ri-chat-history-line:before{content:""}.ri-chat-new-fill:before{content:""}.ri-chat-new-line:before{content:""}.ri-chat-off-fill:before{content:""}.ri-chat-off-line:before{content:""}.ri-chat-poll-fill:before{content:""}.ri-chat-poll-line:before{content:""}.ri-chat-private-fill:before{content:""}.ri-chat-private-line:before{content:""}.ri-chat-quote-fill:before{content:""}.ri-chat-quote-line:before{content:""}.ri-chat-settings-fill:before{content:""}.ri-chat-settings-line:before{content:""}.ri-chat-smile-2-fill:before{content:""}.ri-chat-smile-2-line:before{content:""}.ri-chat-smile-3-fill:before{content:""}.ri-chat-smile-3-line:before{content:""}.ri-chat-smile-fill:before{content:""}.ri-chat-smile-line:before{content:""}.ri-chat-upload-fill:before{content:""}.ri-chat-upload-line:before{content:""}.ri-chat-voice-fill:before{content:""}.ri-chat-voice-line:before{content:""}.ri-check-double-fill:before{content:""}.ri-check-double-line:before{content:""}.ri-check-fill:before{content:""}.ri-check-line:before{content:""}.ri-checkbox-blank-circle-fill:before{content:""}.ri-checkbox-blank-circle-line:before{content:""}.ri-checkbox-blank-fill:before{content:""}.ri-checkbox-blank-line:before{content:""}.ri-checkbox-circle-fill:before{content:""}.ri-checkbox-circle-line:before{content:""}.ri-checkbox-fill:before{content:""}.ri-checkbox-indeterminate-fill:before{content:""}.ri-checkbox-indeterminate-line:before{content:""}.ri-checkbox-line:before{content:""}.ri-checkbox-multiple-blank-fill:before{content:""}.ri-checkbox-multiple-blank-line:before{content:""}.ri-checkbox-multiple-fill:before{content:""}.ri-checkbox-multiple-line:before{content:""}.ri-china-railway-fill:before{content:""}.ri-china-railway-line:before{content:""}.ri-chrome-fill:before{content:""}.ri-chrome-line:before{content:""}.ri-clapperboard-fill:before{content:""}.ri-clapperboard-line:before{content:""}.ri-clipboard-fill:before{content:""}.ri-clipboard-line:before{content:""}.ri-clockwise-2-fill:before{content:""}.ri-clockwise-2-line:before{content:""}.ri-clockwise-fill:before{content:""}.ri-clockwise-line:before{content:""}.ri-close-circle-fill:before{content:""}.ri-close-circle-line:before{content:""}.ri-close-fill:before{content:""}.ri-close-line:before{content:""}.ri-closed-captioning-fill:before{content:""}.ri-closed-captioning-line:before{content:""}.ri-cloud-fill:before{content:""}.ri-cloud-line:before{content:""}.ri-cloud-off-fill:before{content:""}.ri-cloud-off-line:before{content:""}.ri-cloud-windy-fill:before{content:""}.ri-cloud-windy-line:before{content:""}.ri-cloudy-2-fill:before{content:""}.ri-cloudy-2-line:before{content:""}.ri-cloudy-fill:before{content:""}.ri-cloudy-line:before{content:""}.ri-code-box-fill:before{content:""}.ri-code-box-line:before{content:""}.ri-code-fill:before{content:""}.ri-code-line:before{content:""}.ri-code-s-fill:before{content:""}.ri-code-s-line:before{content:""}.ri-code-s-slash-fill:before{content:""}.ri-code-s-slash-line:before{content:""}.ri-code-view:before{content:""}.ri-codepen-fill:before{content:""}.ri-codepen-line:before{content:""}.ri-coin-fill:before{content:""}.ri-coin-line:before{content:""}.ri-coins-fill:before{content:""}.ri-coins-line:before{content:""}.ri-collage-fill:before{content:""}.ri-collage-line:before{content:""}.ri-command-fill:before{content:""}.ri-command-line:before{content:""}.ri-community-fill:before{content:""}.ri-community-line:before{content:""}.ri-compass-2-fill:before{content:""}.ri-compass-2-line:before{content:""}.ri-compass-3-fill:before{content:""}.ri-compass-3-line:before{content:""}.ri-compass-4-fill:before{content:""}.ri-compass-4-line:before{content:""}.ri-compass-discover-fill:before{content:""}.ri-compass-discover-line:before{content:""}.ri-compass-fill:before{content:""}.ri-compass-line:before{content:""}.ri-compasses-2-fill:before{content:""}.ri-compasses-2-line:before{content:""}.ri-compasses-fill:before{content:""}.ri-compasses-line:before{content:""}.ri-computer-fill:before{content:""}.ri-computer-line:before{content:""}.ri-contacts-book-2-fill:before{content:""}.ri-contacts-book-2-line:before{content:""}.ri-contacts-book-fill:before{content:""}.ri-contacts-book-line:before{content:""}.ri-contacts-book-upload-fill:before{content:""}.ri-contacts-book-upload-line:before{content:""}.ri-contacts-fill:before{content:""}.ri-contacts-line:before{content:""}.ri-contrast-2-fill:before{content:""}.ri-contrast-2-line:before{content:""}.ri-contrast-drop-2-fill:before{content:""}.ri-contrast-drop-2-line:before{content:""}.ri-contrast-drop-fill:before{content:""}.ri-contrast-drop-line:before{content:""}.ri-contrast-fill:before{content:""}.ri-contrast-line:before{content:""}.ri-copper-coin-fill:before{content:""}.ri-copper-coin-line:before{content:""}.ri-copper-diamond-fill:before{content:""}.ri-copper-diamond-line:before{content:""}.ri-copyleft-fill:before{content:""}.ri-copyleft-line:before{content:""}.ri-copyright-fill:before{content:""}.ri-copyright-line:before{content:""}.ri-coreos-fill:before{content:""}.ri-coreos-line:before{content:""}.ri-coupon-2-fill:before{content:""}.ri-coupon-2-line:before{content:""}.ri-coupon-3-fill:before{content:""}.ri-coupon-3-line:before{content:""}.ri-coupon-4-fill:before{content:""}.ri-coupon-4-line:before{content:""}.ri-coupon-5-fill:before{content:""}.ri-coupon-5-line:before{content:""}.ri-coupon-fill:before{content:""}.ri-coupon-line:before{content:""}.ri-cpu-fill:before{content:""}.ri-cpu-line:before{content:""}.ri-creative-commons-by-fill:before{content:""}.ri-creative-commons-by-line:before{content:""}.ri-creative-commons-fill:before{content:""}.ri-creative-commons-line:before{content:""}.ri-creative-commons-nc-fill:before{content:""}.ri-creative-commons-nc-line:before{content:""}.ri-creative-commons-nd-fill:before{content:""}.ri-creative-commons-nd-line:before{content:""}.ri-creative-commons-sa-fill:before{content:""}.ri-creative-commons-sa-line:before{content:""}.ri-creative-commons-zero-fill:before{content:""}.ri-creative-commons-zero-line:before{content:""}.ri-criminal-fill:before{content:""}.ri-criminal-line:before{content:""}.ri-crop-2-fill:before{content:""}.ri-crop-2-line:before{content:""}.ri-crop-fill:before{content:""}.ri-crop-line:before{content:""}.ri-css3-fill:before{content:""}.ri-css3-line:before{content:""}.ri-cup-fill:before{content:""}.ri-cup-line:before{content:""}.ri-currency-fill:before{content:""}.ri-currency-line:before{content:""}.ri-cursor-fill:before{content:""}.ri-cursor-line:before{content:""}.ri-customer-service-2-fill:before{content:""}.ri-customer-service-2-line:before{content:""}.ri-customer-service-fill:before{content:""}.ri-customer-service-line:before{content:""}.ri-dashboard-2-fill:before{content:""}.ri-dashboard-2-line:before{content:""}.ri-dashboard-3-fill:before{content:""}.ri-dashboard-3-line:before{content:""}.ri-dashboard-fill:before{content:""}.ri-dashboard-line:before{content:""}.ri-database-2-fill:before{content:""}.ri-database-2-line:before{content:""}.ri-database-fill:before{content:""}.ri-database-line:before{content:""}.ri-delete-back-2-fill:before{content:""}.ri-delete-back-2-line:before{content:""}.ri-delete-back-fill:before{content:""}.ri-delete-back-line:before{content:""}.ri-delete-bin-2-fill:before{content:""}.ri-delete-bin-2-line:before{content:""}.ri-delete-bin-3-fill:before{content:""}.ri-delete-bin-3-line:before{content:""}.ri-delete-bin-4-fill:before{content:""}.ri-delete-bin-4-line:before{content:""}.ri-delete-bin-5-fill:before{content:""}.ri-delete-bin-5-line:before{content:""}.ri-delete-bin-6-fill:before{content:""}.ri-delete-bin-6-line:before{content:""}.ri-delete-bin-7-fill:before{content:""}.ri-delete-bin-7-line:before{content:""}.ri-delete-bin-fill:before{content:""}.ri-delete-bin-line:before{content:""}.ri-delete-column:before{content:""}.ri-delete-row:before{content:""}.ri-device-fill:before{content:""}.ri-device-line:before{content:""}.ri-device-recover-fill:before{content:""}.ri-device-recover-line:before{content:""}.ri-dingding-fill:before{content:""}.ri-dingding-line:before{content:""}.ri-direction-fill:before{content:""}.ri-direction-line:before{content:""}.ri-disc-fill:before{content:""}.ri-disc-line:before{content:""}.ri-discord-fill:before{content:""}.ri-discord-line:before{content:""}.ri-discuss-fill:before{content:""}.ri-discuss-line:before{content:""}.ri-dislike-fill:before{content:""}.ri-dislike-line:before{content:""}.ri-disqus-fill:before{content:""}.ri-disqus-line:before{content:""}.ri-divide-fill:before{content:""}.ri-divide-line:before{content:""}.ri-donut-chart-fill:before{content:""}.ri-donut-chart-line:before{content:""}.ri-door-closed-fill:before{content:""}.ri-door-closed-line:before{content:""}.ri-door-fill:before{content:""}.ri-door-line:before{content:""}.ri-door-lock-box-fill:before{content:""}.ri-door-lock-box-line:before{content:""}.ri-door-lock-fill:before{content:""}.ri-door-lock-line:before{content:""}.ri-door-open-fill:before{content:""}.ri-door-open-line:before{content:""}.ri-dossier-fill:before{content:""}.ri-dossier-line:before{content:""}.ri-douban-fill:before{content:""}.ri-douban-line:before{content:""}.ri-double-quotes-l:before{content:""}.ri-double-quotes-r:before{content:""}.ri-download-2-fill:before{content:""}.ri-download-2-line:before{content:""}.ri-download-cloud-2-fill:before{content:""}.ri-download-cloud-2-line:before{content:""}.ri-download-cloud-fill:before{content:""}.ri-download-cloud-line:before{content:""}.ri-download-fill:before{content:""}.ri-download-line:before{content:""}.ri-draft-fill:before{content:""}.ri-draft-line:before{content:""}.ri-drag-drop-fill:before{content:""}.ri-drag-drop-line:before{content:""}.ri-drag-move-2-fill:before{content:""}.ri-drag-move-2-line:before{content:""}.ri-drag-move-fill:before{content:""}.ri-drag-move-line:before{content:""}.ri-dribbble-fill:before{content:""}.ri-dribbble-line:before{content:""}.ri-drive-fill:before{content:""}.ri-drive-line:before{content:""}.ri-drizzle-fill:before{content:""}.ri-drizzle-line:before{content:""}.ri-drop-fill:before{content:""}.ri-drop-line:before{content:""}.ri-dropbox-fill:before{content:""}.ri-dropbox-line:before{content:""}.ri-dual-sim-1-fill:before{content:""}.ri-dual-sim-1-line:before{content:""}.ri-dual-sim-2-fill:before{content:""}.ri-dual-sim-2-line:before{content:""}.ri-dv-fill:before{content:""}.ri-dv-line:before{content:""}.ri-dvd-fill:before{content:""}.ri-dvd-line:before{content:""}.ri-e-bike-2-fill:before{content:""}.ri-e-bike-2-line:before{content:""}.ri-e-bike-fill:before{content:""}.ri-e-bike-line:before{content:""}.ri-earth-fill:before{content:""}.ri-earth-line:before{content:""}.ri-earthquake-fill:before{content:""}.ri-earthquake-line:before{content:""}.ri-edge-fill:before{content:""}.ri-edge-line:before{content:""}.ri-edit-2-fill:before{content:""}.ri-edit-2-line:before{content:""}.ri-edit-box-fill:before{content:""}.ri-edit-box-line:before{content:""}.ri-edit-circle-fill:before{content:""}.ri-edit-circle-line:before{content:""}.ri-edit-fill:before{content:""}.ri-edit-line:before{content:""}.ri-eject-fill:before{content:""}.ri-eject-line:before{content:""}.ri-emotion-2-fill:before{content:""}.ri-emotion-2-line:before{content:""}.ri-emotion-fill:before{content:""}.ri-emotion-happy-fill:before{content:""}.ri-emotion-happy-line:before{content:""}.ri-emotion-laugh-fill:before{content:""}.ri-emotion-laugh-line:before{content:""}.ri-emotion-line:before{content:""}.ri-emotion-normal-fill:before{content:""}.ri-emotion-normal-line:before{content:""}.ri-emotion-sad-fill:before{content:""}.ri-emotion-sad-line:before{content:""}.ri-emotion-unhappy-fill:before{content:""}.ri-emotion-unhappy-line:before{content:""}.ri-empathize-fill:before{content:""}.ri-empathize-line:before{content:""}.ri-emphasis-cn:before{content:""}.ri-emphasis:before{content:""}.ri-english-input:before{content:""}.ri-equalizer-fill:before{content:""}.ri-equalizer-line:before{content:""}.ri-eraser-fill:before{content:""}.ri-eraser-line:before{content:""}.ri-error-warning-fill:before{content:""}.ri-error-warning-line:before{content:""}.ri-evernote-fill:before{content:""}.ri-evernote-line:before{content:""}.ri-exchange-box-fill:before{content:""}.ri-exchange-box-line:before{content:""}.ri-exchange-cny-fill:before{content:""}.ri-exchange-cny-line:before{content:""}.ri-exchange-dollar-fill:before{content:""}.ri-exchange-dollar-line:before{content:""}.ri-exchange-fill:before{content:""}.ri-exchange-funds-fill:before{content:""}.ri-exchange-funds-line:before{content:""}.ri-exchange-line:before{content:""}.ri-external-link-fill:before{content:""}.ri-external-link-line:before{content:""}.ri-eye-2-fill:before{content:""}.ri-eye-2-line:before{content:""}.ri-eye-close-fill:before{content:""}.ri-eye-close-line:before{content:""}.ri-eye-fill:before{content:""}.ri-eye-line:before{content:""}.ri-eye-off-fill:before{content:""}.ri-eye-off-line:before{content:""}.ri-facebook-box-fill:before{content:""}.ri-facebook-box-line:before{content:""}.ri-facebook-circle-fill:before{content:""}.ri-facebook-circle-line:before{content:""}.ri-facebook-fill:before{content:""}.ri-facebook-line:before{content:""}.ri-fahrenheit-fill:before{content:""}.ri-fahrenheit-line:before{content:""}.ri-feedback-fill:before{content:""}.ri-feedback-line:before{content:""}.ri-file-2-fill:before{content:""}.ri-file-2-line:before{content:""}.ri-file-3-fill:before{content:""}.ri-file-3-line:before{content:""}.ri-file-4-fill:before{content:""}.ri-file-4-line:before{content:""}.ri-file-add-fill:before{content:""}.ri-file-add-line:before{content:""}.ri-file-chart-2-fill:before{content:""}.ri-file-chart-2-line:before{content:""}.ri-file-chart-fill:before{content:""}.ri-file-chart-line:before{content:""}.ri-file-cloud-fill:before{content:""}.ri-file-cloud-line:before{content:""}.ri-file-code-fill:before{content:""}.ri-file-code-line:before{content:""}.ri-file-copy-2-fill:before{content:""}.ri-file-copy-2-line:before{content:""}.ri-file-copy-fill:before{content:""}.ri-file-copy-line:before{content:""}.ri-file-damage-fill:before{content:""}.ri-file-damage-line:before{content:""}.ri-file-download-fill:before{content:""}.ri-file-download-line:before{content:""}.ri-file-edit-fill:before{content:""}.ri-file-edit-line:before{content:""}.ri-file-excel-2-fill:before{content:""}.ri-file-excel-2-line:before{content:""}.ri-file-excel-fill:before{content:""}.ri-file-excel-line:before{content:""}.ri-file-fill:before{content:""}.ri-file-forbid-fill:before{content:""}.ri-file-forbid-line:before{content:""}.ri-file-gif-fill:before{content:""}.ri-file-gif-line:before{content:""}.ri-file-history-fill:before{content:""}.ri-file-history-line:before{content:""}.ri-file-hwp-fill:before{content:""}.ri-file-hwp-line:before{content:""}.ri-file-info-fill:before{content:""}.ri-file-info-line:before{content:""}.ri-file-line:before{content:""}.ri-file-list-2-fill:before{content:""}.ri-file-list-2-line:before{content:""}.ri-file-list-3-fill:before{content:""}.ri-file-list-3-line:before{content:""}.ri-file-list-fill:before{content:""}.ri-file-list-line:before{content:""}.ri-file-lock-fill:before{content:""}.ri-file-lock-line:before{content:""}.ri-file-marked-fill:before{content:""}.ri-file-marked-line:before{content:""}.ri-file-music-fill:before{content:""}.ri-file-music-line:before{content:""}.ri-file-paper-2-fill:before{content:""}.ri-file-paper-2-line:before{content:""}.ri-file-paper-fill:before{content:""}.ri-file-paper-line:before{content:""}.ri-file-pdf-fill:before{content:""}.ri-file-pdf-line:before{content:""}.ri-file-ppt-2-fill:before{content:""}.ri-file-ppt-2-line:before{content:""}.ri-file-ppt-fill:before{content:""}.ri-file-ppt-line:before{content:""}.ri-file-reduce-fill:before{content:""}.ri-file-reduce-line:before{content:""}.ri-file-search-fill:before{content:""}.ri-file-search-line:before{content:""}.ri-file-settings-fill:before{content:""}.ri-file-settings-line:before{content:""}.ri-file-shield-2-fill:before{content:""}.ri-file-shield-2-line:before{content:""}.ri-file-shield-fill:before{content:""}.ri-file-shield-line:before{content:""}.ri-file-shred-fill:before{content:""}.ri-file-shred-line:before{content:""}.ri-file-text-fill:before{content:""}.ri-file-text-line:before{content:""}.ri-file-transfer-fill:before{content:""}.ri-file-transfer-line:before{content:""}.ri-file-unknow-fill:before{content:""}.ri-file-unknow-line:before{content:""}.ri-file-upload-fill:before{content:""}.ri-file-upload-line:before{content:""}.ri-file-user-fill:before{content:""}.ri-file-user-line:before{content:""}.ri-file-warning-fill:before{content:""}.ri-file-warning-line:before{content:""}.ri-file-word-2-fill:before{content:""}.ri-file-word-2-line:before{content:""}.ri-file-word-fill:before{content:""}.ri-file-word-line:before{content:""}.ri-file-zip-fill:before{content:""}.ri-file-zip-line:before{content:""}.ri-film-fill:before{content:""}.ri-film-line:before{content:""}.ri-filter-2-fill:before{content:""}.ri-filter-2-line:before{content:""}.ri-filter-3-fill:before{content:""}.ri-filter-3-line:before{content:""}.ri-filter-fill:before{content:""}.ri-filter-line:before{content:""}.ri-filter-off-fill:before{content:""}.ri-filter-off-line:before{content:""}.ri-find-replace-fill:before{content:""}.ri-find-replace-line:before{content:""}.ri-finder-fill:before{content:""}.ri-finder-line:before{content:""}.ri-fingerprint-2-fill:before{content:""}.ri-fingerprint-2-line:before{content:""}.ri-fingerprint-fill:before{content:""}.ri-fingerprint-line:before{content:""}.ri-fire-fill:before{content:""}.ri-fire-line:before{content:""}.ri-firefox-fill:before{content:""}.ri-firefox-line:before{content:""}.ri-first-aid-kit-fill:before{content:""}.ri-first-aid-kit-line:before{content:""}.ri-flag-2-fill:before{content:""}.ri-flag-2-line:before{content:""}.ri-flag-fill:before{content:""}.ri-flag-line:before{content:""}.ri-flashlight-fill:before{content:""}.ri-flashlight-line:before{content:""}.ri-flask-fill:before{content:""}.ri-flask-line:before{content:""}.ri-flight-land-fill:before{content:""}.ri-flight-land-line:before{content:""}.ri-flight-takeoff-fill:before{content:""}.ri-flight-takeoff-line:before{content:""}.ri-flood-fill:before{content:""}.ri-flood-line:before{content:""}.ri-flow-chart:before{content:""}.ri-flutter-fill:before{content:""}.ri-flutter-line:before{content:""}.ri-focus-2-fill:before{content:""}.ri-focus-2-line:before{content:""}.ri-focus-3-fill:before{content:""}.ri-focus-3-line:before{content:""}.ri-focus-fill:before{content:""}.ri-focus-line:before{content:""}.ri-foggy-fill:before{content:""}.ri-foggy-line:before{content:""}.ri-folder-2-fill:before{content:""}.ri-folder-2-line:before{content:""}.ri-folder-3-fill:before{content:""}.ri-folder-3-line:before{content:""}.ri-folder-4-fill:before{content:""}.ri-folder-4-line:before{content:""}.ri-folder-5-fill:before{content:""}.ri-folder-5-line:before{content:""}.ri-folder-add-fill:before{content:""}.ri-folder-add-line:before{content:""}.ri-folder-chart-2-fill:before{content:""}.ri-folder-chart-2-line:before{content:""}.ri-folder-chart-fill:before{content:""}.ri-folder-chart-line:before{content:""}.ri-folder-download-fill:before{content:""}.ri-folder-download-line:before{content:""}.ri-folder-fill:before{content:""}.ri-folder-forbid-fill:before{content:""}.ri-folder-forbid-line:before{content:""}.ri-folder-history-fill:before{content:""}.ri-folder-history-line:before{content:""}.ri-folder-info-fill:before{content:""}.ri-folder-info-line:before{content:""}.ri-folder-keyhole-fill:before{content:""}.ri-folder-keyhole-line:before{content:""}.ri-folder-line:before{content:""}.ri-folder-lock-fill:before{content:""}.ri-folder-lock-line:before{content:""}.ri-folder-music-fill:before{content:""}.ri-folder-music-line:before{content:""}.ri-folder-open-fill:before{content:""}.ri-folder-open-line:before{content:""}.ri-folder-received-fill:before{content:""}.ri-folder-received-line:before{content:""}.ri-folder-reduce-fill:before{content:""}.ri-folder-reduce-line:before{content:""}.ri-folder-settings-fill:before{content:""}.ri-folder-settings-line:before{content:""}.ri-folder-shared-fill:before{content:""}.ri-folder-shared-line:before{content:""}.ri-folder-shield-2-fill:before{content:""}.ri-folder-shield-2-line:before{content:""}.ri-folder-shield-fill:before{content:""}.ri-folder-shield-line:before{content:""}.ri-folder-transfer-fill:before{content:""}.ri-folder-transfer-line:before{content:""}.ri-folder-unknow-fill:before{content:""}.ri-folder-unknow-line:before{content:""}.ri-folder-upload-fill:before{content:""}.ri-folder-upload-line:before{content:""}.ri-folder-user-fill:before{content:""}.ri-folder-user-line:before{content:""}.ri-folder-warning-fill:before{content:""}.ri-folder-warning-line:before{content:""}.ri-folder-zip-fill:before{content:""}.ri-folder-zip-line:before{content:""}.ri-folders-fill:before{content:""}.ri-folders-line:before{content:""}.ri-font-color:before{content:""}.ri-font-size-2:before{content:""}.ri-font-size:before{content:""}.ri-football-fill:before{content:""}.ri-football-line:before{content:""}.ri-footprint-fill:before{content:""}.ri-footprint-line:before{content:""}.ri-forbid-2-fill:before{content:""}.ri-forbid-2-line:before{content:""}.ri-forbid-fill:before{content:""}.ri-forbid-line:before{content:""}.ri-format-clear:before{content:""}.ri-fridge-fill:before{content:""}.ri-fridge-line:before{content:""}.ri-fullscreen-exit-fill:before{content:""}.ri-fullscreen-exit-line:before{content:""}.ri-fullscreen-fill:before{content:""}.ri-fullscreen-line:before{content:""}.ri-function-fill:before{content:""}.ri-function-line:before{content:""}.ri-functions:before{content:""}.ri-funds-box-fill:before{content:""}.ri-funds-box-line:before{content:""}.ri-funds-fill:before{content:""}.ri-funds-line:before{content:""}.ri-gallery-fill:before{content:""}.ri-gallery-line:before{content:""}.ri-gallery-upload-fill:before{content:""}.ri-gallery-upload-line:before{content:""}.ri-game-fill:before{content:""}.ri-game-line:before{content:""}.ri-gamepad-fill:before{content:""}.ri-gamepad-line:before{content:""}.ri-gas-station-fill:before{content:""}.ri-gas-station-line:before{content:""}.ri-gatsby-fill:before{content:""}.ri-gatsby-line:before{content:""}.ri-genderless-fill:before{content:""}.ri-genderless-line:before{content:""}.ri-ghost-2-fill:before{content:""}.ri-ghost-2-line:before{content:""}.ri-ghost-fill:before{content:""}.ri-ghost-line:before{content:""}.ri-ghost-smile-fill:before{content:""}.ri-ghost-smile-line:before{content:""}.ri-gift-2-fill:before{content:""}.ri-gift-2-line:before{content:""}.ri-gift-fill:before{content:""}.ri-gift-line:before{content:""}.ri-git-branch-fill:before{content:""}.ri-git-branch-line:before{content:""}.ri-git-commit-fill:before{content:""}.ri-git-commit-line:before{content:""}.ri-git-merge-fill:before{content:""}.ri-git-merge-line:before{content:""}.ri-git-pull-request-fill:before{content:""}.ri-git-pull-request-line:before{content:""}.ri-git-repository-commits-fill:before{content:""}.ri-git-repository-commits-line:before{content:""}.ri-git-repository-fill:before{content:""}.ri-git-repository-line:before{content:""}.ri-git-repository-private-fill:before{content:""}.ri-git-repository-private-line:before{content:""}.ri-github-fill:before{content:""}.ri-github-line:before{content:""}.ri-gitlab-fill:before{content:""}.ri-gitlab-line:before{content:""}.ri-global-fill:before{content:""}.ri-global-line:before{content:""}.ri-globe-fill:before{content:""}.ri-globe-line:before{content:""}.ri-goblet-fill:before{content:""}.ri-goblet-line:before{content:""}.ri-google-fill:before{content:""}.ri-google-line:before{content:""}.ri-google-play-fill:before{content:""}.ri-google-play-line:before{content:""}.ri-government-fill:before{content:""}.ri-government-line:before{content:""}.ri-gps-fill:before{content:""}.ri-gps-line:before{content:""}.ri-gradienter-fill:before{content:""}.ri-gradienter-line:before{content:""}.ri-grid-fill:before{content:""}.ri-grid-line:before{content:""}.ri-group-2-fill:before{content:""}.ri-group-2-line:before{content:""}.ri-group-fill:before{content:""}.ri-group-line:before{content:""}.ri-guide-fill:before{content:""}.ri-guide-line:before{content:""}.ri-h-1:before{content:""}.ri-h-2:before{content:""}.ri-h-3:before{content:""}.ri-h-4:before{content:""}.ri-h-5:before{content:""}.ri-h-6:before{content:""}.ri-hail-fill:before{content:""}.ri-hail-line:before{content:""}.ri-hammer-fill:before{content:""}.ri-hammer-line:before{content:""}.ri-hand-coin-fill:before{content:""}.ri-hand-coin-line:before{content:""}.ri-hand-heart-fill:before{content:""}.ri-hand-heart-line:before{content:""}.ri-hand-sanitizer-fill:before{content:""}.ri-hand-sanitizer-line:before{content:""}.ri-handbag-fill:before{content:""}.ri-handbag-line:before{content:""}.ri-hard-drive-2-fill:before{content:""}.ri-hard-drive-2-line:before{content:""}.ri-hard-drive-fill:before{content:""}.ri-hard-drive-line:before{content:""}.ri-hashtag:before{content:""}.ri-haze-2-fill:before{content:""}.ri-haze-2-line:before{content:""}.ri-haze-fill:before{content:""}.ri-haze-line:before{content:""}.ri-hd-fill:before{content:""}.ri-hd-line:before{content:""}.ri-heading:before{content:""}.ri-headphone-fill:before{content:""}.ri-headphone-line:before{content:""}.ri-health-book-fill:before{content:""}.ri-health-book-line:before{content:""}.ri-heart-2-fill:before{content:""}.ri-heart-2-line:before{content:""}.ri-heart-3-fill:before{content:""}.ri-heart-3-line:before{content:""}.ri-heart-add-fill:before{content:""}.ri-heart-add-line:before{content:""}.ri-heart-fill:before{content:""}.ri-heart-line:before{content:""}.ri-heart-pulse-fill:before{content:""}.ri-heart-pulse-line:before{content:""}.ri-hearts-fill:before{content:""}.ri-hearts-line:before{content:""}.ri-heavy-showers-fill:before{content:""}.ri-heavy-showers-line:before{content:""}.ri-history-fill:before{content:""}.ri-history-line:before{content:""}.ri-home-2-fill:before{content:""}.ri-home-2-line:before{content:""}.ri-home-3-fill:before{content:""}.ri-home-3-line:before{content:""}.ri-home-4-fill:before{content:""}.ri-home-4-line:before{content:""}.ri-home-5-fill:before{content:""}.ri-home-5-line:before{content:""}.ri-home-6-fill:before{content:""}.ri-home-6-line:before{content:""}.ri-home-7-fill:before{content:""}.ri-home-7-line:before{content:""}.ri-home-8-fill:before{content:""}.ri-home-8-line:before{content:""}.ri-home-fill:before{content:""}.ri-home-gear-fill:before{content:""}.ri-home-gear-line:before{content:""}.ri-home-heart-fill:before{content:""}.ri-home-heart-line:before{content:""}.ri-home-line:before{content:""}.ri-home-smile-2-fill:before{content:""}.ri-home-smile-2-line:before{content:""}.ri-home-smile-fill:before{content:""}.ri-home-smile-line:before{content:""}.ri-home-wifi-fill:before{content:""}.ri-home-wifi-line:before{content:""}.ri-honor-of-kings-fill:before{content:""}.ri-honor-of-kings-line:before{content:""}.ri-honour-fill:before{content:""}.ri-honour-line:before{content:""}.ri-hospital-fill:before{content:""}.ri-hospital-line:before{content:""}.ri-hotel-bed-fill:before{content:""}.ri-hotel-bed-line:before{content:""}.ri-hotel-fill:before{content:""}.ri-hotel-line:before{content:""}.ri-hotspot-fill:before{content:""}.ri-hotspot-line:before{content:""}.ri-hq-fill:before{content:""}.ri-hq-line:before{content:""}.ri-html5-fill:before{content:""}.ri-html5-line:before{content:""}.ri-ie-fill:before{content:""}.ri-ie-line:before{content:""}.ri-image-2-fill:before{content:""}.ri-image-2-line:before{content:""}.ri-image-add-fill:before{content:""}.ri-image-add-line:before{content:""}.ri-image-edit-fill:before{content:""}.ri-image-edit-line:before{content:""}.ri-image-fill:before{content:""}.ri-image-line:before{content:""}.ri-inbox-archive-fill:before{content:""}.ri-inbox-archive-line:before{content:""}.ri-inbox-fill:before{content:""}.ri-inbox-line:before{content:""}.ri-inbox-unarchive-fill:before{content:""}.ri-inbox-unarchive-line:before{content:""}.ri-increase-decrease-fill:before{content:""}.ri-increase-decrease-line:before{content:""}.ri-indent-decrease:before{content:""}.ri-indent-increase:before{content:""}.ri-indeterminate-circle-fill:before{content:""}.ri-indeterminate-circle-line:before{content:""}.ri-information-fill:before{content:""}.ri-information-line:before{content:""}.ri-infrared-thermometer-fill:before{content:""}.ri-infrared-thermometer-line:before{content:""}.ri-ink-bottle-fill:before{content:""}.ri-ink-bottle-line:before{content:""}.ri-input-cursor-move:before{content:""}.ri-input-method-fill:before{content:""}.ri-input-method-line:before{content:""}.ri-insert-column-left:before{content:""}.ri-insert-column-right:before{content:""}.ri-insert-row-bottom:before{content:""}.ri-insert-row-top:before{content:""}.ri-instagram-fill:before{content:""}.ri-instagram-line:before{content:""}.ri-install-fill:before{content:""}.ri-install-line:before{content:""}.ri-invision-fill:before{content:""}.ri-invision-line:before{content:""}.ri-italic:before{content:""}.ri-kakao-talk-fill:before{content:""}.ri-kakao-talk-line:before{content:""}.ri-key-2-fill:before{content:""}.ri-key-2-line:before{content:""}.ri-key-fill:before{content:""}.ri-key-line:before{content:""}.ri-keyboard-box-fill:before{content:""}.ri-keyboard-box-line:before{content:""}.ri-keyboard-fill:before{content:""}.ri-keyboard-line:before{content:""}.ri-keynote-fill:before{content:""}.ri-keynote-line:before{content:""}.ri-knife-blood-fill:before{content:""}.ri-knife-blood-line:before{content:""}.ri-knife-fill:before{content:""}.ri-knife-line:before{content:""}.ri-landscape-fill:before{content:""}.ri-landscape-line:before{content:""}.ri-layout-2-fill:before{content:""}.ri-layout-2-line:before{content:""}.ri-layout-3-fill:before{content:""}.ri-layout-3-line:before{content:""}.ri-layout-4-fill:before{content:""}.ri-layout-4-line:before{content:""}.ri-layout-5-fill:before{content:""}.ri-layout-5-line:before{content:""}.ri-layout-6-fill:before{content:""}.ri-layout-6-line:before{content:""}.ri-layout-bottom-2-fill:before{content:""}.ri-layout-bottom-2-line:before{content:""}.ri-layout-bottom-fill:before{content:""}.ri-layout-bottom-line:before{content:""}.ri-layout-column-fill:before{content:""}.ri-layout-column-line:before{content:""}.ri-layout-fill:before{content:""}.ri-layout-grid-fill:before{content:""}.ri-layout-grid-line:before{content:""}.ri-layout-left-2-fill:before{content:""}.ri-layout-left-2-line:before{content:""}.ri-layout-left-fill:before{content:""}.ri-layout-left-line:before{content:""}.ri-layout-line:before{content:""}.ri-layout-masonry-fill:before{content:""}.ri-layout-masonry-line:before{content:""}.ri-layout-right-2-fill:before{content:""}.ri-layout-right-2-line:before{content:""}.ri-layout-right-fill:before{content:""}.ri-layout-right-line:before{content:""}.ri-layout-row-fill:before{content:""}.ri-layout-row-line:before{content:""}.ri-layout-top-2-fill:before{content:""}.ri-layout-top-2-line:before{content:""}.ri-layout-top-fill:before{content:""}.ri-layout-top-line:before{content:""}.ri-leaf-fill:before{content:""}.ri-leaf-line:before{content:""}.ri-lifebuoy-fill:before{content:""}.ri-lifebuoy-line:before{content:""}.ri-lightbulb-fill:before{content:""}.ri-lightbulb-flash-fill:before{content:""}.ri-lightbulb-flash-line:before{content:""}.ri-lightbulb-line:before{content:""}.ri-line-chart-fill:before{content:""}.ri-line-chart-line:before{content:""}.ri-line-fill:before{content:""}.ri-line-height:before{content:""}.ri-line-line:before{content:""}.ri-link-m:before{content:""}.ri-link-unlink-m:before{content:""}.ri-link-unlink:before{content:""}.ri-link:before{content:""}.ri-linkedin-box-fill:before{content:""}.ri-linkedin-box-line:before{content:""}.ri-linkedin-fill:before{content:""}.ri-linkedin-line:before{content:""}.ri-links-fill:before{content:""}.ri-links-line:before{content:""}.ri-list-check-2:before{content:""}.ri-list-check:before{content:""}.ri-list-ordered:before{content:""}.ri-list-settings-fill:before{content:""}.ri-list-settings-line:before{content:""}.ri-list-unordered:before{content:""}.ri-live-fill:before{content:""}.ri-live-line:before{content:""}.ri-loader-2-fill:before{content:""}.ri-loader-2-line:before{content:""}.ri-loader-3-fill:before{content:""}.ri-loader-3-line:before{content:""}.ri-loader-4-fill:before{content:""}.ri-loader-4-line:before{content:""}.ri-loader-5-fill:before{content:""}.ri-loader-5-line:before{content:""}.ri-loader-fill:before{content:""}.ri-loader-line:before{content:""}.ri-lock-2-fill:before{content:""}.ri-lock-2-line:before{content:""}.ri-lock-fill:before{content:""}.ri-lock-line:before{content:""}.ri-lock-password-fill:before{content:""}.ri-lock-password-line:before{content:""}.ri-lock-unlock-fill:before{content:""}.ri-lock-unlock-line:before{content:""}.ri-login-box-fill:before{content:""}.ri-login-box-line:before{content:""}.ri-login-circle-fill:before{content:""}.ri-login-circle-line:before{content:""}.ri-logout-box-fill:before{content:""}.ri-logout-box-line:before{content:""}.ri-logout-box-r-fill:before{content:""}.ri-logout-box-r-line:before{content:""}.ri-logout-circle-fill:before{content:""}.ri-logout-circle-line:before{content:""}.ri-logout-circle-r-fill:before{content:""}.ri-logout-circle-r-line:before{content:""}.ri-luggage-cart-fill:before{content:""}.ri-luggage-cart-line:before{content:""}.ri-luggage-deposit-fill:before{content:""}.ri-luggage-deposit-line:before{content:""}.ri-lungs-fill:before{content:""}.ri-lungs-line:before{content:""}.ri-mac-fill:before{content:""}.ri-mac-line:before{content:""}.ri-macbook-fill:before{content:""}.ri-macbook-line:before{content:""}.ri-magic-fill:before{content:""}.ri-magic-line:before{content:""}.ri-mail-add-fill:before{content:""}.ri-mail-add-line:before{content:""}.ri-mail-check-fill:before{content:""}.ri-mail-check-line:before{content:""}.ri-mail-close-fill:before{content:""}.ri-mail-close-line:before{content:""}.ri-mail-download-fill:before{content:""}.ri-mail-download-line:before{content:""}.ri-mail-fill:before{content:""}.ri-mail-forbid-fill:before{content:""}.ri-mail-forbid-line:before{content:""}.ri-mail-line:before{content:""}.ri-mail-lock-fill:before{content:""}.ri-mail-lock-line:before{content:""}.ri-mail-open-fill:before{content:""}.ri-mail-open-line:before{content:""}.ri-mail-send-fill:before{content:""}.ri-mail-send-line:before{content:""}.ri-mail-settings-fill:before{content:""}.ri-mail-settings-line:before{content:""}.ri-mail-star-fill:before{content:""}.ri-mail-star-line:before{content:""}.ri-mail-unread-fill:before{content:""}.ri-mail-unread-line:before{content:""}.ri-mail-volume-fill:before{content:""}.ri-mail-volume-line:before{content:""}.ri-map-2-fill:before{content:""}.ri-map-2-line:before{content:""}.ri-map-fill:before{content:""}.ri-map-line:before{content:""}.ri-map-pin-2-fill:before{content:""}.ri-map-pin-2-line:before{content:""}.ri-map-pin-3-fill:before{content:""}.ri-map-pin-3-line:before{content:""}.ri-map-pin-4-fill:before{content:""}.ri-map-pin-4-line:before{content:""}.ri-map-pin-5-fill:before{content:""}.ri-map-pin-5-line:before{content:""}.ri-map-pin-add-fill:before{content:""}.ri-map-pin-add-line:before{content:""}.ri-map-pin-fill:before{content:""}.ri-map-pin-line:before{content:""}.ri-map-pin-range-fill:before{content:""}.ri-map-pin-range-line:before{content:""}.ri-map-pin-time-fill:before{content:""}.ri-map-pin-time-line:before{content:""}.ri-map-pin-user-fill:before{content:""}.ri-map-pin-user-line:before{content:""}.ri-mark-pen-fill:before{content:""}.ri-mark-pen-line:before{content:""}.ri-markdown-fill:before{content:""}.ri-markdown-line:before{content:""}.ri-markup-fill:before{content:""}.ri-markup-line:before{content:""}.ri-mastercard-fill:before{content:""}.ri-mastercard-line:before{content:""}.ri-mastodon-fill:before{content:""}.ri-mastodon-line:before{content:""}.ri-medal-2-fill:before{content:""}.ri-medal-2-line:before{content:""}.ri-medal-fill:before{content:""}.ri-medal-line:before{content:""}.ri-medicine-bottle-fill:before{content:""}.ri-medicine-bottle-line:before{content:""}.ri-medium-fill:before{content:""}.ri-medium-line:before{content:""}.ri-men-fill:before{content:""}.ri-men-line:before{content:""}.ri-mental-health-fill:before{content:""}.ri-mental-health-line:before{content:""}.ri-menu-2-fill:before{content:""}.ri-menu-2-line:before{content:""}.ri-menu-3-fill:before{content:""}.ri-menu-3-line:before{content:""}.ri-menu-4-fill:before{content:""}.ri-menu-4-line:before{content:""}.ri-menu-5-fill:before{content:""}.ri-menu-5-line:before{content:""}.ri-menu-add-fill:before{content:""}.ri-menu-add-line:before{content:""}.ri-menu-fill:before{content:""}.ri-menu-fold-fill:before{content:""}.ri-menu-fold-line:before{content:""}.ri-menu-line:before{content:""}.ri-menu-unfold-fill:before{content:""}.ri-menu-unfold-line:before{content:""}.ri-merge-cells-horizontal:before{content:""}.ri-merge-cells-vertical:before{content:""}.ri-message-2-fill:before{content:""}.ri-message-2-line:before{content:""}.ri-message-3-fill:before{content:""}.ri-message-3-line:before{content:""}.ri-message-fill:before{content:""}.ri-message-line:before{content:""}.ri-messenger-fill:before{content:""}.ri-messenger-line:before{content:""}.ri-meteor-fill:before{content:""}.ri-meteor-line:before{content:""}.ri-mic-2-fill:before{content:""}.ri-mic-2-line:before{content:""}.ri-mic-fill:before{content:""}.ri-mic-line:before{content:""}.ri-mic-off-fill:before{content:""}.ri-mic-off-line:before{content:""}.ri-mickey-fill:before{content:""}.ri-mickey-line:before{content:""}.ri-microscope-fill:before{content:""}.ri-microscope-line:before{content:""}.ri-microsoft-fill:before{content:""}.ri-microsoft-line:before{content:""}.ri-mind-map:before{content:""}.ri-mini-program-fill:before{content:""}.ri-mini-program-line:before{content:""}.ri-mist-fill:before{content:""}.ri-mist-line:before{content:""}.ri-money-cny-box-fill:before{content:""}.ri-money-cny-box-line:before{content:""}.ri-money-cny-circle-fill:before{content:""}.ri-money-cny-circle-line:before{content:""}.ri-money-dollar-box-fill:before{content:""}.ri-money-dollar-box-line:before{content:""}.ri-money-dollar-circle-fill:before{content:""}.ri-money-dollar-circle-line:before{content:""}.ri-money-euro-box-fill:before{content:""}.ri-money-euro-box-line:before{content:""}.ri-money-euro-circle-fill:before{content:""}.ri-money-euro-circle-line:before{content:""}.ri-money-pound-box-fill:before{content:""}.ri-money-pound-box-line:before{content:""}.ri-money-pound-circle-fill:before{content:""}.ri-money-pound-circle-line:before{content:""}.ri-moon-clear-fill:before{content:""}.ri-moon-clear-line:before{content:""}.ri-moon-cloudy-fill:before{content:""}.ri-moon-cloudy-line:before{content:""}.ri-moon-fill:before{content:""}.ri-moon-foggy-fill:before{content:""}.ri-moon-foggy-line:before{content:""}.ri-moon-line:before{content:""}.ri-more-2-fill:before{content:""}.ri-more-2-line:before{content:""}.ri-more-fill:before{content:""}.ri-more-line:before{content:""}.ri-motorbike-fill:before{content:""}.ri-motorbike-line:before{content:""}.ri-mouse-fill:before{content:""}.ri-mouse-line:before{content:""}.ri-movie-2-fill:before{content:""}.ri-movie-2-line:before{content:""}.ri-movie-fill:before{content:""}.ri-movie-line:before{content:""}.ri-music-2-fill:before{content:""}.ri-music-2-line:before{content:""}.ri-music-fill:before{content:""}.ri-music-line:before{content:""}.ri-mv-fill:before{content:""}.ri-mv-line:before{content:""}.ri-navigation-fill:before{content:""}.ri-navigation-line:before{content:""}.ri-netease-cloud-music-fill:before{content:""}.ri-netease-cloud-music-line:before{content:""}.ri-netflix-fill:before{content:""}.ri-netflix-line:before{content:""}.ri-newspaper-fill:before{content:""}.ri-newspaper-line:before{content:""}.ri-node-tree:before{content:""}.ri-notification-2-fill:before{content:""}.ri-notification-2-line:before{content:""}.ri-notification-3-fill:before{content:""}.ri-notification-3-line:before{content:""}.ri-notification-4-fill:before{content:""}.ri-notification-4-line:before{content:""}.ri-notification-badge-fill:before{content:""}.ri-notification-badge-line:before{content:""}.ri-notification-fill:before{content:""}.ri-notification-line:before{content:""}.ri-notification-off-fill:before{content:""}.ri-notification-off-line:before{content:""}.ri-npmjs-fill:before{content:""}.ri-npmjs-line:before{content:""}.ri-number-0:before{content:""}.ri-number-1:before{content:""}.ri-number-2:before{content:""}.ri-number-3:before{content:""}.ri-number-4:before{content:""}.ri-number-5:before{content:""}.ri-number-6:before{content:""}.ri-number-7:before{content:""}.ri-number-8:before{content:""}.ri-number-9:before{content:""}.ri-numbers-fill:before{content:""}.ri-numbers-line:before{content:""}.ri-nurse-fill:before{content:""}.ri-nurse-line:before{content:""}.ri-oil-fill:before{content:""}.ri-oil-line:before{content:""}.ri-omega:before{content:""}.ri-open-arm-fill:before{content:""}.ri-open-arm-line:before{content:""}.ri-open-source-fill:before{content:""}.ri-open-source-line:before{content:""}.ri-opera-fill:before{content:""}.ri-opera-line:before{content:""}.ri-order-play-fill:before{content:""}.ri-order-play-line:before{content:""}.ri-organization-chart:before{content:""}.ri-outlet-2-fill:before{content:""}.ri-outlet-2-line:before{content:""}.ri-outlet-fill:before{content:""}.ri-outlet-line:before{content:""}.ri-page-separator:before{content:""}.ri-pages-fill:before{content:""}.ri-pages-line:before{content:""}.ri-paint-brush-fill:before{content:""}.ri-paint-brush-line:before{content:""}.ri-paint-fill:before{content:""}.ri-paint-line:before{content:""}.ri-palette-fill:before{content:""}.ri-palette-line:before{content:""}.ri-pantone-fill:before{content:""}.ri-pantone-line:before{content:""}.ri-paragraph:before{content:""}.ri-parent-fill:before{content:""}.ri-parent-line:before{content:""}.ri-parentheses-fill:before{content:""}.ri-parentheses-line:before{content:""}.ri-parking-box-fill:before{content:""}.ri-parking-box-line:before{content:""}.ri-parking-fill:before{content:""}.ri-parking-line:before{content:""}.ri-passport-fill:before{content:""}.ri-passport-line:before{content:""}.ri-patreon-fill:before{content:""}.ri-patreon-line:before{content:""}.ri-pause-circle-fill:before{content:""}.ri-pause-circle-line:before{content:""}.ri-pause-fill:before{content:""}.ri-pause-line:before{content:""}.ri-pause-mini-fill:before{content:""}.ri-pause-mini-line:before{content:""}.ri-paypal-fill:before{content:""}.ri-paypal-line:before{content:""}.ri-pen-nib-fill:before{content:""}.ri-pen-nib-line:before{content:""}.ri-pencil-fill:before{content:""}.ri-pencil-line:before{content:""}.ri-pencil-ruler-2-fill:before{content:""}.ri-pencil-ruler-2-line:before{content:""}.ri-pencil-ruler-fill:before{content:""}.ri-pencil-ruler-line:before{content:""}.ri-percent-fill:before{content:""}.ri-percent-line:before{content:""}.ri-phone-camera-fill:before{content:""}.ri-phone-camera-line:before{content:""}.ri-phone-fill:before{content:""}.ri-phone-find-fill:before{content:""}.ri-phone-find-line:before{content:""}.ri-phone-line:before{content:""}.ri-phone-lock-fill:before{content:""}.ri-phone-lock-line:before{content:""}.ri-picture-in-picture-2-fill:before{content:""}.ri-picture-in-picture-2-line:before{content:""}.ri-picture-in-picture-exit-fill:before{content:""}.ri-picture-in-picture-exit-line:before{content:""}.ri-picture-in-picture-fill:before{content:""}.ri-picture-in-picture-line:before{content:""}.ri-pie-chart-2-fill:before{content:""}.ri-pie-chart-2-line:before{content:""}.ri-pie-chart-box-fill:before{content:""}.ri-pie-chart-box-line:before{content:""}.ri-pie-chart-fill:before{content:""}.ri-pie-chart-line:before{content:""}.ri-pin-distance-fill:before{content:""}.ri-pin-distance-line:before{content:""}.ri-ping-pong-fill:before{content:""}.ri-ping-pong-line:before{content:""}.ri-pinterest-fill:before{content:""}.ri-pinterest-line:before{content:""}.ri-pinyin-input:before{content:""}.ri-pixelfed-fill:before{content:""}.ri-pixelfed-line:before{content:""}.ri-plane-fill:before{content:""}.ri-plane-line:before{content:""}.ri-plant-fill:before{content:""}.ri-plant-line:before{content:""}.ri-play-circle-fill:before{content:""}.ri-play-circle-line:before{content:""}.ri-play-fill:before{content:""}.ri-play-line:before{content:""}.ri-play-list-2-fill:before{content:""}.ri-play-list-2-line:before{content:""}.ri-play-list-add-fill:before{content:""}.ri-play-list-add-line:before{content:""}.ri-play-list-fill:before{content:""}.ri-play-list-line:before{content:""}.ri-play-mini-fill:before{content:""}.ri-play-mini-line:before{content:""}.ri-playstation-fill:before{content:""}.ri-playstation-line:before{content:""}.ri-plug-2-fill:before{content:""}.ri-plug-2-line:before{content:""}.ri-plug-fill:before{content:""}.ri-plug-line:before{content:""}.ri-polaroid-2-fill:before{content:""}.ri-polaroid-2-line:before{content:""}.ri-polaroid-fill:before{content:""}.ri-polaroid-line:before{content:""}.ri-police-car-fill:before{content:""}.ri-police-car-line:before{content:""}.ri-price-tag-2-fill:before{content:""}.ri-price-tag-2-line:before{content:""}.ri-price-tag-3-fill:before{content:""}.ri-price-tag-3-line:before{content:""}.ri-price-tag-fill:before{content:""}.ri-price-tag-line:before{content:""}.ri-printer-cloud-fill:before{content:""}.ri-printer-cloud-line:before{content:""}.ri-printer-fill:before{content:""}.ri-printer-line:before{content:""}.ri-product-hunt-fill:before{content:""}.ri-product-hunt-line:before{content:""}.ri-profile-fill:before{content:""}.ri-profile-line:before{content:""}.ri-projector-2-fill:before{content:""}.ri-projector-2-line:before{content:""}.ri-projector-fill:before{content:""}.ri-projector-line:before{content:""}.ri-psychotherapy-fill:before{content:""}.ri-psychotherapy-line:before{content:""}.ri-pulse-fill:before{content:""}.ri-pulse-line:before{content:""}.ri-pushpin-2-fill:before{content:""}.ri-pushpin-2-line:before{content:""}.ri-pushpin-fill:before{content:""}.ri-pushpin-line:before{content:""}.ri-qq-fill:before{content:""}.ri-qq-line:before{content:""}.ri-qr-code-fill:before{content:""}.ri-qr-code-line:before{content:""}.ri-qr-scan-2-fill:before{content:""}.ri-qr-scan-2-line:before{content:""}.ri-qr-scan-fill:before{content:""}.ri-qr-scan-line:before{content:""}.ri-question-answer-fill:before{content:""}.ri-question-answer-line:before{content:""}.ri-question-fill:before{content:""}.ri-question-line:before{content:""}.ri-question-mark:before{content:""}.ri-questionnaire-fill:before{content:""}.ri-questionnaire-line:before{content:""}.ri-quill-pen-fill:before{content:""}.ri-quill-pen-line:before{content:""}.ri-radar-fill:before{content:""}.ri-radar-line:before{content:""}.ri-radio-2-fill:before{content:""}.ri-radio-2-line:before{content:""}.ri-radio-button-fill:before{content:""}.ri-radio-button-line:before{content:""}.ri-radio-fill:before{content:""}.ri-radio-line:before{content:""}.ri-rainbow-fill:before{content:""}.ri-rainbow-line:before{content:""}.ri-rainy-fill:before{content:""}.ri-rainy-line:before{content:""}.ri-reactjs-fill:before{content:""}.ri-reactjs-line:before{content:""}.ri-record-circle-fill:before{content:""}.ri-record-circle-line:before{content:""}.ri-record-mail-fill:before{content:""}.ri-record-mail-line:before{content:""}.ri-recycle-fill:before{content:""}.ri-recycle-line:before{content:""}.ri-red-packet-fill:before{content:""}.ri-red-packet-line:before{content:""}.ri-reddit-fill:before{content:""}.ri-reddit-line:before{content:""}.ri-refresh-fill:before{content:""}.ri-refresh-line:before{content:""}.ri-refund-2-fill:before{content:""}.ri-refund-2-line:before{content:""}.ri-refund-fill:before{content:""}.ri-refund-line:before{content:""}.ri-registered-fill:before{content:""}.ri-registered-line:before{content:""}.ri-remixicon-fill:before{content:""}.ri-remixicon-line:before{content:""}.ri-remote-control-2-fill:before{content:""}.ri-remote-control-2-line:before{content:""}.ri-remote-control-fill:before{content:""}.ri-remote-control-line:before{content:""}.ri-repeat-2-fill:before{content:""}.ri-repeat-2-line:before{content:""}.ri-repeat-fill:before{content:""}.ri-repeat-line:before{content:""}.ri-repeat-one-fill:before{content:""}.ri-repeat-one-line:before{content:""}.ri-reply-all-fill:before{content:""}.ri-reply-all-line:before{content:""}.ri-reply-fill:before{content:""}.ri-reply-line:before{content:""}.ri-reserved-fill:before{content:""}.ri-reserved-line:before{content:""}.ri-rest-time-fill:before{content:""}.ri-rest-time-line:before{content:""}.ri-restart-fill:before{content:""}.ri-restart-line:before{content:""}.ri-restaurant-2-fill:before{content:""}.ri-restaurant-2-line:before{content:""}.ri-restaurant-fill:before{content:""}.ri-restaurant-line:before{content:""}.ri-rewind-fill:before{content:""}.ri-rewind-line:before{content:""}.ri-rewind-mini-fill:before{content:""}.ri-rewind-mini-line:before{content:""}.ri-rhythm-fill:before{content:""}.ri-rhythm-line:before{content:""}.ri-riding-fill:before{content:""}.ri-riding-line:before{content:""}.ri-road-map-fill:before{content:""}.ri-road-map-line:before{content:""}.ri-roadster-fill:before{content:""}.ri-roadster-line:before{content:""}.ri-robot-fill:before{content:""}.ri-robot-line:before{content:""}.ri-rocket-2-fill:before{content:""}.ri-rocket-2-line:before{content:""}.ri-rocket-fill:before{content:""}.ri-rocket-line:before{content:""}.ri-rotate-lock-fill:before{content:""}.ri-rotate-lock-line:before{content:""}.ri-rounded-corner:before{content:""}.ri-route-fill:before{content:""}.ri-route-line:before{content:""}.ri-router-fill:before{content:""}.ri-router-line:before{content:""}.ri-rss-fill:before{content:""}.ri-rss-line:before{content:""}.ri-ruler-2-fill:before{content:""}.ri-ruler-2-line:before{content:""}.ri-ruler-fill:before{content:""}.ri-ruler-line:before{content:""}.ri-run-fill:before{content:""}.ri-run-line:before{content:""}.ri-safari-fill:before{content:""}.ri-safari-line:before{content:""}.ri-safe-2-fill:before{content:""}.ri-safe-2-line:before{content:""}.ri-safe-fill:before{content:""}.ri-safe-line:before{content:""}.ri-sailboat-fill:before{content:""}.ri-sailboat-line:before{content:""}.ri-save-2-fill:before{content:""}.ri-save-2-line:before{content:""}.ri-save-3-fill:before{content:""}.ri-save-3-line:before{content:""}.ri-save-fill:before{content:""}.ri-save-line:before{content:""}.ri-scales-2-fill:before{content:""}.ri-scales-2-line:before{content:""}.ri-scales-3-fill:before{content:""}.ri-scales-3-line:before{content:""}.ri-scales-fill:before{content:""}.ri-scales-line:before{content:""}.ri-scan-2-fill:before{content:""}.ri-scan-2-line:before{content:""}.ri-scan-fill:before{content:""}.ri-scan-line:before{content:""}.ri-scissors-2-fill:before{content:""}.ri-scissors-2-line:before{content:""}.ri-scissors-cut-fill:before{content:""}.ri-scissors-cut-line:before{content:""}.ri-scissors-fill:before{content:""}.ri-scissors-line:before{content:""}.ri-screenshot-2-fill:before{content:""}.ri-screenshot-2-line:before{content:""}.ri-screenshot-fill:before{content:""}.ri-screenshot-line:before{content:""}.ri-sd-card-fill:before{content:""}.ri-sd-card-line:before{content:""}.ri-sd-card-mini-fill:before{content:""}.ri-sd-card-mini-line:before{content:""}.ri-search-2-fill:before{content:""}.ri-search-2-line:before{content:""}.ri-search-eye-fill:before{content:""}.ri-search-eye-line:before{content:""}.ri-search-fill:before{content:""}.ri-search-line:before{content:""}.ri-secure-payment-fill:before{content:""}.ri-secure-payment-line:before{content:""}.ri-seedling-fill:before{content:""}.ri-seedling-line:before{content:""}.ri-send-backward:before{content:""}.ri-send-plane-2-fill:before{content:""}.ri-send-plane-2-line:before{content:""}.ri-send-plane-fill:before{content:""}.ri-send-plane-line:before{content:""}.ri-send-to-back:before{content:""}.ri-sensor-fill:before{content:""}.ri-sensor-line:before{content:""}.ri-separator:before{content:""}.ri-server-fill:before{content:""}.ri-server-line:before{content:""}.ri-service-fill:before{content:""}.ri-service-line:before{content:""}.ri-settings-2-fill:before{content:""}.ri-settings-2-line:before{content:""}.ri-settings-3-fill:before{content:""}.ri-settings-3-line:before{content:""}.ri-settings-4-fill:before{content:""}.ri-settings-4-line:before{content:""}.ri-settings-5-fill:before{content:""}.ri-settings-5-line:before{content:""}.ri-settings-6-fill:before{content:""}.ri-settings-6-line:before{content:""}.ri-settings-fill:before{content:""}.ri-settings-line:before{content:""}.ri-shape-2-fill:before{content:""}.ri-shape-2-line:before{content:""}.ri-shape-fill:before{content:""}.ri-shape-line:before{content:""}.ri-share-box-fill:before{content:""}.ri-share-box-line:before{content:""}.ri-share-circle-fill:before{content:""}.ri-share-circle-line:before{content:""}.ri-share-fill:before{content:""}.ri-share-forward-2-fill:before{content:""}.ri-share-forward-2-line:before{content:""}.ri-share-forward-box-fill:before{content:""}.ri-share-forward-box-line:before{content:""}.ri-share-forward-fill:before{content:""}.ri-share-forward-line:before{content:""}.ri-share-line:before{content:""}.ri-shield-check-fill:before{content:""}.ri-shield-check-line:before{content:""}.ri-shield-cross-fill:before{content:""}.ri-shield-cross-line:before{content:""}.ri-shield-fill:before{content:""}.ri-shield-flash-fill:before{content:""}.ri-shield-flash-line:before{content:""}.ri-shield-keyhole-fill:before{content:""}.ri-shield-keyhole-line:before{content:""}.ri-shield-line:before{content:""}.ri-shield-star-fill:before{content:""}.ri-shield-star-line:before{content:""}.ri-shield-user-fill:before{content:""}.ri-shield-user-line:before{content:""}.ri-ship-2-fill:before{content:""}.ri-ship-2-line:before{content:""}.ri-ship-fill:before{content:""}.ri-ship-line:before{content:""}.ri-shirt-fill:before{content:""}.ri-shirt-line:before{content:""}.ri-shopping-bag-2-fill:before{content:""}.ri-shopping-bag-2-line:before{content:""}.ri-shopping-bag-3-fill:before{content:""}.ri-shopping-bag-3-line:before{content:""}.ri-shopping-bag-fill:before{content:""}.ri-shopping-bag-line:before{content:""}.ri-shopping-basket-2-fill:before{content:""}.ri-shopping-basket-2-line:before{content:""}.ri-shopping-basket-fill:before{content:""}.ri-shopping-basket-line:before{content:""}.ri-shopping-cart-2-fill:before{content:""}.ri-shopping-cart-2-line:before{content:""}.ri-shopping-cart-fill:before{content:""}.ri-shopping-cart-line:before{content:""}.ri-showers-fill:before{content:""}.ri-showers-line:before{content:""}.ri-shuffle-fill:before{content:""}.ri-shuffle-line:before{content:""}.ri-shut-down-fill:before{content:""}.ri-shut-down-line:before{content:""}.ri-side-bar-fill:before{content:""}.ri-side-bar-line:before{content:""}.ri-signal-tower-fill:before{content:""}.ri-signal-tower-line:before{content:""}.ri-signal-wifi-1-fill:before{content:""}.ri-signal-wifi-1-line:before{content:""}.ri-signal-wifi-2-fill:before{content:""}.ri-signal-wifi-2-line:before{content:""}.ri-signal-wifi-3-fill:before{content:""}.ri-signal-wifi-3-line:before{content:""}.ri-signal-wifi-error-fill:before{content:""}.ri-signal-wifi-error-line:before{content:""}.ri-signal-wifi-fill:before{content:""}.ri-signal-wifi-line:before{content:""}.ri-signal-wifi-off-fill:before{content:""}.ri-signal-wifi-off-line:before{content:""}.ri-sim-card-2-fill:before{content:""}.ri-sim-card-2-line:before{content:""}.ri-sim-card-fill:before{content:""}.ri-sim-card-line:before{content:""}.ri-single-quotes-l:before{content:""}.ri-single-quotes-r:before{content:""}.ri-sip-fill:before{content:""}.ri-sip-line:before{content:""}.ri-skip-back-fill:before{content:""}.ri-skip-back-line:before{content:""}.ri-skip-back-mini-fill:before{content:""}.ri-skip-back-mini-line:before{content:""}.ri-skip-forward-fill:before{content:""}.ri-skip-forward-line:before{content:""}.ri-skip-forward-mini-fill:before{content:""}.ri-skip-forward-mini-line:before{content:""}.ri-skull-2-fill:before{content:""}.ri-skull-2-line:before{content:""}.ri-skull-fill:before{content:""}.ri-skull-line:before{content:""}.ri-skype-fill:before{content:""}.ri-skype-line:before{content:""}.ri-slack-fill:before{content:""}.ri-slack-line:before{content:""}.ri-slice-fill:before{content:""}.ri-slice-line:before{content:""}.ri-slideshow-2-fill:before{content:""}.ri-slideshow-2-line:before{content:""}.ri-slideshow-3-fill:before{content:""}.ri-slideshow-3-line:before{content:""}.ri-slideshow-4-fill:before{content:""}.ri-slideshow-4-line:before{content:""}.ri-slideshow-fill:before{content:""}.ri-slideshow-line:before{content:""}.ri-smartphone-fill:before{content:""}.ri-smartphone-line:before{content:""}.ri-snapchat-fill:before{content:""}.ri-snapchat-line:before{content:""}.ri-snowy-fill:before{content:""}.ri-snowy-line:before{content:""}.ri-sort-asc:before{content:""}.ri-sort-desc:before{content:""}.ri-sound-module-fill:before{content:""}.ri-sound-module-line:before{content:""}.ri-soundcloud-fill:before{content:""}.ri-soundcloud-line:before{content:""}.ri-space-ship-fill:before{content:""}.ri-space-ship-line:before{content:""}.ri-space:before{content:""}.ri-spam-2-fill:before{content:""}.ri-spam-2-line:before{content:""}.ri-spam-3-fill:before{content:""}.ri-spam-3-line:before{content:""}.ri-spam-fill:before{content:""}.ri-spam-line:before{content:""}.ri-speaker-2-fill:before{content:""}.ri-speaker-2-line:before{content:""}.ri-speaker-3-fill:before{content:""}.ri-speaker-3-line:before{content:""}.ri-speaker-fill:before{content:""}.ri-speaker-line:before{content:""}.ri-spectrum-fill:before{content:""}.ri-spectrum-line:before{content:""}.ri-speed-fill:before{content:""}.ri-speed-line:before{content:""}.ri-speed-mini-fill:before{content:""}.ri-speed-mini-line:before{content:""}.ri-split-cells-horizontal:before{content:""}.ri-split-cells-vertical:before{content:""}.ri-spotify-fill:before{content:""}.ri-spotify-line:before{content:""}.ri-spy-fill:before{content:""}.ri-spy-line:before{content:""}.ri-stack-fill:before{content:""}.ri-stack-line:before{content:""}.ri-stack-overflow-fill:before{content:""}.ri-stack-overflow-line:before{content:""}.ri-stackshare-fill:before{content:""}.ri-stackshare-line:before{content:""}.ri-star-fill:before{content:""}.ri-star-half-fill:before{content:""}.ri-star-half-line:before{content:""}.ri-star-half-s-fill:before{content:""}.ri-star-half-s-line:before{content:""}.ri-star-line:before{content:""}.ri-star-s-fill:before{content:""}.ri-star-s-line:before{content:""}.ri-star-smile-fill:before{content:""}.ri-star-smile-line:before{content:""}.ri-steam-fill:before{content:""}.ri-steam-line:before{content:""}.ri-steering-2-fill:before{content:""}.ri-steering-2-line:before{content:""}.ri-steering-fill:before{content:""}.ri-steering-line:before{content:""}.ri-stethoscope-fill:before{content:""}.ri-stethoscope-line:before{content:""}.ri-sticky-note-2-fill:before{content:""}.ri-sticky-note-2-line:before{content:""}.ri-sticky-note-fill:before{content:""}.ri-sticky-note-line:before{content:""}.ri-stock-fill:before{content:""}.ri-stock-line:before{content:""}.ri-stop-circle-fill:before{content:""}.ri-stop-circle-line:before{content:""}.ri-stop-fill:before{content:""}.ri-stop-line:before{content:""}.ri-stop-mini-fill:before{content:""}.ri-stop-mini-line:before{content:""}.ri-store-2-fill:before{content:""}.ri-store-2-line:before{content:""}.ri-store-3-fill:before{content:""}.ri-store-3-line:before{content:""}.ri-store-fill:before{content:""}.ri-store-line:before{content:""}.ri-strikethrough-2:before{content:""}.ri-strikethrough:before{content:""}.ri-subscript-2:before{content:""}.ri-subscript:before{content:""}.ri-subtract-fill:before{content:""}.ri-subtract-line:before{content:""}.ri-subway-fill:before{content:""}.ri-subway-line:before{content:""}.ri-subway-wifi-fill:before{content:""}.ri-subway-wifi-line:before{content:""}.ri-suitcase-2-fill:before{content:""}.ri-suitcase-2-line:before{content:""}.ri-suitcase-3-fill:before{content:""}.ri-suitcase-3-line:before{content:""}.ri-suitcase-fill:before{content:""}.ri-suitcase-line:before{content:""}.ri-sun-cloudy-fill:before{content:""}.ri-sun-cloudy-line:before{content:""}.ri-sun-fill:before{content:""}.ri-sun-foggy-fill:before{content:""}.ri-sun-foggy-line:before{content:""}.ri-sun-line:before{content:""}.ri-superscript-2:before{content:""}.ri-superscript:before{content:""}.ri-surgical-mask-fill:before{content:""}.ri-surgical-mask-line:before{content:""}.ri-surround-sound-fill:before{content:""}.ri-surround-sound-line:before{content:""}.ri-survey-fill:before{content:""}.ri-survey-line:before{content:""}.ri-swap-box-fill:before{content:""}.ri-swap-box-line:before{content:""}.ri-swap-fill:before{content:""}.ri-swap-line:before{content:""}.ri-switch-fill:before{content:""}.ri-switch-line:before{content:""}.ri-sword-fill:before{content:""}.ri-sword-line:before{content:""}.ri-syringe-fill:before{content:""}.ri-syringe-line:before{content:""}.ri-t-box-fill:before{content:""}.ri-t-box-line:before{content:""}.ri-t-shirt-2-fill:before{content:""}.ri-t-shirt-2-line:before{content:""}.ri-t-shirt-air-fill:before{content:""}.ri-t-shirt-air-line:before{content:""}.ri-t-shirt-fill:before{content:""}.ri-t-shirt-line:before{content:""}.ri-table-2:before{content:""}.ri-table-alt-fill:before{content:""}.ri-table-alt-line:before{content:""}.ri-table-fill:before{content:""}.ri-table-line:before{content:""}.ri-tablet-fill:before{content:""}.ri-tablet-line:before{content:""}.ri-takeaway-fill:before{content:""}.ri-takeaway-line:before{content:""}.ri-taobao-fill:before{content:""}.ri-taobao-line:before{content:""}.ri-tape-fill:before{content:""}.ri-tape-line:before{content:""}.ri-task-fill:before{content:""}.ri-task-line:before{content:""}.ri-taxi-fill:before{content:""}.ri-taxi-line:before{content:""}.ri-taxi-wifi-fill:before{content:""}.ri-taxi-wifi-line:before{content:""}.ri-team-fill:before{content:""}.ri-team-line:before{content:""}.ri-telegram-fill:before{content:""}.ri-telegram-line:before{content:""}.ri-temp-cold-fill:before{content:""}.ri-temp-cold-line:before{content:""}.ri-temp-hot-fill:before{content:""}.ri-temp-hot-line:before{content:""}.ri-terminal-box-fill:before{content:""}.ri-terminal-box-line:before{content:""}.ri-terminal-fill:before{content:""}.ri-terminal-line:before{content:""}.ri-terminal-window-fill:before{content:""}.ri-terminal-window-line:before{content:""}.ri-test-tube-fill:before{content:""}.ri-test-tube-line:before{content:""}.ri-text-direction-l:before{content:""}.ri-text-direction-r:before{content:""}.ri-text-spacing:before{content:""}.ri-text-wrap:before{content:""}.ri-text:before{content:""}.ri-thermometer-fill:before{content:""}.ri-thermometer-line:before{content:""}.ri-thumb-down-fill:before{content:""}.ri-thumb-down-line:before{content:""}.ri-thumb-up-fill:before{content:""}.ri-thumb-up-line:before{content:""}.ri-thunderstorms-fill:before{content:""}.ri-thunderstorms-line:before{content:""}.ri-ticket-2-fill:before{content:""}.ri-ticket-2-line:before{content:""}.ri-ticket-fill:before{content:""}.ri-ticket-line:before{content:""}.ri-time-fill:before{content:""}.ri-time-line:before{content:""}.ri-timer-2-fill:before{content:""}.ri-timer-2-line:before{content:""}.ri-timer-fill:before{content:""}.ri-timer-flash-fill:before{content:""}.ri-timer-flash-line:before{content:""}.ri-timer-line:before{content:""}.ri-todo-fill:before{content:""}.ri-todo-line:before{content:""}.ri-toggle-fill:before{content:""}.ri-toggle-line:before{content:""}.ri-tools-fill:before{content:""}.ri-tools-line:before{content:""}.ri-tornado-fill:before{content:""}.ri-tornado-line:before{content:""}.ri-trademark-fill:before{content:""}.ri-trademark-line:before{content:""}.ri-traffic-light-fill:before{content:""}.ri-traffic-light-line:before{content:""}.ri-train-fill:before{content:""}.ri-train-line:before{content:""}.ri-train-wifi-fill:before{content:""}.ri-train-wifi-line:before{content:""}.ri-translate-2:before{content:""}.ri-translate:before{content:""}.ri-travesti-fill:before{content:""}.ri-travesti-line:before{content:""}.ri-treasure-map-fill:before{content:""}.ri-treasure-map-line:before{content:""}.ri-trello-fill:before{content:""}.ri-trello-line:before{content:""}.ri-trophy-fill:before{content:""}.ri-trophy-line:before{content:""}.ri-truck-fill:before{content:""}.ri-truck-line:before{content:""}.ri-tumblr-fill:before{content:""}.ri-tumblr-line:before{content:""}.ri-tv-2-fill:before{content:""}.ri-tv-2-line:before{content:""}.ri-tv-fill:before{content:""}.ri-tv-line:before{content:""}.ri-twitch-fill:before{content:""}.ri-twitch-line:before{content:""}.ri-twitter-fill:before{content:""}.ri-twitter-line:before{content:""}.ri-typhoon-fill:before{content:""}.ri-typhoon-line:before{content:""}.ri-u-disk-fill:before{content:""}.ri-u-disk-line:before{content:""}.ri-ubuntu-fill:before{content:""}.ri-ubuntu-line:before{content:""}.ri-umbrella-fill:before{content:""}.ri-umbrella-line:before{content:""}.ri-underline:before{content:""}.ri-uninstall-fill:before{content:""}.ri-uninstall-line:before{content:""}.ri-unsplash-fill:before{content:""}.ri-unsplash-line:before{content:""}.ri-upload-2-fill:before{content:""}.ri-upload-2-line:before{content:""}.ri-upload-cloud-2-fill:before{content:""}.ri-upload-cloud-2-line:before{content:""}.ri-upload-cloud-fill:before{content:""}.ri-upload-cloud-line:before{content:""}.ri-upload-fill:before{content:""}.ri-upload-line:before{content:""}.ri-usb-fill:before{content:""}.ri-usb-line:before{content:""}.ri-user-2-fill:before{content:""}.ri-user-2-line:before{content:""}.ri-user-3-fill:before{content:""}.ri-user-3-line:before{content:""}.ri-user-4-fill:before{content:""}.ri-user-4-line:before{content:""}.ri-user-5-fill:before{content:""}.ri-user-5-line:before{content:""}.ri-user-6-fill:before{content:""}.ri-user-6-line:before{content:""}.ri-user-add-fill:before{content:""}.ri-user-add-line:before{content:""}.ri-user-fill:before{content:""}.ri-user-follow-fill:before{content:""}.ri-user-follow-line:before{content:""}.ri-user-heart-fill:before{content:""}.ri-user-heart-line:before{content:""}.ri-user-line:before{content:""}.ri-user-location-fill:before{content:""}.ri-user-location-line:before{content:""}.ri-user-received-2-fill:before{content:""}.ri-user-received-2-line:before{content:""}.ri-user-received-fill:before{content:""}.ri-user-received-line:before{content:""}.ri-user-search-fill:before{content:""}.ri-user-search-line:before{content:""}.ri-user-settings-fill:before{content:""}.ri-user-settings-line:before{content:""}.ri-user-shared-2-fill:before{content:""}.ri-user-shared-2-line:before{content:""}.ri-user-shared-fill:before{content:""}.ri-user-shared-line:before{content:""}.ri-user-smile-fill:before{content:""}.ri-user-smile-line:before{content:""}.ri-user-star-fill:before{content:""}.ri-user-star-line:before{content:""}.ri-user-unfollow-fill:before{content:""}.ri-user-unfollow-line:before{content:""}.ri-user-voice-fill:before{content:""}.ri-user-voice-line:before{content:""}.ri-video-add-fill:before{content:""}.ri-video-add-line:before{content:""}.ri-video-chat-fill:before{content:""}.ri-video-chat-line:before{content:""}.ri-video-download-fill:before{content:""}.ri-video-download-line:before{content:""}.ri-video-fill:before{content:""}.ri-video-line:before{content:""}.ri-video-upload-fill:before{content:""}.ri-video-upload-line:before{content:""}.ri-vidicon-2-fill:before{content:""}.ri-vidicon-2-line:before{content:""}.ri-vidicon-fill:before{content:""}.ri-vidicon-line:before{content:""}.ri-vimeo-fill:before{content:""}.ri-vimeo-line:before{content:""}.ri-vip-crown-2-fill:before{content:""}.ri-vip-crown-2-line:before{content:""}.ri-vip-crown-fill:before{content:""}.ri-vip-crown-line:before{content:""}.ri-vip-diamond-fill:before{content:""}.ri-vip-diamond-line:before{content:""}.ri-vip-fill:before{content:""}.ri-vip-line:before{content:""}.ri-virus-fill:before{content:""}.ri-virus-line:before{content:""}.ri-visa-fill:before{content:""}.ri-visa-line:before{content:""}.ri-voice-recognition-fill:before{content:""}.ri-voice-recognition-line:before{content:""}.ri-voiceprint-fill:before{content:""}.ri-voiceprint-line:before{content:""}.ri-volume-down-fill:before{content:""}.ri-volume-down-line:before{content:""}.ri-volume-mute-fill:before{content:""}.ri-volume-mute-line:before{content:""}.ri-volume-off-vibrate-fill:before{content:""}.ri-volume-off-vibrate-line:before{content:""}.ri-volume-up-fill:before{content:""}.ri-volume-up-line:before{content:""}.ri-volume-vibrate-fill:before{content:""}.ri-volume-vibrate-line:before{content:""}.ri-vuejs-fill:before{content:""}.ri-vuejs-line:before{content:""}.ri-walk-fill:before{content:""}.ri-walk-line:before{content:""}.ri-wallet-2-fill:before{content:""}.ri-wallet-2-line:before{content:""}.ri-wallet-3-fill:before{content:""}.ri-wallet-3-line:before{content:""}.ri-wallet-fill:before{content:""}.ri-wallet-line:before{content:""}.ri-water-flash-fill:before{content:""}.ri-water-flash-line:before{content:""}.ri-webcam-fill:before{content:""}.ri-webcam-line:before{content:""}.ri-wechat-2-fill:before{content:""}.ri-wechat-2-line:before{content:""}.ri-wechat-fill:before{content:""}.ri-wechat-line:before{content:""}.ri-wechat-pay-fill:before{content:""}.ri-wechat-pay-line:before{content:""}.ri-weibo-fill:before{content:""}.ri-weibo-line:before{content:""}.ri-whatsapp-fill:before{content:""}.ri-whatsapp-line:before{content:""}.ri-wheelchair-fill:before{content:""}.ri-wheelchair-line:before{content:""}.ri-wifi-fill:before{content:""}.ri-wifi-line:before{content:""}.ri-wifi-off-fill:before{content:""}.ri-wifi-off-line:before{content:""}.ri-window-2-fill:before{content:""}.ri-window-2-line:before{content:""}.ri-window-fill:before{content:""}.ri-window-line:before{content:""}.ri-windows-fill:before{content:""}.ri-windows-line:before{content:""}.ri-windy-fill:before{content:""}.ri-windy-line:before{content:""}.ri-wireless-charging-fill:before{content:""}.ri-wireless-charging-line:before{content:""}.ri-women-fill:before{content:""}.ri-women-line:before{content:""}.ri-wubi-input:before{content:""}.ri-xbox-fill:before{content:""}.ri-xbox-line:before{content:""}.ri-xing-fill:before{content:""}.ri-xing-line:before{content:""}.ri-youtube-fill:before{content:""}.ri-youtube-line:before{content:""}.ri-zcool-fill:before{content:""}.ri-zcool-line:before{content:""}.ri-zhihu-fill:before{content:""}.ri-zhihu-line:before{content:""}.ri-zoom-in-fill:before{content:""}.ri-zoom-in-line:before{content:""}.ri-zoom-out-fill:before{content:""}.ri-zoom-out-line:before{content:""}.ri-zzz-fill:before{content:""}.ri-zzz-line:before{content:""}.ri-arrow-down-double-fill:before{content:""}.ri-arrow-down-double-line:before{content:""}.ri-arrow-left-double-fill:before{content:""}.ri-arrow-left-double-line:before{content:""}.ri-arrow-right-double-fill:before{content:""}.ri-arrow-right-double-line:before{content:""}.ri-arrow-turn-back-fill:before{content:""}.ri-arrow-turn-back-line:before{content:""}.ri-arrow-turn-forward-fill:before{content:""}.ri-arrow-turn-forward-line:before{content:""}.ri-arrow-up-double-fill:before{content:""}.ri-arrow-up-double-line:before{content:""}.ri-bard-fill:before{content:""}.ri-bard-line:before{content:""}.ri-bootstrap-fill:before{content:""}.ri-bootstrap-line:before{content:""}.ri-box-1-fill:before{content:""}.ri-box-1-line:before{content:""}.ri-box-2-fill:before{content:""}.ri-box-2-line:before{content:""}.ri-box-3-fill:before{content:""}.ri-box-3-line:before{content:""}.ri-brain-fill:before{content:""}.ri-brain-line:before{content:""}.ri-candle-fill:before{content:""}.ri-candle-line:before{content:""}.ri-cash-fill:before{content:""}.ri-cash-line:before{content:""}.ri-contract-left-fill:before{content:""}.ri-contract-left-line:before{content:""}.ri-contract-left-right-fill:before{content:""}.ri-contract-left-right-line:before{content:""}.ri-contract-right-fill:before{content:""}.ri-contract-right-line:before{content:""}.ri-contract-up-down-fill:before{content:""}.ri-contract-up-down-line:before{content:""}.ri-copilot-fill:before{content:""}.ri-copilot-line:before{content:""}.ri-corner-down-left-fill:before{content:""}.ri-corner-down-left-line:before{content:""}.ri-corner-down-right-fill:before{content:""}.ri-corner-down-right-line:before{content:""}.ri-corner-left-down-fill:before{content:""}.ri-corner-left-down-line:before{content:""}.ri-corner-left-up-fill:before{content:""}.ri-corner-left-up-line:before{content:""}.ri-corner-right-down-fill:before{content:""}.ri-corner-right-down-line:before{content:""}.ri-corner-right-up-fill:before{content:""}.ri-corner-right-up-line:before{content:""}.ri-corner-up-left-double-fill:before{content:""}.ri-corner-up-left-double-line:before{content:""}.ri-corner-up-left-fill:before{content:""}.ri-corner-up-left-line:before{content:""}.ri-corner-up-right-double-fill:before{content:""}.ri-corner-up-right-double-line:before{content:""}.ri-corner-up-right-fill:before{content:""}.ri-corner-up-right-line:before{content:""}.ri-cross-fill:before{content:""}.ri-cross-line:before{content:""}.ri-edge-new-fill:before{content:""}.ri-edge-new-line:before{content:""}.ri-equal-fill:before{content:""}.ri-equal-line:before{content:""}.ri-expand-left-fill:before{content:""}.ri-expand-left-line:before{content:""}.ri-expand-left-right-fill:before{content:""}.ri-expand-left-right-line:before{content:""}.ri-expand-right-fill:before{content:""}.ri-expand-right-line:before{content:""}.ri-expand-up-down-fill:before{content:""}.ri-expand-up-down-line:before{content:""}.ri-flickr-fill:before{content:""}.ri-flickr-line:before{content:""}.ri-forward-10-fill:before{content:""}.ri-forward-10-line:before{content:""}.ri-forward-15-fill:before{content:""}.ri-forward-15-line:before{content:""}.ri-forward-30-fill:before{content:""}.ri-forward-30-line:before{content:""}.ri-forward-5-fill:before{content:""}.ri-forward-5-line:before{content:""}.ri-graduation-cap-fill:before{content:""}.ri-graduation-cap-line:before{content:""}.ri-home-office-fill:before{content:""}.ri-home-office-line:before{content:""}.ri-hourglass-2-fill:before{content:""}.ri-hourglass-2-line:before{content:""}.ri-hourglass-fill:before{content:""}.ri-hourglass-line:before{content:""}.ri-javascript-fill:before{content:""}.ri-javascript-line:before{content:""}.ri-loop-left-fill:before{content:""}.ri-loop-left-line:before{content:""}.ri-loop-right-fill:before{content:""}.ri-loop-right-line:before{content:""}.ri-memories-fill:before{content:""}.ri-memories-line:before{content:""}.ri-meta-fill:before{content:""}.ri-meta-line:before{content:""}.ri-microsoft-loop-fill:before{content:""}.ri-microsoft-loop-line:before{content:""}.ri-nft-fill:before{content:""}.ri-nft-line:before{content:""}.ri-notion-fill:before{content:""}.ri-notion-line:before{content:""}.ri-openai-fill:before{content:""}.ri-openai-line:before{content:""}.ri-overline:before{content:""}.ri-p2p-fill:before{content:""}.ri-p2p-line:before{content:""}.ri-presentation-fill:before{content:""}.ri-presentation-line:before{content:""}.ri-replay-10-fill:before{content:""}.ri-replay-10-line:before{content:""}.ri-replay-15-fill:before{content:""}.ri-replay-15-line:before{content:""}.ri-replay-30-fill:before{content:""}.ri-replay-30-line:before{content:""}.ri-replay-5-fill:before{content:""}.ri-replay-5-line:before{content:""}.ri-school-fill:before{content:""}.ri-school-line:before{content:""}.ri-shining-2-fill:before{content:""}.ri-shining-2-line:before{content:""}.ri-shining-fill:before{content:""}.ri-shining-line:before{content:""}.ri-sketching:before{content:""}.ri-skip-down-fill:before{content:""}.ri-skip-down-line:before{content:""}.ri-skip-left-fill:before{content:""}.ri-skip-left-line:before{content:""}.ri-skip-right-fill:before{content:""}.ri-skip-right-line:before{content:""}.ri-skip-up-fill:before{content:""}.ri-skip-up-line:before{content:""}.ri-slow-down-fill:before{content:""}.ri-slow-down-line:before{content:""}.ri-sparkling-2-fill:before{content:""}.ri-sparkling-2-line:before{content:""}.ri-sparkling-fill:before{content:""}.ri-sparkling-line:before{content:""}.ri-speak-fill:before{content:""}.ri-speak-line:before{content:""}.ri-speed-up-fill:before{content:""}.ri-speed-up-line:before{content:""}.ri-tiktok-fill:before{content:""}.ri-tiktok-line:before{content:""}.ri-token-swap-fill:before{content:""}.ri-token-swap-line:before{content:""}.ri-unpin-fill:before{content:""}.ri-unpin-line:before{content:""}.ri-wechat-channels-fill:before{content:""}.ri-wechat-channels-line:before{content:""}.ri-wordpress-fill:before{content:""}.ri-wordpress-line:before{content:""}.ri-blender-fill:before{content:""}.ri-blender-line:before{content:""}.ri-emoji-sticker-fill:before{content:""}.ri-emoji-sticker-line:before{content:""}.ri-git-close-pull-request-fill:before{content:""}.ri-git-close-pull-request-line:before{content:""}.ri-instance-fill:before{content:""}.ri-instance-line:before{content:""}.ri-megaphone-fill:before{content:""}.ri-megaphone-line:before{content:""}.ri-pass-expired-fill:before{content:""}.ri-pass-expired-line:before{content:""}.ri-pass-pending-fill:before{content:""}.ri-pass-pending-line:before{content:""}.ri-pass-valid-fill:before{content:""}.ri-pass-valid-line:before{content:""}.ri-ai-generate:before{content:""}.ri-calendar-close-fill:before{content:""}.ri-calendar-close-line:before{content:""}.ri-draggable:before{content:""}.ri-font-family:before{content:""}.ri-font-mono:before{content:""}.ri-font-sans-serif:before{content:""}.ri-font-sans:before{content:""}.ri-hard-drive-3-fill:before{content:""}.ri-hard-drive-3-line:before{content:""}.ri-kick-fill:before{content:""}.ri-kick-line:before{content:""}.ri-list-check-3:before{content:""}.ri-list-indefinite:before{content:""}.ri-list-ordered-2:before{content:""}.ri-list-radio:before{content:""}.ri-openbase-fill:before{content:""}.ri-openbase-line:before{content:""}.ri-planet-fill:before{content:""}.ri-planet-line:before{content:""}.ri-prohibited-fill:before{content:""}.ri-prohibited-line:before{content:""}.ri-quote-text:before{content:""}.ri-seo-fill:before{content:""}.ri-seo-line:before{content:""}.ri-slash-commands:before{content:""}.ri-archive-2-fill:before{content:""}.ri-archive-2-line:before{content:""}.ri-inbox-2-fill:before{content:""}.ri-inbox-2-line:before{content:""}.ri-shake-hands-fill:before{content:""}.ri-shake-hands-line:before{content:""}.ri-supabase-fill:before{content:""}.ri-supabase-line:before{content:""}.ri-water-percent-fill:before{content:""}.ri-water-percent-line:before{content:""}.ri-yuque-fill:before{content:""}.ri-yuque-line:before{content:""}.ri-crosshair-2-fill:before{content:""}.ri-crosshair-2-line:before{content:""}.ri-crosshair-fill:before{content:""}.ri-crosshair-line:before{content:""}.ri-file-close-fill:before{content:""}.ri-file-close-line:before{content:""}.ri-infinity-fill:before{content:""}.ri-infinity-line:before{content:""}.ri-rfid-fill:before{content:""}.ri-rfid-line:before{content:""}.ri-slash-commands-2:before{content:""}.ri-user-forbid-fill:before{content:""}.ri-user-forbid-line:before{content:""}.ri-beer-fill:before{content:""}.ri-beer-line:before{content:""}.ri-circle-fill:before{content:""}.ri-circle-line:before{content:""}.ri-dropdown-list:before{content:""}.ri-file-image-fill:before{content:""}.ri-file-image-line:before{content:""}.ri-file-pdf-2-fill:before{content:""}.ri-file-pdf-2-line:before{content:""}.ri-file-video-fill:before{content:""}.ri-file-video-line:before{content:""}.ri-folder-image-fill:before{content:""}.ri-folder-image-line:before{content:""}.ri-folder-video-fill:before{content:""}.ri-folder-video-line:before{content:""}.ri-hexagon-fill:before{content:""}.ri-hexagon-line:before{content:""}.ri-menu-search-fill:before{content:""}.ri-menu-search-line:before{content:""}.ri-octagon-fill:before{content:""}.ri-octagon-line:before{content:""}.ri-pentagon-fill:before{content:""}.ri-pentagon-line:before{content:""}.ri-rectangle-fill:before{content:""}.ri-rectangle-line:before{content:""}.ri-robot-2-fill:before{content:""}.ri-robot-2-line:before{content:""}.ri-shapes-fill:before{content:""}.ri-shapes-line:before{content:""}.ri-square-fill:before{content:""}.ri-square-line:before{content:""}.ri-tent-fill:before{content:""}.ri-tent-line:before{content:""}.ri-threads-fill:before{content:""}.ri-threads-line:before{content:""}.ri-tree-fill:before{content:""}.ri-tree-line:before{content:""}.ri-triangle-fill:before{content:""}.ri-triangle-line:before{content:""}.ri-twitter-x-fill:before{content:""}.ri-twitter-x-line:before{content:""}.ri-verified-badge-fill:before{content:""}.ri-verified-badge-line:before{content:""}.ri-armchair-fill:before{content:""}.ri-armchair-line:before{content:""}.ri-bnb-fill:before{content:""}.ri-bnb-line:before{content:""}.ri-bread-fill:before{content:""}.ri-bread-line:before{content:""}.ri-btc-fill:before{content:""}.ri-btc-line:before{content:""}.ri-calendar-schedule-fill:before{content:""}.ri-calendar-schedule-line:before{content:""}.ri-dice-1-fill:before{content:""}.ri-dice-1-line:before{content:""}.ri-dice-2-fill:before{content:""}.ri-dice-2-line:before{content:""}.ri-dice-3-fill:before{content:""}.ri-dice-3-line:before{content:""}.ri-dice-4-fill:before{content:""}.ri-dice-4-line:before{content:""}.ri-dice-5-fill:before{content:""}.ri-dice-5-line:before{content:""}.ri-dice-6-fill:before{content:""}.ri-dice-6-line:before{content:""}.ri-dice-fill:before{content:""}.ri-dice-line:before{content:""}.ri-drinks-fill:before{content:""}.ri-drinks-line:before{content:""}.ri-equalizer-2-fill:before{content:""}.ri-equalizer-2-line:before{content:""}.ri-equalizer-3-fill:before{content:""}.ri-equalizer-3-line:before{content:""}.ri-eth-fill:before{content:""}.ri-eth-line:before{content:""}.ri-flower-fill:before{content:""}.ri-flower-line:before{content:""}.ri-glasses-2-fill:before{content:""}.ri-glasses-2-line:before{content:""}.ri-glasses-fill:before{content:""}.ri-glasses-line:before{content:""}.ri-goggles-fill:before{content:""}.ri-goggles-line:before{content:""}.ri-image-circle-fill:before{content:""}.ri-image-circle-line:before{content:""}.ri-info-i:before{content:""}.ri-money-rupee-circle-fill:before{content:""}.ri-money-rupee-circle-line:before{content:""}.ri-news-fill:before{content:""}.ri-news-line:before{content:""}.ri-robot-3-fill:before{content:""}.ri-robot-3-line:before{content:""}.ri-share-2-fill:before{content:""}.ri-share-2-line:before{content:""}.ri-sofa-fill:before{content:""}.ri-sofa-line:before{content:""}.ri-svelte-fill:before{content:""}.ri-svelte-line:before{content:""}.ri-vk-fill:before{content:""}.ri-vk-line:before{content:""}.ri-xrp-fill:before{content:""}.ri-xrp-line:before{content:""}.ri-xtz-fill:before{content:""}.ri-xtz-line:before{content:""}.ri-archive-stack-fill:before{content:""}.ri-archive-stack-line:before{content:""}.ri-bowl-fill:before{content:""}.ri-bowl-line:before{content:""}.ri-calendar-view:before{content:""}.ri-carousel-view:before{content:""}.ri-code-block:before{content:""}.ri-color-filter-fill:before{content:""}.ri-color-filter-line:before{content:""}.ri-contacts-book-3-fill:before{content:""}.ri-contacts-book-3-line:before{content:""}.ri-contract-fill:before{content:""}.ri-contract-line:before{content:""}.ri-drinks-2-fill:before{content:""}.ri-drinks-2-line:before{content:""}.ri-export-fill:before{content:""}.ri-export-line:before{content:""}.ri-file-check-fill:before{content:""}.ri-file-check-line:before{content:""}.ri-focus-mode:before{content:""}.ri-folder-6-fill:before{content:""}.ri-folder-6-line:before{content:""}.ri-folder-check-fill:before{content:""}.ri-folder-check-line:before{content:""}.ri-folder-close-fill:before{content:""}.ri-folder-close-line:before{content:""}.ri-folder-cloud-fill:before{content:""}.ri-folder-cloud-line:before{content:""}.ri-gallery-view-2:before{content:""}.ri-gallery-view:before{content:""}.ri-hand:before{content:""}.ri-import-fill:before{content:""}.ri-import-line:before{content:""}.ri-information-2-fill:before{content:""}.ri-information-2-line:before{content:""}.ri-kanban-view-2:before{content:""}.ri-kanban-view:before{content:""}.ri-list-view:before{content:""}.ri-lock-star-fill:before{content:""}.ri-lock-star-line:before{content:""}.ri-puzzle-2-fill:before{content:""}.ri-puzzle-2-line:before{content:""}.ri-puzzle-fill:before{content:""}.ri-puzzle-line:before{content:""}.ri-ram-2-fill:before{content:""}.ri-ram-2-line:before{content:""}.ri-ram-fill:before{content:""}.ri-ram-line:before{content:""}.ri-receipt-fill:before{content:""}.ri-receipt-line:before{content:""}.ri-shadow-fill:before{content:""}.ri-shadow-line:before{content:""}.ri-sidebar-fold-fill:before{content:""}.ri-sidebar-fold-line:before{content:""}.ri-sidebar-unfold-fill:before{content:""}.ri-sidebar-unfold-line:before{content:""}.ri-slideshow-view:before{content:""}.ri-sort-alphabet-asc:before{content:""}.ri-sort-alphabet-desc:before{content:""}.ri-sort-number-asc:before{content:""}.ri-sort-number-desc:before{content:""}.ri-stacked-view:before{content:""}.ri-sticky-note-add-fill:before{content:""}.ri-sticky-note-add-line:before{content:""}.ri-swap-2-fill:before{content:""}.ri-swap-2-line:before{content:""}.ri-swap-3-fill:before{content:""}.ri-swap-3-line:before{content:""}.ri-table-3:before{content:""}.ri-table-view:before{content:""}.ri-text-block:before{content:""}.ri-text-snippet:before{content:""}.ri-timeline-view:before{content:""}.ri-blogger-fill:before{content:""}.ri-blogger-line:before{content:""}.ri-chat-thread-fill:before{content:""}.ri-chat-thread-line:before{content:""}.ri-discount-percent-fill:before{content:""}.ri-discount-percent-line:before{content:""}.ri-exchange-2-fill:before{content:""}.ri-exchange-2-line:before{content:""}.ri-git-fork-fill:before{content:""}.ri-git-fork-line:before{content:""}.ri-input-field:before{content:""}.ri-progress-1-fill:before{content:""}.ri-progress-1-line:before{content:""}.ri-progress-2-fill:before{content:""}.ri-progress-2-line:before{content:""}.ri-progress-3-fill:before{content:""}.ri-progress-3-line:before{content:""}.ri-progress-4-fill:before{content:""}.ri-progress-4-line:before{content:""}.ri-progress-5-fill:before{content:""}.ri-progress-5-line:before{content:""}.ri-progress-6-fill:before{content:""}.ri-progress-6-line:before{content:""}.ri-progress-7-fill:before{content:""}.ri-progress-7-line:before{content:""}.ri-progress-8-fill:before{content:""}.ri-progress-8-line:before{content:""}.ri-remix-run-fill:before{content:""}.ri-remix-run-line:before{content:""}.ri-signpost-fill:before{content:""}.ri-signpost-line:before{content:""}.ri-time-zone-fill:before{content:""}.ri-time-zone-line:before{content:""}.ri-arrow-down-wide-fill:before{content:""}.ri-arrow-down-wide-line:before{content:""}.ri-arrow-left-wide-fill:before{content:""}.ri-arrow-left-wide-line:before{content:""}.ri-arrow-right-wide-fill:before{content:""}.ri-arrow-right-wide-line:before{content:""}.ri-arrow-up-wide-fill:before{content:""}.ri-arrow-up-wide-line:before{content:""}.ri-bluesky-fill:before{content:""}.ri-bluesky-line:before{content:""}.ri-expand-height-fill:before{content:""}.ri-expand-height-line:before{content:""}.ri-expand-width-fill:before{content:""}.ri-expand-width-line:before{content:""}.ri-forward-end-fill:before{content:""}.ri-forward-end-line:before{content:""}.ri-forward-end-mini-fill:before{content:""}.ri-forward-end-mini-line:before{content:""}.ri-friendica-fill:before{content:""}.ri-friendica-line:before{content:""}.ri-git-pr-draft-fill:before{content:""}.ri-git-pr-draft-line:before{content:""}.ri-play-reverse-fill:before{content:""}.ri-play-reverse-line:before{content:""}.ri-play-reverse-mini-fill:before{content:""}.ri-play-reverse-mini-line:before{content:""}.ri-rewind-start-fill:before{content:""}.ri-rewind-start-line:before{content:""}.ri-rewind-start-mini-fill:before{content:""}.ri-rewind-start-mini-line:before{content:""}.ri-scroll-to-bottom-fill:before{content:""}.ri-scroll-to-bottom-line:before{content:""}.ri-add-large-fill:before{content:""}.ri-add-large-line:before{content:""}.ri-aed-electrodes-fill:before{content:""}.ri-aed-electrodes-line:before{content:""}.ri-aed-fill:before{content:""}.ri-aed-line:before{content:""}.ri-alibaba-cloud-fill:before{content:""}.ri-alibaba-cloud-line:before{content:""}.ri-align-item-bottom-fill:before{content:""}.ri-align-item-bottom-line:before{content:""}.ri-align-item-horizontal-center-fill:before{content:""}.ri-align-item-horizontal-center-line:before{content:""}.ri-align-item-left-fill:before{content:""}.ri-align-item-left-line:before{content:""}.ri-align-item-right-fill:before{content:""}.ri-align-item-right-line:before{content:""}.ri-align-item-top-fill:before{content:""}.ri-align-item-top-line:before{content:""}.ri-align-item-vertical-center-fill:before{content:""}.ri-align-item-vertical-center-line:before{content:""}.ri-apps-2-add-fill:before{content:""}.ri-apps-2-add-line:before{content:""}.ri-close-large-fill:before{content:""}.ri-close-large-line:before{content:""}.ri-collapse-diagonal-2-fill:before{content:""}.ri-collapse-diagonal-2-line:before{content:""}.ri-collapse-diagonal-fill:before{content:""}.ri-collapse-diagonal-line:before{content:""}.ri-dashboard-horizontal-fill:before{content:""}.ri-dashboard-horizontal-line:before{content:""}.ri-expand-diagonal-2-fill:before{content:""}.ri-expand-diagonal-2-line:before{content:""}.ri-expand-diagonal-fill:before{content:""}.ri-expand-diagonal-line:before{content:""}.ri-firebase-fill:before{content:""}.ri-firebase-line:before{content:""}.ri-flip-horizontal-2-fill:before{content:""}.ri-flip-horizontal-2-line:before{content:""}.ri-flip-horizontal-fill:before{content:""}.ri-flip-horizontal-line:before{content:""}.ri-flip-vertical-2-fill:before{content:""}.ri-flip-vertical-2-line:before{content:""}.ri-flip-vertical-fill:before{content:""}.ri-flip-vertical-line:before{content:""}.ri-formula:before{content:""}.ri-function-add-fill:before{content:""}.ri-function-add-line:before{content:""}.ri-goblet-2-fill:before{content:""}.ri-goblet-2-line:before{content:""}.ri-golf-ball-fill:before{content:""}.ri-golf-ball-line:before{content:""}.ri-group-3-fill:before{content:""}.ri-group-3-line:before{content:""}.ri-heart-add-2-fill:before{content:""}.ri-heart-add-2-line:before{content:""}.ri-id-card-fill:before{content:""}.ri-id-card-line:before{content:""}.ri-information-off-fill:before{content:""}.ri-information-off-line:before{content:""}.ri-java-fill:before{content:""}.ri-java-line:before{content:""}.ri-layout-grid-2-fill:before{content:""}.ri-layout-grid-2-line:before{content:""}.ri-layout-horizontal-fill:before{content:""}.ri-layout-horizontal-line:before{content:""}.ri-layout-vertical-fill:before{content:""}.ri-layout-vertical-line:before{content:""}.ri-menu-fold-2-fill:before{content:""}.ri-menu-fold-2-line:before{content:""}.ri-menu-fold-3-fill:before{content:""}.ri-menu-fold-3-line:before{content:""}.ri-menu-fold-4-fill:before{content:""}.ri-menu-fold-4-line:before{content:""}.ri-menu-unfold-2-fill:before{content:""}.ri-menu-unfold-2-line:before{content:""}.ri-menu-unfold-3-fill:before{content:""}.ri-menu-unfold-3-line:before{content:""}.ri-menu-unfold-4-fill:before{content:""}.ri-menu-unfold-4-line:before{content:""}.ri-mobile-download-fill:before{content:""}.ri-mobile-download-line:before{content:""}.ri-nextjs-fill:before{content:""}.ri-nextjs-line:before{content:""}.ri-nodejs-fill:before{content:""}.ri-nodejs-line:before{content:""}.ri-pause-large-fill:before{content:""}.ri-pause-large-line:before{content:""}.ri-play-large-fill:before{content:""}.ri-play-large-line:before{content:""}.ri-play-reverse-large-fill:before{content:""}.ri-play-reverse-large-line:before{content:""}.ri-police-badge-fill:before{content:""}.ri-police-badge-line:before{content:""}.ri-prohibited-2-fill:before{content:""}.ri-prohibited-2-line:before{content:""}.ri-shopping-bag-4-fill:before{content:""}.ri-shopping-bag-4-line:before{content:""}.ri-snowflake-fill:before{content:""}.ri-snowflake-line:before{content:""}.ri-square-root:before{content:""}.ri-stop-large-fill:before{content:""}.ri-stop-large-line:before{content:""}.ri-tailwind-css-fill:before{content:""}.ri-tailwind-css-line:before{content:""}.ri-tooth-fill:before{content:""}.ri-tooth-line:before{content:""}.ri-video-off-fill:before{content:""}.ri-video-off-line:before{content:""}.ri-video-on-fill:before{content:""}.ri-video-on-line:before{content:""}.ri-webhook-fill:before{content:""}.ri-webhook-line:before{content:""}.ri-weight-fill:before{content:""}.ri-weight-line:before{content:""}.ri-book-shelf-fill:before{content:""}.ri-book-shelf-line:before{content:""}.ri-brain-2-fill:before{content:""}.ri-brain-2-line:before{content:""}.ri-chat-search-fill:before{content:""}.ri-chat-search-line:before{content:""}.ri-chat-unread-fill:before{content:""}.ri-chat-unread-line:before{content:""}.ri-collapse-horizontal-fill:before{content:""}.ri-collapse-horizontal-line:before{content:""}.ri-collapse-vertical-fill:before{content:""}.ri-collapse-vertical-line:before{content:""}.ri-dna-fill:before{content:""}.ri-dna-line:before{content:""}.ri-dropper-fill:before{content:""}.ri-dropper-line:before{content:""}.ri-expand-diagonal-s-2-fill:before{content:""}.ri-expand-diagonal-s-2-line:before{content:""}.ri-expand-diagonal-s-fill:before{content:""}.ri-expand-diagonal-s-line:before{content:""}.ri-expand-horizontal-fill:before{content:""}.ri-expand-horizontal-line:before{content:""}.ri-expand-horizontal-s-fill:before{content:""}.ri-expand-horizontal-s-line:before{content:""}.ri-expand-vertical-fill:before{content:""}.ri-expand-vertical-line:before{content:""}.ri-expand-vertical-s-fill:before{content:""}.ri-expand-vertical-s-line:before{content:""}.ri-gemini-fill:before{content:""}.ri-gemini-line:before{content:""}.ri-reset-left-fill:before{content:""}.ri-reset-left-line:before{content:""}.ri-reset-right-fill:before{content:""}.ri-reset-right-line:before{content:""}.ri-stairs-fill:before{content:""}.ri-stairs-line:before{content:""}.ri-telegram-2-fill:before{content:""}.ri-telegram-2-line:before{content:""}.ri-triangular-flag-fill:before{content:""}.ri-triangular-flag-line:before{content:""}.ri-user-minus-fill:before{content:""}.ri-user-minus-line:before{content:""}.ri-account-box-2-fill:before{content:""}.ri-account-box-2-line:before{content:""}.ri-account-circle-2-fill:before{content:""}.ri-account-circle-2-line:before{content:""}.ri-alarm-snooze-fill:before{content:""}.ri-alarm-snooze-line:before{content:""}.ri-arrow-down-box-fill:before{content:""}.ri-arrow-down-box-line:before{content:""}.ri-arrow-left-box-fill:before{content:""}.ri-arrow-left-box-line:before{content:""}.ri-arrow-left-down-box-fill:before{content:""}.ri-arrow-left-down-box-line:before{content:""}.ri-arrow-left-up-box-fill:before{content:""}.ri-arrow-left-up-box-line:before{content:""}.ri-arrow-right-box-fill:before{content:""}.ri-arrow-right-box-line:before{content:""}.ri-arrow-right-down-box-fill:before{content:""}.ri-arrow-right-down-box-line:before{content:""}.ri-arrow-right-up-box-fill:before{content:""}.ri-arrow-right-up-box-line:before{content:""}.ri-arrow-up-box-fill:before{content:""}.ri-arrow-up-box-line:before{content:""}.ri-bar-chart-box-ai-fill:before{content:""}.ri-bar-chart-box-ai-line:before{content:""}.ri-brush-ai-fill:before{content:""}.ri-brush-ai-line:before{content:""}.ri-camera-ai-fill:before{content:""}.ri-camera-ai-line:before{content:""}.ri-chat-ai-fill:before{content:""}.ri-chat-ai-line:before{content:""}.ri-chat-smile-ai-fill:before{content:""}.ri-chat-smile-ai-line:before{content:""}.ri-chat-voice-ai-fill:before{content:""}.ri-chat-voice-ai-line:before{content:""}.ri-code-ai-fill:before{content:""}.ri-code-ai-line:before{content:""}.ri-color-filter-ai-fill:before{content:""}.ri-color-filter-ai-line:before{content:""}.ri-custom-size:before{content:""}.ri-fediverse-fill:before{content:""}.ri-fediverse-line:before{content:""}.ri-flag-off-fill:before{content:""}.ri-flag-off-line:before{content:""}.ri-home-9-fill:before{content:""}.ri-home-9-line:before{content:""}.ri-image-ai-fill:before{content:""}.ri-image-ai-line:before{content:""}.ri-image-circle-ai-fill:before{content:""}.ri-image-circle-ai-line:before{content:""}.ri-info-card-fill:before{content:""}.ri-info-card-line:before{content:""}.ri-landscape-ai-fill:before{content:""}.ri-landscape-ai-line:before{content:""}.ri-letter-spacing-2:before{content:""}.ri-line-height-2:before{content:""}.ri-mail-ai-fill:before{content:""}.ri-mail-ai-line:before{content:""}.ri-mic-2-ai-fill:before{content:""}.ri-mic-2-ai-line:before{content:""}.ri-mic-ai-fill:before{content:""}.ri-mic-ai-line:before{content:""}.ri-movie-ai-fill:before{content:""}.ri-movie-ai-line:before{content:""}.ri-music-ai-fill:before{content:""}.ri-music-ai-line:before{content:""}.ri-notification-snooze-fill:before{content:""}.ri-notification-snooze-line:before{content:""}.ri-php-fill:before{content:""}.ri-php-line:before{content:""}.ri-pix-fill:before{content:""}.ri-pix-line:before{content:""}.ri-pulse-ai-fill:before{content:""}.ri-pulse-ai-line:before{content:""}.ri-quill-pen-ai-fill:before{content:""}.ri-quill-pen-ai-line:before{content:""}.ri-speak-ai-fill:before{content:""}.ri-speak-ai-line:before{content:""}.ri-star-off-fill:before{content:""}.ri-star-off-line:before{content:""}.ri-translate-ai-2:before{content:""}.ri-translate-ai:before{content:""}.ri-user-community-fill:before{content:""}.ri-user-community-line:before{content:""}.ri-vercel-fill:before{content:""}.ri-vercel-line:before{content:""}.ri-video-ai-fill:before{content:""}.ri-video-ai-line:before{content:""}.ri-video-on-ai-fill:before{content:""}.ri-video-on-ai-line:before{content:""}.ri-voice-ai-fill:before{content:""}.ri-voice-ai-line:before{content:""}.ri-ai-generate-2:before{content:""}.ri-ai-generate-text:before{content:""}.ri-anthropic-fill:before{content:""}.ri-anthropic-line:before{content:""}.ri-apps-2-ai-fill:before{content:""}.ri-apps-2-ai-line:before{content:""}.ri-camera-lens-ai-fill:before{content:""}.ri-camera-lens-ai-line:before{content:""}.ri-clapperboard-ai-fill:before{content:""}.ri-clapperboard-ai-line:before{content:""}.ri-claude-fill:before{content:""}.ri-claude-line:before{content:""}.ri-closed-captioning-ai-fill:before{content:""}.ri-closed-captioning-ai-line:before{content:""}.ri-dvd-ai-fill:before{content:""}.ri-dvd-ai-line:before{content:""}.ri-film-ai-fill:before{content:""}.ri-film-ai-line:before{content:""}.ri-font-size-ai:before{content:""}.ri-mixtral-fill:before{content:""}.ri-mixtral-line:before{content:""}.ri-movie-2-ai-fill:before{content:""}.ri-movie-2-ai-line:before{content:""}.ri-mv-ai-fill:before{content:""}.ri-mv-ai-line:before{content:""}.ri-perplexity-fill:before{content:""}.ri-perplexity-line:before{content:""}.ri-poker-clubs-fill:before{content:""}.ri-poker-clubs-line:before{content:""}.ri-poker-diamonds-fill:before{content:""}.ri-poker-diamonds-line:before{content:""}.ri-poker-hearts-fill:before{content:""}.ri-poker-hearts-line:before{content:""}.ri-poker-spades-fill:before{content:""}.ri-poker-spades-line:before{content:""}.ri-safe-3-fill:before{content:""}.ri-safe-3-line:before{content:""}.ri-accessibility-fill:before{content:""}.ri-accessibility-line:before{content:""}.ri-alarm-add-fill:before{content:""}.ri-alarm-add-line:before{content:""}.ri-arrow-down-long-fill:before{content:""}.ri-arrow-down-long-line:before{content:""}.ri-arrow-left-down-long-fill:before{content:""}.ri-arrow-left-down-long-line:before{content:""}.ri-arrow-left-long-fill:before{content:""}.ri-arrow-left-long-line:before{content:""}.ri-arrow-left-up-long-fill:before{content:""}.ri-arrow-left-up-long-line:before{content:""}.ri-arrow-right-down-long-fill:before{content:""}.ri-arrow-right-down-long-line:before{content:""}.ri-arrow-right-long-fill:before{content:""}.ri-arrow-right-long-line:before{content:""}.ri-arrow-right-up-long-fill:before{content:""}.ri-arrow-right-up-long-line:before{content:""}.ri-arrow-up-long-fill:before{content:""}.ri-arrow-up-long-line:before{content:""}.ri-chess-fill:before{content:""}.ri-chess-line:before{content:""}.ri-diamond-fill:before{content:""}.ri-diamond-line:before{content:""}.ri-diamond-ring-fill:before{content:""}.ri-diamond-ring-line:before{content:""}.ri-figma-fill:before{content:""}.ri-figma-line:before{content:""}.ri-firefox-browser-fill:before{content:""}.ri-firefox-browser-line:before{content:""}.ri-jewelry-fill:before{content:""}.ri-jewelry-line:before{content:""}.ri-multi-image-fill:before{content:""}.ri-multi-image-line:before{content:""}.ri-no-credit-card-fill:before{content:""}.ri-no-credit-card-line:before{content:""}.ri-service-bell-fill:before{content:""}.ri-service-bell-line:before{content:""}:root,.dropdown,.base-surface{--baseFontFamily:"IBM Plex Sans", system-ui, sans-serif, emoji;--monospaceFontFamily:"IBM Plex Mono", ui-monospace, monospace, emoji;--iconFontFamily:"remixicon";--primaryColor:#25272d;--primaryAlt1Color:color-mix(in srgb, var(--primaryColor), white 8%);--primaryAlt2Color:color-mix(in srgb, var(--primaryColor), white 13%);--primaryTxtColor:#fff;--primaryTxtHintColor:color-mix(in srgb, var(--primaryTxtColor), transparent 35%);--primaryTxtDisabledColor:color-mix(in srgb, var(--primaryTxtColor), transparent 50%);--secondaryColor:#e4e8ec;--secondaryAlt1Color:#dce0e5;--secondaryAlt2Color:#d3d9df;--secondaryTxtColor:var(--primaryColor);--secondaryTxtHintColor:color-mix(in srgb, var(--secondaryTxtColor), transparent 35%);--secondaryTxtDisabledColor:color-mix(in srgb, var(--secondaryTxtColor), transparent 50%);--infoColor:#3376e5;--infoAlt1Color:color-mix(in srgb, var(--infoColor), white 10%);--infoAlt2Color:color-mix(in srgb, var(--infoColor), white 15%);--infoTxtColor:var(--primaryTxtColor);--infoTxtHintColor:var(--primaryTxtHintColor);--infoTxtDisabledColor:var(--primaryTxtDisabledColor);--successColor:#22a96d;--successAlt1Color:color-mix(in srgb, var(--successColor), white 10%);--successAlt2Color:color-mix(in srgb, var(--successColor), white 15%);--successTxtColor:var(--primaryTxtColor);--successTxtHintColor:var(--primaryTxtHintColor);--successTxtDisabledColor:var(--primaryTxtDisabledColor);--warningColor:#e57534;--warningAlt1Color:color-mix(in srgb, var(--warningColor), white 10%);--warningAlt2Color:color-mix(in srgb, var(--warningColor), white 15%);--warningTxtColor:var(--primaryTxtColor);--warningTxtHintColor:var(--primaryTxtHintColor);--warningTxtDisabledColor:var(--primaryTxtDisabledColor);--dangerColor:#c33751;--dangerAlt1Color:color-mix(in srgb, var(--dangerColor), white 10%);--dangerAlt2Color:color-mix(in srgb, var(--dangerColor), white 15%);--dangerTxtColor:var(--primaryTxtColor);--dangerTxtHintColor:var(--primaryTxtHintColor);--dangerTxtDisabledColor:var(--primaryTxtDisabledColor);--accentColor:#1055c9;--accentAlt1Color:color-mix(in srgb, var(--accentColor), white 10%);--accentAlt2Color:color-mix(in srgb, var(--accentColor), white 15%);--accentTxtColor:#fff;--accentTxtHintColor:color-mix(in srgb, var(--accentTxtColor), transparent 40%);--accentTxtDisabledColor:color-mix(in srgb, var(--accentTxtColor), transparent 50%);--surfaceColor:#fff;--surfaceAlt1Color:#f8f9fa;--surfaceAlt2Color:#e4e8ec;--surfaceAlt3Color:#dce0e5;--surfaceAlt4Color:#d3d9df;--surfaceAlt5Color:#ccd4db;--surfaceTxtColor:#25272d;--surfaceTxtHintColor:#687278;--surfaceTxtDisabledColor:#a2a9ae;--surfaceAccentColor:color-mix(in srgb, var(--accentColor), white 80%);--surfaceInfoColor:color-mix(in srgb, var(--infoColor), white 80%);--surfaceSuccessColor:color-mix(in srgb, var(--successColor), white 80%);--surfaceWarningColor:color-mix(in srgb, var(--warningColor), white 80%);--surfaceDangerColor:color-mix(in srgb, var(--dangerColor), white 80%);--selectionColor:#465a7340;--inputColor:var(--surfaceAlt2Color);--inputFocusColor:var(--surfaceAlt3Color);--inputBorderColor:var(--surfaceAlt4Color);scrollbar-color:var(--surfaceAlt4Color) transparent;--prismFunctionColor:#7c00aa;--prismStringColor:#116b00;--prismNumberColor:#246f86;--prismPropertyColor:#246f86;--prismKeywordColor:#a04900;--prismCommentColor:var(--surfaceTxtHintColor);--prismPunctuationColor:var(--surfaceTxtColor);--modalAnimationSpeed:.2s;--modalOverlayColor:#465a7340;--tooltipTxtColor:#fff;--tooltipSurfaceColor:#25272de6;--boxShadow:0px 8px 5px -5px #2224241a;--leftBoxShadow:-1px 0px 5px 0 #2224241a;--rightBoxShadow:1px 0px 5px 0 #2224241a;--topScrollShadow:0px 2px 8px 1px #22242426;--bottomScrollShadow:0px -2px 8px 1px #22242426;--leftScrollShadow:3px 0px 5px 0px #22242433;--rightScrollShadow:-3px 0px 5px 0px #22242433;--fontSize:14px;--smFontSize:13px;--lgFontSize:15px;--lineHeight:22px;--smLineHeight:16px;--lgLineHeight:24px;--btnHeight:45px;--smBtnHeight:35px;--lgBtnHeight:52px;--spacing:30px;--smSpacing:20px;--inputPadding:13px;--inputBorderRadius:var(--borderRadius);--borderRadius:5px;--lgBorderRadius:15px;--pageSidebarWidth:240px;--wrapperWidth:840px;--smWrapperWidth:430px;--lgWrapperWidth:1150px;--loaderIcon:"";--loaderAnimationSpeed:1.1s;--animationSpeed:.15s;--activeAnimationSpeed:70ms;--slideAnimationSpeed:.2s;accent-color:var(--primaryColor)}[data-color-scheme=dark],[data-color-scheme=dark] .dropdown{--surfaceColor:#1c1c1c;--surfaceAlt1Color:color-mix(in srgb, var(--surfaceColor), white 2%);--surfaceAlt2Color:color-mix(in srgb, var(--surfaceColor), white 7%);--surfaceAlt3Color:color-mix(in srgb, var(--surfaceColor), white 12%);--surfaceAlt4Color:color-mix(in srgb, var(--surfaceColor), white 14%);--surfaceAlt5Color:color-mix(in srgb, var(--surfaceColor), white 16%);--surfaceTxtColor:#ebebeb;--tooltipTxtColor:var(--surfaceTxtColor);--surfaceTxtHintColor:color-mix(in srgb, var(--surfaceTxtColor), transparent 45%);--surfaceTxtDisabledColor:color-mix(in srgb, var(--surfaceTxtColor), transparent 65%);--secondaryColor:var(--surfaceAlt2Color);--secondaryTxtColor:var(--surfaceTxtColor);--secondaryAlt1Color:var(--surfaceAlt3Color);--secondaryAlt2Color:var(--surfaceAlt4Color);--primaryColor:#121212;--primaryTxtColor:var(--surfaceTxtColor);--primaryAlt1Color:color-mix(in srgb, var(--primaryColor), black 12%);--primaryAlt2Color:color-mix(in srgb, var(--primaryColor), black 25%);--primaryTxtHintColor:color-mix(in srgb, var(--primaryTxtColor), transparent 35%);--primaryTxtDisabledColor:color-mix(in srgb, var(--primaryTxtColor), transparent 50%);--infoColor:#4273bd;--infoTxtColor:var(--surfaceTxtColor);--infoAlt1Color:color-mix(in srgb, var(--infoColor), black 10%);--infoAlt2Color:color-mix(in srgb, var(--infoColor), black 15%);--surfaceInfoColor:color-mix(in srgb, var(--infoColor), var(--surfaceColor) 45%);--successColor:#27865d;--successTxtColor:var(--surfaceTxtColor);--successAlt1Color:color-mix(in srgb, var(--successColor), black 10%);--successAlt2Color:color-mix(in srgb, var(--successColor), black 15%);--surfaceSuccessColor:color-mix(in srgb, var(--successColor), var(--surfaceColor) 45%);--warningColor:#cf4e17;--warningTxtColor:var(--surfaceTxtColor);--warningAlt1Color:color-mix(in srgb, var(--warningColor), black 10%);--warningAlt2Color:color-mix(in srgb, var(--warningColor), black 15%);--surfaceWarningColor:color-mix(in srgb, var(--warningColor), var(--surfaceColor) 45%);--dangerColor:#bb303e;--dangerTxtColor:var(--surfaceTxtColor);--dangerAlt1Color:color-mix(in srgb, var(--dangerColor), black 10%);--dangerAlt2Color:color-mix(in srgb, var(--dangerColor), black 15%);--surfaceDangerColor:color-mix(in srgb, var(--dangerColor), var(--surfaceColor) 45%);--selectionColor:#ffffff38;--inputColor:var(--surfaceAlt2Color);--inputFocusColor:var(--surfaceAlt3Color);--inputBorderColor:var(--surfaceAlt4Color);scrollbar-color:var(--surfaceAlt5Color) transparent;--modalOverlayColor:#12121280;--tooltipSurfaceColor:#161616e6;--boxShadow:0px 8px 5px -5px #0000004d;--leftBoxShadow:-1px 0px 5px 0 #0000004d;--rightBoxShadow:1px 0px 5px 0 #0000004d;--topScrollShadow:0px 2px 8px 1px #00000073;--bottomScrollShadow:0px -2px 8px 1px #00000073;--leftScrollShadow:3px 0px 5px 0px #0006;--rightScrollShadow:-3px 0px 5px 0px #0006;--prismFunctionColor:#ab7abd;--prismStringColor:#87b47e;--prismNumberColor:#86b9bb;--prismPropertyColor:#90c2d1;--prismKeywordColor:#b3703d;--prismCommentColor:var(--surfaceTxtHintColor);--prismPunctuationColor:var(--surfaceTxtColor)}html .accent-surface{--surfaceColor:var(--accentColor);--surfaceAlt1Color:color-mix(in srgb, var(--surfaceColor), white 5%);--surfaceAlt2Color:color-mix(in srgb, var(--surfaceColor), white 10%);--surfaceAlt3Color:color-mix(in srgb, var(--surfaceColor), white 20%);--surfaceAlt4Color:color-mix(in srgb, var(--surfaceColor), white 30%);--surfaceAlt5Color:color-mix(in srgb, var(--surfaceColor), white 40%);--surfaceTxtColor:#fff;--surfaceTxtHintColor:color-mix(in srgb, var(--surfaceTxtColor), transparent 35%);--surfaceTxtDisabledColor:color-mix(in srgb, var(--surfaceTxtColor), transparent 60%);--surfaceAccentColor:color-mix(in srgb, var(--surfaceColor), white 20%);--surfaceInfoColor:color-mix(in srgb, var(--infoColor), transparent 40%);--surfaceSuccessColor:color-mix(in srgb, var(--successColor), transparent 40%);--surfaceWarningColor:color-mix(in srgb, var(--warningColor), transparent 40%);--surfaceDangerColor:color-mix(in srgb, var(--dangerColor), transparent 40%);--inputColor:var(--surfaceAlt3Color);--inputFocusColor:var(--surfaceAlt4Color);--inputBorderColor:var(--surfaceAlt5Color);--selectionColor:var(--surfaceAccentColor);scrollbar-color:var(--surfaceAlt4Color) transparent}@media (width<=900px){:root,.dropdown,.base-surface{--spacing:20px;--smSpacing:15px;--btnHeight:40px;--smBtnHeight:30px;--lgBtnHeight:47px}}@keyframes rotate{0%{pointer-events:none}to{pointer-events:none;transform:rotate(360deg)}}@keyframes halfRotate{0%{pointer-events:none}to{pointer-events:none;transform:rotate(180deg)}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes slideTop{0%{opacity:0;transform:translateY(-3px)}to{opacity:1;transform:translateY(0)}}@keyframes scaleIn{0%{opacity:0;transform:scale(.85)}to{opacity:1;transform:scale(1)}}code[class*=language-],pre[class*=language-]{color:var(--surfaceTxtHintColor);font-family:var(--monospaceFontFamily);text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;tab-size:4;-webkit-hyphens:none;hyphens:none;font-size:1em;line-height:1.5}pre[class*=language-]{margin:.5em 0;padding:1em;overflow:auto}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:var(--prismCommentColor)}.token.punctuation{color:var(--prismPunctuationColor)}.token.namespace{opacity:.7}.token.constant,.token.boolean,.token.number{color:var(--prismNumberColor)}.token.deleted,.token.property,.token.symbol,.token.tag,.token.tag .token.punctuation{color:var(--prismPropertyColor)}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string,.token.url{color:var(--prismStringColor)}.token.atrule,.token.attr-value,.token.keyword{color:var(--prismKeywordColor)}.token.class-name,.token.function,.token.important,.token.regex,.token.variable{color:var(--prismFunctionColor)}.token.bold{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{font:inherit;vertical-align:baseline;border:0;margin:0;padding:0}*,:before,:after{box-sizing:border-box}[class^=ri-],[class*=\ ri-]{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-size:1.15em;font-style:normal;font-weight:400;font-family:var(--iconFontFamily)!important}:is([class^=ri-],[class*=\ ri-]):before{vertical-align:top}:focus-visible{outline:2px dotted var(--selectionColor)}::selection{background:var(--selectionColor)}:root{interpolate-size:allow-keywords}[data-dragover=true]{opacity:.5}html,body{font-optical-sizing:auto;font-size:var(--fontSize);font-family:var(--baseFontFamily);line-height:var(--lineHeight);color:var(--surfaceTxtColor);background:var(--surfaceColor);letter-spacing:.004em;height:100%}h1,h2,h3,h4,h5,h6{font-weight:400;display:block}h1{font-size:26px;line-height:32px}h2{font-size:24px;line-height:30px}h3{font-size:22px;line-height:28px}h4{font-size:20px;line-height:26px}h5{font-size:18px;line-height:24px}h6{font-size:16px;line-height:24px}em{font-style:italic}strong{font-weight:700}small{font-size:var(--smFontSize);line-height:var(--smLineHeight)}sub,sup{font-size:.8em;line-height:1;position:relative}sup{vertical-align:top}sub{vertical-align:bottom}p{margin:10px 0;display:block}p:first-child{margin-top:0}p:last-child{margin-bottom:0}a{color:inherit}a:hover{text-decoration:none}ol,ul{padding-left:var(--spacing);margin:10px 0}:is(ol,ul) li{margin-top:5px;margin-bottom:5px}:is(ol,ul) li:first-child{margin-top:0}:is(ol,ul) li:last-child{margin-bottom:0}ol{list-style:decimal}ul{list-style:outside}cite{font-style:italic}blockquote{quotes:none;color:var(--surfaceTxtHintColor);border-left:3px solid var(--surfaceAlt2Color);margin:10px 0;padding-left:10px;font-style:italic;display:block;position:relative}blockquote:before,blockquote:after{content:none;display:none}blockquote>:first-child{margin-top:0}blockquote>:last-child{margin-bottom:0}pre{white-space:pre-wrap;border-radius:var(--borderRadius)}pre[class*=language-]{background:0 0;margin:0;padding:0;overflow:visible}code,code[class*=language-]{letter-spacing:0;font-size:.93em;line-height:1.5;font-family:var(--monospaceFontFamily);background:var(--surfaceAlt2Color);border-radius:var(--borderRadius);tab-size:4;padding:1px 3px;display:inline-block}pre :is(code,code[class*=language-]),:is(code,code[class*=language-]).block{width:100%;padding:max(10px, calc(var(--inputPadding) - 5px)) max(10px, var(--inputPadding));white-space:pre-wrap;display:block}.code-wrapper{outline:0}.code-wrapper code{scrollbar-width:thin;overflow:auto}.code-wrapper .footnote{color:var(--surfaceTxtHintColor);font-size:var(--smFontSize);line-height:var(--smLineHeight);margin:5px 0 0}.code-wrapper .footnote:empty{display:none}hr{width:100%;height:1px;color:inherit;margin:var(--spacing) 0;background:var(--surfaceAlt2Color);border:0;flex-shrink:0;display:block}hr.sm{margin:var(--smSpacing) 0}.block,.full-width{width:100%}.flex{align-items:center;gap:var(--spacing);display:flex!important}.inline-flex{align-items:center;gap:var(--smSpacing);display:inline-flex!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-fill{flex:auto!important}.wrapper{width:var(--wrapperWidth);max-width:100%;margin:0 auto}.wrapper.sm{width:var(--smWrapperWidth)}.wrapper.lg{width:var(--lgWrapperWidth)}.app>.wrapper{max-width:calc(100% - var(--spacing))}[hidden],.hidden{display:none!important}.slide,[data-slide]{transition:margin var(--slideAnimationSpeed), padding var(--slideAnimationSpeed), height var(--slideAnimationSpeed), opacity var(--slideAnimationSpeed), display var(--slideAnimationSpeed), overflow var(--slideAnimationSpeed) step-end;transition-behavior:allow-discrete}@starting-style{.slide,[data-slide]{opacity:0!important;height:0!important;margin-top:0!important;margin-bottom:0!important;padding-top:0!important;padding-bottom:0!important;overflow:hidden!important}}:is(.slide,[data-slide]).hidden{opacity:0!important;height:0!important;transition:margin var(--slideAnimationSpeed), padding var(--slideAnimationSpeed), height var(--slideAnimationSpeed), opacity var(--slideAnimationSpeed), display var(--slideAnimationSpeed), overflow var(--slideAnimationSpeed) step-start!important;transition-behavior:allow-discrete!important;margin-top:0!important;margin-bottom:0!important;padding-top:0!important;padding-bottom:0!important;overflow:hidden!important}.txt-base{line-height:var(--lineHeight)!important;font-size:var(--fontSize)!important}.txt-sm{line-height:var(--smLineHeight)!important;font-size:var(--smFontSize)!important}.txt-lg{line-height:var(--lgLineHeight)!important;font-size:var(--lgFontSize)!important}.txt-center{text-align:center!important}.txt-left{text-align:left!important}.txt-right{text-align:right!important}.txt-warning{color:var(--warningColor)!important}.txt-success{color:var(--successColor)!important}.txt-danger{color:var(--dangerColor)!important}.txt-info{color:var(--infoColor)!important}.txt-accent{color:var(--accentColor)!important}.txt-primary{color:var(--surfaceTxtColor)!important}.txt-hint{color:var(--surfaceTxtHintColor)!important}.txt-disabled{color:var(--surfaceTxtDisabledColor)!important}.txt-bold{font-weight:700!important}.txt-normal{font-weight:400!important}.txt-strikethrough{text-decoration:line-through!important}.txt-code{letter-spacing:0;font-size:.93em!important;font-family:var(--monospaceFontFamily)!important;white-space:pre-wrap!important;text-align:left!important}.txt-nowrap{white-space:nowrap!important}.txt-ellipsis{vertical-align:top;flex-shrink:1;min-width:10px;max-width:100%;display:inline-block;overflow:hidden;white-space:nowrap!important;text-overflow:ellipsis!important}.faded{opacity:.6;transition:opacity var(--animationSpeed)}.link-faded{opacity:.6;cursor:pointer;transition:all var(--animationSpeed);text-decoration:none}.link-faded:hover,.link-faded:focus-visible,.link-faded[data-popover-state=true],.link-faded:active{opacity:1}.link-primary,.link-hint{--linkColor:var(--surfaceTxtHintColor);--linkFocusColor:var(--surfaceTxtColor);vertical-align:top;cursor:pointer;border-radius:var(--borderRadius);color:var(--linkColor);transition:color var(--animationSpeed), opacity var(--animationSpeed);-webkit-user-select:none;user-select:none;align-items:center;gap:5px;text-decoration:none;display:inline-flex}:is(.link-primary,.link-hint):hover,:is(.link-primary,.link-hint):focus-visible{color:var(--linkFocusColor)}:is(.link-primary,.link-hint)[data-popover-state=true],:is(.link-primary,.link-hint):active{color:var(--linkFocusColor);transition-duration:var(--activeAnimationSpeed)}.link-primary{--linkColor:var(--surfaceTxtColor);--linkFocusColor:var(--surfaceTxtColor)}.link-primary:hover,.link-primary:focus-visible,.link-primary[data-popover-state=true],.link-primary:active{opacity:.9}.gap-base{gap:var(--spacing)!important}.gap-sm{gap:var(--smSpacing)!important}.gap-auto{gap:auto!important}.gap-0{gap:0!important}.gap-5{gap:5px!important}.gap-10{gap:10px!important}.gap-15{gap:15px!important}.gap-20{gap:20px!important}.gap-25{gap:25px!important}.gap-30{gap:30px!important}.m-base{margin:var(--spacing)!important}.m-t-base{margin-top:var(--spacing)!important}.m-r-base{margin-right:var(--spacing)!important}.m-b-base{margin-bottom:var(--spacing)!important}.m-l-base{margin-left:var(--spacing)!important}.m-sm{margin:var(--smSpacing)!important}.m-t-sm{margin-top:var(--smSpacing)!important}.m-r-sm{margin-right:var(--smSpacing)!important}.m-b-sm{margin-bottom:var(--smSpacing)!important}.m-l-sm{margin-left:var(--smSpacing)!important}.m-auto{margin:auto!important}.m-t-auto{margin-top:auto!important}.m-r-auto{margin-right:auto!important}.m-b-auto{margin-bottom:auto!important}.m-l-auto{margin-left:auto!important}.m-0{margin:0!important}.m-t-0{margin-top:0!important}.m-r-0{margin-right:0!important}.m-b-0{margin-bottom:0!important}.m-l-0{margin-left:0!important}.m-5{margin:5px!important}.m-t-5{margin-top:5px!important}.m-r-5{margin-right:5px!important}.m-b-5{margin-bottom:5px!important}.m-l-5{margin-left:5px!important}.m-10{margin:10px!important}.m-t-10{margin-top:10px!important}.m-r-10{margin-right:10px!important}.m-b-10{margin-bottom:10px!important}.m-l-10{margin-left:10px!important}.m-15{margin:15px!important}.m-t-15{margin-top:15px!important}.m-r-15{margin-right:15px!important}.m-b-15{margin-bottom:15px!important}.m-l-15{margin-left:15px!important}.m-20{margin:20px!important}.m-t-20{margin-top:20px!important}.m-r-20{margin-right:20px!important}.m-b-20{margin-bottom:20px!important}.m-l-20{margin-left:20px!important}.m-t-25{margin-top:25px!important}.m-r-25{margin-right:25px!important}.m-b-25{margin-bottom:25px!important}.m-l-25{margin-left:25px!important}.m-t-30{margin-top:30px!important}.m-r-30{margin-right:30px!important}.m-b-30{margin-bottom:30px!important}.m-l-30{margin-left:30px!important}.p-base{padding:var(--spacing)!important}.p-t-base{padding-top:var(--spacing)!important}.p-r-base{padding-right:var(--spacing)!important}.p-b-base{padding-bottom:var(--spacing)!important}.p-l-base{padding-left:var(--spacing)!important}.p-sm{padding:var(--smSpacing)!important}.p-t-sm{padding-top:var(--smSpacing)!important}.p-r-sm{padding-right:var(--smSpacing)!important}.p-b-sm{padding-bottom:var(--smSpacing)!important}.p-l-sm{padding-left:var(--smSpacing)!important}.p-0{padding:0!important}.p-t-0{padding-top:0!important}.p-r-0{padding-right:0!important}.p-b-0{padding-bottom:0!important}.p-l-0{padding-left:0!important}.p-5{padding:5px!important}.p-t-5{padding-top:5px!important}.p-r-5{padding-right:5px!important}.p-b-5{padding-bottom:5px!important}.p-l-5{padding-left:5px!important}.p-10{padding:10px!important}.p-t-10{padding-top:10px!important}.p-r-10{padding-right:10px!important}.p-b-10{padding-bottom:10px!important}.p-l-10{padding-left:10px!important}.p-15{padding:15px!important}.p-t-15{padding-top:15px!important}.p-r-15{padding-right:15px!important}.p-b-15{padding-bottom:15px!important}.p-l-15{padding-left:15px!important}.p-20{padding:20px!important}.p-t-20{padding-top:20px!important}.p-r-20{padding-right:20px!important}.p-b-20{padding-bottom:20px!important}.p-l-20{padding-left:20px!important}.p-t-25{padding-top:25px!important}.p-r-25{padding-right:25px!important}.p-b-25{padding-bottom:25px!important}.p-l-25{padding-left:25px!important}.p-t-30{padding-top:30px!important}.p-r-30{padding-right:30px!important}.p-b-30{padding-bottom:30px!important}.p-l-30{padding-left:30px!important}.loader{--loaderSize:24px;vertical-align:top;text-align:center;color:var(--surfaceAlt5Color);opacity:0;animation:scaleIn var(--animationSpeed) .1s forwards;align-items:center;gap:5px;line-height:1;display:inline-flex}.loader:before{content:var(--loaderIcon);-webkit-user-select:none;user-select:none;text-align:left;color:currentColor;font-weight:400;line-height:var(--loaderSize);font-size:var(--loaderSize);font-family:var(--iconFontFamily);animation:rotate var(--loaderAnimationSpeed) infinite linear;align-content:center;display:block}.loader.sm{--loaderSize:20px;font-size:var(--smFontSize)}.loader.lg{--loaderSize:33px;font-size:var(--lgFontSize)}@keyframes shine{to{background-position-x:-200%}}.skeleton-loader{border-radius:var(--borderRadius);background:var(--surfaceAlt1Color);background:linear-gradient(90deg, var(--surfaceAlt1Color) 10%, var(--surfaceAlt2Color) 25%, var(--surfaceAlt1Color) 60%);width:100%;height:12px;animation:fadeIn var(--animationSpeed), shine 1s linear infinite;background-size:200% 100%;margin:5px 0;display:block;position:relative}.skeleton-loader.sm{height:8px}.skeleton-loader.lg{height:18px}.alert{width:100%;min-height:var(--lgBtnHeight);padding:max(5px, calc(var(--inputPadding) - 5px)) max(10px, var(--inputPadding));border-radius:var(--borderRadius);color:var(--alertTxtColor);background:var(--surfaceAlt2Color);border:2px solid var(--surfaceAlt4Color);flex-shrink:0;align-content:center;display:block}.alert code,.alert .label,.alert hr{background:color-mix(in srgb, currentColor, transparent 90%)}.alert p{margin:5px 0}.alert p:first-child{margin-top:0}.alert p:last-child{margin-bottom:0}.alert.info{border-color:var(--infoColor);background:var(--surfaceInfoColor)}.alert.success{border-color:var(--successColor);background:var(--surfaceSuccessColor)}.alert.warning{border-color:var(--warningColor);background:var(--surfaceWarningColor)}.alert.danger{border-color:var(--dangerColor);background:var(--surfaceDangerColor)}.label{text-align:center;vertical-align:middle;word-break:break-word;width:auto;min-width:0;max-width:100%;min-height:25px;font-weight:400;font-size:var(--smFontSize);line-height:calc(var(--smFontSize) + 2px);border-radius:var(--borderRadius);color:var(--surfaceTxtColor);background:var(--surfaceAlt3Color);border:0px solid var(--surfaceAlt5Color);flex-shrink:0;justify-content:center;align-items:center;gap:5px;padding:5px 7px;text-decoration:none;display:inline-flex}.label .label{border-width:1px;border-color:var(--surfaceAlt5Color)}.label.sm{min-height:20px;font-size:calc(var(--smFontSize) - 2px);padding:3px 5px}.label.info{background:var(--surfaceInfoColor)}.label.info .label{border-color:var(--infoColor)}.label.success{background:var(--surfaceSuccessColor)}.label.success .label{border-color:var(--successColor)}.label.warning{background:var(--surfaceWarningColor)}.label.warning .label{border-color:var(--warningColor)}.label.danger{background:var(--surfaceDangerColor)}.label.danger .label{border-color:var(--dangerColor)}.label.handle{cursor:pointer;-webkit-user-select:none;user-select:none;transition:opacity var(--animationSpeed);outline:0}.label.handle:hover,.label.handle:focus-visible{opacity:.9}.label.handle:active{opacity:.8;transition-duration:var(--activeAnimationSpeed)}.breadcrumbs{min-width:0;max-width:calc(100% - 50px);color:var(--surfaceTxtHintColor);align-items:center;gap:30px;font-size:1.286rem;font-weight:400;display:inline-flex}.breadcrumbs>div,.breadcrumbs>span,.breadcrumbs>a,.breadcrumbs>button{position:relative}:is(.breadcrumbs>div,.breadcrumbs>span,.breadcrumbs>a,.breadcrumbs>button):after{content:"/";vertical-align:top;cursor:default;-webkit-user-select:none;user-select:none;pointer-events:none;height:100%;color:var(--surfaceTxtDisabledColor);align-content:center;font-size:.85em;line-height:1;display:inline-block;position:absolute;top:0;right:-18px}:is(.breadcrumbs>div,.breadcrumbs>span,.breadcrumbs>a,.breadcrumbs>button):last-child{color:var(--surfaceTxtColor);white-space:nowrap;text-overflow:ellipsis;flex-shrink:1;min-width:0;max-width:400px;position:relative;overflow:hidden}:is(.breadcrumbs>div,.breadcrumbs>span,.breadcrumbs>a,.breadcrumbs>button):last-child:after{content:none;display:none}@media (width<=800px){.breadcrumbs>div,.breadcrumbs>span,.breadcrumbs>a,.breadcrumbs>button{display:none}:is(.breadcrumbs>div,.breadcrumbs>span,.breadcrumbs>a,.breadcrumbs>button):last-child{display:block}}.breadcrumbs a{cursor:pointer;color:var(--surfaceTxtHintColor);transition:color var(--animationSpeed);outline:0;text-decoration:none}.breadcrumbs a:hover,.breadcrumbs a:focus-visible{color:var(--surfaceTxtColor)}.copy-to-clipboard{cursor:pointer;-webkit-user-select:none;user-select:none;vertical-align:middle;color:var(--surfaceTxtHintColor);transition:color var(--animationSpeed);outline:0;align-items:center;gap:5px;line-height:1;text-decoration:none;display:inline-flex}.copy-to-clipboard .copy-icon{font-size:var(--smFontSize)}.copy-to-clipboard:hover,.copy-to-clipboard:active{color:var(--surfaceTxtColor)}.copy-to-clipboard.active{color:var(--successColor)}.label .copy-to-clipboard{align-self:stretch;margin:-5px;padding:5px}.thumb{--thumbSize:var(--btnHeight);width:var(--thumbSize);aspect-ratio:1;background:var(--surfaceAlt1Color);border-radius:var(--borderRadius);text-align:center;color:var(--surfaceTxtHintColor);font-size:var(--fontSize);line-height:var(--lineHeight);transition:border var(--animationSpeed), opacity var(--animationSpeed);border:1px solid var(--surfaceAlt3Color);outline:0;flex-shrink:0;justify-content:center;align-items:center;text-decoration:none;display:inline-flex;overflow:hidden}.thumb img{width:100%;max-width:100%;height:auto;transition:opacity var(--animationSpeed)}.thumb.loading{background:linear-gradient(90deg, var(--surfaceAlt1Color) 0%, var(--surfaceColor) 25%, var(--surfaceAlt1Color) 50%);background-size:200% 100%;animation:.85s linear .2s infinite shine}.thumb.loading img{opacity:0}.thumb.lg{--thumbSize:80px;font-size:var(--lgFontSize)}.thumb.sm{--thumbSize:var(--smBtnHeight);font-size:var(--smFontSize);line-height:var(--smLineHeight)}.thumb.info{border:1px solid var(--infoColor)}.thumb.success{border:1px solid var(--successColor)}.thumb.warning{border:1px solid var(--warningColor)}.thumb.danger{border:1px solid var(--dangerColor)}.hanlde.thumb,button.thumb,a.thumb{cursor:pointer}:is(.hanlde.thumb,button.thumb,a.thumb):hover,:is(.hanlde.thumb,button.thumb,a.thumb):active,:is(.hanlde.thumb,button.thumb,a.thumb):focus-visible{opacity:.8}:is(.hanlde.thumb,button.thumb,a.thumb):active{opacity:.6;transition-duration:var(--activeAnimationSpeed)}.animate-fadeIn{animation:fadeIn var(--animationSpeed)}.animate-delayed-fadeIn{opacity:0;animation:fadeIn var(--animationSpeed) var(--animationSpeed) forwards}.formatted-date .missing-value,.formatted-date .primary-date,.formatted-date .secondary-date{width:100%;display:block}.formatted-date .secondary-date{color:var(--surfaceTxtHintColor);font-size:var(--smFontSize);line-height:var(--smLineHeight);margin-top:3px}.formatted-date .missing-value{text-align:left}.formatted-date.short{text-align:center;white-space:nowrap}.formatted-date.short .secondary-date{margin-top:0;font-size:.87em}.section-heading{color:var(--surfaceTxtHintColor);margin:0 0 var(--smSpacing);align-items:center;gap:10px;display:flex}.section-heading .field{width:auto}.main-logo{max-height:40px}.dragline{z-index:9;cursor:ew-resize;box-sizing:content-box;-webkit-user-select:none;user-select:none;width:2px;height:100%;transition:background var(--animationSpeed);background:0 0;flex-shrink:0;margin:0;padding:0;position:relative}.dragline:hover,.dragline:active,.dragline.dragging{z-index:101;background:var(--surfaceAlt3Color)}.pb-tooltip{z-index:99999;text-align:center;white-space:pre-line;word-break:break-word;background:var(--tooltipSurfaceColor);border-radius:var(--borderRadius);min-width:30px;max-width:300px;max-height:90%;color:var(--tooltipTxtColor);font-weight:400;font-size:var(--smFontSize);line-height:var(--smLineHeight);font-family:var(--baseFontFamily);scrollbar-width:thin;pointer-events:none;opacity:0;transition:opacity var(--animationSpeed), display var(--animationSpeed) allow-discrete;padding:3px 5px;position:fixed}.pb-tooltip:popover-open{opacity:1;display:block}@starting-style{.pb-tooltip:popover-open{opacity:0}}.pb-tooltip[data-style=code]{text-align:left;white-space:pre-wrap;max-width:350px;font-family:var(--monospaceFontFamily)}@keyframes toastEntrance{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}@keyframes toastContentEntrance{0%{overflow:hidden}}.toast{--toastWidth:420px;--toastSurfaceColor:var(--surfaceInfoColor);--toastSurfaceTxtColor:var(--surfaceTxtColor);--toastAccentColor:var(--infoColor);width:var(--toastWidth);max-width:95%;margin:0 0 var(--smSpacing);animation:toastEntrance var(--animationSpeed);grid-template-rows:1fr;display:grid;position:sticky;top:0}.toast .toast-container{border-radius:var(--borderRadius);width:100%;line-height:var(--lineHeight);color:var(--toastSurfaceTxtColor);background:var(--toastSurfaceColor);box-shadow:var(--boxShadow);animation:toastContentEntrance var(--animationSpeed);align-items:stretch;display:flex}.toast .toast-content{text-align:left;word-break:break-word;width:100%;min-height:var(--lgBtnHeight);padding:max(5px, calc(var(--inputPadding) - 5px)) var(--inputPadding);align-items:center;gap:10px;display:flex}.toast .toast-icon{min-height:0;color:var(--toastAccentColor);border-top-left-radius:var(--borderRadius);border-bottom-left-radius:var(--borderRadius);border-right:1px solid color-mix(in srgb, var(--toastAccentColor), transparent 90%);align-content:center;margin-left:-1px;padding:5px 10px;font-size:1.2em}[data-color-scheme=dark] :is(.toast .toast-icon){border-color:color-mix(in srgb, var(--toastAccentColor), transparent 75%)}.toast .toast-icon:before{content:"";font-family:var(--iconFontFamily)}.toast .toast-remove{margin:-5px -10px -5px 0}.toast.success{--toastSurfaceColor:var(--surfaceSuccessColor);--toastAccentColor:var(--successColor)}.toast.success .toast-icon:before{content:""}.toast.error{--toastSurfaceColor:var(--surfaceDangerColor);--toastAccentColor:var(--dangerColor)}.toast.error .toast-icon:before{content:""}.toast.removing{transition:opacity var(--animationSpeed), margin var(--animationSpeed), grid-template-rows var(--animationSpeed);opacity:0;grid-template-rows:0fr;margin:0}.toast.removing .toast-container{overflow:hidden}.toasts-container{z-index:9999;bottom:var(--spacing);width:100%;display:block;position:fixed;left:0;pointer-events:none!important}.toasts-container .toast{pointer-events:auto;margin-left:auto;margin-right:auto}.list{--borderColor:var(--surfaceAlt3Color);width:100%;color:var(--surfaceTxtColor);background:var(--surfaceColor);border:1px solid var(--borderColor);border-radius:var(--borderRadius);scrollbar-width:thin;display:block;position:relative;overflow:auto}.list:empty{display:none}.list .list-content{scrollbar-width:thin;width:100%;max-height:425px;display:block;overflow:auto}.list .list-item{word-break:break-word;width:100%;padding:10px var(--inputPadding);min-height:54px;box-shadow:0px -1px 0 0 var(--borderColor);transition:background var(--animationSpeed);outline:0;align-items:center;gap:10px;display:flex;position:relative}.list .list-item .content{-webkit-user-select:text;user-select:text;align-items:center;gap:5px;width:100%;min-width:0;max-width:100%;line-height:1.5;display:flex}.list .list-item .actions{flex-shrink:0;align-items:center;gap:10px;margin:-1px -5px -1px 0;display:inline-flex}.list .list-item .actions.autohide{opacity:0;transition:transform var(--animationSpeed), opacity var(--animationSpeed), visibility var(--animationSpeed);transform:translate(5px)}@media only screen and (hover:none){.list .list-item .actions.autohide{opacity:1;transform:translate(0)}}.list .list-item>.btn.block{border-radius:0;width:100%}.list .list-item>.btn.block.sm{border-radius:var(--borderRadius)}.list .list-item:has(>.btn.block){min-height:0;padding:0}.list .list-item:has(>.btn.sm.block){padding:5px}:is(.list .list-item:hover,.list .list-item:focus-visible,.list .list-item:focus-within,.list .list-item:active) .actions.autohide{opacity:1;transform:translate(0)}.list .list-item[data-dragstart=true],.list .list-item.active{background:var(--surfaceAlt1Color)}.list .list-item[data-dragstart=true] .actions.autohide{opacity:0}:is(.list .list-item:not(.disabled).highlight,.list .list-item:not(.disabled).handle):hover,:is(.list .list-item:not(.disabled).highlight,.list .list-item:not(.disabled).handle):focus-visible{background:var(--surfaceAlt1Color)}:is(.list .list-item:not(.disabled).highlight,.list .list-item:not(.disabled).handle):active{background:var(--surfaceAlt2Color);transition-duration:var(--activeAnimationSpeed)}.list .list-item.handle{cursor:pointer;-webkit-user-select:none;user-select:none}.list .list-item.deleted{cursor:default}.list .list-item.deleted .content{opacity:.6}.list .list-item.deleted .content .txt{text-decoration:line-through}.list .list-item.disabled:not(.active){cursor:default;opacity:.6}.list.sm .list-item{min-height:var(--btnHeight);padding:5px 10px}.grid{--gridGap:var(--spacing);row-gap:var(--gridGap);margin:0 calc(-.5 * var(--gridGap));flex-wrap:wrap;flex-grow:1;display:flex;position:relative;container-type:inline-size}.grid.center{align-items:center}.grid.sm{--gridGap:var(--smSpacing)}.grid>[class^=col-]{width:100%;min-width:0;min-height:1px;margin:0 calc(.5 * var(--gridGap));position:relative}.grid>[class^=col-]>hr:first-child{margin-top:0}.grid>[class^=col-]>hr:last-child{margin-bottom:0}.grid .col-12{width:calc(100% - var(--gridGap))}.grid .col-11{width:calc(91.6667% - var(--gridGap))}.grid .col-10{width:calc(83.3333% - var(--gridGap))}.grid .col-9{width:calc(75% - var(--gridGap))}.grid .col-8{width:calc(66.6667% - var(--gridGap))}.grid .col-7{width:calc(58.3333% - var(--gridGap))}.grid .col-6{width:calc(50% - var(--gridGap))}.grid .col-5{width:calc(41.6667% - var(--gridGap))}.grid .col-4{width:calc(33.3333% - var(--gridGap))}.grid .col-3{width:calc(25% - var(--gridGap))}.grid .col-2{width:calc(16.6667% - var(--gridGap))}.grid .col-1{width:calc(8.33333% - var(--gridGap))}@container (width>=380px){.grid .col-sm-12{width:calc(100% - var(--gridGap))}.grid .col-sm-11{width:calc(91.6667% - var(--gridGap))}.grid .col-sm-10{width:calc(83.3333% - var(--gridGap))}.grid .col-sm-9{width:calc(75% - var(--gridGap))}.grid .col-sm-8{width:calc(66.6667% - var(--gridGap))}.grid .col-sm-7{width:calc(58.3333% - var(--gridGap))}.grid .col-sm-6{width:calc(50% - var(--gridGap))}.grid .col-sm-5{width:calc(41.6667% - var(--gridGap))}.grid .col-sm-4{width:calc(33.3333% - var(--gridGap))}.grid .col-sm-3{width:calc(25% - var(--gridGap))}.grid .col-sm-2{width:calc(16.6667% - var(--gridGap))}.grid .col-sm-1{width:calc(8.33333% - var(--gridGap))}}@container (width>=550px){.grid .col-md-12{width:calc(100% - var(--gridGap))}.grid .col-md-11{width:calc(91.6667% - var(--gridGap))}.grid .col-md-10{width:calc(83.3333% - var(--gridGap))}.grid .col-md-9{width:calc(75% - var(--gridGap))}.grid .col-md-8{width:calc(66.6667% - var(--gridGap))}.grid .col-md-7{width:calc(58.3333% - var(--gridGap))}.grid .col-md-6{width:calc(50% - var(--gridGap))}.grid .col-md-5{width:calc(41.6667% - var(--gridGap))}.grid .col-md-4{width:calc(33.3333% - var(--gridGap))}.grid .col-md-3{width:calc(25% - var(--gridGap))}.grid .col-md-2{width:calc(16.6667% - var(--gridGap))}.grid .col-md-1{width:calc(8.33333% - var(--gridGap))}}@container (width>=650px){.grid .col-lg-12{width:calc(100% - var(--gridGap))}.grid .col-lg-11{width:calc(91.6667% - var(--gridGap))}.grid .col-lg-10{width:calc(83.3333% - var(--gridGap))}.grid .col-lg-9{width:calc(75% - var(--gridGap))}.grid .col-lg-8{width:calc(66.6667% - var(--gridGap))}.grid .col-lg-7{width:calc(58.3333% - var(--gridGap))}.grid .col-lg-6{width:calc(50% - var(--gridGap))}.grid .col-lg-5{width:calc(41.6667% - var(--gridGap))}.grid .col-lg-4{width:calc(33.3333% - var(--gridGap))}.grid .col-lg-3{width:calc(25% - var(--gridGap))}.grid .col-lg-2{width:calc(16.6667% - var(--gridGap))}.grid .col-lg-1{width:calc(8.33333% - var(--gridGap))}}table{--tableSpacing:var(--inputPadding);border-collapse:separate;border-spacing:0;width:100%}table td,table th{padding:10px max(var(--tableSpacing), 10px);vertical-align:middle;text-align:left;height:var(--btnHeight);background:var(--surfaceColor);border:1px solid var(--surfaceAlt2Color);border-bottom-width:0}:is(table td,table th):not(:first-child){border-left:0}:is(table td,table th):not(:last-child){border-right:0}@media (width<=600px){table td,table th{padding:5px}}table>:first-child>tr:first-child>:first-child{border-top-left-radius:var(--borderRadius)}table>:first-child>tr:first-child>:last-child{border-top-right-radius:var(--borderRadius)}table>:last-child>tr:last-child>:first-child{border-bottom-left-radius:var(--borderRadius)}table>:last-child>tr:last-child>:last-child{border-bottom-right-radius:var(--borderRadius)}table>:last-child>tr:last-child>td,table>:last-child>tr:last-child>th{border-width:1px}table th{color:var(--surfaceTxtHintColor);font-size:.98em;font-weight:700;line-height:1.4}table th.sort-handle{cursor:pointer;-webkit-user-select:none;user-select:none;outline:0;padding-right:40px;position:relative}:is(table th.sort-handle.asc,table th.sort-handle.desc):before{content:"";right:var(--tableSpacing);color:inherit;opacity:.5;font-weight:400;font-family:var(--iconFontFamily);transition:opacity var(--animationSpeed);display:block;position:absolute;top:50%;transform:translateY(-50%)}table th.sort-handle.desc:before{content:""}table th.sort-handle:hover,table th.sort-handle:focus-visible{background:var(--surfaceAlt1Color)}:is(table th.sort-handle:hover,table th.sort-handle:focus-visible):before{opacity:1}table th.sort-handle:active{background:var(--surfaceAlt2Color)}:is(table td,table th).min-width{white-space:nowrap;width:1%!important;min-width:0!important}:is(table td,table th).col-bulk-select,:is(table td,table th).col-meta{z-index:2;word-break:normal;white-space:nowrap;position:sticky}:is(table td,table th).col-bulk-select{width:1%;min-width:62px;padding-right:5px;left:0}:is(table td,table th).col-bulk-select .field{width:auto}:is(table td,table th).col-bulk-select .loader{margin-left:-2px;position:absolute;top:10px}:is(table td,table th).col-bulk-select:after{content:"";opacity:0;pointer-events:none;width:1px;height:100%;box-shadow:var(--leftScrollShadow);display:block;position:absolute;top:0;right:0}@container scroll-state(scrollable:left){:is(table td,table th).col-bulk-select:after{opacity:1;transition:opacity var(--animationSpeed)}}@media (width<=600px){:is(table td,table th).col-bulk-select{min-width:50px;padding-right:0}}:is(table td,table th).col-meta{text-align:right;width:1%;padding-top:4px;padding-bottom:5px;padding-left:10px;right:0}:is(table td,table th).col-meta:before{content:"";opacity:0;pointer-events:none;width:1px;height:100%;box-shadow:var(--rightScrollShadow);display:block;position:absolute;top:0;left:0}@container scroll-state(scrollable:right){:is(table td,table th).col-meta:before{opacity:1;transition:opacity var(--animationSpeed)}}:is(table td,table th).col-meta>i{vertical-align:top;color:var(--surfaceTxtDisabledColor);transition:color var(--animationSpeed), transform var(--animationSpeed);will-change:transform, color;display:inline-block}@media (width<=600px){:is(table td,table th).col-meta{padding-left:5px}}:is(table td,table th).col-field-type-date,:is(table td,table th).col-field-type-autodate,:is(table td,table th).col-field-type-geopoint{white-space:nowrap;width:1%}:is(table td,table th).col-field-type-relation{min-width:190px;max-width:500px}:is(table td,table th).col-field-type-text{max-width:300px}:is(table td,table th).col-field-type-editor{min-width:400px}:is(table td,table th).col-field-type-select{min-width:180px}:is(table td,table th).col-field-type-url{max-width:300px}:is(table td,table th).col-field-type-email{white-space:nowrap;min-width:120px}:is(table td,table th).col-field-type-number{white-space:nowrap}:is(table td,table th).col-field-type-file{min-width:120px}:is(table td,table th).col-field-type-json{max-width:300px}:is(table td,table th).col-field-name-id{white-space:nowrap;width:190px}table .col-copy{padding-left:0}table .col-copy .copy-to-clipboard{opacity:0;transition:color var(--animationSpeed), opacity var(--animationSpeed), transform var(--animationSpeed);transform:translate(3px)}table tr:hover .col-copy .copy-to-clipboard{opacity:1;transform:translate(0)}table thead th{background:var(--surfaceAlt1Color)}table thead.sticky{z-index:3;position:sticky;top:0}table thead.sticky:after{content:"";z-index:-1;opacity:0;pointer-events:none;width:100%;height:1px;box-shadow:var(--topScrollShadow);display:block;position:absolute;bottom:0;left:0}@container scroll-state(scrollable:top){table thead.sticky:after{opacity:1;transition:opacity var(--animationSpeed)}}table tr.handle{cursor:pointer;outline:0}table tr.handle td,table tr.handle th{transition:background var(--animationSpeed);will-change:background}:is(table tr.handle:hover,table tr.handle:focus-within) td,:is(table tr.handle:hover,table tr.handle:focus-within) th{background:var(--surfaceAlt1Color)}:is(table tr.handle:hover,table tr.handle:focus-within) .col-meta>i{color:var(--surfaceTxtColor);transform:translate(3px)}table tr.handle.active td,table tr.handle.active th{background:var(--surfaceAlt2Color)}table.optimize td,table.optimize th,table.optimize .col-bulk-select label:before,table.optimize .col-bulk-select label:after,table.optimize .col-meta i{transition:none!important}table.optimize .col-meta i{transform:none!important}.page-table-wrapper{width:auto;min-height:130px;margin-left:calc(-1 * var(--spacing));margin-right:calc(-1 * var(--spacing));container-type:inline-size scroll-state;display:block;overflow:auto}.page-table-wrapper .label{word-break:normal}.page-table-wrapper .load-more-btn,.page-table-wrapper .sticky-content{z-index:3;width:100%;left:var(--spacing);right:var(--spacing);max-width:calc(100cqw - (2 * var(--spacing)));position:sticky}.page-table-wrapper table{border-radius:0;min-width:900px}.page-table-wrapper table>:first-child>tr:first-child>:first-child{border-top-left-radius:0}.page-table-wrapper table>:first-child>tr:first-child>:last-child{border-top-right-radius:0}.page-table-wrapper table>:last-child>tr:last-child>:first-child{border-bottom-left-radius:0}.page-table-wrapper table>:last-child>tr:last-child>:last-child{border-bottom-right-radius:0}.page-table-wrapper td,.page-table-wrapper th{border:0;border-bottom:1px solid var(--surfaceAlt2Color);border-radius:0}:is(.page-table-wrapper td,.page-table-wrapper th):first-child{padding-left:var(--spacing)}:is(.page-table-wrapper td,.page-table-wrapper th):last-child{padding-right:var(--spacing)}.page-table-wrapper th{background:var(--surfaceColor)}.page-table-wrapper th.sort-handle{border-radius:var(--borderRadius) var(--borderRadius) 0 0}.page-table-wrapper tbody td,.page-table-wrapper tbody th{border-radius:0;height:59px}.page-table-wrapper:before,.page-table-wrapper:after{content:"";z-index:9;opacity:0;pointer-events:none;width:100%;height:1px;display:block;position:sticky;left:0}.page-table-wrapper:before{box-shadow:var(--topScrollShadow);top:0}@container scroll-state(scrollable:top){.page-table-wrapper:before{opacity:1;transition:opacity var(--animationSpeed)}}.page-table-wrapper:after{box-shadow:var(--bottomScrollShadow);margin-top:-1px;bottom:0}@container scroll-state(scrollable:bottom){.page-table-wrapper:after{opacity:1;transition:opacity var(--animationSpeed)}}.page-table-wrapper:has(thead.sticky):before{content:none;display:none}@media (width<=600px){table.responsive-table{border-collapse:collapse;table-layout:fixed;min-width:0}table.responsive-table tr{border:1px solid var(--surfaceAlt2Color);flex-flow:wrap;gap:5px;width:100%;padding:10px;display:flex}table.responsive-table tr~tr{border-top:0}table.responsive-table .col-copy{display:none}table.responsive-table td,table.responsive-table th{word-break:break-word;margin:0;display:block;white-space:normal!important;background:0 0!important;border:0!important;width:100%!important;min-width:0!important;max-width:none!important;height:auto!important;min-height:0!important;padding:0!important}:is(table.responsive-table td,table.responsive-table th):before{content:attr(data-name);width:100%;color:var(--surfaceTxtHintColor);font-weight:700;font-size:var(--smFontSize);line-height:var(--lineHeight);display:block}:is(table.responsive-table td,table.responsive-table th).col-bulk-select{align-content:center;margin-top:0;padding:0}:is(table.responsive-table td,table.responsive-table th).col-meta{display:none}:is(:is(table.responsive-table td,table.responsive-table th).col-bulk-select,:is(table.responsive-table td,table.responsive-table th).col-meta):before,:is(:is(table.responsive-table td,table.responsive-table th).col-bulk-select,:is(table.responsive-table td,table.responsive-table th).col-meta):after{display:none}:is(table.responsive-table td,table.responsive-table th) .formatted-date.short{align-items:center;gap:5px;display:inline-flex}:is(table.responsive-table td,table.responsive-table th) .formatted-date.short .secondary-date,:is(table.responsive-table td,table.responsive-table th) .formatted-date.short .primary-date{width:auto;margin:0}:is(table.responsive-table td,table.responsive-table th) .record-field-view{text-align:left}table.responsive-table td.col-bulk-select{width:auto!important}table.responsive-table td.col-field-name-id{flex-grow:1;width:auto!important}table.responsive-table td.col-field-name-id:before{display:none}table.responsive-table td.col-field-name-id .label{width:100%;font-weight:700}table.responsive-table thead tr{background:var(--surfaceColor);border:0;flex-direction:row;padding-top:0}table.responsive-table thead th{display:none}table.responsive-table thead th.col-meta,table.responsive-table thead th.col-bulk-select{margin:0;display:inline-block;width:auto!important}table.responsive-table thead th.col-meta{margin-left:auto;padding:0}table.responsive-table .label{word-break:break-word}.page-table-wrapper .responsive-table{border:0}.page-table-wrapper .responsive-table tr{padding-left:var(--spacing);padding-right:var(--spacing);border-left:0;border-right:0}}button{color:inherit;text-align:left;font-family:inherit;font-size:inherit;font-weight:inherit;line-height:inherit;letter-spacing:inherit;background:0 0;border:0;outline:0;padding:0}.btn{--btnGap:8px;cursor:pointer;justify-content:center;align-items:center;gap:var(--btnGap);-webkit-user-select:none;user-select:none;min-height:var(--btnHeight);text-align:center;min-width:0;max-width:100%;color:var(--primaryTxtColor);font-weight:700;line-height:1;font-size:var(--fontSize);font-family:var(--baseFontFamily);border-radius:var(--borderRadius);background:var(--primaryColor);transition:color var(--animationSpeed), border-color var(--animationSpeed), background var(--animationSpeed);border:0;outline:0;flex-shrink:0;margin:0;padding:5px 25px;text-decoration:none;display:inline-flex;position:relative}.btn:hover,.btn:focus-visible{background:var(--primaryAlt1Color)}.btn[data-popover-state=true],.btn:active{background:var(--primaryAlt2Color);transition-duration:var(--activeAnimationSpeed)}.btn i{font-size:1.25em}.btn.secondary{color:var(--secondaryTxtColor);background:var(--secondaryColor)}.btn.secondary:hover,.btn.secondary:focus-visible{background:var(--secondaryAlt1Color)}.btn.secondary[data-popover-state=true],.btn.secondary:active{background:var(--secondaryAlt2Color)}.btn.info{color:var(--infoTxtColor);background:var(--infoColor)}.btn.info:hover,.btn.info:focus-visible{background:var(--infoAlt1Color)}.btn.info[data-popover-state=true],.btn.info:active{background:var(--infoAlt2Color)}.btn.success{color:var(--successTxtColor);background:var(--successColor)}.btn.success:hover,.btn.success:focus-visible{background:var(--successAlt1Color)}.btn.success[data-popover-state=true],.btn.success:active{background:var(--successAlt2Color)}.btn.warning{color:var(--warningTxtColor);background:var(--warningColor)}.btn.warning:hover,.btn.warning:focus-visible{background:var(--warningAlt1Color)}.btn.warning[data-popover-state=true],.btn.warning:active{background:var(--warningAlt2Color)}.btn.danger{color:var(--dangerTxtColor);background:var(--dangerColor)}.btn.danger:hover,.btn.danger:focus-visible{background:var(--dangerAlt1Color)}.btn.danger[data-popover-state=true],.btn.danger:active{background:var(--dangerAlt2Color)}.btn.outline{border:2px solid}.btn.outline,.btn.transparent{color:var(--surfaceTxtColor);background:var(--surfaceColor)}[data-color-scheme=dark] :is(.btn.outline,.btn.transparent){border-color:var(--surfaceAlt3Color)}[data-color-scheme=dark] :is(.btn.outline,.btn.transparent):hover,[data-color-scheme=dark] :is(.btn.outline,.btn.transparent):focus-visible{background:var(--surfaceAlt4Color)}[data-color-scheme=dark] :is(.btn.outline,.btn.transparent)[data-popover-state=true],[data-color-scheme=dark] :is(.btn.outline,.btn.transparent):active{background:var(--surfaceAlt5Color)}:is(.btn.outline,.btn.transparent):hover,:is(.btn.outline,.btn.transparent):focus-visible{background:var(--surfaceAlt1Color)}:is(.btn.outline,.btn.transparent)[data-popover-state=true],:is(.btn.outline,.btn.transparent):active{background:var(--surfaceAlt2Color)}:is(.btn.outline,.btn.transparent).secondary{color:var(--surfaceTxtHintColor)}:is(.btn.outline,.btn.transparent).info{color:var(--infoColor)}:is(.btn.outline,.btn.transparent).success{color:var(--successColor)}:is(.btn.outline,.btn.transparent).warning{color:var(--warningColor)}:is(.btn.outline,.btn.transparent).danger{color:var(--dangerColor)}:is(:is(.btn.outline,.btn.transparent).secondary,:is(.btn.outline,.btn.transparent).info,:is(.btn.outline,.btn.transparent).success,:is(.btn.outline,.btn.transparent).warning,:is(.btn.outline,.btn.transparent).danger):hover,:is(:is(.btn.outline,.btn.transparent).secondary,:is(.btn.outline,.btn.transparent).info,:is(.btn.outline,.btn.transparent).success,:is(.btn.outline,.btn.transparent).warning,:is(.btn.outline,.btn.transparent).danger):focus-visible{background:color-mix(in srgb, currentColor, transparent 92%)}:is(:is(.btn.outline,.btn.transparent).secondary,:is(.btn.outline,.btn.transparent).info,:is(.btn.outline,.btn.transparent).success,:is(.btn.outline,.btn.transparent).warning,:is(.btn.outline,.btn.transparent).danger)[data-popover-state=true],:is(:is(.btn.outline,.btn.transparent).secondary,:is(.btn.outline,.btn.transparent).info,:is(.btn.outline,.btn.transparent).success,:is(.btn.outline,.btn.transparent).warning,:is(.btn.outline,.btn.transparent).danger):active{background:color-mix(in srgb, currentColor, transparent 85%)}.btn.transparent{background:0 0}.btn.transparent:hover,.btn.transparent:focus-visible{background:var(--surfaceAlt2Color)}.btn.transparent[data-popover-state=true],.btn.transparent:active{background:var(--surfaceAlt4Color)}.btn.pill{border-radius:30px}.btn.expanded{min-width:130px}.btn.expanded-sm{min-width:100px}.btn.expanded-lg{min-width:160px}.btn.sm{--btnGap:5px;min-height:var(--smBtnHeight);font-size:var(--smFontSize);line-height:var(--smLineHeight);padding-left:12px;padding-right:12px}.btn.sm i{font-size:1.15em}.btn.sm.expanded{min-width:110px}.btn.sm.expanded-sm{min-width:85px}.btn.sm.expanded-lg{min-width:130px}.btn.lg{min-height:var(--lgBtnHeight);font-size:var(--lgFontSize)}.btn.lg i{font-size:1.4em}.btn.lg.expanded{min-width:150px}.btn.lg.expanded-sm{min-width:120px}.btn.lg.expanded-lg{min-width:190px}.btn.circle{--circleSize:var(--btnHeight);min-height:0;width:var(--circleSize);height:var(--circleSize);text-align:center;border-radius:50%;flex-shrink:0;align-content:center;padding:0!important}.btn.circle i{vertical-align:top;display:inline-block}.btn.circle i:before{vertical-align:top;line-height:26px;display:inline-block}.btn.circle.sm{--circleSize:var(--smBtnHeight)}.btn.circle.lg{--circleSize:var(--lgBtnHeight)}.btn.next i{will-change:transform;transition:transform var(--animationSpeed)}:is(.btn.next:hover,.btn.next:active,.btn.next:focus-visible) i{transform:translate(3px)}:is(.btn[data-rotate=true],.btn.rotate) i{animation:.3s halfRotate}.btn[data-loading=true],.btn.loading{cursor:default}:is(.btn[data-loading=true],.btn.loading):after{--size:24px;content:var(--loaderIcon);width:var(--size);height:var(--size);line-height:var(--size);margin-top:calc(var(--size) / -2);margin-left:calc(var(--size) / -2);color:inherit;text-align:center;font-size:1.5em;font-weight:400;font-family:var(--iconFontFamily);will-change:transform;animation:fadeIn var(--animationSpeed), rotate var(--loaderAnimationSpeed) infinite linear;flex-shrink:0;padding:0;display:block;position:absolute;top:50%;left:50%}:is(.btn[data-loading=true],.btn.loading)>*{opacity:0}.btn.disabled,.btn[disabled]{cursor:default;color:var(--surfaceTxtDisabledColor)!important;background:var(--surfaceAlt2Color)!important}:is(.btn.disabled,.btn[disabled]).outline,:is(.btn.disabled,.btn[disabled]).transparent{color:var(--surfaceTxtDisabledColor);background:0 0!important}:is(.btn.disabled,.btn[disabled]).outline{border-color:var(--surfaceAlt2Color)}.btns{align-items:stretch;min-width:0;display:inline-flex}.btns>.btn~.btn{border-left:1px solid var(--primaryAlt1Color);border-top-left-radius:0;border-bottom-left-radius:0}.btns>.btn~.btn.outline{border-left:0}.btns>.btn~.btn.secondary{border-left-color:var(--secondaryAlt1Color)}.btns>.btn~.btn.info{border-left-color:var(--infoAlt1Color)}.btns>.btn~.btn.success{border-left-color:var(--successAlt1Color)}.btns>.btn~.btn.warning{border-left-color:var(--warningAlt1Color)}.btns>.btn~.btn.danger{border-left-color:var(--dangerAlt1Color)}.btns>.btn~.btn.disabled,.btns>.btn~.btn[disabled]{border-left-color:var(--surfaceAlt3Color)}.btns>.btn:has(~.btn){border-top-right-radius:0;border-bottom-right-radius:0}.btns.full-width{width:100%}.btns.full-width>.btn{flex:1 1 0;min-width:0}.field:after{content:"";clear:both;display:table}.field input,.field textarea,.field select,.field .input{vertical-align:top;width:100%;letter-spacing:inherit;padding:10px var(--inputPadding);color:var(--surfaceTxtColor);font-weight:400;line-height:1;font-size:var(--fontSize);font-family:var(--baseFontFamily);scrollbar-width:thin;scrollbar-color:var(--surfaceAlt5Color) transparent;background:0 0;border:0;outline:0;align-self:stretch;margin:0;display:inline-block}[data-color-scheme=dark] :is(.field input,.field textarea,.field select,.field .input){--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}:is(.field input,.field textarea,.field select,.field .input)::placeholder{-webkit-user-select:none;user-select:none;color:var(--surfaceTxtDisabledColor);font-weight:inherit;font-family:inherit}:is(.field input,.field textarea,.field select,.field .input):autofill{-webkit-text-fill-color:var(--surfaceTxtColor);box-shadow:0 0 0 50px var(--inputColor) inset;transition:box-shadow var(--animationSpeed);background:0 0}:is(.field input,.field textarea,.field select,.field .input):focus,:is(.field input,.field textarea,.field select,.field .input):focus-visible,:is(.field input,.field textarea,.field select,.field .input):focus-within{outline:0}:is(:is(.field input,.field textarea,.field select,.field .input):focus,:is(.field input,.field textarea,.field select,.field .input):focus-visible,:is(.field input,.field textarea,.field select,.field .input):focus-within):autofill{box-shadow:0 0 0 50px var(--inputFocusColor) inset}:is(.field input,.field textarea,.field select,.field .input).readonly,:is(.field input,.field textarea,.field select,.field .input)[readonly]{cursor:default;color:var(--surfaceTxtHintColor)}:is(.field input,.field textarea,.field select,.field .input).disabled,:is(.field input,.field textarea,.field select,.field .input)[disabled]{cursor:default;color:var(--surfaceTxtDisabledColor)}:is(.field input,.field textarea,.field select,.field .input):only-child{height:100%}.field input[type=range]{-webkit-appearance:none;left:var(--inputPadding);width:calc(100% - 2 * var(--inputPadding));background:var(--surfaceAlt5Color);height:6px;min-height:0;accent-color:var(--surfaceTxtHintColor);margin-top:16px;margin-bottom:16px;padding:0;position:relative;border-radius:var(--borderRadius)!important}.field input[type=color]{-webkit-appearance:none;border:0;height:36px;min-height:0;margin:1px 0}.field input[type=color]::-webkit-color-swatch-wrapper{border-radius:var(--borderRadius);padding:0}.field input[type=color]::-webkit-color-swatch{border-radius:var(--borderRadius);border:none}.field input[type=color]::-moz-color-swatch{border-radius:var(--borderRadius);border:none}.field .autoexpand-wrapper>.input,.field textarea{resize:vertical;min-height:38px;max-height:300px;padding-top:8px;padding-bottom:9px;line-height:1.5}@supports (field-sizing:content){:is(.field .autoexpand-wrapper>.input,.field textarea).autoexpand{field-sizing:content;resize:none}}.field .autoexpand-wrapper{display:grid}.field .autoexpand-wrapper>textarea{resize:none}.field .autoexpand-wrapper>.input{white-space:pre-wrap;visibility:hidden;overflow:auto}.field .autoexpand-wrapper>.input,.field .autoexpand-wrapper>textarea{grid-area:1/1/2/2}.field.addon{width:auto;color:var(--surfaceTxtHintColor);flex-shrink:0;align-items:center;display:inline-flex}.field.addon:after{content:none;display:none}.field:focus-within:not(.addon){background:var(--inputFocusColor)}.field:focus-within:not(.addon) label{color:var(--surfaceTxtColor)}.field-list,.field{border-radius:var(--inputBorderRadius);background:var(--inputColor);width:100%;min-width:0;transition:background var(--animationSpeed);outline:0;display:block;position:relative}:is(.field-list,.field):not(.addon)>:first-child:not(.btn){border-top-left-radius:inherit;border-top-right-radius:inherit}:is(.field-list,.field):not(.addon)>:last-child:not(.btn){border-bottom-left-radius:inherit;border-bottom-right-radius:inherit}:is(.field-list,.field) label{width:100%;min-height:24px;padding:9px var(--inputPadding) 1px;white-space:normal;color:var(--surfaceTxtHintColor);font-weight:700;line-height:1;font-size:var(--smFontSize);transition:color var(--animationSpeed);align-self:center;align-items:center;gap:5px;display:flex}:is(.field-list,.field) label i{padding:0;font-size:1em}:is(.field-list,.field) label:has(~.autoexpand-wrapper textarea[required]):after{vertical-align:top;content:"*";color:var(--dangerColor);margin:-5px 0 0 -2px;font-size:.75em;line-height:1}:is(.field-list,.field) label:has(~[required]):after{vertical-align:top;content:"*";color:var(--dangerColor);margin:-5px 0 0 -2px;font-size:.75em;line-height:1}:is(.field-list,.field) label:has(~.input.required):after{vertical-align:top;content:"*";color:var(--dangerColor);margin:-5px 0 0 -2px;font-size:.75em;line-height:1}:is(:is(.field-list,.field).required label,:is(.field-list,.field) label.required,:is(.field-list,.field) .required>label):after{vertical-align:top;content:"*";color:var(--dangerColor);margin:-5px 0 0 -2px;font-size:.75em;line-height:1}:is(.field-list,.field).disabled label,:is(.field-list,.field) label.disabled,:is(.field-list,.field) .disabled>label{color:var(--surfaceTxtDisabledColor)}:is(.field-list,.field) label:has(~.autoexpand-wrapper textarea[disabled]){color:var(--surfaceTxtDisabledColor)}:is(.field-list,.field) label:has(~.autoexpand-wrapper textarea[disabled]):after{color:inherit}:is(.field-list,.field) label:has(~[disabled]){color:var(--surfaceTxtDisabledColor)}:is(.field-list,.field) label:has(~[disabled]):after{color:inherit}:is(.field-list,.field) label:has(~.input.disabled){color:var(--surfaceTxtDisabledColor)}:is(.field-list,.field) label:has(~.input.disabled):after{color:inherit}:is(:is(.field-list,.field).disabled label,:is(.field-list,.field) label.disabled,:is(.field-list,.field) .disabled>label):after{color:inherit}:is(.field-list,.field).addon label{padding:0}:is(.field-list,.field) hr{background:var(--inputBorderColor);height:1px}:is(.field-list,.field).error,.error :is(.field-list,.field){color:var(--dangerColor)}:is(:is(.field-list,.field).error,.error :is(.field-list,.field)) label,:is(:is(.field-list,.field).error,.error :is(.field-list,.field)) input,:is(:is(.field-list,.field).error,.error :is(.field-list,.field)) textarea,:is(:is(.field-list,.field).error,.error :is(.field-list,.field)) select,:is(:is(.field-list,.field).error,.error :is(.field-list,.field)) .input{color:var(--dangerColor)!important}:is(.field-list,.field).error,.error :is(.field-list,.field){--inputColor:color-mix(in srgb, var(--dangerColor), var(--surfaceColor) 80%);--inputFocusColor:color-mix(in srgb, var(--dangerColor), var(--surfaceColor) 75%);--inputBorderColor:color-mix(in srgb, var(--dangerColor), var(--surfaceColor) 70%)}.field-content{flex-direction:column;gap:3px;margin-top:4px;padding:3px;display:flex;position:relative}.field-content .list{border:1px solid var(--inputBorderColor);background:0 0;border-left:0;border-right:0;border-radius:0;gap:0;width:auto;max-height:300px;margin:0 -3px}.field-content .list .list-item{box-shadow:none;border-top:1px solid var(--inputBorderColor);margin:0}.field-content .list .list-item:first-child{border-top:0}.field-content .list .list-item:not(.disabled)[data-dragstart=true],.field-content .list .list-item:not(.disabled).active,.field-content .list .list-item:not(.disabled).handle:hover,.field-content .list .list-item:not(.disabled).handle:active,.field-content .list .list-item:not(.disabled).highlight:hover,.field-content .list .list-item:not(.disabled).highlight:active{background:var(--inputFocusColor)}.field-list .field-list-content{scrollbar-width:thin;max-height:500px;color:var(--surfaceTxtColor);overflow:auto}.field-list label+.field-list-content{margin-top:8px}.field-list .field-list-item{width:100%;padding:var(--inputPadding);min-height:var(--btnHeight);border-top:1px solid var(--inputBorderColor);align-items:center;gap:10px;display:flex}.field-list .field-list-item.p-0:first-child>.field,.field-list .field-list-item.p-0:first-child>.fields .field{border-radius:0}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])){--checkboxSize:20px;--checkboxActiveColor:var(--successColor);--checkboxBorderColor:var(--surfaceAlt3Color);--checkboxBorderFocusColor:var(--surfaceAlt5Color);text-align:left;background:0 0;align-items:center;gap:10px;padding:0;display:flex}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])):focus-visible,:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])):focus-within{background:0 0}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])):after{content:none;display:none}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input{opacity:0;width:1px;height:1px;position:absolute}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) label{padding:0 0 0 calc(var(--checkboxSize) + 7px);-webkit-user-select:none;user-select:none;cursor:pointer;width:auto;color:var(--surfaceTxtColor);font-weight:400;font-size:var(--fontSize);line-height:var(--smLineHeight);min-height:var(--checkboxSize);background:0 0;margin:0}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) label:before{content:"";width:var(--checkboxSize);height:var(--checkboxSize);border-radius:var(--borderRadius);background:var(--surfaceColor);border:2px solid var(--checkboxBorderColor);transition:border-color var(--animationSpeed), box-shadow var(--animationSpeed), background var(--animationSpeed);display:block;position:absolute;top:0;left:0}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) label:after{content:"";z-index:1;width:var(--checkboxSize);height:var(--checkboxSize);text-align:center;font-size:1.15rem;font-weight:400;font-family:var(--iconFontFamily);color:var(--checkboxActiveColor);opacity:0;transition:transform var(--activeAnimationSpeed), opacity var(--activeAnimationSpeed);justify-content:center;align-items:center;display:flex;position:absolute;top:0;left:0;transform:translateY(2px)}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input:focus-visible~label,:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) label:hover,:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) label:focus-within{--checkboxBorderColor:var(--checkboxBorderFocusColor);background:0 0}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input[type=radio]~label:before{border-radius:50%}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input[type=radio]~label:after{content:"";font-size:.7em}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input:checked~label{--checkboxBorderColor:var(--checkboxActiveColor)}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input:checked~label:after{opacity:1;transform:scale(1)}:is(:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input:checked:focus-visible~label,:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input:checked~label:hover,:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input:checked~label:focus-within):before{box-shadow:0px 0px 0px 3px color-mix(in srgb, var(--checkboxActiveColor), transparent 85%)}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input.sm~label{--checkboxSize:18px;font-size:var(--smFontSize);padding-left:calc(var(--checkboxSize) + 5px);gap:3px}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input.sm~label:after{font-size:1.12em}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])){--switchH:24px;--switchW:41px;--switchTranslateX:0}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input.switch~label{--switchTranslateX:0;min-height:var(--switchH);padding-left:calc(var(--switchW) + 8px)}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input.switch~label:before{width:var(--switchW);height:var(--switchH);background:var(--checkboxBorderColor);border:0;border-radius:7px}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input.switch~label:after{content:"";height:calc(var(--switchH) - 8px);width:calc(var(--switchH) - 5px);opacity:1;transform:translateX(var(--switchTranslateX));background:#fff;border-radius:5px;top:4px;left:4px;box-shadow:1px 0 2px #0003}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input.switch~label:active:after{transform:scale(.92)}@supports (corner-shape:squircle){:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input.switch~label:before{corner-shape:squircle;border-radius:11px}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input.switch~label:after{corner-shape:squircle;border-radius:7px}}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input.switch:checked~label{--switchTranslateX:calc(var(--switchW) - var(--switchH) - 3px)}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input.switch:checked~label:before{background:var(--checkboxActiveColor)}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input.switch:checked~label:after{left:4px;box-shadow:-1px 0 2px #0003}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input.switch:checked~label:active:after{transform:scale(.92) translateX(var(--switchTranslateX))}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input.switch.sm~label{--switchH:21px;--switchW:35px}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input[disabled]~label{cursor:default;color:var(--surfaceTxtHintColor)}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input[disabled]~label:before{opacity:.6}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input[disabled]~label:after{box-shadow:none!important}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])).error label{--checkboxActiveColor:var(--dangerColor);color:var(--dangerColor)}.flex>.field,.inline-flex>.field{width:auto}.input.select{align-items:center;gap:10px;min-height:38px;padding:5px 0;display:flex}.input.select .selected-container{width:100%;padding:1px var(--inputPadding);color:var(--surfaceTxtHintColor);cursor:pointer;outline:0;flex-wrap:wrap;align-self:stretch;align-items:center;gap:5px;margin:0;display:flex}.input.select .selected-container:after{content:"";text-align:right;font-size:1.2rem;font-family:var(--iconFontFamily);transition:color var(--animationSpeed);align-content:center;margin-left:auto;margin-right:-2px;display:block}.input.select .selected-item{color:var(--surfaceTxtColor);border-radius:var(--borderRadius);background:0 0;align-items:center;gap:5px;padding:0;display:inline-flex}.input.select .selected-item .link-hint{font-size:var(--smFontSize);padding:1px}.input.select .dropdown{border-width:2px;border-color:var(--inputFocusColor);min-width:100px;margin:-2px 0}.input.select.single .selected-item{flex-grow:1}.input.select.single .selected-item .btn-option-unset{margin-left:auto}.input.select.single .selected-container{flex-wrap:nowrap}.input.select.multiple .selected-item{color:var(--surfaceTxtColor);background:var(--surfaceColor);padding:3px 4px}.input.select:has(.dropdown:popover-open) .selected-container:after{content:"";color:var(--surfaceTxtColor)}.input.select.disabled .selected-container{cursor:default}.input.select.disabled .selected-container:after{color:var(--surfaceTxtDisabledColor)}.input.select.disabled .selected-item{color:var(--surfaceTxtHintColor)}.fields{align-items:stretch;width:100%;display:flex}.fields .field:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.fields .field:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.fields .delimiter{background:var(--inputFocusColor);flex-shrink:0;width:1px;min-height:0}.fields .addon{padding:0 var(--inputPadding);align-items:center;gap:5px;display:inline-flex}.fields .addon:has(+.field){padding-right:0}.fields .field+.addon{padding-left:0}.fields .field:focus-within+.addon{background:var(--inputFocusColor)}.fields .addon:has(+.field:focus-within){background:var(--inputFocusColor)}.fields.error .delimiter{background:color-mix(in srgb, var(--dangerColor), var(--surfaceColor) 70%)}.field-help{width:100%;font-size:var(--smFontSize);line-height:var(--smLineHeight);color:var(--surfaceTxtHintColor);margin:7px 0 0;display:block}.field-help.error{color:var(--dangerColor)}.field-help code{font-size:.9em;line-height:1.4}.field-help p{margin:5px 0}.searchbar{min-height:var(--btnHeight);max-width:100%}.searchbar input,.searchbar .input{height:100%}.searchbar .field{--inputBorderRadius:30px}.searchbar .field:not(.addon){flex-grow:1;width:0}.pb-tinymce{width:100%;display:block}.field .pb-tinymce .tox-tinymce{border-width:2px;border-color:#0000}.field label~.pb-tinymce{margin-top:7px}.input.code-editor{cursor:text;scrollbar-width:thin;flex-shrink:0;align-items:stretch;width:100%;min-height:150px;max-height:350px;display:flex;position:relative;overflow:auto}.input.code-editor .code-editor-container{width:100%;position:relative}.input.code-editor .editor-content,.input.code-editor .highlight-overlay{tab-size:4;-webkit-hyphens:none;hyphens:none;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;text-align:left;letter-spacing:normal;width:100%;min-height:100%;font-size:.96rem;font-weight:400;line-height:1.5;font-family:var(--monospaceFontFamily);border:0;outline:0;margin:0;padding:0;display:block}.input.code-editor .highlight-overlay{pointer-events:none;position:absolute;top:0;left:0}.input.code-editor .editor-content{color:#0000;resize:none;caret-color:var(--surfaceTxtHintColor)}.input.code-editor .editor-content:empty:before{content:attr(data-placeholder);color:var(--surfaceTxtDisabledColor);white-space:normal}.input.code-editor.single-line{align-items:center;min-height:0;padding-top:9px;padding-bottom:9px}.input.code-editor.pre-wrap .editor-content,.input.code-editor.pre-wrap .highlight-overlay{white-space:pre-wrap}.dropdown{width:anchor-size(width);text-align:left;min-width:150px;max-height:330px;font-weight:400;font-size:var(--fontSize);line-height:var(--smLineHeight);color:var(--surfaceTxtColor);background:var(--surfaceColor);box-shadow:var(--boxShadow);scrollbar-width:thin;border-radius:var(--borderRadius);border:1px solid var(--surfaceAlt3Color);outline:0;flex-direction:column;gap:5px;margin:5px 0;padding:5px;display:inline-flex;overflow:auto}.dropdown[popover]{position-area:bottom span-left;position-try-fallbacks:flip-block;opacity:0;transition:opacity var(--animationSpeed), transform var(--animationSpeed), display var(--animationSpeed) allow-discrete, overlay var(--animationSpeed) allow-discrete;display:none;transform:translateY(3px)}.dropdown[popover]:popover-open{opacity:1;display:inline-flex;transform:translateY(0)}@starting-style{.dropdown[popover]:popover-open{opacity:0;transform:translateY(3px)}}@supports not (fill:context-stroke){.dropdown[popover]{position-try-fallbacks:none!important;transition:none!important}}.dropdown[popover].left{position-area:bottom span-right}.dropdown hr{margin:0}.dropdown .dropdown-item{cursor:pointer;-webkit-user-select:none;user-select:none;word-break:break-word;width:100%;color:inherit;transition:background var(--animationSpeed);border-radius:var(--borderRadius);outline:0;align-items:center;gap:8px;padding:10px 12px;text-decoration:none;display:flex}.dropdown .dropdown-item:hover{background:var(--surfaceAlt1Color)}.dropdown .dropdown-item:focus-visible{outline:2px dotted var(--surfaceAlt3Color)}.dropdown .dropdown-item:active{background:var(--surfaceAlt2Color);transition-duration:var(--activeAnimationSpeed)}.dropdown .dropdown-item.active{background:var(--surfaceAlt2Color);transition-duration:var(--activeAnimationSpeed)}.dropdown .dropdown-item.active code{background:var(--surfaceAlt1Color)}.dropdown .dropdown-item.active:hover{background:var(--surfaceAlt3Color);transition-duration:var(--animationSpeed)}.dropdown .dropdown-item[disabled],.dropdown .dropdown-item.disabled{cursor:default;color:var(--surfaceTxtDisabledColor);background:0 0}:is(.dropdown .dropdown-item input[type=checkbox],.dropdown .dropdown-item input[type=radio])~label{width:100%}.dropdown>.field,.dropdown>.fields-group{margin:0}:is(.dropdown .field:has(.search),.dropdown .fields-group:has(.search)){border-bottom:1px solid var(--surfaceAlt1Color);background:0 0;margin:0}:is(.dropdown .field:has(.search),.dropdown .fields-group:has(.search)) .field,:is(.dropdown .field:has(.search),.dropdown .fields-group:has(.search)) input.search{background:0 0;border-bottom:0}.dropdown .dropdown-search{border-bottom:1px solid var(--surfaceAlt2Color)}.dropdown .dropdown-search .field{background:0 0!important}.dropdown.nowrap,.dropdown.txt-nowrap{white-space:nowrap;width:auto}.dropdown.sm{min-width:100px;font-size:var(--smFontSize)}.dropdown.sm .dropdown-item{gap:5px;padding:7px}.dropdown.autocomplete{z-index:99999;word-break:break-word;max-width:350px;max-height:200px;font-size:var(--smFontSize);gap:3px;padding:3px;position:fixed}.dropdown.autocomplete .dropdown-item{padding:2px 4px;line-height:1.2}.dropdown.history-searchbar-dropdown[popover]{min-width:300px;max-width:450px}.accordion{--siblingSpacing:var(--smSpacing);--contentPadding:var(--smSpacing);width:100%;color:var(--surfaceTxtColor);border-radius:var(--borderRadius);background:var(--surfaceAlt1Color);border:1px solid var(--surfaceAlt3Color);transition:border var(--animationSpeed), border-radius var(--animationSpeed), box-shadow var(--animationSpeed), margin var(--animationSpeed);display:block;position:relative}.accordion summary{width:100%;min-height:var(--lgBtnHeight);cursor:pointer;padding:10px var(--smSpacing);padding-right:calc(var(--smSpacing) + 30px);background:var(--surfaceColor);border-radius:var(--borderRadius);-webkit-user-select:none;user-select:none;transition:background var(--animationSpeed), border var(--animationSpeed), border-radius var(--animationSpeed);align-items:center;gap:10px;display:flex;position:relative}.accordion summary::marker{content:"";display:none}.accordion summary:before{right:calc(var(--smSpacing) - 3px);content:"";width:20px;height:20px;font-size:1.3rem;font-weight:400;line-height:20px;font-family:var(--iconFontFamily);color:var(--surfaceTxtHintColor);transition:color var(--animationSpeed);flex-shrink:0;align-content:center;margin-top:-10px;display:block;position:absolute;top:50%}.accordion summary:not([tabindex="-1"]):hover,.accordion summary:not([tabindex="-1"]):focus-visible{background:var(--surfaceAlt1Color);outline:0}:is(.accordion summary:not([tabindex="-1"]):hover,.accordion summary:not([tabindex="-1"]):focus-visible):before{color:var(--surfaceTxtColor)}.accordion summary[tabindex="-1"]{cursor:default;-webkit-user-select:auto;user-select:auto;padding-right:var(--smSpacing);outline:0}.accordion summary[tabindex="-1"]:before{content:none;display:none}.accordion::details-content{height:0;padding:0 var(--contentPadding);background:var(--surfaceColor);border-bottom-left-radius:var(--borderRadius);border-bottom-right-radius:var(--borderRadius);transition:border var(--slideAnimationSpeed) step-end, height var(--slideAnimationSpeed), padding var(--slideAnimationSpeed), content-visibility var(--slideAnimationSpeed), overflow var(--slideAnimationSpeed) step-start;transition-behavior:allow-discrete;overflow:clip}.accordion:has(+.accordion){border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion:has(+.accordion) summary{border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion:not([open]):has(+.accordion[open]){border-bottom-left-radius:var(--borderRadius);border-bottom-right-radius:var(--borderRadius);border-bottom-width:1px}.accordion:not([open]):has(+.accordion[open]) summary{border-bottom-left-radius:var(--borderRadius);border-bottom-right-radius:var(--borderRadius)}.accordion+.accordion{border-top-width:0;border-top-left-radius:0;border-top-right-radius:0}.accordion+.accordion summary{border-top-left-radius:0;border-top-right-radius:0}.accordion+.accordion[open]{margin-top:var(--siblingSpacing)}.accordion[open]{border-radius:var(--borderRadius);box-shadow:var(--boxShadow);border-width:1px}.accordion[open] summary{background:var(--surfaceAlt1Color);border-radius:var(--borderRadius) var(--borderRadius) 0 0}.accordion[open] summary:before{content:""}.accordion[open]::details-content{border-top:1px solid var(--surfaceAlt3Color);padding-top:var(--contentPadding);padding-bottom:var(--contentPadding);height:auto;transition:height var(--slideAnimationSpeed), padding var(--slideAnimationSpeed), content-visibility var(--slideAnimationSpeed), overflow var(--slideAnimationSpeed) step-end;transition-behavior:allow-discrete;overflow:visible}.accordion[open]+.accordion{margin-top:var(--siblingSpacing);border-top-left-radius:var(--borderRadius);border-top-right-radius:var(--borderRadius);border-top-width:1px}.accordion[open]+.accordion summary{border-top-left-radius:var(--borderRadius);border-top-right-radius:var(--borderRadius)}.accordion.noanimation::details-content{transition:none!important}.bulkbar{z-index:11;bottom:var(--spacing);opacity:1;width:450px;max-width:95%;padding:5px var(--smSpacing);min-height:var(--lgBtnHeight);background:var(--surfaceColor);box-shadow:var(--boxShadow);border:1px solid var(--surfaceAlt3Color);transition-behavior:allow-discrete;transition-property:opacity,transform,display;transition-duration:var(--animationSpeed);border-radius:50px;flex-wrap:wrap;align-items:center;gap:10px;display:flex;position:fixed;left:50%;transform:translate(-50%,10px)}@starting-style{.bulkbar{opacity:0;transform:translate(-50%,10px)}}.bulkbar[hidden],.bulkbar.hidden{opacity:0;transform:translate(-50%,10px)}.bulkbar>.btn:first-child{margin-left:-5px}.bulkbar>.btn:last-child{margin-right:-5px}@media (width<=480px){.bulkbar{border-radius:var(--borderRadius);padding:min(10px, var(--smSpacing)) var(--smSpacing)}.bulkbar>.txt,.bulkbar>.content{width:100%}}.bulkbar-wrapper{z-index:11;width:100%;left:0;bottom:var(--smSpacing);pointer-events:none;position:sticky;right:0}.bulkbar-wrapper .bulkbar{margin:var(--smSpacing) auto;pointer-events:auto;position:relative;bottom:0;left:auto;right:auto;transform:translateY(0)}@starting-style{.bulkbar-wrapper .bulkbar{transform:translateY(10px)}}.bulkbar-wrapper .bulkbar[hidden],.bulkbar-wrapper .bulkbar.hidden{transform:translateY(10px)}.tabs-header{z-index:0;border-bottom:2px solid var(--surfaceAlt2Color);align-items:center;gap:10px;width:100%;display:flex;position:relative}.tabs-header .tab-item{cursor:pointer;text-align:center;-webkit-user-select:none;user-select:none;min-width:0;min-height:var(--btnHeight);padding:5px var(--smSpacing);border-top-left-radius:var(--borderRadius);border-top-right-radius:var(--borderRadius);color:var(--surfaceTxtHintColor);transition:color var(--animationSpeed), background var(--animationSpeed);outline:0;justify-content:center;align-items:center;gap:5px;display:inline-flex}.tabs-header .tab-item:hover,.tabs-header .tab-item:focus-visible{color:var(--surfaceTxtColor);background:var(--surfaceAlt1Color)}.tabs-header .tab-item.active,.tabs-header .tab-item:active{color:var(--surfaceTxtColor);background:var(--surfaceAlt2Color);transition-duration:var(--activeAnimationSpeed)}.tabs-header.equal-width .tab-item{flex:1 0 0;padding:5px}.code-block-tabs-content{z-index:1;margin-top:calc(-1 * (var(--borderRadius)));position:relative}.code-block-tabs-content code.block{padding:max(10px, calc(var(--smSpacing) - 5px)) max(10px, var(--smSpacing))}.modal{z-index:1000;opacity:0;word-break:break-word;width:620px;max-width:100%;height:100%;color:var(--surfaceTxtColor);background:var(--surfaceColor);backface-visibility:hidden;transition-property:opacity,transform,width,display;transition-duration:var(--modalAnimationSpeed);transition-behavior:allow-discrete;box-shadow:var(--leftBoxShadow);overscroll-behavior:none;border:0;outline:0;flex-direction:column;margin:0;display:none;position:fixed;top:0;right:0;transform:translate(30px)}.modal[data-modal-state=open]{opacity:1;display:flex;transform:translate(0)}@starting-style{.modal[data-modal-state=open]{opacity:0;transform:translate(30px)}}.modal:before{content:"";z-index:-2;background:var(--modalOverlayColor);width:200vw;height:200vh;display:block;position:fixed;top:-100vh;left:-100vw}.modal:after{content:"";z-index:-1;border-radius:inherit;background:inherit;width:100%;height:100%;display:block;position:absolute;top:0;left:0}.modal .modal-header,.modal .modal-content,.modal .modal-footer{padding:var(--spacing);outline:0}.modal .modal-header,.modal .modal-footer{align-items:center;gap:var(--smSpacing);width:100%;padding-top:calc(var(--spacing) - 10px);padding-bottom:calc(var(--spacing) - 10px);display:flex}.modal .modal-footer{border-top:1px solid var(--surfaceAlt2Color);margin-top:auto}.modal .modal-content{scrollbar-width:thin;flex-grow:1;position:relative;overflow:auto}.modal .modal-content>:first-child{margin-top:0}.modal .modal-content>:last-child{margin-bottom:0}.modal .modal-header .modal-close-btn.transparent{margin-right:-10px}.modal .modal-header:not(.isolated)~.modal-content{padding-top:5px}.modal .modal-header.isolated{background:var(--surfaceAlt1Color);border-bottom:1px solid var(--surfaceAlt2Color);border-top-left-radius:inherit;border-top-right-radius:inherit}.modal .modal-title{align-content:center}.modal.sm{width:480px}.modal.lg{width:880px}.modal.full{width:100%}.modal.preview,.modal.popup{max-width:95%;height:min-content;max-height:95%;box-shadow:var(--boxShadow);border-radius:var(--lgBorderRadius);margin:auto;inset:0;transform:scale(.98)}:is(.modal.preview,.modal.popup).full{width:95%}:is(.modal.preview,.modal.popup) .modal-header,:is(.modal.preview,.modal.popup) .modal-content,:is(.modal.preview,.modal.popup) .modal-footer{border-radius:inherit}:is(.modal.preview,.modal.popup) .modal-header,:is(.modal.preview,.modal.popup) .modal-footer{padding-top:calc(var(--spacing) - 5px);padding-bottom:calc(var(--spacing) - 5px)}:is(:is(.modal.preview,.modal.popup) .modal-header:has(~.modal-content),:is(.modal.preview,.modal.popup) .modal-header:has(~.modal-footer),:is(.modal.preview,.modal.popup) .modal-content:has(~.modal-footer)){border-bottom-right-radius:0;border-bottom-left-radius:0}:is(.modal.preview,.modal.popup) .modal-header~.modal-content,:is(.modal.preview,.modal.popup) .modal-content~.modal-footer,:is(.modal.preview,.modal.popup) .modal-header~.modal-footer{border-top-left-radius:0;border-top-right-radius:0}:is(.modal.preview,.modal.popup) .modal-footer{background:var(--surfaceAlt1Color)}:is(.modal.preview,.modal.popup)[data-modal-state=open]{transform:scale(1)}@starting-style{:is(.modal.preview,.modal.popup)[data-modal-state=open]{transform:scale(.98)}}.modal.preview{width:min-content;min-width:400px;min-height:300px}.modal.preview .modal-footer{padding:var(--smSpacing)}.modal.preview .modal-content{text-align:center;justify-content:center;align-items:flex-start;width:100%;max-width:100%;margin:auto;padding:0;display:flex}.modal.preview .modal-content:has(img){width:max-content}.modal.preview .modal-content:before,.modal.preview .modal-content:after{content:none}.modal.preview .modal-content img{width:100%;margin:auto}.modal.preview .modal-content object{width:100%;height:100%;margin:auto}.modal.preview:has(object){width:75%;height:85%}@media (width<=900px){.modal.preview:has(object){width:90%;height:90%}}.modal.preview.preview-audio{width:500px;height:400px}.tox-fullscreen .modal{transition:none;width:100%!important}.modal-content{--topShadowOffset:calc(var(--spacing) + 1px);--bottomShadowOffset:calc(var(--spacing) + 1px);min-height:92px;container-type:scroll-state}.modal-content:before,.modal-content:after{content:"";z-index:9;width:auto;height:1px;margin-left:calc(-1 * var(--spacing));margin-right:calc(-1 * var(--spacing));pointer-events:none;opacity:0;display:block;position:sticky;left:0}.modal-content:before{top:calc(100% + var(--bottomShadowOffset));margin-bottom:calc(-1 * var(--bottomShadowOffset));margin-top:var(--bottomShadowOffset);box-shadow:var(--bottomScrollShadow)}@container scroll-state(scrollable:bottom){.modal-content:before{opacity:1;transition:opacity var(--animationSpeed)}}.modal-content:after{top:auto;bottom:calc(100% + var(--topShadowOffset));margin-top:calc(-1 * var(--topShadowOffset));margin-bottom:var(--topShadowOffset);box-shadow:var(--topScrollShadow)}@container scroll-state(scrollable:top){.modal-content:after{opacity:1;transition:opacity var(--animationSpeed)}}.modal-header:not(.isolated)~.modal-content:after{--topShadowOffset:6px}.modal-header .tabs-header{flex-grow:1;width:auto}.modal-header .tabs-header .tab-item{font-size:var(--lgFontSize)}.modal-header.isolated :is(.modal-header .tabs-header):last-child{padding:0 var(--spacing);margin-left:calc(-1 * var(--spacing));margin-right:calc(-1 * var(--spacing));margin-bottom:calc(-1 * var(--spacing) + 9px);border-bottom:0}.modal-header.isolated :is(.modal-header .tabs-header):last-child .tab-item{border:1px solid #0000;border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.modal-header.isolated :is(.modal-header .tabs-header):last-child .tab-item:hover,.modal-header.isolated :is(.modal-header .tabs-header):last-child .tab-item:focus-visible{background:var(--surfaceAlt2Color)}.modal-header.isolated :is(.modal-header .tabs-header):last-child .tab-item:active{background:var(--surfaceAlt3Color)}.modal-header.isolated :is(.modal-header .tabs-header):last-child .tab-item.active{background:var(--surfaceColor);border-color:var(--surfaceAlt2Color)}.popup .modal-header.isolated :is(.modal-header .tabs-header):last-child{margin-bottom:calc(-1 * var(--spacing) + 4px)}.app{flex-direction:column;width:100%;height:100%;padding:0;display:flex}.app:has(.accent-surface){background:var(--accentColor);color:var(--accentTxtColor)}.app-header{--headerItemsGap:var(--inputPadding);align-items:center;gap:var(--headerItemsGap);width:100%;min-height:38px;font-size:var(--smFontSize);line-height:var(--smLineHeight);flex-shrink:0;padding:2px 10px;display:flex}@media (display-mode:minimal-ui) or (display-mode:standalone){.app-header{border-top:1px solid var(--accentAlt1Color);border-bottom:1px solid #0000}}.app-header .logo{vertical-align:top;border-radius:var(--borderRadius);justify-content:center;align-items:center;gap:5px;text-decoration:none;display:inline-flex}.app-header .logo img{vertical-align:top;max-height:26px}@media (width<=400px){.app-header{gap:7px;padding-left:5px;padding-right:5px}}.app-main-nav{align-items:center;gap:max(var(--headerItemsGap), 5px);scrollbar-width:thin;overflow:auto;overflow:overlay;margin:0 -3px;padding:3px;display:inline-flex;position:relative;container-type:scroll-state}.app-main-nav:before,.app-main-nav:after{content:"";z-index:9;opacity:0;pointer-events:none;flex-shrink:0;align-self:stretch;width:1px;height:auto;margin:0;display:block;position:sticky}.app-main-nav:before{margin-right:calc(-1 * (var(--headerItemsGap) + 1px));left:-5px;box-shadow:2px 0 4px 1px #0006}@container scroll-state(scrollable:left){.app-main-nav:before{opacity:1;transition:opacity var(--animationSpeed)}}.app-main-nav:after{margin-left:calc(-1 * (var(--headerItemsGap) + 1px));right:-5px;box-shadow:-2px 0 4px 1px #0006}@container scroll-state(scrollable:right){.app-main-nav:after{opacity:1;transition:opacity var(--animationSpeed)}}@media (width<=500px){.app-main-nav{--headerItemsGap:5px}}.app-header .header-link{-webkit-user-select:none;user-select:none;cursor:pointer;color:var(--accentTxtHintColor);border-radius:var(--borderRadius);transition:color var(--animationSpeed), background var(--animationSpeed);align-items:center;gap:5px;padding:4px 5px;font-weight:700;text-decoration:none;display:inline-flex}.app-header .header-link[data-popover-state=true],.app-header .header-link.active,.app-header .header-link:hover,.app-header .header-link:focus-visible{color:var(--accentTxtColor);background:var(--surfaceAlt2Color)}.app-header .header-link:active{background:var(--surfaceAlt3Color);transition-duration:var(--activeAnimationSpeed)}.app-header .logged-user{max-width:300px}@media (width<=600px){.app-header .logged-user .superuser-name{display:none}}.page{height:100%;color:var(--surfaceTxtColor);background:var(--surfaceColor);border-radius:var(--lgBorderRadius);display:flex;position:relative;overflow:auto}.page:first-child{border-top-left-radius:0;border-top-right-radius:0}.page:last-child{border-bottom-right-radius:0;border-bottom-left-radius:0}.page-content{z-index:0;width:100%;min-width:0;padding:var(--spacing);flex-grow:1;overflow:auto}.page-header{column-gap:var(--smSpacing);row-gap:calc(var(--smSpacing) - 5px);width:100%;min-height:var(--btnHeight);margin:-10px 0 calc(var(--spacing) - 10px);flex-wrap:wrap;flex-shrink:0;align-items:center;display:flex}.page-header .searchbar{flex-grow:1;width:auto}.page-header .page-header-secondary-btns,.page-header .page-header-primary-btns{flex-shrink:0;justify-content:flex-end;display:inline-flex}.page-header-secondary-btns{gap:10px}@media (width<=550px){.page-header-secondary-btns{gap:5px}}.page-header-primary-btns{gap:var(--smSpacing);margin-left:auto}.page-header-primary-btns .btn{white-space:nowrap;flex-wrap:nowrap}@media (width<=950px){.page-header-primary-btns{gap:5px}.page-header-primary-btns .btn{flex-grow:1;flex-basis:0;padding-left:10px;padding-right:10px}}@media (width<=550px){.page-header-primary-btns{gap:5px}.page-header-primary-btns .api-preview-btn{display:none}.page-header-primary-btns .btn:not(.circle){aspect-ratio:1;border-radius:50%;width:auto;min-width:0;padding:0}.page-header-primary-btns .btn:not(.circle) i+.txt{display:none}}.page-footer{z-index:10;align-items:center;gap:var(--smSpacing);width:auto;color:var(--surfaceTxtHintColor);font-size:var(--smFontSize);line-height:var(--smLineHeight);box-shadow:0px -1px 0px 0px var(--surfaceAlt2Color);padding:10px var(--spacing);margin:auto calc(-1 * var(--spacing)) calc(-1 * var(--spacing));flex-wrap:wrap;display:flex}.page-footer a{transition:color var(--animationSpeed);border-radius:var(--borderRadius);text-decoration:none}.page-footer a:hover,.page-footer a:focus-visible{color:var(--surfaceTxtColor)}.page-footer .credits{--creditsGap:20px;align-items:center;gap:var(--creditsGap);margin:0 0 0 auto;display:inline-flex}.page-footer .credits .credit-item{color:var(--surfaceTxtDisabledColor);align-items:center;gap:5px;display:inline-flex;position:relative}.page-footer .credits .credit-item:before{content:"";pointer-events:none;-webkit-user-select:none;user-select:none;top:50%;left:calc(var(--creditsGap) / -2);opacity:.5;background:var(--surfaceTxtDisabledColor);width:1px;height:10px;margin-top:-5px;position:absolute}.page-footer .credits .credit-item:first-child:before{content:none;display:none}.page-footer .credits .credit-item:hover,.page-footer .credits .credit-item:active,.page-footer .credits .credit-item:focus-within{color:var(--surfaceTxtColor)}.page-sidebar{--sidebarSpacing:calc(var(--spacing) / 2);width:var(--pageSidebarWidth);min-width:var(--pageSidebarWidth);background:var(--surfaceAlt1Color);border-right:1px solid var(--surfaceAlt2Color);backface-visibility:hidden;flex-direction:column;flex-shrink:0;max-width:min(400px,80%);display:flex;position:relative;overflow:auto}.page-sidebar .sidebar-content{padding:var(--sidebarSpacing);position:relative}.page-sidebar .sidebar-content.scrollable{scrollbar-width:thin;--topShadowOffset:calc(var(--sidebarSpacing) + 1px);--bottomShadowOffset:calc(var(--sidebarSpacing) + 1px);overflow:auto;container-type:scroll-state}.page-sidebar .sidebar-content.scrollable:before,.page-sidebar .sidebar-content.scrollable:after{content:"";z-index:9;width:auto;height:1px;margin-left:calc(-1 * var(--sidebarSpacing));margin-right:calc(-1 * var(--sidebarSpacing));pointer-events:none;opacity:0;display:block;position:sticky;left:0}.page-sidebar .sidebar-content.scrollable:before{top:calc(100% + var(--bottomShadowOffset));margin-bottom:calc(-1 * var(--bottomShadowOffset));margin-top:var(--bottomShadowOffset);box-shadow:var(--bottomScrollShadow)}@container scroll-state(scrollable:bottom){.page-sidebar .sidebar-content.scrollable:before{opacity:1;transition:opacity var(--animationSpeed)}}.page-sidebar .sidebar-content.scrollable:after{top:auto;bottom:calc(100% + var(--topShadowOffset));margin-top:calc(-1 * var(--topShadowOffset));margin-bottom:var(--topShadowOffset);box-shadow:var(--topScrollShadow)}@container scroll-state(scrollable:top){.page-sidebar .sidebar-content.scrollable:after{opacity:1;transition:opacity var(--animationSpeed)}}.page-sidebar .sidebar-content:has(+.sidebar-content){--bottomShadowOffset:1px;padding-bottom:0}.page-sidebar .sidebar-search{--inputPadding:var(--sidebarSpacing);border-bottom:1px solid var(--surfaceAlt2Color);--inputColor:var(--surfaceAlt1Color);--inputFocusColor:var(--surfaceAlt2Color);--inputBorderColor:var(--surfaceAlt3Color);align-items:center;gap:5px;min-height:50px;display:flex}.page-sidebar .sidebar-search .fields{height:100%}.page-sidebar .sidebar-search .field{border-radius:0;height:100%}.page-sidebar .sidebar-search .field input{height:100%}.page-sidebar hr{margin:var(--sidebarSpacing) 0}.page-sidebar .nav-group{box-shadow:none;margin:0 0 var(--sidebarSpacing);background:0 0;border:0;border-radius:0;padding:0;transition:none}.page-sidebar .nav-group:last-child{margin:0}.page-sidebar .nav-group summary{-webkit-user-select:none;user-select:none;min-height:30px;font-weight:700;font-size:var(--smFontSize);color:var(--surfaceTxtHintColor);transition:color var(--animationSpeed);background:0 0;border:0;border-radius:0;align-items:center;gap:5px;margin:0;padding:0;display:flex}.page-sidebar .nav-group summary::marker{content:"";display:none}.page-sidebar .nav-group summary:before{content:"";width:20px;height:20px;font-size:1em;font-weight:400;line-height:20px;font-family:var(--iconFontFamily);flex-shrink:0;order:1;align-content:center;margin:0;padding:0;display:block;position:relative;inset:auto}.page-sidebar .nav-group summary .actions{opacity:0;transition:transform var(--animationSpeed), opacity var(--animationSpeed);order:100;align-items:center;gap:5px;margin:0 0 0 auto;display:inline-flex;transform:translate(3px)}.page-sidebar .nav-group summary .actions:focus-within{opacity:1;transform:translate(0)}.page-sidebar .nav-group summary:hover,.page-sidebar .nav-group summary:active,.page-sidebar .nav-group summary:focus-visible{cursor:pointer;color:var(--surfaceTxtColor)}:is(.page-sidebar .nav-group summary:hover,.page-sidebar .nav-group summary:active,.page-sidebar .nav-group summary:focus-visible) .actions{opacity:1;transform:translate(0)}.page-sidebar .nav-group summary[tabindex="-1"]{cursor:default;color:var(--surfaceTxtHintColor)}.page-sidebar .nav-group summary[tabindex="-1"]:before{content:none;display:none}.page-sidebar .nav-group summary:empty{display:none}.page-sidebar .nav-group::details-content{background:0 0;border:0;flex-direction:column;gap:5px;margin:0;padding:0;display:flex}.page-sidebar .nav-group[open] summary:before{content:""}.compact :is(.page-sidebar .nav-group)::details-content{gap:2px}.page-sidebar .nav-group.compact::details-content{gap:2px}.page-sidebar .nav-group .nav-item{cursor:pointer;width:100%;font-size:var(--lgFontSize);color:var(--surfaceTxtHintColor);min-height:var(--btnHeight);border-radius:var(--borderRadius);transition:color var(--animationSpeed), background var(--animationSpeed);outline:0;align-items:center;gap:10px;padding:5px 10px;text-decoration:none;display:flex}.page-sidebar .nav-group .nav-item .txt{text-overflow:ellipsis;overflow:hidden}.page-sidebar .nav-group .nav-item .pin{color:var(--surfaceTxtDisabledColor);border-radius:var(--borderRadius);opacity:0;transition:color var(--animationSpeed), opacity var(--animationSpeed), transform var(--animationSpeed);margin-left:auto;margin-right:-5px;padding:2px;font-size:.8em;transform:translate(3px)}.page-sidebar .nav-group .nav-item .pin:hover,.page-sidebar .nav-group .nav-item .pin:focus-visible{color:var(--surfaceTxtColor)}.page-sidebar .nav-group .nav-item:hover .pin,.page-sidebar .nav-group .nav-item:focus-visible .pin,.page-sidebar .nav-group .nav-item:active .pin,.page-sidebar .nav-group .nav-item .pin:hover,.page-sidebar .nav-group .nav-item .pin:focus-visible,.page-sidebar .nav-group .nav-item .pin:active,.page-sidebar .nav-group .nav-item .pin.active{opacity:1;transform:translate(0)}.page-sidebar .nav-group .nav-item:hover,.page-sidebar .nav-group .nav-item:focus-visible,.page-sidebar .nav-group .nav-item:active,.page-sidebar .nav-group .nav-item.active{background:var(--surfaceAlt2Color)}.page-sidebar .nav-group .nav-item.active{color:var(--surfaceTxtColor)}.page-sidebar .nav-group .nav-item:active{background:var(--surfaceAlt3Color);transition-duration:var(--activeAnimationSpeed)}.page-sidebar .dragline{position:absolute;top:0;right:0}.page-sidebar[data-responsive=true]{z-index:9;opacity:0;visibility:hidden;height:100%;transition:opacity var(--animationSpeed), visibility var(--animationSpeed), transform var(--animationSpeed);position:absolute;top:0;left:0;transform:translate(-30px)}.page-sidebar[data-responsive=true] .dragline{display:none}.page-sidebar[data-responsive=true].active{opacity:1;visibility:visible;box-shadow:100px 0px 0px 1000px var(--modalOverlayColor);transform:translate(0)}.btn.responsive-sidebar-btn{min-height:var(--smBtnHeight);align-self:stretch;height:auto;margin:0 -5px;padding:0 10px}.btn.responsive-sidebar-btn i{margin-top:2px}.collections-sidebar .sidebar-content.collections-list{--topShadowOffset:6px;--bottomShadowOffset:6px;padding-top:5px;padding-bottom:5px}.page-content.full-height{flex-direction:column;align-items:normal;gap:0;display:flex}@supports not (fill:context-stroke){.page-content.full-height{overflow-x:hidden}}.rule-field{min-height:67px}.rule-field .code-editor{z-index:0;min-height:41px}.rule-field .code-editor .editor-content:empty:before{font-family:var(--baseFontFamily)}.rule-field .code-editor .highlight-overlay{color:var(--surfaceTxtColor)}.rule-field .code-editor .editor-content,.rule-field .code-editor .highlight-overlay{white-space:pre-wrap}.rule-field .superuser-toggle,.rule-field .superuser-toggle:last-child:not(.btn){border-radius:0;border-bottom-left-radius:var(--borderRadius);border-top-right-radius:var(--borderRadius)}.rule-field .superuser-toggle{z-index:3;cursor:pointer;-webkit-user-select:none;user-select:none;font-weight:700;line-height:1;font-size:var(--smFontSize);color:var(--surfaceTxtHintColor);background:var(--inputFocusColor);border:1px solid var(--surfaceAlt5Color);transition:color var(--animationSpeed), background var(--animationSpeed);border-top:0;border-right:0;outline:0;align-items:center;gap:5px;padding:8px 10px;display:inline-flex;position:absolute;top:0;right:0}.rule-field .superuser-toggle:hover,.rule-field .superuser-toggle:active,.rule-field .superuser-toggle:focus-visible{color:var(--successColor);background:var(--inputBorderColor)}.rule-field .superuser-toggle[disabled]{cursor:not-allowed;color:var(--surfaceTxtDisabledColor);background:var(--surfaceAlt3Color)}.rule-field .input:focus-within~.superuser-toggle{background:var(--inputBorderColor)}.rule-field label{z-index:2;position:relative}.rule-field label .superusers-label{opacity:1;visibility:visible;transition:opacity var(--animationSpeed), visibility var(--animationSpeed), transform var(--animationSpeed);transform:translate(0)}.rule-field label .superusers-label.hidden,.rule-field label .superusers-label[hidden]{opacity:0;visibility:hidden;transform:translate(3px);display:inline!important}.rule-field .unlock-overlay{z-index:1;cursor:pointer;-webkit-user-select:none;user-select:none;width:100%;height:100%;font-weight:700;font-size:var(--smFontSize);color:var(--successColor);padding:5px var(--smSpacing);border-radius:var(--borderRadius);background:var(--surfaceAlt1Color);border:2px solid var(--surfaceAlt2Color);transition:border-color var(--animationSpeed);justify-content:flex-end;align-items:center;gap:10px;display:flex;position:absolute;top:0;left:0}.rule-field .unlock-overlay .txt{opacity:0;transition:opacity var(--animationSpeed), transform var(--animationSpeed);transform:translate(3px)}.rule-field .unlock-overlay i{font-size:1.4em}.rule-field .unlock-overlay:hover,.rule-field .unlock-overlay:active,.rule-field .unlock-overlay:focus-visible{border-color:var(--surfaceAlt4Color)}:is(.rule-field .unlock-overlay:hover,.rule-field .unlock-overlay:active,.rule-field .unlock-overlay:focus-visible) .txt{opacity:1;transform:translate(0)}.rule-field .unlock-overlay:active{border-color:var(--surfaceAlt5Color);transition-duration:var(--activeAnimationSpeed)}.rule-field .unlock-overlay[disabled]{cursor:not-allowed}.rule-field .unlock-overlay[disabled] .txt{display:none}.rule-field.locked label{pointer-events:none}.rate-limit-table{background:0 0}.rate-limit-table tr,.rate-limit-table td,.rate-limit-table th{background:0 0;border:0;height:auto;min-height:0;padding:0}.rate-limit-table th{font-size:var(--smFontSize);line-height:var(--smLineHeight);padding:0 0 10px}.rate-limit-table .rate-limit-row .fields,.rate-limit-table .rate-limit-row .field,.rate-limit-table .rate-limit-row input,.rate-limit-table .rate-limit-row select,.rate-limit-table .rate-limit-row .input,.rate-limit-table .rate-limit-row .select{background:0 0;border-radius:0;height:100%}.rate-limit-table .rate-limit-row td{background:var(--inputColor);border-left:1px solid var(--inputBorderColor);border-top:1px solid var(--inputBorderColor);transition:background var(--animationSpeed)}.rate-limit-table .rate-limit-row td:first-child{border-left:0}.rate-limit-table .rate-limit-row td:focus-within{background:var(--inputFocusColor)}.rate-limit-table .rate-limit-row td:has(.error:not(.hidden)){background:var(--surfaceDangerColor)}.rate-limit-table .rate-limit-row:first-child td{border-top:0}.rate-limit-table .rate-limit-row:first-child td:first-child{border-top-left-radius:var(--borderRadius)}.rate-limit-table .rate-limit-row:first-child td:last-child{border-top-right-radius:var(--borderRadius)}.rate-limit-table .rate-limit-row:last-child td:first-child{border-bottom-left-radius:var(--borderRadius)}.rate-limit-table .rate-limit-row:last-child td:last-child{border-bottom-right-radius:var(--borderRadius)}.rate-limit-table td.col-new-btn{padding:3px}.rate-limit-table td.col-new-btn:focus-within{background:var(--inputColor)}.rate-limit-table .col-label{width:60%}.rate-limit-table .col-requests,.rate-limit-table .col-duration{width:15%}.rate-limit-table .col-audience{white-space:nowrap;width:1px;min-width:130px}.rate-limit-table .col-audience .selected-container{flex-wrap:nowrap}.rate-limit-table .col-action{width:1px;min-width:0;padding:0 3px}.rate-limit-table-wrapper{overflow:auto}.rate-limit-table-wrapper .rate-limit-table{min-width:550px}.export-panel{background:var(--surfaceAlt1Color);border-radius:var(--borderRadius);align-items:stretch;width:100%;height:550px;display:flex}.export-panel .export-list{gap:var(--smSpacing);scrollbar-width:thin;width:230px;min-height:0;padding:var(--smSpacing);word-break:break-word;flex-direction:column;flex-shrink:0;display:flex;overflow:auto}.export-panel .export-list .list-item{width:100%;margin:0}.export-panel .export-preview{flex-grow:1;position:relative;overflow:hidden}.export-panel .export-preview .ctrls{right:var(--smSpacing);top:calc(var(--smSpacing) - 5px);align-items:center;gap:10px;display:flex;position:absolute}.export-panel .export-preview .code-wrapper{width:100%;height:100%}.export-panel .export-preview .code-wrapper code{white-space:pre;background:0 0;border-radius:0;height:100%}.export-panel>*{border-left:1px solid var(--surfaceAlt2Color)}.export-panel>:first-child{border-top-left-radius:var(--borderRadius);border-bottom-left-radius:var(--borderRadius);border-left:0}.export-panel>:last-child{border-top-right-radius:var(--borderRadius);border-bottom-right-radius:var(--borderRadius)}@media (width<=700px){.export-panel{flex-flow:wrap;height:auto}.export-panel>*{border:0;border-top:1px solid var(--surfaceAlt2Color);border-radius:0}.export-panel>:first-child{border-top-left-radius:var(--borderRadius);border-top-right-radius:var(--borderRadius);border-top:0}.export-panel>:last-child{border-bottom-left-radius:var(--borderRadius);border-bottom-right-radius:var(--borderRadius)}.export-panel .export-list{width:100%;max-height:200px}.export-panel .export-preview{max-height:300px}}.import-change-label{min-width:65px}.collections-diff-table-title{width:100%;margin:0 0 var(--smSpacing);flex-wrap:wrap;align-items:center;gap:10px;display:flex}.collections-diff-table-title .import-change-label{font-weight:700}.collections-diff-table{color:var(--surfaceTxtHintColor);border-radius:var(--borderRadius)}.collections-diff-table tr{background:0 0}.collections-diff-table th,.collections-diff-table td{height:auto;padding:2px 10px}.collections-diff-table th{color:var(--surfaceTxtColor);padding:4px 10px}.collections-diff-table thead tr{background:var(--surfaceAlt2Color)}.collections-diff-table thead tr th{background:0 0}.collections-diff-table .changed-none-col{color:var(--surfaceTxtDisabledColor);background:var(--surfaceAlt1Color)}.collections-diff-table .changed-old-col{color:var(--surfaceTxtColor);background:var(--surfaceDangerColor)}.collections-diff-table .changed-new-col{color:var(--surfaceTxtColor);background:var(--surfaceSuccessColor)}.collections-diff-table .field-key-col{padding-left:var(--spacing);white-space:nowrap}.collections-diff-table .diff-value{white-space:break-spaces}.collections-diff-table tbody th{background:var(--surfaceAlt1Color)}.collections-diff-table-wrapper+.collections-diff-table-wrapper{margin-top:var(--spacing)}.logs-chart{text-align:center;touch-action:none;width:100%;height:0;max-height:0;transition:opacity var(--animationSpeed), max-height var(--slideAnimationSpeed), height var(--slideAnimationSpeed);flex-shrink:0;align-content:center;margin:0;display:block;position:relative;overflow:hidden}.logs-chart .chart-tooltip{width:;z-index:3;white-space:nowrap;text-align:center;font-size:var(--smFontSize);border-radius:var(--borderRadius);color:var(--tooltipTxtColor);background:var(--tooltipSurfaceColor);pointer-events:none;box-shadow:var(--boxShadow);padding:5px;line-height:1;position:absolute}.logs-chart .chart-tooltip .content-primary{font-weight:700}.logs-chart .chart-tooltip .content-secondary{opacity:.6;margin:3px 0 1px;font-size:.85em}.logs-chart:has(.uplot){height:auto;max-height:180px}.logs-chart .uplot{padding-bottom:var(--smSpacing)}.logs-chart.loading{opacity:.7}.logs-chart.pending,.logs-chart.empty-list:not(.zoomed),.logs-chart.nodata:not(.nonempty-list){height:0;max-height:0}.logs-chart.zoomed .u-over+.u-axis{cursor:ew-resize}.logs-reset-zoom-ctrl{z-index:2;vertical-align:top;text-align:center;cursor:pointer;opacity:.7;font-weight:700;font-size:var(--smFontSize);border-radius:var(--borderRadius);color:var(--surfaceTxtColor);background:var(--surfaceAlt3Color);transition:opacity var(--animationSpeed);outline:0;gap:5px;padding:5px 8px;line-height:1;display:inline-block;position:absolute;top:0;left:50%;transform:translate(-50%)}.logs-reset-zoom-ctrl .content-secondary{margin-top:5px;font-size:.85em;font-weight:400}.logs-reset-zoom-ctrl:hover,.logs-reset-zoom-ctrl:active,.logs-reset-zoom-ctrl:focus-visible{opacity:1}.logs-chart-loader{z-index:1;position:absolute;top:50%;left:50%;transform:translate(-50%,-80%)}.logs-chart-container{width:100%;display:block}.logs-table .col-field-name-level{white-space:nowrap;width:1px}.logs-table .col-field-name-message{word-break:break-word}.logs-table .col-field-name-message .content-primary{-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.logs-table .col-field-name-message .content-secondary{flex-wrap:wrap;gap:10px;margin-top:7px;display:flex}.logs-table .log-request .col-field-name-message .content-primary{word-break:break-all}.logs-include-superuser-requests{width:150px}.logs-include-superuser-requests .txt{margin-top:-3px;line-height:1}.log-level-label{justify-content:center;min-width:80px;font-size:.8em;font-weight:700}.log-level-label:before{content:"";background:var(--surfaceAlt5Color);border-radius:6px;width:6px;height:6px}.log-level-label.level-0:before{background:var(--successColor)}.log-level-label.level-4:before{background:var(--warningColor)}.log-level-label.level-8:before{background:var(--dangerColor)}.page-logs .page-content{padding-top:0}.page-logs .page-header{margin:10px 0}@media (width<=750px){.page-header .logs-searchbar{order:99;width:100%}}@media (width<=600px){.page-table-wrapper .logs-table{table-layout:fixed;width:100%;min-width:0}.page-table-wrapper .logs-table thead{display:none}.page-table-wrapper .logs-table tbody tr{border-top:1px solid var(--surfaceAlt2Color);flex-wrap:wrap;width:100%;padding:10px 0;display:flex}.page-table-wrapper .logs-table tbody td{height:auto;padding:5px var(--spacing);white-space:normal;border:0;width:100%;min-height:0;display:block;background:0 0!important}.page-table-wrapper .logs-table tbody td.col-bulk-select{width:auto;padding:0 0 0 var(--spacing);align-content:center}.page-table-wrapper .logs-table tbody td.col-field-name-level{flex-grow:1;width:auto;padding-left:0}.page-table-wrapper .logs-table .log-level-label{width:100%}.page-table-wrapper .logs-table .col-field-name-message .content-secondary{gap:5px}.page-table-wrapper .logs-table .col-field-name-message .content-secondary .label{word-break:break-word}.page-table-wrapper .logs-table .col-meta{display:none}.page-table-wrapper .logs-table~.bulkbar{margin-left:auto;margin-right:auto;position:sticky;left:0;transform:none!important}}.map-container{flex-direction:column;width:100%;height:100%;min-height:200px;display:flex;position:relative}.map-box{z-index:1;flex-grow:1;width:100%;height:100%;min-height:0}.map-search{--searchWidth:400px;z-index:2;top:10px;left:calc((100% - var(--searchWidth)) / 2);height:auto;width:var(--searchWidth);opacity:.8;max-width:90%;transition:opacity var(--animationSpeed);position:absolute}.map-search input{background:inherit}.map-search .field{box-shadow:var(--boxShadow);background:var(--surfaceColor)!important}.map-search .dropdown{border:0;max-height:180px}.map-search:focus-within{opacity:1}.record-summary{flex-shrink:1;justify-content:flex-start;padding:3px 5px}.record-summary .thumb{--thumbSize:28px;border-width:1px;font-size:75%}.record-summary i{font-size:1em}.record-summary .record-preview-icon{align-content:center;height:100%;margin:0 -5px;padding:0 5px}.record-summary .record-summary .thumb{--thumbSize:24px}.record-summary .record-summary .record-summary .thumb{--thumbSize:20px}.field-content .record-summary{border-width:1px}.record-summary .record-field-view{vertical-align:top;display:inline-block}td .record-summary .field-type-text,td .record-summary .field-type-editor,td .record-summary .field-type-json{max-width:250px}.record-summary-dropdown{font-family:var(--monospaceFontFamily);font-size:var(--smFontSize);line-height:var(--smLineHeight);width:100%;max-width:350px;max-height:305px;min-width:anchor-size(width);-webkit-user-select:text;user-select:text;cursor:default}.record-summary-dropdown[popover]{position-area:bottom span-right;position-try-fallbacks:flip-block}.record-summary-dropdown .record-header{width:100%;font-family:var(--baseFontFamily);align-items:center;gap:10px;padding:5px;display:flex}.record-summary-dropdown .record-json{scrollbar-width:thin;overflow:auto}.record-file-picker-collection-select-btn{padding-left:var(--smSpacing);padding-right:calc(var(--smSpacing) - 5px);white-space:nowrap;width:27%}.record-file-picker-collection-select-btn .collection-name{white-space:nowrap;text-overflow:ellipsis;max-width:100%;overflow:hidden}.record-file-picker-thumb-select{min-width:170px}.record-file-picker-list{grid-gap:var(--smSpacing);grid-template-columns:1fr 1fr 1fr 1fr 1fr 1fr 1fr;display:grid}.record-file-picker-list .list-item{--thumbSize:100%;border-width:2px}@media only screen and (width<=700px){.record-file-picker-list{grid-template-columns:1fr 1fr 1fr 1fr 1fr}}@media only screen and (width<=450px){.record-file-picker-list{grid-template-columns:1fr 1fr 1fr}}.record-file-picker-modal{width:var(--wrapperWidth)}@media only screen and (width<=550px){.record-file-picker-modal .modal-header{flex-wrap:wrap}.record-file-picker-modal .record-file-picker-collection-select-btn{flex-grow:1}.record-file-picker-modal .records-searchbar-wrapper{flex-grow:1;order:99}.record-file-picker-modal .record-file-picker-thumb-select{min-width:100px}.record-file-picker-modal .modal-footer .btn.expanded,.record-file-picker-modal .modal-footer .btn.expanded-sm,.record-file-picker-modal .modal-footer .btn.expanded-lg{min-width:50px}}.records-picker-list{max-height:400px}.records-picker-selected-list{flex-wrap:wrap;align-items:stretch;gap:10px;max-width:100%;display:flex}.records-picker-modal .modal-header .collection-name{text-overflow:ellipsis;white-space:nowrap;flex-shrink:0;min-width:0;max-width:250px;overflow:hidden}@media only screen and (width<=550px){.records-picker-modal .modal-header{flex-wrap:wrap}.records-picker-modal .modal-header .collection-name{flex-grow:1;flex-shrink:1;max-width:80%;margin-right:auto}.records-picker-modal .searchbar{flex-grow:1;order:99}}.accordion.record-field-settings{--fieldsGap:10px;border-color:var(--surfaceAlt4Color)}.accordion.record-field-settings summary{align-items:center;gap:var(--fieldsGap);background:var(--inputColor);min-height:0;padding:4px;display:flex;container-type:inline-size}.accordion.record-field-settings summary .header-fields{gap:var(--fieldsGap);align-content:center;align-items:stretch;width:100%;display:flex}.accordion.record-field-settings summary .header-fields .header-select{max-width:150px}.accordion.record-field-settings summary .header-fields>*{position:relative}.accordion.record-field-settings summary .header-fields>*:before{content:"";background:var(--surfaceAlt4Color);flex-shrink:0;width:1px;height:auto;min-height:0;position:absolute;top:-4px;bottom:-4px;right:-5px}.accordion.record-field-settings summary .btn i.ri-settings-3-line{transition:color var(--animationSpeed), transform var(--animationSpeed)}.accordion.record-field-settings summary .sort-handle{z-index:2;text-align:left;cursor:pointer;width:30px;height:100%;color:var(--surfaceTxtHintColor);opacity:0;transition:color var(--animationSpeed), opacity var(--animationSpeed), transform var(--animationSpeed);flex-shrink:0;align-content:center;padding-left:7px;position:absolute;top:0;left:-30px;transform:translate(3px)}.accordion.record-field-settings summary .sort-handle:hover,.accordion.record-field-settings summary .sort-handle:active{opacity:1;color:var(--surfaceTxtColor);transform:translate(0)}@container (width<=480px){.accordion.record-field-settings summary .header-fields{border-radius:var(--borderRadius);background:var(--surfaceColor);flex-direction:column;gap:5px;padding:5px}.accordion.record-field-settings summary .header-fields>*{flex-grow:1;max-width:none!important}.accordion.record-field-settings summary .header-fields>*:before{content:none;display:none}}.accordion.record-field-settings:hover summary .sort-handle{opacity:1;transform:translate(0)}.accordion.record-field-settings .record-field-settings-footer{align-items:center;gap:var(--smSpacing);margin-top:var(--smSpacing);flex-wrap:wrap;display:flex}.accordion.record-field-settings .record-field-settings-footer>.field,.accordion.record-field-settings .record-field-settings-footer>.fields{width:auto}.accordion.record-field-settings summary+.record-field-settings-footer{margin-top:0}.accordion.record-field-settings .field-labels{z-index:2;pointer-events:none;-webkit-user-select:none;user-select:none;align-items:center;gap:2px;display:flex;position:absolute;top:2px;right:2px}.accordion.record-field-settings .field-labels .label{min-height:0;padding:2px 3px;font-size:.7em;line-height:1.2}.accordion.record-field-settings[open]{box-shadow:0px 0px 0px 1px var(--surfaceAlt3Color)}.accordion.record-field-settings[open] summary{outline:0}.accordion.record-field-settings[open] summary .btn i.ri-settings-3-line{color:var(--surfaceTxtColor);transform:rotate(-30deg)}.accordion.record-field-settings.deleted summary{background:var(--surfaceAlt1Color)}.accordion.record-field-settings.deleted summary .field,.accordion.record-field-settings.deleted summary .fields{background:0 0!important}.accordion.record-field-settings.deleted .header-fields{opacity:.7}.accordion.record-field-settings.deleted .header-fields>:after{background:var(--surfaceAlt2Color)}.header-select.field-select-choices-input,.header-select.collections-select{min-width:200px}.record-field-view{vertical-align:top;text-align:left;align-content:center;min-width:0;max-width:100%;display:block}.record-field-view:empty{display:none!important}.record-field-view .missing-value{vertical-align:top;color:var(--surfaceTxtDisabledColor);font-size:var(--smFontSize);align-content:center;display:inline-block}.record-field-view .missing-value:before{content:"N/A"}.record-field-view .delimiter{line-height:1;font-size:var(--smFontSize);color:var(--surfaceTxtDisabledColor)}.record-field-view .marker{line-height:1;font-size:var(--smFontSize);color:var(--surfaceTxtHintColor);align-content:center;padding-right:5px}.field-type-bool.record-field-view{min-width:auto}.field-type-bool.record-field-view .label{min-width:48px}.field-type-json.record-field-view{white-space:pre-wrap;scrollbar-width:thin;max-height:400px;overflow:auto}.field-type-json .input.code-editor{min-height:38px}.field-type-json .json-state{position:absolute;top:10px;right:10px}.field-type-geoPoint.record-field-view{white-space:nowrap}.field-type-geoPoint.record-field-input .field-list-item label:after{content:none;display:none}.more-marker{color:var(--surfaceTxtHintColor);align-self:flex-end}.field-type-select .field-select-choices-input>input{font-size:var(--smFontSize)}.field-type-select .field-select-choices-dropdown{max-height:400px}.field-type-select .field-select-choices-dropdown textarea{font-size:var(--smFontSize);max-height:300px}:is(.field-type-select,.field-type-file).record-field-view{flex-wrap:wrap;align-items:center;gap:5px;min-width:auto;display:inline-flex}.field-type-editor.record-field-view{width:100%}.record-summary .field-type-editor.record-field-view{width:auto}.field-type-editor.record-field-input .field{min-height:296px}.field-type-editor.record-field-input .loading-textarea{resize:none}.field-type-editor.record-field-input .pb-tinymce{animation:fadeIn var(--animationSpeed)}.field-type-relation.record-field-view{flex-wrap:wrap;align-items:stretch;gap:10px;display:inline-flex}.record-summary .field-type-relation.record-field-view{gap:5px}:is(.field-type-autodate,.field-type-date).record-field-view{flex-shrink:0}.hidden-field-blur{filter:blur(2.5px);transition:filter var(--animationSpeed)}.hidden-field-blur:hover,.hidden-field-blur:active,.hidden-field-blur:focus-within{filter:none;transition-delay:0s}.records-list-columns-dropdown{max-height:230px}.record-upsert-modal{width:720px}.record-upsert-modal .modal-title{min-height:var(--smBtnHeight);align-content:center}.record-upsert-modal .modal-content{scrollbar-gutter:stable}:is(.record-upsert-modal,.record-preview-modal):has(.large-modal){width:832px}.collection-upsert-modal{width:720px}.collection-upsert-modal .modal-content{scrollbar-gutter:stable both-edges}.collection-upsert-modal .modal-title{min-height:var(--smBtnHeight)}.collection-upsert-modal .modal-title .collection-name{max-width:220px}.new-collection-field-btn-wrapper{margin:var(--smSpacing) 0}.field-types-dropdown{border:2px solid var(--primaryColor);flex-flow:wrap;gap:0;margin:-5px 0}.field-types-dropdown .dropdown-item{width:25%;margin:2px 0;padding:10px}@media (width<=600px){.field-types-dropdown .dropdown-item{width:50%}}.indexes-list{flex-wrap:wrap;gap:10px;display:flex}.code-editor.collection-index-input{min-height:101px}.query-caveats-dropdown{width:300px;padding:10px}.query-caveats-dropdown ul{margin:0;padding-left:20px}.query-state{position:absolute;top:10px;right:10px}.collection-changes-list{margin:10px 0}.collection-changes-list li{line-height:var(--smLineHeight);align-content:center;margin:10px 0}.collection-changes-list li i{display:inline-block}.collection-changes-list li .label{vertical-align:baseline}.collection-changes-list .rule-content{font-family:var(--monospaceFontFamily);font-size:var(--smFontSize);line-height:var(--smLineHeight);white-space:pre-wrap;text-align:left;margin:5px 0}.collection-changes-list .rule-content.old-rule{color:var(--surfaceTxtHintColor)}.collection-changes-list .rule-content.new-rule{color:var(--successColor)}.oidc-userinfo-options{border:2px solid var(--inputFocusColor);border-radius:var(--borderRadius);padding:var(--smSpacing)}.view-query-sample-wrapper{position:relative}.view-query-sample-wrapper .loader{z-index:1;position:absolute;top:10px;right:10px}.view-query-sample code{z-index:0;border:1px solid var(--surfaceAlt2Color);background:0 0;max-height:300px}.provider-logo{--size:40px;text-align:center;width:var(--size);height:var(--size);line-height:1;font-size:var(--fontSize);color:var(--surfaceTxtDisabledColor);background:var(--surfaceAlt2Color);border-radius:var(--borderRadius);transition:background var(--animationSpeed);flex-shrink:0;justify-content:center;align-items:center;padding:8px;display:flex}.provider-logo:empty:before{content:"";font-family:var(--iconFontFamily)}.provider-logo img{width:100%;height:auto}.handle:hover .provider-logo,.handle:focus-visible .provider-logo{background:var(--surfaceAlt3Color)}.handle.active .provider-logo,.handle:active .provider-logo{transition-duration:var(--activeAnimationSpeed);background:var(--surfaceAlt4Color)}.provider-card{width:100%;min-height:var(--lgBtnHeight);border-radius:var(--borderRadius);border:1px solid var(--surfaceAlt2Color);align-items:center;gap:10px;padding:5px;display:flex}.provider-card .content{word-break:break-word;font-size:var(--fontSize);flex-grow:1;line-height:1.1}.provider-card .content p{margin:5px 0}.provider-card .content p:first-child{margin-top:0}.provider-card .content p:last-child{margin-bottom:0}.provider-card .content .primary-txt,.provider-card .content .secondary-txt{display:block}.provider-card .content .secondary-txt{color:var(--surfaceTxtHintColor);font-size:var(--smFontSize);margin:2px 0}.provider-card.handle{cursor:pointer;-webkit-user-select:none;user-select:none;transition:background var(--animationSpeed);outline:0;text-decoration:none}.provider-card.handle:hover,.provider-card.handle:focus-visible{background:var(--surfaceAlt1Color)}.provider-card.error{border-color:var(--dangerColor);background:var(--surfaceDangerColor)}.btn.add-provider-btn{font-size:var(--fontSize);line-height:var(--lineHeight);height:100%}.api-preview-content{scrollbar-width:thin;padding:calc(var(--smSpacing) + 5px) var(--spacing) calc(var(--spacing) - 5px);padding:calc(var(--spacing) - 5px) var(--spacing) calc(var(--spacing) - 5px);flex-grow:1;overflow:auto}.api-preview-content-header{width:100%;margin:0 0 calc(var(--smSpacing) + 5px);align-items:center;display:flex}.api-preview-content-header .preview-close-btn{margin:-10px 0}.api-preview-sidebar{scrollbar-width:thin;background:var(--surfaceAlt1Color);width:195px;padding:var(--inputPadding);border-right:1px solid var(--surfaceAlt2Color);flex-shrink:0;overflow:auto}.api-preview-nav{flex-direction:column;gap:5px;display:flex}.api-preview-nav hr{margin:var(--inputPadding) 0}.api-preview-nav .nav-item{width:100%;padding:5px var(--inputPadding);cursor:pointer;font-size:var(--lgFontSize);min-height:var(--btnHeight);border-radius:var(--borderRadius);color:var(--surfaceTxtHintColor);transition:color var(--animationSpeed), background var(--animationSpeed);outline:0;align-items:center;gap:5px;display:flex}.api-preview-nav .nav-item:hover,.api-preview-nav .nav-item:focus-visible,.api-preview-nav .nav-item.active{background:var(--surfaceAlt2Color)}.api-preview-nav .nav-item.active{color:var(--surfaceTxtColor)}.api-preview-nav .nav-item:active{background:var(--surfaceAlt3Color);transition-duration:var(--activeAnimationSpeed)}.api-preview-nav .nav-item[disabled]{cursor:not-allowed;color:var(--surfaceTxtDisabledColor);background:0 0}.api-preview-alert{margin:var(--smSpacing) 0;align-items:center;gap:10px;display:flex}.api-preview-alert .method{flex-shrink:0;font-weight:700}.api-preview-alert .path{flex-grow:1}.api-preview-alert .extra{color:var(--surfaceTxtHintColor);text-align:right;max-width:180px;margin:-3px 0 -3px auto;font-size:.85em}.api-preview-table{width:100%;margin:var(--smSpacing) 0}code.filter-op{text-align:center;min-width:30px}.api-preview-modal{width:900px;max-width:95%}.api-preview-modal .modal-content{align-items:stretch;padding:0;display:flex}.api-preview-modal .modal-content:before,.api-preview-modal .modal-content:after{content:none;display:none}@media (width<=550px){.api-preview-modal .modal-content{flex-direction:column}.api-preview-modal .api-preview-content>.content{min-width:550px}.api-preview-modal .api-preview-sidebar{border:0;border-bottom:2px solid var(--surfaceAlt3Color);width:100%;max-height:150px}}.color-picker{border-radius:var(--borderRadius);background:0 0;background:var(--surfaceColor);align-items:stretch;gap:3px;width:100%;min-height:28px;padding:3px;display:flex}.color-picker .predefined-colors-btn{padding:0 3px}.color-picker .color-picker-input-wrapper{flex-grow:1;height:auto;display:block;position:relative}.color-picker .color-picker-input-wrapper input[type=color]{-webkit-appearance:none;cursor:cell;border-radius:var(--borderRadius);border:0;width:100%;height:100%;margin:0;padding:0}.color-picker .color-picker-input-wrapper input[type=color]::-webkit-color-swatch-wrapper{padding:0}.color-picker .color-picker-input-wrapper input[type=color]::-webkit-color-swatch{border:none;border-radius:0}.color-picker .color-picker-input-wrapper .result{text-align:center;width:100%;height:100%;font-size:.85rem;line-height:1;font-family:var(--monospaceFontFamily);pointer-events:none;white-space:nowrap;text-overflow:ellipsis;align-content:center;padding:0 2px;position:absolute;top:0;left:0;overflow:hidden}.predefined-colors-list{-webkit-user-select:none;user-select:none;flex-wrap:wrap;gap:5px;display:flex}.predefined-colors-list .color{cursor:pointer;border-radius:var(--borderRadius);width:22px;height:22px;transition:opacity var(--animationSpeed);flex-shrink:0}.predefined-colors-list .color:focus-visible{outline-color:var(--surfaceAlt5Color)}.predefined-colors-list .color:hover,.predefined-colors-list .color:active{opacity:.8}.predefined-colors-list .color:active{opacity:.7;transition-duration:var(--activeAnimationSpeed)}.predefined-colors-dropdown{width:auto;min-width:25px;max-width:170px}.field .color-picker{width:auto;margin:4px}.field label~.color-picker{margin-top:6px}.modal.collections-overview-modal{width:1500px;max-width:90%;min-height:90%;max-height:90%;transform:translateY(3px)}.modal.collections-overview-modal[data-modal-state=open]{transform:translateY(0)}@starting-style{.modal.collections-overview-modal[data-modal-state=open]{transform:translateY(3px)}}.modal.collections-overview-modal .modal-header{padding-top:var(--smSpacing)}.modal.collections-overview-modal .modal-content.erd-tab{align-items:stretch;padding:0;display:flex}.modal.collections-overview-modal .modal-content.erd-tab:before,.modal.collections-overview-modal .modal-content.erd-tab:after{content:none;display:none}.modal.collections-overview-modal .modal-content.rules-tab{scrollbar-gutter:stable;padding-top:0}.modal.collections-overview-modal .rules-table{word-break:break-word;min-width:700px;margin-top:calc(var(--spacing) - 5px)}.modal.collections-overview-modal .rules-table .collection-name{text-overflow:ellipsis;width:200px;overflow:hidden}.modal.collections-overview-modal .rules-table thead{top:-3px}.modal.collections-overview-modal .rule-btns{gap:5px;width:100%;display:flex}.modal.collections-overview-modal .rule-btns .btn{flex:1 1 0;padding:5px}.modal.collections-overview-modal .rule-btns .btn.outline{padding:3px}@media (width<=700px){.modal.collections-overview-modal{max-width:95%;min-height:95%;max-height:95%}.modal.collections-overview-modal .rules-table .collection-name{max-width:130px}}.erd-table-row{width:100%;min-height:var(--smBtnHeight);background:var(--surfaceColor);color:var(--surfaceTxtColor);border:0;border-top:1px solid var(--surfaceAlt2Color);align-items:center;gap:5px;padding:2px 5px;font-weight:400;display:flex}.erd-table-row .txt-ellipsis{min-width:0}.erd-table-row.header{color:inherit;background:inherit;text-align:center;border:0;justify-content:center;font-weight:700}.erd-table-row.type-relation{background:var(--surfaceAlt1Color)}.erd-table-row:first-child,.erd-table-row.header+.erd-table-row{border-top-left-radius:var(--borderRadius);border-top-right-radius:var(--borderRadius);border-top:0}.erd-table-row:last-child{border-bottom-left-radius:var(--borderRadius);border-bottom-right-radius:var(--borderRadius)}.erd-table-row i{font-size:1em}.erd-table-row .field-icon{color:var(--surfaceTxtHintColor)}.erd-table-row .field-name{vertical-align:top;white-space:nowrap;text-overflow:ellipsis;flex-shrink:1;min-width:10px;max-width:100%;display:inline-block;overflow:hidden}.erd-table-row .field-hidden-label{min-height:0;padding:3px;font-size:.78em;line-height:1}.erd-table-row .meta{white-space:nowrap;color:var(--surfaceTxtHintColor);flex-grow:0;flex-shrink:0;margin-left:auto;font-size:.9em;display:flex}.erd-table{z-index:2;text-align:left;-webkit-user-select:none;user-select:none;word-break:break-word;background:var(--surfaceAlt4Color);width:200px;color:var(--surfaceTxtHintColor);border-radius:var(--borderRadius);font-size:var(--smFontSize);line-height:var(--smLineHeight);transition:opacity var(--animationSpeed), background var(--animationSpeed), color var(--animationSpeed), box-shadow var(--animationSpeed);padding:2px;display:block;position:relative}.erd-table.active{color:var(--surfaceTxtColor);background:var(--surfaceAlt5Color)}.erd-table.system{color:var(--dangerTxtColor);background:var(--dangerColor)}.erd-table.active,.erd-table:hover{box-shadow:var(--boxShadow)}.erd{cursor:grab;background-image:radial-gradient(var(--surfaceAlt2Color) 1px, transparent 1px);border-radius:var(--borderRadius);touch-action:none;background-size:20px 20px;width:100%;height:auto;min-height:300px;display:block;position:relative;overflow:hidden}.erd .erd-area{-webkit-user-select:none;user-select:none;transform-origin:0 0;position:relative;top:0;left:0}.erd .erd-table{position:absolute;top:0;left:0}.erd .erd-paths{--pathColor:var(--surfaceAlt2Color);z-index:0;pointer-events:none;width:2000px;height:8000px;position:absolute;top:0;left:0}.erd .paths-group{fill:none;stroke-width:2px;stroke:var(--pathColor)}.erd .erd-paths.front{--pathColor:var(--surfaceTxtDisabledColor);z-index:2}.erd .erd-paths.front .active-from{stroke:var(--surfaceTxtHintColor)}.erd .erd-nav{z-index:3;right:var(--smSpacing);bottom:var(--smSpacing);flex-direction:column;align-items:center;gap:10px;display:flex;position:absolute}.erd.updating .erd-paths,.erd.updating .erd-table{opacity:0}.erd.panning{cursor:grabbing}.erd.active .erd-table{opacity:.7}.erd.active .erd-table.active{opacity:1}.erd.active .erd-paths.back{opacity:.3}.app.hide-controls .btn-collection-settings,.app.hide-controls .sidebar-content.new-collection,.app.hide-controls .settings-sidebar [data-group=Sync]{display:none}.field.sql-console-field .input{resize:vertical;height:250px;min-height:150px;max-height:400px}@media (height<=650px){.field.sql-console-field .input{height:150px}}.sql-console-history-dropdown{width:450px}.sql-console-history-dropdown.no-items{width:300px}.sql-console-history-dropdown .dropdown-item{font-size:.9em;line-height:1.2;font-family:var(--monospaceFontFamily);gap:5px;padding:5px}.sql-console-history-dropdown .dropdown-item .query{-webkit-line-clamp:3;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.leaflet-pane,.leaflet-tile,.leaflet-marker-icon,.leaflet-marker-shadow,.leaflet-tile-container,.leaflet-pane>svg,.leaflet-pane>canvas,.leaflet-zoom-box,.leaflet-image-layer,.leaflet-layer{position:absolute;top:0;left:0}.leaflet-container{overflow:hidden}.leaflet-tile,.leaflet-marker-icon,.leaflet-marker-shadow{-webkit-user-select:none;user-select:none;-webkit-user-drag:none}.leaflet-tile::selection{background:0 0}.leaflet-safari .leaflet-tile{image-rendering:-webkit-optimize-contrast}.leaflet-safari .leaflet-tile-container{-webkit-transform-origin:0 0;width:1600px;height:1600px}.leaflet-marker-icon,.leaflet-marker-shadow{display:block}.leaflet-container .leaflet-overlay-pane svg{max-width:none!important;max-height:none!important}.leaflet-container .leaflet-marker-pane img,.leaflet-container .leaflet-shadow-pane img,.leaflet-container .leaflet-tile-pane img,.leaflet-container img.leaflet-image-layer,.leaflet-container .leaflet-tile{width:auto;padding:0;max-width:none!important;max-height:none!important}.leaflet-container img.leaflet-tile{mix-blend-mode:plus-lighter}.leaflet-container.leaflet-touch-zoom{-ms-touch-action:pan-x pan-y;touch-action:pan-x pan-y}.leaflet-container.leaflet-touch-drag{-ms-touch-action:pinch-zoom;touch-action:none;touch-action:pinch-zoom}.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom{-ms-touch-action:none;touch-action:none}.leaflet-container{-webkit-tap-highlight-color:transparent}.leaflet-container a{-webkit-tap-highlight-color:#33b5e566}.leaflet-tile{filter:inherit;visibility:hidden}.leaflet-tile-loaded{visibility:inherit}.leaflet-zoom-box{box-sizing:border-box;z-index:800;width:0;height:0}.leaflet-overlay-pane svg{-moz-user-select:none}.leaflet-pane{z-index:400}.leaflet-tile-pane{z-index:200}.leaflet-overlay-pane{z-index:400}.leaflet-shadow-pane{z-index:500}.leaflet-marker-pane{z-index:600}.leaflet-tooltip-pane{z-index:650}.leaflet-popup-pane{z-index:700}.leaflet-map-pane canvas{z-index:100}.leaflet-map-pane svg{z-index:200}.leaflet-vml-shape{width:1px;height:1px}.lvml{behavior:url(#default#VML);display:inline-block;position:absolute}.leaflet-control{z-index:800;pointer-events:visiblePainted;pointer-events:auto;position:relative}.leaflet-top,.leaflet-bottom{z-index:1000;pointer-events:none;position:absolute}.leaflet-top{top:0}.leaflet-right{right:0}.leaflet-bottom{bottom:0}.leaflet-left{left:0}.leaflet-control{float:left;clear:both}.leaflet-right .leaflet-control{float:right}.leaflet-top .leaflet-control{margin-top:10px}.leaflet-bottom .leaflet-control{margin-bottom:10px}.leaflet-left .leaflet-control{margin-left:10px}.leaflet-right .leaflet-control{margin-right:10px}.leaflet-fade-anim .leaflet-popup{opacity:0;transition:opacity .2s linear}.leaflet-fade-anim .leaflet-map-pane .leaflet-popup{opacity:1}.leaflet-zoom-animated{transform-origin:0 0}svg.leaflet-zoom-animated{will-change:transform}.leaflet-zoom-anim .leaflet-zoom-animated{-webkit-transition:-webkit-transform .25s cubic-bezier(0,0,.25,1);-moz-transition:-moz-transform .25s cubic-bezier(0,0,.25,1);transition:transform .25s cubic-bezier(0,0,.25,1)}.leaflet-zoom-anim .leaflet-tile,.leaflet-pan-anim .leaflet-tile{transition:none}.leaflet-zoom-anim .leaflet-zoom-hide{visibility:hidden}.leaflet-interactive{cursor:pointer}.leaflet-grab{cursor:-webkit-grab;cursor:-moz-grab;cursor:grab}.leaflet-crosshair,.leaflet-crosshair .leaflet-interactive{cursor:crosshair}.leaflet-popup-pane,.leaflet-control{cursor:auto}.leaflet-dragging .leaflet-grab,.leaflet-dragging .leaflet-grab .leaflet-interactive,.leaflet-dragging .leaflet-marker-draggable{cursor:move;cursor:-webkit-grabbing;cursor:-moz-grabbing;cursor:grabbing}.leaflet-marker-icon,.leaflet-marker-shadow,.leaflet-image-layer,.leaflet-pane>svg path,.leaflet-tile-container{pointer-events:none}.leaflet-marker-icon.leaflet-interactive,.leaflet-image-layer.leaflet-interactive,.leaflet-pane>svg path.leaflet-interactive,svg.leaflet-image-layer.leaflet-interactive path{pointer-events:visiblePainted;pointer-events:auto}.leaflet-container{outline-offset:1px;background:#ddd}.leaflet-container a{color:#0078a8}.leaflet-zoom-box{background:#ffffff80;border:2px dotted #38f}.leaflet-container{font-family:Helvetica Neue,Arial,Helvetica,sans-serif;font-size:.75rem;line-height:1.5}.leaflet-bar{border-radius:4px;box-shadow:0 1px 5px #000000a6}.leaflet-bar a{text-align:center;color:#000;background-color:#fff;border-bottom:1px solid #ccc;width:26px;height:26px;line-height:26px;text-decoration:none;display:block}.leaflet-bar a,.leaflet-control-layers-toggle{background-position:50%;background-repeat:no-repeat;display:block}.leaflet-bar a:hover,.leaflet-bar a:focus{background-color:#f4f4f4}.leaflet-bar a:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.leaflet-bar a:last-child{border-bottom:none;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.leaflet-bar a.leaflet-disabled{cursor:default;color:#bbb;background-color:#f4f4f4}.leaflet-touch .leaflet-bar a{width:30px;height:30px;line-height:30px}.leaflet-touch .leaflet-bar a:first-child{border-top-left-radius:2px;border-top-right-radius:2px}.leaflet-touch .leaflet-bar a:last-child{border-bottom-right-radius:2px;border-bottom-left-radius:2px}.leaflet-control-zoom-in,.leaflet-control-zoom-out{text-indent:1px;font:700 18px Lucida Console,Monaco,monospace}.leaflet-touch .leaflet-control-zoom-in,.leaflet-touch .leaflet-control-zoom-out{font-size:22px}.leaflet-control-layers{background:#fff;border-radius:5px;box-shadow:0 1px 5px #0006}.leaflet-control-layers-toggle{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAQAAAADQ4RFAAACf0lEQVR4AY1UM3gkARTePdvdoTxXKc+qTl3aU5U6b2Kbkz3Gtq3Zw6ziLGNPzrYx7946Tr6/ee/XeCQ4D3ykPtL5tHno4n0d/h3+xfuWHGLX81cn7r0iTNzjr7LrlxCqPtkbTQEHeqOrTy4Yyt3VCi/IOB0v7rVC7q45Q3Gr5K6jt+3Gl5nCoDD4MtO+j96Wu8atmhGqcNGHObuf8OM/x3AMx38+4Z2sPqzCxRFK2aF2e5Jol56XTLyggAMTL56XOMoS1W4pOyjUcGGQdZxU6qRh7B9Zp+PfpOFlqt0zyDZckPi1ttmIp03jX8gyJ8a/PG2yutpS/Vol7peZIbZcKBAEEheEIAgFbDkz5H6Zrkm2hVWGiXKiF4Ycw0RWKdtC16Q7qe3X4iOMxruonzegJzWaXFrU9utOSsLUmrc0YjeWYjCW4PDMADElpJSSQ0vQvA1Tm6/JlKnqFs1EGyZiFCqnRZTEJJJiKRYzVYzJck2Rm6P4iH+cmSY0YzimYa8l0EtTODFWhcMIMVqdsI2uiTvKmTisIDHJ3od5GILVhBCarCfVRmo4uTjkhrhzkiBV7SsaqS+TzrzM1qpGGUFt28pIySQHR6h7F6KSwGWm97ay+Z+ZqMcEjEWebE7wxCSQwpkhJqoZA5ivCdZDjJepuJ9IQjGGUmuXJdBFUygxVqVsxFsLMbDe8ZbDYVCGKxs+W080max1hFCarCfV+C1KATwcnvE9gRRuMP2prdbWGowm1KB1y+zwMMENkM755cJ2yPDtqhTI6ED1M/82yIDtC/4j4BijjeObflpO9I9MwXTCsSX8jWAFeHr05WoLTJ5G8IQVS/7vwR6ohirYM7f6HzYpogfS3R2OAAAAAElFTkSuQmCC);width:36px;height:36px}.leaflet-retina .leaflet-control-layers-toggle{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAA0CAQAAABvcdNgAAAEsklEQVR4AWL4TydIhpZK1kpWOlg0w3ZXP6D2soBtG42jeI6ZmQTHzAxiTbSJsYLjO9HhP+WOmcuhciVnmHVQcJnp7DFvScowZorad/+V/fVzMdMT2g9Cv9guXGv/7pYOrXh2U+RRR3dSd9JRx6bIFc/ekqHI29JC6pJ5ZEh1yWkhkbcFeSjxgx3L2m1cb1C7bceyxA+CNjT/Ifff+/kDk2u/w/33/IeCMOSaWZ4glosqT3DNnNZQ7Cs58/3Ce5HL78iZH/vKVIaYlqzfdLu8Vi7dnvUbEza5Idt36tquZFldl6N5Z/POLof0XLK61mZCmJSWjVF9tEjUluu74IUXvgttuVIHE7YxSkaYhJZam7yiM9Pv82JYfl9nptxZaxMJE4YSPty+vF0+Y2up9d3wwijfjZbabqm/3bZ9ecKHsiGmRflnn1MW4pjHf9oLufyn2z3y1D6n8g8TZhxyzipLNPnAUpsOiuWimg52psrTZYnOWYNDTMuWBWa0tJb4rgq1UvmutpaYEbZlwU3CLJm/ayYjHW5/h7xWLn9Hh1vepDkyf7dE7MtT5LR4e7yYpHrkhOUpEfssBLq2pPhAqoSWKUkk7EDqkmK6RrCEzqDjhNDWNE+XSMvkJRDWlZTmCW0l0PHQGRZY5t1L83kT0Y3l2SItk5JAWHl2dCOBm+fPu3fo5/3v61RMCO9Jx2EEYYhb0rmNQMX/vm7gqOEJLcXTGw3CAuRNeyaPWwjR8PRqKQ1PDA/dpv+on9Shox52WFnx0KY8onHayrJzm87i5h9xGw/tfkev0jGsQizqezUKjk12hBMKJ4kbCqGPVNXudyyrShovGw5CgxsRICxF6aRmSjlBnHRzg7Gx8fKqEubI2rahQYdR1YgDIRQO7JvQyD52hoIQx0mxa0ODtW2Iozn1le2iIRdzwWewedyZzewidueOGqlsn1MvcnQpuVwLGG3/IR1hIKxCjelIDZ8ldqWz25jWAsnldEnK0Zxro19TGVb2ffIZEsIO89EIEDvKMPrzmBOQcKQ+rroye6NgRRxqR4U8EAkz0CL6uSGOm6KQCdWjvjRiSP1BPalCRS5iQYiEIvxuBMJEWgzSoHADcVMuN7IuqqTeyUPq22qFimFtxDyBBJEwNyt6TM88blFHao/6tWWhuuOM4SAK4EI4QmFHA+SEyWlp4EQoJ13cYGzMu7yszEIBOm2rVmHUNqwAIQabISNMRstmdhNWcFLsSm+0tjJH1MdRxO5Nx0WDMhCtgD6OKgZeljJqJKc9po8juskR9XN0Y1lZ3mWjLR9JCO1jRDMd0fpYC2VnvjBSEFg7wBENc0R9HFlb0xvF1+TBEpF68d+DHR6IOWVv2BECtxo46hOFUBd/APU57WIoEwJhIi2CdpyZX0m93BZicktMj1AS9dClteUFAUNUIEygRZCtik5zSxI9MubTBH1GOiHsiLJ3OCoSZkILa9PxiN0EbvhsAo8tdAf9Seepd36lGWHmtNANTv5Jd0z4QYyeo/UEJqxKRpg5LZx6btLPsOaEmdMyxYdlc8LMaJnikDlhclqmPiQnTEpLUIZEwkRagjYkEibQErwhkTAKCLQEbUgkzJQWc/0PstHHcfEdQ+UAAAAASUVORK5CYII=);background-size:26px 26px}.leaflet-touch .leaflet-control-layers-toggle{width:44px;height:44px}.leaflet-control-layers .leaflet-control-layers-list,.leaflet-control-layers-expanded .leaflet-control-layers-toggle{display:none}.leaflet-control-layers-expanded .leaflet-control-layers-list{display:block;position:relative}.leaflet-control-layers-expanded{color:#333;background:#fff;padding:6px 10px 6px 6px}.leaflet-control-layers-scrollbar{padding-right:5px;overflow:hidden scroll}.leaflet-control-layers-selector{margin-top:2px;position:relative;top:1px}.leaflet-control-layers label{font-size:1.08333em;display:block}.leaflet-control-layers-separator{border-top:1px solid #ddd;height:0;margin:5px -10px 5px -6px}.leaflet-default-icon-path{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAApCAYAAADAk4LOAAAFgUlEQVR4Aa1XA5BjWRTN2oW17d3YaZtr2962HUzbDNpjszW24mRt28p47v7zq/bXZtrp/lWnXr337j3nPCe85NcypgSFdugCpW5YoDAMRaIMqRi6aKq5E3YqDQO3qAwjVWrD8Ncq/RBpykd8oZUb/kaJutow8r1aP9II0WmLKLIsJyv1w/kqw9Ch2MYdB++12Onxee/QMwvf4/Dk/Lfp/i4nxTXtOoQ4pW5Aj7wpici1A9erdAN2OH64x8OSP9j3Ft3b7aWkTg/Fm91siTra0f9on5sQr9INejH6CUUUpavjFNq1B+Oadhxmnfa8RfEmN8VNAsQhPqF55xHkMzz3jSmChWU6f7/XZKNH+9+hBLOHYozuKQPxyMPUKkrX/K0uWnfFaJGS1QPRtZsOPtr3NsW0uyh6NNCOkU3Yz+bXbT3I8G3xE5EXLXtCXbbqwCO9zPQYPRTZ5vIDXD7U+w7rFDEoUUf7ibHIR4y6bLVPXrz8JVZEql13trxwue/uDivd3fkWRbS6/IA2bID4uk0UpF1N8qLlbBlXs4Ee7HLTfV1j54APvODnSfOWBqtKVvjgLKzF5YdEk5ewRkGlK0i33Eofffc7HT56jD7/6U+qH3Cx7SBLNntH5YIPvODnyfIXZYRVDPqgHtLs5ABHD3YzLuespb7t79FY34DjMwrVrcTuwlT55YMPvOBnRrJ4VXTdNnYug5ucHLBjEpt30701A3Ts+HEa73u6dT3FNWwflY86eMHPk+Yu+i6pzUpRrW7SNDg5JHR4KapmM5Wv2E8Tfcb1HoqqHMHU+uWDD7zg54mz5/2BSnizi9T1Dg4QQXLToGNCkb6tb1NU+QAlGr1++eADrzhn/u8Q2YZhQVlZ5+CAOtqfbhmaUCS1ezNFVm2imDbPmPng5wmz+gwh+oHDce0eUtQ6OGDIyR0uUhUsoO3vfDmmgOezH0mZN59x7MBi++WDL1g/eEiU3avlidO671bkLfwbw5XV2P8Pzo0ydy4t2/0eu33xYSOMOD8hTf4CrBtGMSoXfPLchX+J0ruSePw3LZeK0juPJbYzrhkH0io7B3k164hiGvawhOKMLkrQLyVpZg8rHFW7E2uHOL888IBPlNZ1FPzstSJM694fWr6RwpvcJK60+0HCILTBzZLFNdtAzJaohze60T8qBzyh5ZuOg5e7uwQppofEmf2++DYvmySqGBuKaicF1blQjhuHdvCIMvp8whTTfZzI7RldpwtSzL+F1+wkdZ2TBOW2gIF88PBTzD/gpeREAMEbxnJcaJHNHrpzji0gQCS6hdkEeYt9DF/2qPcEC8RM28Hwmr3sdNyht00byAut2k3gufWNtgtOEOFGUwcXWNDbdNbpgBGxEvKkOQsxivJx33iow0Vw5S6SVTrpVq11ysA2Rp7gTfPfktc6zhtXBBC+adRLshf6sG2RfHPZ5EAc4sVZ83yCN00Fk/4kggu40ZTvIEm5g24qtU4KjBrx/BTTH8ifVASAG7gKrnWxJDcU7x8X6Ecczhm3o6YicvsLXWfh3Ch1W0k8x0nXF+0fFxgt4phz8QvypiwCCFKMqXCnqXExjq10beH+UUA7+nG6mdG/Pu0f3LgFcGrl2s0kNNjpmoJ9o4B29CMO8dMT4Q5ox8uitF6fqsrJOr8qnwNbRzv6hSnG5wP+64C7h9lp30hKNtKdWjtdkbuPA19nJ7Tz3zR/ibgARbhb4AlhavcBebmTHcFl2fvYEnW0ox9xMxKBS8btJ+KiEbq9zA4RthQXDhPa0T9TEe69gWupwc6uBUphquXgf+/FrIjweHQS4/pduMe5ERUMHUd9xv8ZR98CxkS4F2n3EUrUZ10EYNw7BWm9x1GiPssi3GgiGRDKWRYZfXlON+dfNbM+GgIwYdwAAAAASUVORK5CYII=)}.leaflet-container .leaflet-control-attribution{background:#fffc;margin:0}.leaflet-control-attribution,.leaflet-control-scale-line{color:#333;padding:0 5px;line-height:1.4}.leaflet-control-attribution a{text-decoration:none}.leaflet-control-attribution a:hover,.leaflet-control-attribution a:focus{text-decoration:underline}.leaflet-attribution-flag{width:1em;height:.6669em;vertical-align:baseline!important;display:inline!important}.leaflet-left .leaflet-control-scale{margin-left:5px}.leaflet-bottom .leaflet-control-scale{margin-bottom:5px}.leaflet-control-scale-line{white-space:nowrap;box-sizing:border-box;text-shadow:1px 1px #fff;background:#fffc;border:2px solid #777;border-top:none;padding:2px 5px 1px;line-height:1.1}.leaflet-control-scale-line:not(:first-child){border-top:2px solid #777;border-bottom:none;margin-top:-2px}.leaflet-control-scale-line:not(:first-child):not(:last-child){border-bottom:2px solid #777}.leaflet-touch .leaflet-control-attribution,.leaflet-touch .leaflet-control-layers,.leaflet-touch .leaflet-bar{box-shadow:none}.leaflet-touch .leaflet-control-layers,.leaflet-touch .leaflet-bar{background-clip:padding-box;border:2px solid #0003}.leaflet-popup{text-align:center;margin-bottom:20px;position:absolute}.leaflet-popup-content-wrapper{text-align:left;border-radius:12px;padding:1px}.leaflet-popup-content{min-height:1px;margin:13px 24px 13px 20px;font-size:1.08333em;line-height:1.3}.leaflet-popup-content p{margin:1.3em 0}.leaflet-popup-tip-container{pointer-events:none;width:40px;height:20px;margin-top:-1px;margin-left:-20px;position:absolute;left:50%;overflow:hidden}.leaflet-popup-tip{pointer-events:auto;width:17px;height:17px;margin:-10px auto 0;padding:1px;transform:rotate(45deg)}.leaflet-popup-content-wrapper,.leaflet-popup-tip{color:#333;background:#fff;box-shadow:0 3px 14px #0006}.leaflet-container a.leaflet-popup-close-button{text-align:center;color:#757575;background:0 0;border:none;width:24px;height:24px;font:16px/24px Tahoma,Verdana,sans-serif;text-decoration:none;position:absolute;top:0;right:0}.leaflet-container a.leaflet-popup-close-button:hover,.leaflet-container a.leaflet-popup-close-button:focus{color:#585858}.leaflet-popup-scrolled{overflow:auto}.leaflet-oldie .leaflet-popup-content-wrapper{-ms-zoom:1}.leaflet-oldie .leaflet-popup-tip{-ms-filter:"progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";width:24px;filter:progid:DXImageTransform.Microsoft.Matrix(M11=.707107, M12=.707107, M21=-.707107, M22=.707107);margin:0 auto}.leaflet-oldie .leaflet-control-zoom,.leaflet-oldie .leaflet-control-layers,.leaflet-oldie .leaflet-popup-content-wrapper,.leaflet-oldie .leaflet-popup-tip{border:1px solid #999}.leaflet-div-icon{background:#fff;border:1px solid #666}.leaflet-tooltip{color:#222;white-space:nowrap;-webkit-user-select:none;user-select:none;pointer-events:none;background-color:#fff;border:1px solid #fff;border-radius:3px;padding:6px;position:absolute;box-shadow:0 1px 3px #0006}.leaflet-tooltip.leaflet-interactive{cursor:pointer;pointer-events:auto}.leaflet-tooltip-top:before,.leaflet-tooltip-bottom:before,.leaflet-tooltip-left:before,.leaflet-tooltip-right:before{pointer-events:none;content:"";background:0 0;border:6px solid #0000;position:absolute}.leaflet-tooltip-bottom{margin-top:6px}.leaflet-tooltip-top{margin-top:-6px}.leaflet-tooltip-bottom:before,.leaflet-tooltip-top:before{margin-left:-6px;left:50%}.leaflet-tooltip-top:before{border-top-color:#fff;margin-bottom:-12px;bottom:0}.leaflet-tooltip-bottom:before{border-bottom-color:#fff;margin-top:-12px;margin-left:-6px;top:0}.leaflet-tooltip-left{margin-left:-6px}.leaflet-tooltip-right{margin-left:6px}.leaflet-tooltip-left:before,.leaflet-tooltip-right:before{margin-top:-6px;top:50%}.leaflet-tooltip-left:before{border-left-color:#fff;margin-right:-12px;right:0}.leaflet-tooltip-right:before{border-right-color:#fff;margin-left:-12px;left:0}@media print{.leaflet-control{-webkit-print-color-adjust:exact;print-color-adjust:exact}} +@font-face{font-display:block;font-family:remixicon;src:url(../fonts/remixicon/remixicon.woff2?t=1734404658139)format("woff2")}@font-face{font-display:block;font-family:IBM Plex Sans;font-style:normal;font-weight:400;src:url(../fonts/ibm-plex-sans/ibm-plex-sans-v23-cyrillic_latin_latin-ext-regular.woff2)format("woff2")}@font-face{font-display:block;font-family:IBM Plex Sans;font-style:italic;font-weight:400;src:url(../fonts/ibm-plex-sans/ibm-plex-sans-v23-cyrillic_latin_latin-ext-italic.woff2)format("woff2")}@font-face{font-display:block;font-family:IBM Plex Sans;font-style:normal;font-weight:600;src:url(../fonts/ibm-plex-sans/ibm-plex-sans-v23-cyrillic_latin_latin-ext-600.woff2)format("woff2")}@font-face{font-display:block;font-family:IBM Plex Sans;font-style:italic;font-weight:600;src:url(../fonts/ibm-plex-sans/ibm-plex-sans-v23-cyrillic_latin_latin-ext-600italic.woff2)format("woff2")}@font-face{font-display:swap;font-family:IBM Plex Mono;font-style:normal;font-weight:400;src:url(../fonts/ibm-plex-mono/ibm-plex-mono-v20-latin-regular.woff2)format("woff2")}@font-face{font-display:swap;font-family:IBM Plex Mono;font-style:normal;font-weight:600;src:url(../fonts/ibm-plex-mono/ibm-plex-mono-v20-latin-600.woff2)format("woff2")}.ri-24-hours-fill:before{content:""}.ri-24-hours-line:before{content:""}.ri-4k-fill:before{content:""}.ri-4k-line:before{content:""}.ri-a-b:before{content:""}.ri-account-box-fill:before{content:""}.ri-account-box-line:before{content:""}.ri-account-circle-fill:before{content:""}.ri-account-circle-line:before{content:""}.ri-account-pin-box-fill:before{content:""}.ri-account-pin-box-line:before{content:""}.ri-account-pin-circle-fill:before{content:""}.ri-account-pin-circle-line:before{content:""}.ri-add-box-fill:before{content:""}.ri-add-box-line:before{content:""}.ri-add-circle-fill:before{content:""}.ri-add-circle-line:before{content:""}.ri-add-fill:before{content:""}.ri-add-line:before{content:""}.ri-admin-fill:before{content:""}.ri-admin-line:before{content:""}.ri-advertisement-fill:before{content:""}.ri-advertisement-line:before{content:""}.ri-airplay-fill:before{content:""}.ri-airplay-line:before{content:""}.ri-alarm-fill:before{content:""}.ri-alarm-line:before{content:""}.ri-alarm-warning-fill:before{content:""}.ri-alarm-warning-line:before{content:""}.ri-album-fill:before{content:""}.ri-album-line:before{content:""}.ri-alert-fill:before{content:""}.ri-alert-line:before{content:""}.ri-aliens-fill:before{content:""}.ri-aliens-line:before{content:""}.ri-align-bottom:before{content:""}.ri-align-center:before{content:""}.ri-align-justify:before{content:""}.ri-align-left:before{content:""}.ri-align-right:before{content:""}.ri-align-top:before{content:""}.ri-align-vertically:before{content:""}.ri-alipay-fill:before{content:""}.ri-alipay-line:before{content:""}.ri-amazon-fill:before{content:""}.ri-amazon-line:before{content:""}.ri-anchor-fill:before{content:""}.ri-anchor-line:before{content:""}.ri-ancient-gate-fill:before{content:""}.ri-ancient-gate-line:before{content:""}.ri-ancient-pavilion-fill:before{content:""}.ri-ancient-pavilion-line:before{content:""}.ri-android-fill:before{content:""}.ri-android-line:before{content:""}.ri-angularjs-fill:before{content:""}.ri-angularjs-line:before{content:""}.ri-anticlockwise-2-fill:before{content:""}.ri-anticlockwise-2-line:before{content:""}.ri-anticlockwise-fill:before{content:""}.ri-anticlockwise-line:before{content:""}.ri-app-store-fill:before{content:""}.ri-app-store-line:before{content:""}.ri-apple-fill:before{content:""}.ri-apple-line:before{content:""}.ri-apps-2-fill:before{content:""}.ri-apps-2-line:before{content:""}.ri-apps-fill:before{content:""}.ri-apps-line:before{content:""}.ri-archive-drawer-fill:before{content:""}.ri-archive-drawer-line:before{content:""}.ri-archive-fill:before{content:""}.ri-archive-line:before{content:""}.ri-arrow-down-circle-fill:before{content:""}.ri-arrow-down-circle-line:before{content:""}.ri-arrow-down-fill:before{content:""}.ri-arrow-down-line:before{content:""}.ri-arrow-down-s-fill:before{content:""}.ri-arrow-down-s-line:before{content:""}.ri-arrow-drop-down-fill:before{content:""}.ri-arrow-drop-down-line:before{content:""}.ri-arrow-drop-left-fill:before{content:""}.ri-arrow-drop-left-line:before{content:""}.ri-arrow-drop-right-fill:before{content:""}.ri-arrow-drop-right-line:before{content:""}.ri-arrow-drop-up-fill:before{content:""}.ri-arrow-drop-up-line:before{content:""}.ri-arrow-go-back-fill:before{content:""}.ri-arrow-go-back-line:before{content:""}.ri-arrow-go-forward-fill:before{content:""}.ri-arrow-go-forward-line:before{content:""}.ri-arrow-left-circle-fill:before{content:""}.ri-arrow-left-circle-line:before{content:""}.ri-arrow-left-down-fill:before{content:""}.ri-arrow-left-down-line:before{content:""}.ri-arrow-left-fill:before{content:""}.ri-arrow-left-line:before{content:""}.ri-arrow-left-right-fill:before{content:""}.ri-arrow-left-right-line:before{content:""}.ri-arrow-left-s-fill:before{content:""}.ri-arrow-left-s-line:before{content:""}.ri-arrow-left-up-fill:before{content:""}.ri-arrow-left-up-line:before{content:""}.ri-arrow-right-circle-fill:before{content:""}.ri-arrow-right-circle-line:before{content:""}.ri-arrow-right-down-fill:before{content:""}.ri-arrow-right-down-line:before{content:""}.ri-arrow-right-fill:before{content:""}.ri-arrow-right-line:before{content:""}.ri-arrow-right-s-fill:before{content:""}.ri-arrow-right-s-line:before{content:""}.ri-arrow-right-up-fill:before{content:""}.ri-arrow-right-up-line:before{content:""}.ri-arrow-up-circle-fill:before{content:""}.ri-arrow-up-circle-line:before{content:""}.ri-arrow-up-down-fill:before{content:""}.ri-arrow-up-down-line:before{content:""}.ri-arrow-up-fill:before{content:""}.ri-arrow-up-line:before{content:""}.ri-arrow-up-s-fill:before{content:""}.ri-arrow-up-s-line:before{content:""}.ri-artboard-2-fill:before{content:""}.ri-artboard-2-line:before{content:""}.ri-artboard-fill:before{content:""}.ri-artboard-line:before{content:""}.ri-article-fill:before{content:""}.ri-article-line:before{content:""}.ri-aspect-ratio-fill:before{content:""}.ri-aspect-ratio-line:before{content:""}.ri-asterisk:before{content:""}.ri-at-fill:before{content:""}.ri-at-line:before{content:""}.ri-attachment-2:before{content:""}.ri-attachment-fill:before{content:""}.ri-attachment-line:before{content:""}.ri-auction-fill:before{content:""}.ri-auction-line:before{content:""}.ri-award-fill:before{content:""}.ri-award-line:before{content:""}.ri-baidu-fill:before{content:""}.ri-baidu-line:before{content:""}.ri-ball-pen-fill:before{content:""}.ri-ball-pen-line:before{content:""}.ri-bank-card-2-fill:before{content:""}.ri-bank-card-2-line:before{content:""}.ri-bank-card-fill:before{content:""}.ri-bank-card-line:before{content:""}.ri-bank-fill:before{content:""}.ri-bank-line:before{content:""}.ri-bar-chart-2-fill:before{content:""}.ri-bar-chart-2-line:before{content:""}.ri-bar-chart-box-fill:before{content:""}.ri-bar-chart-box-line:before{content:""}.ri-bar-chart-fill:before{content:""}.ri-bar-chart-grouped-fill:before{content:""}.ri-bar-chart-grouped-line:before{content:""}.ri-bar-chart-horizontal-fill:before{content:""}.ri-bar-chart-horizontal-line:before{content:""}.ri-bar-chart-line:before{content:""}.ri-barcode-box-fill:before{content:""}.ri-barcode-box-line:before{content:""}.ri-barcode-fill:before{content:""}.ri-barcode-line:before{content:""}.ri-barricade-fill:before{content:""}.ri-barricade-line:before{content:""}.ri-base-station-fill:before{content:""}.ri-base-station-line:before{content:""}.ri-basketball-fill:before{content:""}.ri-basketball-line:before{content:""}.ri-battery-2-charge-fill:before{content:""}.ri-battery-2-charge-line:before{content:""}.ri-battery-2-fill:before{content:""}.ri-battery-2-line:before{content:""}.ri-battery-charge-fill:before{content:""}.ri-battery-charge-line:before{content:""}.ri-battery-fill:before{content:""}.ri-battery-line:before{content:""}.ri-battery-low-fill:before{content:""}.ri-battery-low-line:before{content:""}.ri-battery-saver-fill:before{content:""}.ri-battery-saver-line:before{content:""}.ri-battery-share-fill:before{content:""}.ri-battery-share-line:before{content:""}.ri-bear-smile-fill:before{content:""}.ri-bear-smile-line:before{content:""}.ri-behance-fill:before{content:""}.ri-behance-line:before{content:""}.ri-bell-fill:before{content:""}.ri-bell-line:before{content:""}.ri-bike-fill:before{content:""}.ri-bike-line:before{content:""}.ri-bilibili-fill:before{content:""}.ri-bilibili-line:before{content:""}.ri-bill-fill:before{content:""}.ri-bill-line:before{content:""}.ri-billiards-fill:before{content:""}.ri-billiards-line:before{content:""}.ri-bit-coin-fill:before{content:""}.ri-bit-coin-line:before{content:""}.ri-blaze-fill:before{content:""}.ri-blaze-line:before{content:""}.ri-bluetooth-connect-fill:before{content:""}.ri-bluetooth-connect-line:before{content:""}.ri-bluetooth-fill:before{content:""}.ri-bluetooth-line:before{content:""}.ri-blur-off-fill:before{content:""}.ri-blur-off-line:before{content:""}.ri-body-scan-fill:before{content:""}.ri-body-scan-line:before{content:""}.ri-bold:before{content:""}.ri-book-2-fill:before{content:""}.ri-book-2-line:before{content:""}.ri-book-3-fill:before{content:""}.ri-book-3-line:before{content:""}.ri-book-fill:before{content:""}.ri-book-line:before{content:""}.ri-book-marked-fill:before{content:""}.ri-book-marked-line:before{content:""}.ri-book-open-fill:before{content:""}.ri-book-open-line:before{content:""}.ri-book-read-fill:before{content:""}.ri-book-read-line:before{content:""}.ri-booklet-fill:before{content:""}.ri-booklet-line:before{content:""}.ri-bookmark-2-fill:before{content:""}.ri-bookmark-2-line:before{content:""}.ri-bookmark-3-fill:before{content:""}.ri-bookmark-3-line:before{content:""}.ri-bookmark-fill:before{content:""}.ri-bookmark-line:before{content:""}.ri-boxing-fill:before{content:""}.ri-boxing-line:before{content:""}.ri-braces-fill:before{content:""}.ri-braces-line:before{content:""}.ri-brackets-fill:before{content:""}.ri-brackets-line:before{content:""}.ri-briefcase-2-fill:before{content:""}.ri-briefcase-2-line:before{content:""}.ri-briefcase-3-fill:before{content:""}.ri-briefcase-3-line:before{content:""}.ri-briefcase-4-fill:before{content:""}.ri-briefcase-4-line:before{content:""}.ri-briefcase-5-fill:before{content:""}.ri-briefcase-5-line:before{content:""}.ri-briefcase-fill:before{content:""}.ri-briefcase-line:before{content:""}.ri-bring-forward:before{content:""}.ri-bring-to-front:before{content:""}.ri-broadcast-fill:before{content:""}.ri-broadcast-line:before{content:""}.ri-brush-2-fill:before{content:""}.ri-brush-2-line:before{content:""}.ri-brush-3-fill:before{content:""}.ri-brush-3-line:before{content:""}.ri-brush-4-fill:before{content:""}.ri-brush-4-line:before{content:""}.ri-brush-fill:before{content:""}.ri-brush-line:before{content:""}.ri-bubble-chart-fill:before{content:""}.ri-bubble-chart-line:before{content:""}.ri-bug-2-fill:before{content:""}.ri-bug-2-line:before{content:""}.ri-bug-fill:before{content:""}.ri-bug-line:before{content:""}.ri-building-2-fill:before{content:""}.ri-building-2-line:before{content:""}.ri-building-3-fill:before{content:""}.ri-building-3-line:before{content:""}.ri-building-4-fill:before{content:""}.ri-building-4-line:before{content:""}.ri-building-fill:before{content:""}.ri-building-line:before{content:""}.ri-bus-2-fill:before{content:""}.ri-bus-2-line:before{content:""}.ri-bus-fill:before{content:""}.ri-bus-line:before{content:""}.ri-bus-wifi-fill:before{content:""}.ri-bus-wifi-line:before{content:""}.ri-cactus-fill:before{content:""}.ri-cactus-line:before{content:""}.ri-cake-2-fill:before{content:""}.ri-cake-2-line:before{content:""}.ri-cake-3-fill:before{content:""}.ri-cake-3-line:before{content:""}.ri-cake-fill:before{content:""}.ri-cake-line:before{content:""}.ri-calculator-fill:before{content:""}.ri-calculator-line:before{content:""}.ri-calendar-2-fill:before{content:""}.ri-calendar-2-line:before{content:""}.ri-calendar-check-fill:before{content:""}.ri-calendar-check-line:before{content:""}.ri-calendar-event-fill:before{content:""}.ri-calendar-event-line:before{content:""}.ri-calendar-fill:before{content:""}.ri-calendar-line:before{content:""}.ri-calendar-todo-fill:before{content:""}.ri-calendar-todo-line:before{content:""}.ri-camera-2-fill:before{content:""}.ri-camera-2-line:before{content:""}.ri-camera-3-fill:before{content:""}.ri-camera-3-line:before{content:""}.ri-camera-fill:before{content:""}.ri-camera-lens-fill:before{content:""}.ri-camera-lens-line:before{content:""}.ri-camera-line:before{content:""}.ri-camera-off-fill:before{content:""}.ri-camera-off-line:before{content:""}.ri-camera-switch-fill:before{content:""}.ri-camera-switch-line:before{content:""}.ri-capsule-fill:before{content:""}.ri-capsule-line:before{content:""}.ri-car-fill:before{content:""}.ri-car-line:before{content:""}.ri-car-washing-fill:before{content:""}.ri-car-washing-line:before{content:""}.ri-caravan-fill:before{content:""}.ri-caravan-line:before{content:""}.ri-cast-fill:before{content:""}.ri-cast-line:before{content:""}.ri-cellphone-fill:before{content:""}.ri-cellphone-line:before{content:""}.ri-celsius-fill:before{content:""}.ri-celsius-line:before{content:""}.ri-centos-fill:before{content:""}.ri-centos-line:before{content:""}.ri-character-recognition-fill:before{content:""}.ri-character-recognition-line:before{content:""}.ri-charging-pile-2-fill:before{content:""}.ri-charging-pile-2-line:before{content:""}.ri-charging-pile-fill:before{content:""}.ri-charging-pile-line:before{content:""}.ri-chat-1-fill:before{content:""}.ri-chat-1-line:before{content:""}.ri-chat-2-fill:before{content:""}.ri-chat-2-line:before{content:""}.ri-chat-3-fill:before{content:""}.ri-chat-3-line:before{content:""}.ri-chat-4-fill:before{content:""}.ri-chat-4-line:before{content:""}.ri-chat-check-fill:before{content:""}.ri-chat-check-line:before{content:""}.ri-chat-delete-fill:before{content:""}.ri-chat-delete-line:before{content:""}.ri-chat-download-fill:before{content:""}.ri-chat-download-line:before{content:""}.ri-chat-follow-up-fill:before{content:""}.ri-chat-follow-up-line:before{content:""}.ri-chat-forward-fill:before{content:""}.ri-chat-forward-line:before{content:""}.ri-chat-heart-fill:before{content:""}.ri-chat-heart-line:before{content:""}.ri-chat-history-fill:before{content:""}.ri-chat-history-line:before{content:""}.ri-chat-new-fill:before{content:""}.ri-chat-new-line:before{content:""}.ri-chat-off-fill:before{content:""}.ri-chat-off-line:before{content:""}.ri-chat-poll-fill:before{content:""}.ri-chat-poll-line:before{content:""}.ri-chat-private-fill:before{content:""}.ri-chat-private-line:before{content:""}.ri-chat-quote-fill:before{content:""}.ri-chat-quote-line:before{content:""}.ri-chat-settings-fill:before{content:""}.ri-chat-settings-line:before{content:""}.ri-chat-smile-2-fill:before{content:""}.ri-chat-smile-2-line:before{content:""}.ri-chat-smile-3-fill:before{content:""}.ri-chat-smile-3-line:before{content:""}.ri-chat-smile-fill:before{content:""}.ri-chat-smile-line:before{content:""}.ri-chat-upload-fill:before{content:""}.ri-chat-upload-line:before{content:""}.ri-chat-voice-fill:before{content:""}.ri-chat-voice-line:before{content:""}.ri-check-double-fill:before{content:""}.ri-check-double-line:before{content:""}.ri-check-fill:before{content:""}.ri-check-line:before{content:""}.ri-checkbox-blank-circle-fill:before{content:""}.ri-checkbox-blank-circle-line:before{content:""}.ri-checkbox-blank-fill:before{content:""}.ri-checkbox-blank-line:before{content:""}.ri-checkbox-circle-fill:before{content:""}.ri-checkbox-circle-line:before{content:""}.ri-checkbox-fill:before{content:""}.ri-checkbox-indeterminate-fill:before{content:""}.ri-checkbox-indeterminate-line:before{content:""}.ri-checkbox-line:before{content:""}.ri-checkbox-multiple-blank-fill:before{content:""}.ri-checkbox-multiple-blank-line:before{content:""}.ri-checkbox-multiple-fill:before{content:""}.ri-checkbox-multiple-line:before{content:""}.ri-china-railway-fill:before{content:""}.ri-china-railway-line:before{content:""}.ri-chrome-fill:before{content:""}.ri-chrome-line:before{content:""}.ri-clapperboard-fill:before{content:""}.ri-clapperboard-line:before{content:""}.ri-clipboard-fill:before{content:""}.ri-clipboard-line:before{content:""}.ri-clockwise-2-fill:before{content:""}.ri-clockwise-2-line:before{content:""}.ri-clockwise-fill:before{content:""}.ri-clockwise-line:before{content:""}.ri-close-circle-fill:before{content:""}.ri-close-circle-line:before{content:""}.ri-close-fill:before{content:""}.ri-close-line:before{content:""}.ri-closed-captioning-fill:before{content:""}.ri-closed-captioning-line:before{content:""}.ri-cloud-fill:before{content:""}.ri-cloud-line:before{content:""}.ri-cloud-off-fill:before{content:""}.ri-cloud-off-line:before{content:""}.ri-cloud-windy-fill:before{content:""}.ri-cloud-windy-line:before{content:""}.ri-cloudy-2-fill:before{content:""}.ri-cloudy-2-line:before{content:""}.ri-cloudy-fill:before{content:""}.ri-cloudy-line:before{content:""}.ri-code-box-fill:before{content:""}.ri-code-box-line:before{content:""}.ri-code-fill:before{content:""}.ri-code-line:before{content:""}.ri-code-s-fill:before{content:""}.ri-code-s-line:before{content:""}.ri-code-s-slash-fill:before{content:""}.ri-code-s-slash-line:before{content:""}.ri-code-view:before{content:""}.ri-codepen-fill:before{content:""}.ri-codepen-line:before{content:""}.ri-coin-fill:before{content:""}.ri-coin-line:before{content:""}.ri-coins-fill:before{content:""}.ri-coins-line:before{content:""}.ri-collage-fill:before{content:""}.ri-collage-line:before{content:""}.ri-command-fill:before{content:""}.ri-command-line:before{content:""}.ri-community-fill:before{content:""}.ri-community-line:before{content:""}.ri-compass-2-fill:before{content:""}.ri-compass-2-line:before{content:""}.ri-compass-3-fill:before{content:""}.ri-compass-3-line:before{content:""}.ri-compass-4-fill:before{content:""}.ri-compass-4-line:before{content:""}.ri-compass-discover-fill:before{content:""}.ri-compass-discover-line:before{content:""}.ri-compass-fill:before{content:""}.ri-compass-line:before{content:""}.ri-compasses-2-fill:before{content:""}.ri-compasses-2-line:before{content:""}.ri-compasses-fill:before{content:""}.ri-compasses-line:before{content:""}.ri-computer-fill:before{content:""}.ri-computer-line:before{content:""}.ri-contacts-book-2-fill:before{content:""}.ri-contacts-book-2-line:before{content:""}.ri-contacts-book-fill:before{content:""}.ri-contacts-book-line:before{content:""}.ri-contacts-book-upload-fill:before{content:""}.ri-contacts-book-upload-line:before{content:""}.ri-contacts-fill:before{content:""}.ri-contacts-line:before{content:""}.ri-contrast-2-fill:before{content:""}.ri-contrast-2-line:before{content:""}.ri-contrast-drop-2-fill:before{content:""}.ri-contrast-drop-2-line:before{content:""}.ri-contrast-drop-fill:before{content:""}.ri-contrast-drop-line:before{content:""}.ri-contrast-fill:before{content:""}.ri-contrast-line:before{content:""}.ri-copper-coin-fill:before{content:""}.ri-copper-coin-line:before{content:""}.ri-copper-diamond-fill:before{content:""}.ri-copper-diamond-line:before{content:""}.ri-copyleft-fill:before{content:""}.ri-copyleft-line:before{content:""}.ri-copyright-fill:before{content:""}.ri-copyright-line:before{content:""}.ri-coreos-fill:before{content:""}.ri-coreos-line:before{content:""}.ri-coupon-2-fill:before{content:""}.ri-coupon-2-line:before{content:""}.ri-coupon-3-fill:before{content:""}.ri-coupon-3-line:before{content:""}.ri-coupon-4-fill:before{content:""}.ri-coupon-4-line:before{content:""}.ri-coupon-5-fill:before{content:""}.ri-coupon-5-line:before{content:""}.ri-coupon-fill:before{content:""}.ri-coupon-line:before{content:""}.ri-cpu-fill:before{content:""}.ri-cpu-line:before{content:""}.ri-creative-commons-by-fill:before{content:""}.ri-creative-commons-by-line:before{content:""}.ri-creative-commons-fill:before{content:""}.ri-creative-commons-line:before{content:""}.ri-creative-commons-nc-fill:before{content:""}.ri-creative-commons-nc-line:before{content:""}.ri-creative-commons-nd-fill:before{content:""}.ri-creative-commons-nd-line:before{content:""}.ri-creative-commons-sa-fill:before{content:""}.ri-creative-commons-sa-line:before{content:""}.ri-creative-commons-zero-fill:before{content:""}.ri-creative-commons-zero-line:before{content:""}.ri-criminal-fill:before{content:""}.ri-criminal-line:before{content:""}.ri-crop-2-fill:before{content:""}.ri-crop-2-line:before{content:""}.ri-crop-fill:before{content:""}.ri-crop-line:before{content:""}.ri-css3-fill:before{content:""}.ri-css3-line:before{content:""}.ri-cup-fill:before{content:""}.ri-cup-line:before{content:""}.ri-currency-fill:before{content:""}.ri-currency-line:before{content:""}.ri-cursor-fill:before{content:""}.ri-cursor-line:before{content:""}.ri-customer-service-2-fill:before{content:""}.ri-customer-service-2-line:before{content:""}.ri-customer-service-fill:before{content:""}.ri-customer-service-line:before{content:""}.ri-dashboard-2-fill:before{content:""}.ri-dashboard-2-line:before{content:""}.ri-dashboard-3-fill:before{content:""}.ri-dashboard-3-line:before{content:""}.ri-dashboard-fill:before{content:""}.ri-dashboard-line:before{content:""}.ri-database-2-fill:before{content:""}.ri-database-2-line:before{content:""}.ri-database-fill:before{content:""}.ri-database-line:before{content:""}.ri-delete-back-2-fill:before{content:""}.ri-delete-back-2-line:before{content:""}.ri-delete-back-fill:before{content:""}.ri-delete-back-line:before{content:""}.ri-delete-bin-2-fill:before{content:""}.ri-delete-bin-2-line:before{content:""}.ri-delete-bin-3-fill:before{content:""}.ri-delete-bin-3-line:before{content:""}.ri-delete-bin-4-fill:before{content:""}.ri-delete-bin-4-line:before{content:""}.ri-delete-bin-5-fill:before{content:""}.ri-delete-bin-5-line:before{content:""}.ri-delete-bin-6-fill:before{content:""}.ri-delete-bin-6-line:before{content:""}.ri-delete-bin-7-fill:before{content:""}.ri-delete-bin-7-line:before{content:""}.ri-delete-bin-fill:before{content:""}.ri-delete-bin-line:before{content:""}.ri-delete-column:before{content:""}.ri-delete-row:before{content:""}.ri-device-fill:before{content:""}.ri-device-line:before{content:""}.ri-device-recover-fill:before{content:""}.ri-device-recover-line:before{content:""}.ri-dingding-fill:before{content:""}.ri-dingding-line:before{content:""}.ri-direction-fill:before{content:""}.ri-direction-line:before{content:""}.ri-disc-fill:before{content:""}.ri-disc-line:before{content:""}.ri-discord-fill:before{content:""}.ri-discord-line:before{content:""}.ri-discuss-fill:before{content:""}.ri-discuss-line:before{content:""}.ri-dislike-fill:before{content:""}.ri-dislike-line:before{content:""}.ri-disqus-fill:before{content:""}.ri-disqus-line:before{content:""}.ri-divide-fill:before{content:""}.ri-divide-line:before{content:""}.ri-donut-chart-fill:before{content:""}.ri-donut-chart-line:before{content:""}.ri-door-closed-fill:before{content:""}.ri-door-closed-line:before{content:""}.ri-door-fill:before{content:""}.ri-door-line:before{content:""}.ri-door-lock-box-fill:before{content:""}.ri-door-lock-box-line:before{content:""}.ri-door-lock-fill:before{content:""}.ri-door-lock-line:before{content:""}.ri-door-open-fill:before{content:""}.ri-door-open-line:before{content:""}.ri-dossier-fill:before{content:""}.ri-dossier-line:before{content:""}.ri-douban-fill:before{content:""}.ri-douban-line:before{content:""}.ri-double-quotes-l:before{content:""}.ri-double-quotes-r:before{content:""}.ri-download-2-fill:before{content:""}.ri-download-2-line:before{content:""}.ri-download-cloud-2-fill:before{content:""}.ri-download-cloud-2-line:before{content:""}.ri-download-cloud-fill:before{content:""}.ri-download-cloud-line:before{content:""}.ri-download-fill:before{content:""}.ri-download-line:before{content:""}.ri-draft-fill:before{content:""}.ri-draft-line:before{content:""}.ri-drag-drop-fill:before{content:""}.ri-drag-drop-line:before{content:""}.ri-drag-move-2-fill:before{content:""}.ri-drag-move-2-line:before{content:""}.ri-drag-move-fill:before{content:""}.ri-drag-move-line:before{content:""}.ri-dribbble-fill:before{content:""}.ri-dribbble-line:before{content:""}.ri-drive-fill:before{content:""}.ri-drive-line:before{content:""}.ri-drizzle-fill:before{content:""}.ri-drizzle-line:before{content:""}.ri-drop-fill:before{content:""}.ri-drop-line:before{content:""}.ri-dropbox-fill:before{content:""}.ri-dropbox-line:before{content:""}.ri-dual-sim-1-fill:before{content:""}.ri-dual-sim-1-line:before{content:""}.ri-dual-sim-2-fill:before{content:""}.ri-dual-sim-2-line:before{content:""}.ri-dv-fill:before{content:""}.ri-dv-line:before{content:""}.ri-dvd-fill:before{content:""}.ri-dvd-line:before{content:""}.ri-e-bike-2-fill:before{content:""}.ri-e-bike-2-line:before{content:""}.ri-e-bike-fill:before{content:""}.ri-e-bike-line:before{content:""}.ri-earth-fill:before{content:""}.ri-earth-line:before{content:""}.ri-earthquake-fill:before{content:""}.ri-earthquake-line:before{content:""}.ri-edge-fill:before{content:""}.ri-edge-line:before{content:""}.ri-edit-2-fill:before{content:""}.ri-edit-2-line:before{content:""}.ri-edit-box-fill:before{content:""}.ri-edit-box-line:before{content:""}.ri-edit-circle-fill:before{content:""}.ri-edit-circle-line:before{content:""}.ri-edit-fill:before{content:""}.ri-edit-line:before{content:""}.ri-eject-fill:before{content:""}.ri-eject-line:before{content:""}.ri-emotion-2-fill:before{content:""}.ri-emotion-2-line:before{content:""}.ri-emotion-fill:before{content:""}.ri-emotion-happy-fill:before{content:""}.ri-emotion-happy-line:before{content:""}.ri-emotion-laugh-fill:before{content:""}.ri-emotion-laugh-line:before{content:""}.ri-emotion-line:before{content:""}.ri-emotion-normal-fill:before{content:""}.ri-emotion-normal-line:before{content:""}.ri-emotion-sad-fill:before{content:""}.ri-emotion-sad-line:before{content:""}.ri-emotion-unhappy-fill:before{content:""}.ri-emotion-unhappy-line:before{content:""}.ri-empathize-fill:before{content:""}.ri-empathize-line:before{content:""}.ri-emphasis-cn:before{content:""}.ri-emphasis:before{content:""}.ri-english-input:before{content:""}.ri-equalizer-fill:before{content:""}.ri-equalizer-line:before{content:""}.ri-eraser-fill:before{content:""}.ri-eraser-line:before{content:""}.ri-error-warning-fill:before{content:""}.ri-error-warning-line:before{content:""}.ri-evernote-fill:before{content:""}.ri-evernote-line:before{content:""}.ri-exchange-box-fill:before{content:""}.ri-exchange-box-line:before{content:""}.ri-exchange-cny-fill:before{content:""}.ri-exchange-cny-line:before{content:""}.ri-exchange-dollar-fill:before{content:""}.ri-exchange-dollar-line:before{content:""}.ri-exchange-fill:before{content:""}.ri-exchange-funds-fill:before{content:""}.ri-exchange-funds-line:before{content:""}.ri-exchange-line:before{content:""}.ri-external-link-fill:before{content:""}.ri-external-link-line:before{content:""}.ri-eye-2-fill:before{content:""}.ri-eye-2-line:before{content:""}.ri-eye-close-fill:before{content:""}.ri-eye-close-line:before{content:""}.ri-eye-fill:before{content:""}.ri-eye-line:before{content:""}.ri-eye-off-fill:before{content:""}.ri-eye-off-line:before{content:""}.ri-facebook-box-fill:before{content:""}.ri-facebook-box-line:before{content:""}.ri-facebook-circle-fill:before{content:""}.ri-facebook-circle-line:before{content:""}.ri-facebook-fill:before{content:""}.ri-facebook-line:before{content:""}.ri-fahrenheit-fill:before{content:""}.ri-fahrenheit-line:before{content:""}.ri-feedback-fill:before{content:""}.ri-feedback-line:before{content:""}.ri-file-2-fill:before{content:""}.ri-file-2-line:before{content:""}.ri-file-3-fill:before{content:""}.ri-file-3-line:before{content:""}.ri-file-4-fill:before{content:""}.ri-file-4-line:before{content:""}.ri-file-add-fill:before{content:""}.ri-file-add-line:before{content:""}.ri-file-chart-2-fill:before{content:""}.ri-file-chart-2-line:before{content:""}.ri-file-chart-fill:before{content:""}.ri-file-chart-line:before{content:""}.ri-file-cloud-fill:before{content:""}.ri-file-cloud-line:before{content:""}.ri-file-code-fill:before{content:""}.ri-file-code-line:before{content:""}.ri-file-copy-2-fill:before{content:""}.ri-file-copy-2-line:before{content:""}.ri-file-copy-fill:before{content:""}.ri-file-copy-line:before{content:""}.ri-file-damage-fill:before{content:""}.ri-file-damage-line:before{content:""}.ri-file-download-fill:before{content:""}.ri-file-download-line:before{content:""}.ri-file-edit-fill:before{content:""}.ri-file-edit-line:before{content:""}.ri-file-excel-2-fill:before{content:""}.ri-file-excel-2-line:before{content:""}.ri-file-excel-fill:before{content:""}.ri-file-excel-line:before{content:""}.ri-file-fill:before{content:""}.ri-file-forbid-fill:before{content:""}.ri-file-forbid-line:before{content:""}.ri-file-gif-fill:before{content:""}.ri-file-gif-line:before{content:""}.ri-file-history-fill:before{content:""}.ri-file-history-line:before{content:""}.ri-file-hwp-fill:before{content:""}.ri-file-hwp-line:before{content:""}.ri-file-info-fill:before{content:""}.ri-file-info-line:before{content:""}.ri-file-line:before{content:""}.ri-file-list-2-fill:before{content:""}.ri-file-list-2-line:before{content:""}.ri-file-list-3-fill:before{content:""}.ri-file-list-3-line:before{content:""}.ri-file-list-fill:before{content:""}.ri-file-list-line:before{content:""}.ri-file-lock-fill:before{content:""}.ri-file-lock-line:before{content:""}.ri-file-marked-fill:before{content:""}.ri-file-marked-line:before{content:""}.ri-file-music-fill:before{content:""}.ri-file-music-line:before{content:""}.ri-file-paper-2-fill:before{content:""}.ri-file-paper-2-line:before{content:""}.ri-file-paper-fill:before{content:""}.ri-file-paper-line:before{content:""}.ri-file-pdf-fill:before{content:""}.ri-file-pdf-line:before{content:""}.ri-file-ppt-2-fill:before{content:""}.ri-file-ppt-2-line:before{content:""}.ri-file-ppt-fill:before{content:""}.ri-file-ppt-line:before{content:""}.ri-file-reduce-fill:before{content:""}.ri-file-reduce-line:before{content:""}.ri-file-search-fill:before{content:""}.ri-file-search-line:before{content:""}.ri-file-settings-fill:before{content:""}.ri-file-settings-line:before{content:""}.ri-file-shield-2-fill:before{content:""}.ri-file-shield-2-line:before{content:""}.ri-file-shield-fill:before{content:""}.ri-file-shield-line:before{content:""}.ri-file-shred-fill:before{content:""}.ri-file-shred-line:before{content:""}.ri-file-text-fill:before{content:""}.ri-file-text-line:before{content:""}.ri-file-transfer-fill:before{content:""}.ri-file-transfer-line:before{content:""}.ri-file-unknow-fill:before{content:""}.ri-file-unknow-line:before{content:""}.ri-file-upload-fill:before{content:""}.ri-file-upload-line:before{content:""}.ri-file-user-fill:before{content:""}.ri-file-user-line:before{content:""}.ri-file-warning-fill:before{content:""}.ri-file-warning-line:before{content:""}.ri-file-word-2-fill:before{content:""}.ri-file-word-2-line:before{content:""}.ri-file-word-fill:before{content:""}.ri-file-word-line:before{content:""}.ri-file-zip-fill:before{content:""}.ri-file-zip-line:before{content:""}.ri-film-fill:before{content:""}.ri-film-line:before{content:""}.ri-filter-2-fill:before{content:""}.ri-filter-2-line:before{content:""}.ri-filter-3-fill:before{content:""}.ri-filter-3-line:before{content:""}.ri-filter-fill:before{content:""}.ri-filter-line:before{content:""}.ri-filter-off-fill:before{content:""}.ri-filter-off-line:before{content:""}.ri-find-replace-fill:before{content:""}.ri-find-replace-line:before{content:""}.ri-finder-fill:before{content:""}.ri-finder-line:before{content:""}.ri-fingerprint-2-fill:before{content:""}.ri-fingerprint-2-line:before{content:""}.ri-fingerprint-fill:before{content:""}.ri-fingerprint-line:before{content:""}.ri-fire-fill:before{content:""}.ri-fire-line:before{content:""}.ri-firefox-fill:before{content:""}.ri-firefox-line:before{content:""}.ri-first-aid-kit-fill:before{content:""}.ri-first-aid-kit-line:before{content:""}.ri-flag-2-fill:before{content:""}.ri-flag-2-line:before{content:""}.ri-flag-fill:before{content:""}.ri-flag-line:before{content:""}.ri-flashlight-fill:before{content:""}.ri-flashlight-line:before{content:""}.ri-flask-fill:before{content:""}.ri-flask-line:before{content:""}.ri-flight-land-fill:before{content:""}.ri-flight-land-line:before{content:""}.ri-flight-takeoff-fill:before{content:""}.ri-flight-takeoff-line:before{content:""}.ri-flood-fill:before{content:""}.ri-flood-line:before{content:""}.ri-flow-chart:before{content:""}.ri-flutter-fill:before{content:""}.ri-flutter-line:before{content:""}.ri-focus-2-fill:before{content:""}.ri-focus-2-line:before{content:""}.ri-focus-3-fill:before{content:""}.ri-focus-3-line:before{content:""}.ri-focus-fill:before{content:""}.ri-focus-line:before{content:""}.ri-foggy-fill:before{content:""}.ri-foggy-line:before{content:""}.ri-folder-2-fill:before{content:""}.ri-folder-2-line:before{content:""}.ri-folder-3-fill:before{content:""}.ri-folder-3-line:before{content:""}.ri-folder-4-fill:before{content:""}.ri-folder-4-line:before{content:""}.ri-folder-5-fill:before{content:""}.ri-folder-5-line:before{content:""}.ri-folder-add-fill:before{content:""}.ri-folder-add-line:before{content:""}.ri-folder-chart-2-fill:before{content:""}.ri-folder-chart-2-line:before{content:""}.ri-folder-chart-fill:before{content:""}.ri-folder-chart-line:before{content:""}.ri-folder-download-fill:before{content:""}.ri-folder-download-line:before{content:""}.ri-folder-fill:before{content:""}.ri-folder-forbid-fill:before{content:""}.ri-folder-forbid-line:before{content:""}.ri-folder-history-fill:before{content:""}.ri-folder-history-line:before{content:""}.ri-folder-info-fill:before{content:""}.ri-folder-info-line:before{content:""}.ri-folder-keyhole-fill:before{content:""}.ri-folder-keyhole-line:before{content:""}.ri-folder-line:before{content:""}.ri-folder-lock-fill:before{content:""}.ri-folder-lock-line:before{content:""}.ri-folder-music-fill:before{content:""}.ri-folder-music-line:before{content:""}.ri-folder-open-fill:before{content:""}.ri-folder-open-line:before{content:""}.ri-folder-received-fill:before{content:""}.ri-folder-received-line:before{content:""}.ri-folder-reduce-fill:before{content:""}.ri-folder-reduce-line:before{content:""}.ri-folder-settings-fill:before{content:""}.ri-folder-settings-line:before{content:""}.ri-folder-shared-fill:before{content:""}.ri-folder-shared-line:before{content:""}.ri-folder-shield-2-fill:before{content:""}.ri-folder-shield-2-line:before{content:""}.ri-folder-shield-fill:before{content:""}.ri-folder-shield-line:before{content:""}.ri-folder-transfer-fill:before{content:""}.ri-folder-transfer-line:before{content:""}.ri-folder-unknow-fill:before{content:""}.ri-folder-unknow-line:before{content:""}.ri-folder-upload-fill:before{content:""}.ri-folder-upload-line:before{content:""}.ri-folder-user-fill:before{content:""}.ri-folder-user-line:before{content:""}.ri-folder-warning-fill:before{content:""}.ri-folder-warning-line:before{content:""}.ri-folder-zip-fill:before{content:""}.ri-folder-zip-line:before{content:""}.ri-folders-fill:before{content:""}.ri-folders-line:before{content:""}.ri-font-color:before{content:""}.ri-font-size-2:before{content:""}.ri-font-size:before{content:""}.ri-football-fill:before{content:""}.ri-football-line:before{content:""}.ri-footprint-fill:before{content:""}.ri-footprint-line:before{content:""}.ri-forbid-2-fill:before{content:""}.ri-forbid-2-line:before{content:""}.ri-forbid-fill:before{content:""}.ri-forbid-line:before{content:""}.ri-format-clear:before{content:""}.ri-fridge-fill:before{content:""}.ri-fridge-line:before{content:""}.ri-fullscreen-exit-fill:before{content:""}.ri-fullscreen-exit-line:before{content:""}.ri-fullscreen-fill:before{content:""}.ri-fullscreen-line:before{content:""}.ri-function-fill:before{content:""}.ri-function-line:before{content:""}.ri-functions:before{content:""}.ri-funds-box-fill:before{content:""}.ri-funds-box-line:before{content:""}.ri-funds-fill:before{content:""}.ri-funds-line:before{content:""}.ri-gallery-fill:before{content:""}.ri-gallery-line:before{content:""}.ri-gallery-upload-fill:before{content:""}.ri-gallery-upload-line:before{content:""}.ri-game-fill:before{content:""}.ri-game-line:before{content:""}.ri-gamepad-fill:before{content:""}.ri-gamepad-line:before{content:""}.ri-gas-station-fill:before{content:""}.ri-gas-station-line:before{content:""}.ri-gatsby-fill:before{content:""}.ri-gatsby-line:before{content:""}.ri-genderless-fill:before{content:""}.ri-genderless-line:before{content:""}.ri-ghost-2-fill:before{content:""}.ri-ghost-2-line:before{content:""}.ri-ghost-fill:before{content:""}.ri-ghost-line:before{content:""}.ri-ghost-smile-fill:before{content:""}.ri-ghost-smile-line:before{content:""}.ri-gift-2-fill:before{content:""}.ri-gift-2-line:before{content:""}.ri-gift-fill:before{content:""}.ri-gift-line:before{content:""}.ri-git-branch-fill:before{content:""}.ri-git-branch-line:before{content:""}.ri-git-commit-fill:before{content:""}.ri-git-commit-line:before{content:""}.ri-git-merge-fill:before{content:""}.ri-git-merge-line:before{content:""}.ri-git-pull-request-fill:before{content:""}.ri-git-pull-request-line:before{content:""}.ri-git-repository-commits-fill:before{content:""}.ri-git-repository-commits-line:before{content:""}.ri-git-repository-fill:before{content:""}.ri-git-repository-line:before{content:""}.ri-git-repository-private-fill:before{content:""}.ri-git-repository-private-line:before{content:""}.ri-github-fill:before{content:""}.ri-github-line:before{content:""}.ri-gitlab-fill:before{content:""}.ri-gitlab-line:before{content:""}.ri-global-fill:before{content:""}.ri-global-line:before{content:""}.ri-globe-fill:before{content:""}.ri-globe-line:before{content:""}.ri-goblet-fill:before{content:""}.ri-goblet-line:before{content:""}.ri-google-fill:before{content:""}.ri-google-line:before{content:""}.ri-google-play-fill:before{content:""}.ri-google-play-line:before{content:""}.ri-government-fill:before{content:""}.ri-government-line:before{content:""}.ri-gps-fill:before{content:""}.ri-gps-line:before{content:""}.ri-gradienter-fill:before{content:""}.ri-gradienter-line:before{content:""}.ri-grid-fill:before{content:""}.ri-grid-line:before{content:""}.ri-group-2-fill:before{content:""}.ri-group-2-line:before{content:""}.ri-group-fill:before{content:""}.ri-group-line:before{content:""}.ri-guide-fill:before{content:""}.ri-guide-line:before{content:""}.ri-h-1:before{content:""}.ri-h-2:before{content:""}.ri-h-3:before{content:""}.ri-h-4:before{content:""}.ri-h-5:before{content:""}.ri-h-6:before{content:""}.ri-hail-fill:before{content:""}.ri-hail-line:before{content:""}.ri-hammer-fill:before{content:""}.ri-hammer-line:before{content:""}.ri-hand-coin-fill:before{content:""}.ri-hand-coin-line:before{content:""}.ri-hand-heart-fill:before{content:""}.ri-hand-heart-line:before{content:""}.ri-hand-sanitizer-fill:before{content:""}.ri-hand-sanitizer-line:before{content:""}.ri-handbag-fill:before{content:""}.ri-handbag-line:before{content:""}.ri-hard-drive-2-fill:before{content:""}.ri-hard-drive-2-line:before{content:""}.ri-hard-drive-fill:before{content:""}.ri-hard-drive-line:before{content:""}.ri-hashtag:before{content:""}.ri-haze-2-fill:before{content:""}.ri-haze-2-line:before{content:""}.ri-haze-fill:before{content:""}.ri-haze-line:before{content:""}.ri-hd-fill:before{content:""}.ri-hd-line:before{content:""}.ri-heading:before{content:""}.ri-headphone-fill:before{content:""}.ri-headphone-line:before{content:""}.ri-health-book-fill:before{content:""}.ri-health-book-line:before{content:""}.ri-heart-2-fill:before{content:""}.ri-heart-2-line:before{content:""}.ri-heart-3-fill:before{content:""}.ri-heart-3-line:before{content:""}.ri-heart-add-fill:before{content:""}.ri-heart-add-line:before{content:""}.ri-heart-fill:before{content:""}.ri-heart-line:before{content:""}.ri-heart-pulse-fill:before{content:""}.ri-heart-pulse-line:before{content:""}.ri-hearts-fill:before{content:""}.ri-hearts-line:before{content:""}.ri-heavy-showers-fill:before{content:""}.ri-heavy-showers-line:before{content:""}.ri-history-fill:before{content:""}.ri-history-line:before{content:""}.ri-home-2-fill:before{content:""}.ri-home-2-line:before{content:""}.ri-home-3-fill:before{content:""}.ri-home-3-line:before{content:""}.ri-home-4-fill:before{content:""}.ri-home-4-line:before{content:""}.ri-home-5-fill:before{content:""}.ri-home-5-line:before{content:""}.ri-home-6-fill:before{content:""}.ri-home-6-line:before{content:""}.ri-home-7-fill:before{content:""}.ri-home-7-line:before{content:""}.ri-home-8-fill:before{content:""}.ri-home-8-line:before{content:""}.ri-home-fill:before{content:""}.ri-home-gear-fill:before{content:""}.ri-home-gear-line:before{content:""}.ri-home-heart-fill:before{content:""}.ri-home-heart-line:before{content:""}.ri-home-line:before{content:""}.ri-home-smile-2-fill:before{content:""}.ri-home-smile-2-line:before{content:""}.ri-home-smile-fill:before{content:""}.ri-home-smile-line:before{content:""}.ri-home-wifi-fill:before{content:""}.ri-home-wifi-line:before{content:""}.ri-honor-of-kings-fill:before{content:""}.ri-honor-of-kings-line:before{content:""}.ri-honour-fill:before{content:""}.ri-honour-line:before{content:""}.ri-hospital-fill:before{content:""}.ri-hospital-line:before{content:""}.ri-hotel-bed-fill:before{content:""}.ri-hotel-bed-line:before{content:""}.ri-hotel-fill:before{content:""}.ri-hotel-line:before{content:""}.ri-hotspot-fill:before{content:""}.ri-hotspot-line:before{content:""}.ri-hq-fill:before{content:""}.ri-hq-line:before{content:""}.ri-html5-fill:before{content:""}.ri-html5-line:before{content:""}.ri-ie-fill:before{content:""}.ri-ie-line:before{content:""}.ri-image-2-fill:before{content:""}.ri-image-2-line:before{content:""}.ri-image-add-fill:before{content:""}.ri-image-add-line:before{content:""}.ri-image-edit-fill:before{content:""}.ri-image-edit-line:before{content:""}.ri-image-fill:before{content:""}.ri-image-line:before{content:""}.ri-inbox-archive-fill:before{content:""}.ri-inbox-archive-line:before{content:""}.ri-inbox-fill:before{content:""}.ri-inbox-line:before{content:""}.ri-inbox-unarchive-fill:before{content:""}.ri-inbox-unarchive-line:before{content:""}.ri-increase-decrease-fill:before{content:""}.ri-increase-decrease-line:before{content:""}.ri-indent-decrease:before{content:""}.ri-indent-increase:before{content:""}.ri-indeterminate-circle-fill:before{content:""}.ri-indeterminate-circle-line:before{content:""}.ri-information-fill:before{content:""}.ri-information-line:before{content:""}.ri-infrared-thermometer-fill:before{content:""}.ri-infrared-thermometer-line:before{content:""}.ri-ink-bottle-fill:before{content:""}.ri-ink-bottle-line:before{content:""}.ri-input-cursor-move:before{content:""}.ri-input-method-fill:before{content:""}.ri-input-method-line:before{content:""}.ri-insert-column-left:before{content:""}.ri-insert-column-right:before{content:""}.ri-insert-row-bottom:before{content:""}.ri-insert-row-top:before{content:""}.ri-instagram-fill:before{content:""}.ri-instagram-line:before{content:""}.ri-install-fill:before{content:""}.ri-install-line:before{content:""}.ri-invision-fill:before{content:""}.ri-invision-line:before{content:""}.ri-italic:before{content:""}.ri-kakao-talk-fill:before{content:""}.ri-kakao-talk-line:before{content:""}.ri-key-2-fill:before{content:""}.ri-key-2-line:before{content:""}.ri-key-fill:before{content:""}.ri-key-line:before{content:""}.ri-keyboard-box-fill:before{content:""}.ri-keyboard-box-line:before{content:""}.ri-keyboard-fill:before{content:""}.ri-keyboard-line:before{content:""}.ri-keynote-fill:before{content:""}.ri-keynote-line:before{content:""}.ri-knife-blood-fill:before{content:""}.ri-knife-blood-line:before{content:""}.ri-knife-fill:before{content:""}.ri-knife-line:before{content:""}.ri-landscape-fill:before{content:""}.ri-landscape-line:before{content:""}.ri-layout-2-fill:before{content:""}.ri-layout-2-line:before{content:""}.ri-layout-3-fill:before{content:""}.ri-layout-3-line:before{content:""}.ri-layout-4-fill:before{content:""}.ri-layout-4-line:before{content:""}.ri-layout-5-fill:before{content:""}.ri-layout-5-line:before{content:""}.ri-layout-6-fill:before{content:""}.ri-layout-6-line:before{content:""}.ri-layout-bottom-2-fill:before{content:""}.ri-layout-bottom-2-line:before{content:""}.ri-layout-bottom-fill:before{content:""}.ri-layout-bottom-line:before{content:""}.ri-layout-column-fill:before{content:""}.ri-layout-column-line:before{content:""}.ri-layout-fill:before{content:""}.ri-layout-grid-fill:before{content:""}.ri-layout-grid-line:before{content:""}.ri-layout-left-2-fill:before{content:""}.ri-layout-left-2-line:before{content:""}.ri-layout-left-fill:before{content:""}.ri-layout-left-line:before{content:""}.ri-layout-line:before{content:""}.ri-layout-masonry-fill:before{content:""}.ri-layout-masonry-line:before{content:""}.ri-layout-right-2-fill:before{content:""}.ri-layout-right-2-line:before{content:""}.ri-layout-right-fill:before{content:""}.ri-layout-right-line:before{content:""}.ri-layout-row-fill:before{content:""}.ri-layout-row-line:before{content:""}.ri-layout-top-2-fill:before{content:""}.ri-layout-top-2-line:before{content:""}.ri-layout-top-fill:before{content:""}.ri-layout-top-line:before{content:""}.ri-leaf-fill:before{content:""}.ri-leaf-line:before{content:""}.ri-lifebuoy-fill:before{content:""}.ri-lifebuoy-line:before{content:""}.ri-lightbulb-fill:before{content:""}.ri-lightbulb-flash-fill:before{content:""}.ri-lightbulb-flash-line:before{content:""}.ri-lightbulb-line:before{content:""}.ri-line-chart-fill:before{content:""}.ri-line-chart-line:before{content:""}.ri-line-fill:before{content:""}.ri-line-height:before{content:""}.ri-line-line:before{content:""}.ri-link-m:before{content:""}.ri-link-unlink-m:before{content:""}.ri-link-unlink:before{content:""}.ri-link:before{content:""}.ri-linkedin-box-fill:before{content:""}.ri-linkedin-box-line:before{content:""}.ri-linkedin-fill:before{content:""}.ri-linkedin-line:before{content:""}.ri-links-fill:before{content:""}.ri-links-line:before{content:""}.ri-list-check-2:before{content:""}.ri-list-check:before{content:""}.ri-list-ordered:before{content:""}.ri-list-settings-fill:before{content:""}.ri-list-settings-line:before{content:""}.ri-list-unordered:before{content:""}.ri-live-fill:before{content:""}.ri-live-line:before{content:""}.ri-loader-2-fill:before{content:""}.ri-loader-2-line:before{content:""}.ri-loader-3-fill:before{content:""}.ri-loader-3-line:before{content:""}.ri-loader-4-fill:before{content:""}.ri-loader-4-line:before{content:""}.ri-loader-5-fill:before{content:""}.ri-loader-5-line:before{content:""}.ri-loader-fill:before{content:""}.ri-loader-line:before{content:""}.ri-lock-2-fill:before{content:""}.ri-lock-2-line:before{content:""}.ri-lock-fill:before{content:""}.ri-lock-line:before{content:""}.ri-lock-password-fill:before{content:""}.ri-lock-password-line:before{content:""}.ri-lock-unlock-fill:before{content:""}.ri-lock-unlock-line:before{content:""}.ri-login-box-fill:before{content:""}.ri-login-box-line:before{content:""}.ri-login-circle-fill:before{content:""}.ri-login-circle-line:before{content:""}.ri-logout-box-fill:before{content:""}.ri-logout-box-line:before{content:""}.ri-logout-box-r-fill:before{content:""}.ri-logout-box-r-line:before{content:""}.ri-logout-circle-fill:before{content:""}.ri-logout-circle-line:before{content:""}.ri-logout-circle-r-fill:before{content:""}.ri-logout-circle-r-line:before{content:""}.ri-luggage-cart-fill:before{content:""}.ri-luggage-cart-line:before{content:""}.ri-luggage-deposit-fill:before{content:""}.ri-luggage-deposit-line:before{content:""}.ri-lungs-fill:before{content:""}.ri-lungs-line:before{content:""}.ri-mac-fill:before{content:""}.ri-mac-line:before{content:""}.ri-macbook-fill:before{content:""}.ri-macbook-line:before{content:""}.ri-magic-fill:before{content:""}.ri-magic-line:before{content:""}.ri-mail-add-fill:before{content:""}.ri-mail-add-line:before{content:""}.ri-mail-check-fill:before{content:""}.ri-mail-check-line:before{content:""}.ri-mail-close-fill:before{content:""}.ri-mail-close-line:before{content:""}.ri-mail-download-fill:before{content:""}.ri-mail-download-line:before{content:""}.ri-mail-fill:before{content:""}.ri-mail-forbid-fill:before{content:""}.ri-mail-forbid-line:before{content:""}.ri-mail-line:before{content:""}.ri-mail-lock-fill:before{content:""}.ri-mail-lock-line:before{content:""}.ri-mail-open-fill:before{content:""}.ri-mail-open-line:before{content:""}.ri-mail-send-fill:before{content:""}.ri-mail-send-line:before{content:""}.ri-mail-settings-fill:before{content:""}.ri-mail-settings-line:before{content:""}.ri-mail-star-fill:before{content:""}.ri-mail-star-line:before{content:""}.ri-mail-unread-fill:before{content:""}.ri-mail-unread-line:before{content:""}.ri-mail-volume-fill:before{content:""}.ri-mail-volume-line:before{content:""}.ri-map-2-fill:before{content:""}.ri-map-2-line:before{content:""}.ri-map-fill:before{content:""}.ri-map-line:before{content:""}.ri-map-pin-2-fill:before{content:""}.ri-map-pin-2-line:before{content:""}.ri-map-pin-3-fill:before{content:""}.ri-map-pin-3-line:before{content:""}.ri-map-pin-4-fill:before{content:""}.ri-map-pin-4-line:before{content:""}.ri-map-pin-5-fill:before{content:""}.ri-map-pin-5-line:before{content:""}.ri-map-pin-add-fill:before{content:""}.ri-map-pin-add-line:before{content:""}.ri-map-pin-fill:before{content:""}.ri-map-pin-line:before{content:""}.ri-map-pin-range-fill:before{content:""}.ri-map-pin-range-line:before{content:""}.ri-map-pin-time-fill:before{content:""}.ri-map-pin-time-line:before{content:""}.ri-map-pin-user-fill:before{content:""}.ri-map-pin-user-line:before{content:""}.ri-mark-pen-fill:before{content:""}.ri-mark-pen-line:before{content:""}.ri-markdown-fill:before{content:""}.ri-markdown-line:before{content:""}.ri-markup-fill:before{content:""}.ri-markup-line:before{content:""}.ri-mastercard-fill:before{content:""}.ri-mastercard-line:before{content:""}.ri-mastodon-fill:before{content:""}.ri-mastodon-line:before{content:""}.ri-medal-2-fill:before{content:""}.ri-medal-2-line:before{content:""}.ri-medal-fill:before{content:""}.ri-medal-line:before{content:""}.ri-medicine-bottle-fill:before{content:""}.ri-medicine-bottle-line:before{content:""}.ri-medium-fill:before{content:""}.ri-medium-line:before{content:""}.ri-men-fill:before{content:""}.ri-men-line:before{content:""}.ri-mental-health-fill:before{content:""}.ri-mental-health-line:before{content:""}.ri-menu-2-fill:before{content:""}.ri-menu-2-line:before{content:""}.ri-menu-3-fill:before{content:""}.ri-menu-3-line:before{content:""}.ri-menu-4-fill:before{content:""}.ri-menu-4-line:before{content:""}.ri-menu-5-fill:before{content:""}.ri-menu-5-line:before{content:""}.ri-menu-add-fill:before{content:""}.ri-menu-add-line:before{content:""}.ri-menu-fill:before{content:""}.ri-menu-fold-fill:before{content:""}.ri-menu-fold-line:before{content:""}.ri-menu-line:before{content:""}.ri-menu-unfold-fill:before{content:""}.ri-menu-unfold-line:before{content:""}.ri-merge-cells-horizontal:before{content:""}.ri-merge-cells-vertical:before{content:""}.ri-message-2-fill:before{content:""}.ri-message-2-line:before{content:""}.ri-message-3-fill:before{content:""}.ri-message-3-line:before{content:""}.ri-message-fill:before{content:""}.ri-message-line:before{content:""}.ri-messenger-fill:before{content:""}.ri-messenger-line:before{content:""}.ri-meteor-fill:before{content:""}.ri-meteor-line:before{content:""}.ri-mic-2-fill:before{content:""}.ri-mic-2-line:before{content:""}.ri-mic-fill:before{content:""}.ri-mic-line:before{content:""}.ri-mic-off-fill:before{content:""}.ri-mic-off-line:before{content:""}.ri-mickey-fill:before{content:""}.ri-mickey-line:before{content:""}.ri-microscope-fill:before{content:""}.ri-microscope-line:before{content:""}.ri-microsoft-fill:before{content:""}.ri-microsoft-line:before{content:""}.ri-mind-map:before{content:""}.ri-mini-program-fill:before{content:""}.ri-mini-program-line:before{content:""}.ri-mist-fill:before{content:""}.ri-mist-line:before{content:""}.ri-money-cny-box-fill:before{content:""}.ri-money-cny-box-line:before{content:""}.ri-money-cny-circle-fill:before{content:""}.ri-money-cny-circle-line:before{content:""}.ri-money-dollar-box-fill:before{content:""}.ri-money-dollar-box-line:before{content:""}.ri-money-dollar-circle-fill:before{content:""}.ri-money-dollar-circle-line:before{content:""}.ri-money-euro-box-fill:before{content:""}.ri-money-euro-box-line:before{content:""}.ri-money-euro-circle-fill:before{content:""}.ri-money-euro-circle-line:before{content:""}.ri-money-pound-box-fill:before{content:""}.ri-money-pound-box-line:before{content:""}.ri-money-pound-circle-fill:before{content:""}.ri-money-pound-circle-line:before{content:""}.ri-moon-clear-fill:before{content:""}.ri-moon-clear-line:before{content:""}.ri-moon-cloudy-fill:before{content:""}.ri-moon-cloudy-line:before{content:""}.ri-moon-fill:before{content:""}.ri-moon-foggy-fill:before{content:""}.ri-moon-foggy-line:before{content:""}.ri-moon-line:before{content:""}.ri-more-2-fill:before{content:""}.ri-more-2-line:before{content:""}.ri-more-fill:before{content:""}.ri-more-line:before{content:""}.ri-motorbike-fill:before{content:""}.ri-motorbike-line:before{content:""}.ri-mouse-fill:before{content:""}.ri-mouse-line:before{content:""}.ri-movie-2-fill:before{content:""}.ri-movie-2-line:before{content:""}.ri-movie-fill:before{content:""}.ri-movie-line:before{content:""}.ri-music-2-fill:before{content:""}.ri-music-2-line:before{content:""}.ri-music-fill:before{content:""}.ri-music-line:before{content:""}.ri-mv-fill:before{content:""}.ri-mv-line:before{content:""}.ri-navigation-fill:before{content:""}.ri-navigation-line:before{content:""}.ri-netease-cloud-music-fill:before{content:""}.ri-netease-cloud-music-line:before{content:""}.ri-netflix-fill:before{content:""}.ri-netflix-line:before{content:""}.ri-newspaper-fill:before{content:""}.ri-newspaper-line:before{content:""}.ri-node-tree:before{content:""}.ri-notification-2-fill:before{content:""}.ri-notification-2-line:before{content:""}.ri-notification-3-fill:before{content:""}.ri-notification-3-line:before{content:""}.ri-notification-4-fill:before{content:""}.ri-notification-4-line:before{content:""}.ri-notification-badge-fill:before{content:""}.ri-notification-badge-line:before{content:""}.ri-notification-fill:before{content:""}.ri-notification-line:before{content:""}.ri-notification-off-fill:before{content:""}.ri-notification-off-line:before{content:""}.ri-npmjs-fill:before{content:""}.ri-npmjs-line:before{content:""}.ri-number-0:before{content:""}.ri-number-1:before{content:""}.ri-number-2:before{content:""}.ri-number-3:before{content:""}.ri-number-4:before{content:""}.ri-number-5:before{content:""}.ri-number-6:before{content:""}.ri-number-7:before{content:""}.ri-number-8:before{content:""}.ri-number-9:before{content:""}.ri-numbers-fill:before{content:""}.ri-numbers-line:before{content:""}.ri-nurse-fill:before{content:""}.ri-nurse-line:before{content:""}.ri-oil-fill:before{content:""}.ri-oil-line:before{content:""}.ri-omega:before{content:""}.ri-open-arm-fill:before{content:""}.ri-open-arm-line:before{content:""}.ri-open-source-fill:before{content:""}.ri-open-source-line:before{content:""}.ri-opera-fill:before{content:""}.ri-opera-line:before{content:""}.ri-order-play-fill:before{content:""}.ri-order-play-line:before{content:""}.ri-organization-chart:before{content:""}.ri-outlet-2-fill:before{content:""}.ri-outlet-2-line:before{content:""}.ri-outlet-fill:before{content:""}.ri-outlet-line:before{content:""}.ri-page-separator:before{content:""}.ri-pages-fill:before{content:""}.ri-pages-line:before{content:""}.ri-paint-brush-fill:before{content:""}.ri-paint-brush-line:before{content:""}.ri-paint-fill:before{content:""}.ri-paint-line:before{content:""}.ri-palette-fill:before{content:""}.ri-palette-line:before{content:""}.ri-pantone-fill:before{content:""}.ri-pantone-line:before{content:""}.ri-paragraph:before{content:""}.ri-parent-fill:before{content:""}.ri-parent-line:before{content:""}.ri-parentheses-fill:before{content:""}.ri-parentheses-line:before{content:""}.ri-parking-box-fill:before{content:""}.ri-parking-box-line:before{content:""}.ri-parking-fill:before{content:""}.ri-parking-line:before{content:""}.ri-passport-fill:before{content:""}.ri-passport-line:before{content:""}.ri-patreon-fill:before{content:""}.ri-patreon-line:before{content:""}.ri-pause-circle-fill:before{content:""}.ri-pause-circle-line:before{content:""}.ri-pause-fill:before{content:""}.ri-pause-line:before{content:""}.ri-pause-mini-fill:before{content:""}.ri-pause-mini-line:before{content:""}.ri-paypal-fill:before{content:""}.ri-paypal-line:before{content:""}.ri-pen-nib-fill:before{content:""}.ri-pen-nib-line:before{content:""}.ri-pencil-fill:before{content:""}.ri-pencil-line:before{content:""}.ri-pencil-ruler-2-fill:before{content:""}.ri-pencil-ruler-2-line:before{content:""}.ri-pencil-ruler-fill:before{content:""}.ri-pencil-ruler-line:before{content:""}.ri-percent-fill:before{content:""}.ri-percent-line:before{content:""}.ri-phone-camera-fill:before{content:""}.ri-phone-camera-line:before{content:""}.ri-phone-fill:before{content:""}.ri-phone-find-fill:before{content:""}.ri-phone-find-line:before{content:""}.ri-phone-line:before{content:""}.ri-phone-lock-fill:before{content:""}.ri-phone-lock-line:before{content:""}.ri-picture-in-picture-2-fill:before{content:""}.ri-picture-in-picture-2-line:before{content:""}.ri-picture-in-picture-exit-fill:before{content:""}.ri-picture-in-picture-exit-line:before{content:""}.ri-picture-in-picture-fill:before{content:""}.ri-picture-in-picture-line:before{content:""}.ri-pie-chart-2-fill:before{content:""}.ri-pie-chart-2-line:before{content:""}.ri-pie-chart-box-fill:before{content:""}.ri-pie-chart-box-line:before{content:""}.ri-pie-chart-fill:before{content:""}.ri-pie-chart-line:before{content:""}.ri-pin-distance-fill:before{content:""}.ri-pin-distance-line:before{content:""}.ri-ping-pong-fill:before{content:""}.ri-ping-pong-line:before{content:""}.ri-pinterest-fill:before{content:""}.ri-pinterest-line:before{content:""}.ri-pinyin-input:before{content:""}.ri-pixelfed-fill:before{content:""}.ri-pixelfed-line:before{content:""}.ri-plane-fill:before{content:""}.ri-plane-line:before{content:""}.ri-plant-fill:before{content:""}.ri-plant-line:before{content:""}.ri-play-circle-fill:before{content:""}.ri-play-circle-line:before{content:""}.ri-play-fill:before{content:""}.ri-play-line:before{content:""}.ri-play-list-2-fill:before{content:""}.ri-play-list-2-line:before{content:""}.ri-play-list-add-fill:before{content:""}.ri-play-list-add-line:before{content:""}.ri-play-list-fill:before{content:""}.ri-play-list-line:before{content:""}.ri-play-mini-fill:before{content:""}.ri-play-mini-line:before{content:""}.ri-playstation-fill:before{content:""}.ri-playstation-line:before{content:""}.ri-plug-2-fill:before{content:""}.ri-plug-2-line:before{content:""}.ri-plug-fill:before{content:""}.ri-plug-line:before{content:""}.ri-polaroid-2-fill:before{content:""}.ri-polaroid-2-line:before{content:""}.ri-polaroid-fill:before{content:""}.ri-polaroid-line:before{content:""}.ri-police-car-fill:before{content:""}.ri-police-car-line:before{content:""}.ri-price-tag-2-fill:before{content:""}.ri-price-tag-2-line:before{content:""}.ri-price-tag-3-fill:before{content:""}.ri-price-tag-3-line:before{content:""}.ri-price-tag-fill:before{content:""}.ri-price-tag-line:before{content:""}.ri-printer-cloud-fill:before{content:""}.ri-printer-cloud-line:before{content:""}.ri-printer-fill:before{content:""}.ri-printer-line:before{content:""}.ri-product-hunt-fill:before{content:""}.ri-product-hunt-line:before{content:""}.ri-profile-fill:before{content:""}.ri-profile-line:before{content:""}.ri-projector-2-fill:before{content:""}.ri-projector-2-line:before{content:""}.ri-projector-fill:before{content:""}.ri-projector-line:before{content:""}.ri-psychotherapy-fill:before{content:""}.ri-psychotherapy-line:before{content:""}.ri-pulse-fill:before{content:""}.ri-pulse-line:before{content:""}.ri-pushpin-2-fill:before{content:""}.ri-pushpin-2-line:before{content:""}.ri-pushpin-fill:before{content:""}.ri-pushpin-line:before{content:""}.ri-qq-fill:before{content:""}.ri-qq-line:before{content:""}.ri-qr-code-fill:before{content:""}.ri-qr-code-line:before{content:""}.ri-qr-scan-2-fill:before{content:""}.ri-qr-scan-2-line:before{content:""}.ri-qr-scan-fill:before{content:""}.ri-qr-scan-line:before{content:""}.ri-question-answer-fill:before{content:""}.ri-question-answer-line:before{content:""}.ri-question-fill:before{content:""}.ri-question-line:before{content:""}.ri-question-mark:before{content:""}.ri-questionnaire-fill:before{content:""}.ri-questionnaire-line:before{content:""}.ri-quill-pen-fill:before{content:""}.ri-quill-pen-line:before{content:""}.ri-radar-fill:before{content:""}.ri-radar-line:before{content:""}.ri-radio-2-fill:before{content:""}.ri-radio-2-line:before{content:""}.ri-radio-button-fill:before{content:""}.ri-radio-button-line:before{content:""}.ri-radio-fill:before{content:""}.ri-radio-line:before{content:""}.ri-rainbow-fill:before{content:""}.ri-rainbow-line:before{content:""}.ri-rainy-fill:before{content:""}.ri-rainy-line:before{content:""}.ri-reactjs-fill:before{content:""}.ri-reactjs-line:before{content:""}.ri-record-circle-fill:before{content:""}.ri-record-circle-line:before{content:""}.ri-record-mail-fill:before{content:""}.ri-record-mail-line:before{content:""}.ri-recycle-fill:before{content:""}.ri-recycle-line:before{content:""}.ri-red-packet-fill:before{content:""}.ri-red-packet-line:before{content:""}.ri-reddit-fill:before{content:""}.ri-reddit-line:before{content:""}.ri-refresh-fill:before{content:""}.ri-refresh-line:before{content:""}.ri-refund-2-fill:before{content:""}.ri-refund-2-line:before{content:""}.ri-refund-fill:before{content:""}.ri-refund-line:before{content:""}.ri-registered-fill:before{content:""}.ri-registered-line:before{content:""}.ri-remixicon-fill:before{content:""}.ri-remixicon-line:before{content:""}.ri-remote-control-2-fill:before{content:""}.ri-remote-control-2-line:before{content:""}.ri-remote-control-fill:before{content:""}.ri-remote-control-line:before{content:""}.ri-repeat-2-fill:before{content:""}.ri-repeat-2-line:before{content:""}.ri-repeat-fill:before{content:""}.ri-repeat-line:before{content:""}.ri-repeat-one-fill:before{content:""}.ri-repeat-one-line:before{content:""}.ri-reply-all-fill:before{content:""}.ri-reply-all-line:before{content:""}.ri-reply-fill:before{content:""}.ri-reply-line:before{content:""}.ri-reserved-fill:before{content:""}.ri-reserved-line:before{content:""}.ri-rest-time-fill:before{content:""}.ri-rest-time-line:before{content:""}.ri-restart-fill:before{content:""}.ri-restart-line:before{content:""}.ri-restaurant-2-fill:before{content:""}.ri-restaurant-2-line:before{content:""}.ri-restaurant-fill:before{content:""}.ri-restaurant-line:before{content:""}.ri-rewind-fill:before{content:""}.ri-rewind-line:before{content:""}.ri-rewind-mini-fill:before{content:""}.ri-rewind-mini-line:before{content:""}.ri-rhythm-fill:before{content:""}.ri-rhythm-line:before{content:""}.ri-riding-fill:before{content:""}.ri-riding-line:before{content:""}.ri-road-map-fill:before{content:""}.ri-road-map-line:before{content:""}.ri-roadster-fill:before{content:""}.ri-roadster-line:before{content:""}.ri-robot-fill:before{content:""}.ri-robot-line:before{content:""}.ri-rocket-2-fill:before{content:""}.ri-rocket-2-line:before{content:""}.ri-rocket-fill:before{content:""}.ri-rocket-line:before{content:""}.ri-rotate-lock-fill:before{content:""}.ri-rotate-lock-line:before{content:""}.ri-rounded-corner:before{content:""}.ri-route-fill:before{content:""}.ri-route-line:before{content:""}.ri-router-fill:before{content:""}.ri-router-line:before{content:""}.ri-rss-fill:before{content:""}.ri-rss-line:before{content:""}.ri-ruler-2-fill:before{content:""}.ri-ruler-2-line:before{content:""}.ri-ruler-fill:before{content:""}.ri-ruler-line:before{content:""}.ri-run-fill:before{content:""}.ri-run-line:before{content:""}.ri-safari-fill:before{content:""}.ri-safari-line:before{content:""}.ri-safe-2-fill:before{content:""}.ri-safe-2-line:before{content:""}.ri-safe-fill:before{content:""}.ri-safe-line:before{content:""}.ri-sailboat-fill:before{content:""}.ri-sailboat-line:before{content:""}.ri-save-2-fill:before{content:""}.ri-save-2-line:before{content:""}.ri-save-3-fill:before{content:""}.ri-save-3-line:before{content:""}.ri-save-fill:before{content:""}.ri-save-line:before{content:""}.ri-scales-2-fill:before{content:""}.ri-scales-2-line:before{content:""}.ri-scales-3-fill:before{content:""}.ri-scales-3-line:before{content:""}.ri-scales-fill:before{content:""}.ri-scales-line:before{content:""}.ri-scan-2-fill:before{content:""}.ri-scan-2-line:before{content:""}.ri-scan-fill:before{content:""}.ri-scan-line:before{content:""}.ri-scissors-2-fill:before{content:""}.ri-scissors-2-line:before{content:""}.ri-scissors-cut-fill:before{content:""}.ri-scissors-cut-line:before{content:""}.ri-scissors-fill:before{content:""}.ri-scissors-line:before{content:""}.ri-screenshot-2-fill:before{content:""}.ri-screenshot-2-line:before{content:""}.ri-screenshot-fill:before{content:""}.ri-screenshot-line:before{content:""}.ri-sd-card-fill:before{content:""}.ri-sd-card-line:before{content:""}.ri-sd-card-mini-fill:before{content:""}.ri-sd-card-mini-line:before{content:""}.ri-search-2-fill:before{content:""}.ri-search-2-line:before{content:""}.ri-search-eye-fill:before{content:""}.ri-search-eye-line:before{content:""}.ri-search-fill:before{content:""}.ri-search-line:before{content:""}.ri-secure-payment-fill:before{content:""}.ri-secure-payment-line:before{content:""}.ri-seedling-fill:before{content:""}.ri-seedling-line:before{content:""}.ri-send-backward:before{content:""}.ri-send-plane-2-fill:before{content:""}.ri-send-plane-2-line:before{content:""}.ri-send-plane-fill:before{content:""}.ri-send-plane-line:before{content:""}.ri-send-to-back:before{content:""}.ri-sensor-fill:before{content:""}.ri-sensor-line:before{content:""}.ri-separator:before{content:""}.ri-server-fill:before{content:""}.ri-server-line:before{content:""}.ri-service-fill:before{content:""}.ri-service-line:before{content:""}.ri-settings-2-fill:before{content:""}.ri-settings-2-line:before{content:""}.ri-settings-3-fill:before{content:""}.ri-settings-3-line:before{content:""}.ri-settings-4-fill:before{content:""}.ri-settings-4-line:before{content:""}.ri-settings-5-fill:before{content:""}.ri-settings-5-line:before{content:""}.ri-settings-6-fill:before{content:""}.ri-settings-6-line:before{content:""}.ri-settings-fill:before{content:""}.ri-settings-line:before{content:""}.ri-shape-2-fill:before{content:""}.ri-shape-2-line:before{content:""}.ri-shape-fill:before{content:""}.ri-shape-line:before{content:""}.ri-share-box-fill:before{content:""}.ri-share-box-line:before{content:""}.ri-share-circle-fill:before{content:""}.ri-share-circle-line:before{content:""}.ri-share-fill:before{content:""}.ri-share-forward-2-fill:before{content:""}.ri-share-forward-2-line:before{content:""}.ri-share-forward-box-fill:before{content:""}.ri-share-forward-box-line:before{content:""}.ri-share-forward-fill:before{content:""}.ri-share-forward-line:before{content:""}.ri-share-line:before{content:""}.ri-shield-check-fill:before{content:""}.ri-shield-check-line:before{content:""}.ri-shield-cross-fill:before{content:""}.ri-shield-cross-line:before{content:""}.ri-shield-fill:before{content:""}.ri-shield-flash-fill:before{content:""}.ri-shield-flash-line:before{content:""}.ri-shield-keyhole-fill:before{content:""}.ri-shield-keyhole-line:before{content:""}.ri-shield-line:before{content:""}.ri-shield-star-fill:before{content:""}.ri-shield-star-line:before{content:""}.ri-shield-user-fill:before{content:""}.ri-shield-user-line:before{content:""}.ri-ship-2-fill:before{content:""}.ri-ship-2-line:before{content:""}.ri-ship-fill:before{content:""}.ri-ship-line:before{content:""}.ri-shirt-fill:before{content:""}.ri-shirt-line:before{content:""}.ri-shopping-bag-2-fill:before{content:""}.ri-shopping-bag-2-line:before{content:""}.ri-shopping-bag-3-fill:before{content:""}.ri-shopping-bag-3-line:before{content:""}.ri-shopping-bag-fill:before{content:""}.ri-shopping-bag-line:before{content:""}.ri-shopping-basket-2-fill:before{content:""}.ri-shopping-basket-2-line:before{content:""}.ri-shopping-basket-fill:before{content:""}.ri-shopping-basket-line:before{content:""}.ri-shopping-cart-2-fill:before{content:""}.ri-shopping-cart-2-line:before{content:""}.ri-shopping-cart-fill:before{content:""}.ri-shopping-cart-line:before{content:""}.ri-showers-fill:before{content:""}.ri-showers-line:before{content:""}.ri-shuffle-fill:before{content:""}.ri-shuffle-line:before{content:""}.ri-shut-down-fill:before{content:""}.ri-shut-down-line:before{content:""}.ri-side-bar-fill:before{content:""}.ri-side-bar-line:before{content:""}.ri-signal-tower-fill:before{content:""}.ri-signal-tower-line:before{content:""}.ri-signal-wifi-1-fill:before{content:""}.ri-signal-wifi-1-line:before{content:""}.ri-signal-wifi-2-fill:before{content:""}.ri-signal-wifi-2-line:before{content:""}.ri-signal-wifi-3-fill:before{content:""}.ri-signal-wifi-3-line:before{content:""}.ri-signal-wifi-error-fill:before{content:""}.ri-signal-wifi-error-line:before{content:""}.ri-signal-wifi-fill:before{content:""}.ri-signal-wifi-line:before{content:""}.ri-signal-wifi-off-fill:before{content:""}.ri-signal-wifi-off-line:before{content:""}.ri-sim-card-2-fill:before{content:""}.ri-sim-card-2-line:before{content:""}.ri-sim-card-fill:before{content:""}.ri-sim-card-line:before{content:""}.ri-single-quotes-l:before{content:""}.ri-single-quotes-r:before{content:""}.ri-sip-fill:before{content:""}.ri-sip-line:before{content:""}.ri-skip-back-fill:before{content:""}.ri-skip-back-line:before{content:""}.ri-skip-back-mini-fill:before{content:""}.ri-skip-back-mini-line:before{content:""}.ri-skip-forward-fill:before{content:""}.ri-skip-forward-line:before{content:""}.ri-skip-forward-mini-fill:before{content:""}.ri-skip-forward-mini-line:before{content:""}.ri-skull-2-fill:before{content:""}.ri-skull-2-line:before{content:""}.ri-skull-fill:before{content:""}.ri-skull-line:before{content:""}.ri-skype-fill:before{content:""}.ri-skype-line:before{content:""}.ri-slack-fill:before{content:""}.ri-slack-line:before{content:""}.ri-slice-fill:before{content:""}.ri-slice-line:before{content:""}.ri-slideshow-2-fill:before{content:""}.ri-slideshow-2-line:before{content:""}.ri-slideshow-3-fill:before{content:""}.ri-slideshow-3-line:before{content:""}.ri-slideshow-4-fill:before{content:""}.ri-slideshow-4-line:before{content:""}.ri-slideshow-fill:before{content:""}.ri-slideshow-line:before{content:""}.ri-smartphone-fill:before{content:""}.ri-smartphone-line:before{content:""}.ri-snapchat-fill:before{content:""}.ri-snapchat-line:before{content:""}.ri-snowy-fill:before{content:""}.ri-snowy-line:before{content:""}.ri-sort-asc:before{content:""}.ri-sort-desc:before{content:""}.ri-sound-module-fill:before{content:""}.ri-sound-module-line:before{content:""}.ri-soundcloud-fill:before{content:""}.ri-soundcloud-line:before{content:""}.ri-space-ship-fill:before{content:""}.ri-space-ship-line:before{content:""}.ri-space:before{content:""}.ri-spam-2-fill:before{content:""}.ri-spam-2-line:before{content:""}.ri-spam-3-fill:before{content:""}.ri-spam-3-line:before{content:""}.ri-spam-fill:before{content:""}.ri-spam-line:before{content:""}.ri-speaker-2-fill:before{content:""}.ri-speaker-2-line:before{content:""}.ri-speaker-3-fill:before{content:""}.ri-speaker-3-line:before{content:""}.ri-speaker-fill:before{content:""}.ri-speaker-line:before{content:""}.ri-spectrum-fill:before{content:""}.ri-spectrum-line:before{content:""}.ri-speed-fill:before{content:""}.ri-speed-line:before{content:""}.ri-speed-mini-fill:before{content:""}.ri-speed-mini-line:before{content:""}.ri-split-cells-horizontal:before{content:""}.ri-split-cells-vertical:before{content:""}.ri-spotify-fill:before{content:""}.ri-spotify-line:before{content:""}.ri-spy-fill:before{content:""}.ri-spy-line:before{content:""}.ri-stack-fill:before{content:""}.ri-stack-line:before{content:""}.ri-stack-overflow-fill:before{content:""}.ri-stack-overflow-line:before{content:""}.ri-stackshare-fill:before{content:""}.ri-stackshare-line:before{content:""}.ri-star-fill:before{content:""}.ri-star-half-fill:before{content:""}.ri-star-half-line:before{content:""}.ri-star-half-s-fill:before{content:""}.ri-star-half-s-line:before{content:""}.ri-star-line:before{content:""}.ri-star-s-fill:before{content:""}.ri-star-s-line:before{content:""}.ri-star-smile-fill:before{content:""}.ri-star-smile-line:before{content:""}.ri-steam-fill:before{content:""}.ri-steam-line:before{content:""}.ri-steering-2-fill:before{content:""}.ri-steering-2-line:before{content:""}.ri-steering-fill:before{content:""}.ri-steering-line:before{content:""}.ri-stethoscope-fill:before{content:""}.ri-stethoscope-line:before{content:""}.ri-sticky-note-2-fill:before{content:""}.ri-sticky-note-2-line:before{content:""}.ri-sticky-note-fill:before{content:""}.ri-sticky-note-line:before{content:""}.ri-stock-fill:before{content:""}.ri-stock-line:before{content:""}.ri-stop-circle-fill:before{content:""}.ri-stop-circle-line:before{content:""}.ri-stop-fill:before{content:""}.ri-stop-line:before{content:""}.ri-stop-mini-fill:before{content:""}.ri-stop-mini-line:before{content:""}.ri-store-2-fill:before{content:""}.ri-store-2-line:before{content:""}.ri-store-3-fill:before{content:""}.ri-store-3-line:before{content:""}.ri-store-fill:before{content:""}.ri-store-line:before{content:""}.ri-strikethrough-2:before{content:""}.ri-strikethrough:before{content:""}.ri-subscript-2:before{content:""}.ri-subscript:before{content:""}.ri-subtract-fill:before{content:""}.ri-subtract-line:before{content:""}.ri-subway-fill:before{content:""}.ri-subway-line:before{content:""}.ri-subway-wifi-fill:before{content:""}.ri-subway-wifi-line:before{content:""}.ri-suitcase-2-fill:before{content:""}.ri-suitcase-2-line:before{content:""}.ri-suitcase-3-fill:before{content:""}.ri-suitcase-3-line:before{content:""}.ri-suitcase-fill:before{content:""}.ri-suitcase-line:before{content:""}.ri-sun-cloudy-fill:before{content:""}.ri-sun-cloudy-line:before{content:""}.ri-sun-fill:before{content:""}.ri-sun-foggy-fill:before{content:""}.ri-sun-foggy-line:before{content:""}.ri-sun-line:before{content:""}.ri-superscript-2:before{content:""}.ri-superscript:before{content:""}.ri-surgical-mask-fill:before{content:""}.ri-surgical-mask-line:before{content:""}.ri-surround-sound-fill:before{content:""}.ri-surround-sound-line:before{content:""}.ri-survey-fill:before{content:""}.ri-survey-line:before{content:""}.ri-swap-box-fill:before{content:""}.ri-swap-box-line:before{content:""}.ri-swap-fill:before{content:""}.ri-swap-line:before{content:""}.ri-switch-fill:before{content:""}.ri-switch-line:before{content:""}.ri-sword-fill:before{content:""}.ri-sword-line:before{content:""}.ri-syringe-fill:before{content:""}.ri-syringe-line:before{content:""}.ri-t-box-fill:before{content:""}.ri-t-box-line:before{content:""}.ri-t-shirt-2-fill:before{content:""}.ri-t-shirt-2-line:before{content:""}.ri-t-shirt-air-fill:before{content:""}.ri-t-shirt-air-line:before{content:""}.ri-t-shirt-fill:before{content:""}.ri-t-shirt-line:before{content:""}.ri-table-2:before{content:""}.ri-table-alt-fill:before{content:""}.ri-table-alt-line:before{content:""}.ri-table-fill:before{content:""}.ri-table-line:before{content:""}.ri-tablet-fill:before{content:""}.ri-tablet-line:before{content:""}.ri-takeaway-fill:before{content:""}.ri-takeaway-line:before{content:""}.ri-taobao-fill:before{content:""}.ri-taobao-line:before{content:""}.ri-tape-fill:before{content:""}.ri-tape-line:before{content:""}.ri-task-fill:before{content:""}.ri-task-line:before{content:""}.ri-taxi-fill:before{content:""}.ri-taxi-line:before{content:""}.ri-taxi-wifi-fill:before{content:""}.ri-taxi-wifi-line:before{content:""}.ri-team-fill:before{content:""}.ri-team-line:before{content:""}.ri-telegram-fill:before{content:""}.ri-telegram-line:before{content:""}.ri-temp-cold-fill:before{content:""}.ri-temp-cold-line:before{content:""}.ri-temp-hot-fill:before{content:""}.ri-temp-hot-line:before{content:""}.ri-terminal-box-fill:before{content:""}.ri-terminal-box-line:before{content:""}.ri-terminal-fill:before{content:""}.ri-terminal-line:before{content:""}.ri-terminal-window-fill:before{content:""}.ri-terminal-window-line:before{content:""}.ri-test-tube-fill:before{content:""}.ri-test-tube-line:before{content:""}.ri-text-direction-l:before{content:""}.ri-text-direction-r:before{content:""}.ri-text-spacing:before{content:""}.ri-text-wrap:before{content:""}.ri-text:before{content:""}.ri-thermometer-fill:before{content:""}.ri-thermometer-line:before{content:""}.ri-thumb-down-fill:before{content:""}.ri-thumb-down-line:before{content:""}.ri-thumb-up-fill:before{content:""}.ri-thumb-up-line:before{content:""}.ri-thunderstorms-fill:before{content:""}.ri-thunderstorms-line:before{content:""}.ri-ticket-2-fill:before{content:""}.ri-ticket-2-line:before{content:""}.ri-ticket-fill:before{content:""}.ri-ticket-line:before{content:""}.ri-time-fill:before{content:""}.ri-time-line:before{content:""}.ri-timer-2-fill:before{content:""}.ri-timer-2-line:before{content:""}.ri-timer-fill:before{content:""}.ri-timer-flash-fill:before{content:""}.ri-timer-flash-line:before{content:""}.ri-timer-line:before{content:""}.ri-todo-fill:before{content:""}.ri-todo-line:before{content:""}.ri-toggle-fill:before{content:""}.ri-toggle-line:before{content:""}.ri-tools-fill:before{content:""}.ri-tools-line:before{content:""}.ri-tornado-fill:before{content:""}.ri-tornado-line:before{content:""}.ri-trademark-fill:before{content:""}.ri-trademark-line:before{content:""}.ri-traffic-light-fill:before{content:""}.ri-traffic-light-line:before{content:""}.ri-train-fill:before{content:""}.ri-train-line:before{content:""}.ri-train-wifi-fill:before{content:""}.ri-train-wifi-line:before{content:""}.ri-translate-2:before{content:""}.ri-translate:before{content:""}.ri-travesti-fill:before{content:""}.ri-travesti-line:before{content:""}.ri-treasure-map-fill:before{content:""}.ri-treasure-map-line:before{content:""}.ri-trello-fill:before{content:""}.ri-trello-line:before{content:""}.ri-trophy-fill:before{content:""}.ri-trophy-line:before{content:""}.ri-truck-fill:before{content:""}.ri-truck-line:before{content:""}.ri-tumblr-fill:before{content:""}.ri-tumblr-line:before{content:""}.ri-tv-2-fill:before{content:""}.ri-tv-2-line:before{content:""}.ri-tv-fill:before{content:""}.ri-tv-line:before{content:""}.ri-twitch-fill:before{content:""}.ri-twitch-line:before{content:""}.ri-twitter-fill:before{content:""}.ri-twitter-line:before{content:""}.ri-typhoon-fill:before{content:""}.ri-typhoon-line:before{content:""}.ri-u-disk-fill:before{content:""}.ri-u-disk-line:before{content:""}.ri-ubuntu-fill:before{content:""}.ri-ubuntu-line:before{content:""}.ri-umbrella-fill:before{content:""}.ri-umbrella-line:before{content:""}.ri-underline:before{content:""}.ri-uninstall-fill:before{content:""}.ri-uninstall-line:before{content:""}.ri-unsplash-fill:before{content:""}.ri-unsplash-line:before{content:""}.ri-upload-2-fill:before{content:""}.ri-upload-2-line:before{content:""}.ri-upload-cloud-2-fill:before{content:""}.ri-upload-cloud-2-line:before{content:""}.ri-upload-cloud-fill:before{content:""}.ri-upload-cloud-line:before{content:""}.ri-upload-fill:before{content:""}.ri-upload-line:before{content:""}.ri-usb-fill:before{content:""}.ri-usb-line:before{content:""}.ri-user-2-fill:before{content:""}.ri-user-2-line:before{content:""}.ri-user-3-fill:before{content:""}.ri-user-3-line:before{content:""}.ri-user-4-fill:before{content:""}.ri-user-4-line:before{content:""}.ri-user-5-fill:before{content:""}.ri-user-5-line:before{content:""}.ri-user-6-fill:before{content:""}.ri-user-6-line:before{content:""}.ri-user-add-fill:before{content:""}.ri-user-add-line:before{content:""}.ri-user-fill:before{content:""}.ri-user-follow-fill:before{content:""}.ri-user-follow-line:before{content:""}.ri-user-heart-fill:before{content:""}.ri-user-heart-line:before{content:""}.ri-user-line:before{content:""}.ri-user-location-fill:before{content:""}.ri-user-location-line:before{content:""}.ri-user-received-2-fill:before{content:""}.ri-user-received-2-line:before{content:""}.ri-user-received-fill:before{content:""}.ri-user-received-line:before{content:""}.ri-user-search-fill:before{content:""}.ri-user-search-line:before{content:""}.ri-user-settings-fill:before{content:""}.ri-user-settings-line:before{content:""}.ri-user-shared-2-fill:before{content:""}.ri-user-shared-2-line:before{content:""}.ri-user-shared-fill:before{content:""}.ri-user-shared-line:before{content:""}.ri-user-smile-fill:before{content:""}.ri-user-smile-line:before{content:""}.ri-user-star-fill:before{content:""}.ri-user-star-line:before{content:""}.ri-user-unfollow-fill:before{content:""}.ri-user-unfollow-line:before{content:""}.ri-user-voice-fill:before{content:""}.ri-user-voice-line:before{content:""}.ri-video-add-fill:before{content:""}.ri-video-add-line:before{content:""}.ri-video-chat-fill:before{content:""}.ri-video-chat-line:before{content:""}.ri-video-download-fill:before{content:""}.ri-video-download-line:before{content:""}.ri-video-fill:before{content:""}.ri-video-line:before{content:""}.ri-video-upload-fill:before{content:""}.ri-video-upload-line:before{content:""}.ri-vidicon-2-fill:before{content:""}.ri-vidicon-2-line:before{content:""}.ri-vidicon-fill:before{content:""}.ri-vidicon-line:before{content:""}.ri-vimeo-fill:before{content:""}.ri-vimeo-line:before{content:""}.ri-vip-crown-2-fill:before{content:""}.ri-vip-crown-2-line:before{content:""}.ri-vip-crown-fill:before{content:""}.ri-vip-crown-line:before{content:""}.ri-vip-diamond-fill:before{content:""}.ri-vip-diamond-line:before{content:""}.ri-vip-fill:before{content:""}.ri-vip-line:before{content:""}.ri-virus-fill:before{content:""}.ri-virus-line:before{content:""}.ri-visa-fill:before{content:""}.ri-visa-line:before{content:""}.ri-voice-recognition-fill:before{content:""}.ri-voice-recognition-line:before{content:""}.ri-voiceprint-fill:before{content:""}.ri-voiceprint-line:before{content:""}.ri-volume-down-fill:before{content:""}.ri-volume-down-line:before{content:""}.ri-volume-mute-fill:before{content:""}.ri-volume-mute-line:before{content:""}.ri-volume-off-vibrate-fill:before{content:""}.ri-volume-off-vibrate-line:before{content:""}.ri-volume-up-fill:before{content:""}.ri-volume-up-line:before{content:""}.ri-volume-vibrate-fill:before{content:""}.ri-volume-vibrate-line:before{content:""}.ri-vuejs-fill:before{content:""}.ri-vuejs-line:before{content:""}.ri-walk-fill:before{content:""}.ri-walk-line:before{content:""}.ri-wallet-2-fill:before{content:""}.ri-wallet-2-line:before{content:""}.ri-wallet-3-fill:before{content:""}.ri-wallet-3-line:before{content:""}.ri-wallet-fill:before{content:""}.ri-wallet-line:before{content:""}.ri-water-flash-fill:before{content:""}.ri-water-flash-line:before{content:""}.ri-webcam-fill:before{content:""}.ri-webcam-line:before{content:""}.ri-wechat-2-fill:before{content:""}.ri-wechat-2-line:before{content:""}.ri-wechat-fill:before{content:""}.ri-wechat-line:before{content:""}.ri-wechat-pay-fill:before{content:""}.ri-wechat-pay-line:before{content:""}.ri-weibo-fill:before{content:""}.ri-weibo-line:before{content:""}.ri-whatsapp-fill:before{content:""}.ri-whatsapp-line:before{content:""}.ri-wheelchair-fill:before{content:""}.ri-wheelchair-line:before{content:""}.ri-wifi-fill:before{content:""}.ri-wifi-line:before{content:""}.ri-wifi-off-fill:before{content:""}.ri-wifi-off-line:before{content:""}.ri-window-2-fill:before{content:""}.ri-window-2-line:before{content:""}.ri-window-fill:before{content:""}.ri-window-line:before{content:""}.ri-windows-fill:before{content:""}.ri-windows-line:before{content:""}.ri-windy-fill:before{content:""}.ri-windy-line:before{content:""}.ri-wireless-charging-fill:before{content:""}.ri-wireless-charging-line:before{content:""}.ri-women-fill:before{content:""}.ri-women-line:before{content:""}.ri-wubi-input:before{content:""}.ri-xbox-fill:before{content:""}.ri-xbox-line:before{content:""}.ri-xing-fill:before{content:""}.ri-xing-line:before{content:""}.ri-youtube-fill:before{content:""}.ri-youtube-line:before{content:""}.ri-zcool-fill:before{content:""}.ri-zcool-line:before{content:""}.ri-zhihu-fill:before{content:""}.ri-zhihu-line:before{content:""}.ri-zoom-in-fill:before{content:""}.ri-zoom-in-line:before{content:""}.ri-zoom-out-fill:before{content:""}.ri-zoom-out-line:before{content:""}.ri-zzz-fill:before{content:""}.ri-zzz-line:before{content:""}.ri-arrow-down-double-fill:before{content:""}.ri-arrow-down-double-line:before{content:""}.ri-arrow-left-double-fill:before{content:""}.ri-arrow-left-double-line:before{content:""}.ri-arrow-right-double-fill:before{content:""}.ri-arrow-right-double-line:before{content:""}.ri-arrow-turn-back-fill:before{content:""}.ri-arrow-turn-back-line:before{content:""}.ri-arrow-turn-forward-fill:before{content:""}.ri-arrow-turn-forward-line:before{content:""}.ri-arrow-up-double-fill:before{content:""}.ri-arrow-up-double-line:before{content:""}.ri-bard-fill:before{content:""}.ri-bard-line:before{content:""}.ri-bootstrap-fill:before{content:""}.ri-bootstrap-line:before{content:""}.ri-box-1-fill:before{content:""}.ri-box-1-line:before{content:""}.ri-box-2-fill:before{content:""}.ri-box-2-line:before{content:""}.ri-box-3-fill:before{content:""}.ri-box-3-line:before{content:""}.ri-brain-fill:before{content:""}.ri-brain-line:before{content:""}.ri-candle-fill:before{content:""}.ri-candle-line:before{content:""}.ri-cash-fill:before{content:""}.ri-cash-line:before{content:""}.ri-contract-left-fill:before{content:""}.ri-contract-left-line:before{content:""}.ri-contract-left-right-fill:before{content:""}.ri-contract-left-right-line:before{content:""}.ri-contract-right-fill:before{content:""}.ri-contract-right-line:before{content:""}.ri-contract-up-down-fill:before{content:""}.ri-contract-up-down-line:before{content:""}.ri-copilot-fill:before{content:""}.ri-copilot-line:before{content:""}.ri-corner-down-left-fill:before{content:""}.ri-corner-down-left-line:before{content:""}.ri-corner-down-right-fill:before{content:""}.ri-corner-down-right-line:before{content:""}.ri-corner-left-down-fill:before{content:""}.ri-corner-left-down-line:before{content:""}.ri-corner-left-up-fill:before{content:""}.ri-corner-left-up-line:before{content:""}.ri-corner-right-down-fill:before{content:""}.ri-corner-right-down-line:before{content:""}.ri-corner-right-up-fill:before{content:""}.ri-corner-right-up-line:before{content:""}.ri-corner-up-left-double-fill:before{content:""}.ri-corner-up-left-double-line:before{content:""}.ri-corner-up-left-fill:before{content:""}.ri-corner-up-left-line:before{content:""}.ri-corner-up-right-double-fill:before{content:""}.ri-corner-up-right-double-line:before{content:""}.ri-corner-up-right-fill:before{content:""}.ri-corner-up-right-line:before{content:""}.ri-cross-fill:before{content:""}.ri-cross-line:before{content:""}.ri-edge-new-fill:before{content:""}.ri-edge-new-line:before{content:""}.ri-equal-fill:before{content:""}.ri-equal-line:before{content:""}.ri-expand-left-fill:before{content:""}.ri-expand-left-line:before{content:""}.ri-expand-left-right-fill:before{content:""}.ri-expand-left-right-line:before{content:""}.ri-expand-right-fill:before{content:""}.ri-expand-right-line:before{content:""}.ri-expand-up-down-fill:before{content:""}.ri-expand-up-down-line:before{content:""}.ri-flickr-fill:before{content:""}.ri-flickr-line:before{content:""}.ri-forward-10-fill:before{content:""}.ri-forward-10-line:before{content:""}.ri-forward-15-fill:before{content:""}.ri-forward-15-line:before{content:""}.ri-forward-30-fill:before{content:""}.ri-forward-30-line:before{content:""}.ri-forward-5-fill:before{content:""}.ri-forward-5-line:before{content:""}.ri-graduation-cap-fill:before{content:""}.ri-graduation-cap-line:before{content:""}.ri-home-office-fill:before{content:""}.ri-home-office-line:before{content:""}.ri-hourglass-2-fill:before{content:""}.ri-hourglass-2-line:before{content:""}.ri-hourglass-fill:before{content:""}.ri-hourglass-line:before{content:""}.ri-javascript-fill:before{content:""}.ri-javascript-line:before{content:""}.ri-loop-left-fill:before{content:""}.ri-loop-left-line:before{content:""}.ri-loop-right-fill:before{content:""}.ri-loop-right-line:before{content:""}.ri-memories-fill:before{content:""}.ri-memories-line:before{content:""}.ri-meta-fill:before{content:""}.ri-meta-line:before{content:""}.ri-microsoft-loop-fill:before{content:""}.ri-microsoft-loop-line:before{content:""}.ri-nft-fill:before{content:""}.ri-nft-line:before{content:""}.ri-notion-fill:before{content:""}.ri-notion-line:before{content:""}.ri-openai-fill:before{content:""}.ri-openai-line:before{content:""}.ri-overline:before{content:""}.ri-p2p-fill:before{content:""}.ri-p2p-line:before{content:""}.ri-presentation-fill:before{content:""}.ri-presentation-line:before{content:""}.ri-replay-10-fill:before{content:""}.ri-replay-10-line:before{content:""}.ri-replay-15-fill:before{content:""}.ri-replay-15-line:before{content:""}.ri-replay-30-fill:before{content:""}.ri-replay-30-line:before{content:""}.ri-replay-5-fill:before{content:""}.ri-replay-5-line:before{content:""}.ri-school-fill:before{content:""}.ri-school-line:before{content:""}.ri-shining-2-fill:before{content:""}.ri-shining-2-line:before{content:""}.ri-shining-fill:before{content:""}.ri-shining-line:before{content:""}.ri-sketching:before{content:""}.ri-skip-down-fill:before{content:""}.ri-skip-down-line:before{content:""}.ri-skip-left-fill:before{content:""}.ri-skip-left-line:before{content:""}.ri-skip-right-fill:before{content:""}.ri-skip-right-line:before{content:""}.ri-skip-up-fill:before{content:""}.ri-skip-up-line:before{content:""}.ri-slow-down-fill:before{content:""}.ri-slow-down-line:before{content:""}.ri-sparkling-2-fill:before{content:""}.ri-sparkling-2-line:before{content:""}.ri-sparkling-fill:before{content:""}.ri-sparkling-line:before{content:""}.ri-speak-fill:before{content:""}.ri-speak-line:before{content:""}.ri-speed-up-fill:before{content:""}.ri-speed-up-line:before{content:""}.ri-tiktok-fill:before{content:""}.ri-tiktok-line:before{content:""}.ri-token-swap-fill:before{content:""}.ri-token-swap-line:before{content:""}.ri-unpin-fill:before{content:""}.ri-unpin-line:before{content:""}.ri-wechat-channels-fill:before{content:""}.ri-wechat-channels-line:before{content:""}.ri-wordpress-fill:before{content:""}.ri-wordpress-line:before{content:""}.ri-blender-fill:before{content:""}.ri-blender-line:before{content:""}.ri-emoji-sticker-fill:before{content:""}.ri-emoji-sticker-line:before{content:""}.ri-git-close-pull-request-fill:before{content:""}.ri-git-close-pull-request-line:before{content:""}.ri-instance-fill:before{content:""}.ri-instance-line:before{content:""}.ri-megaphone-fill:before{content:""}.ri-megaphone-line:before{content:""}.ri-pass-expired-fill:before{content:""}.ri-pass-expired-line:before{content:""}.ri-pass-pending-fill:before{content:""}.ri-pass-pending-line:before{content:""}.ri-pass-valid-fill:before{content:""}.ri-pass-valid-line:before{content:""}.ri-ai-generate:before{content:""}.ri-calendar-close-fill:before{content:""}.ri-calendar-close-line:before{content:""}.ri-draggable:before{content:""}.ri-font-family:before{content:""}.ri-font-mono:before{content:""}.ri-font-sans-serif:before{content:""}.ri-font-sans:before{content:""}.ri-hard-drive-3-fill:before{content:""}.ri-hard-drive-3-line:before{content:""}.ri-kick-fill:before{content:""}.ri-kick-line:before{content:""}.ri-list-check-3:before{content:""}.ri-list-indefinite:before{content:""}.ri-list-ordered-2:before{content:""}.ri-list-radio:before{content:""}.ri-openbase-fill:before{content:""}.ri-openbase-line:before{content:""}.ri-planet-fill:before{content:""}.ri-planet-line:before{content:""}.ri-prohibited-fill:before{content:""}.ri-prohibited-line:before{content:""}.ri-quote-text:before{content:""}.ri-seo-fill:before{content:""}.ri-seo-line:before{content:""}.ri-slash-commands:before{content:""}.ri-archive-2-fill:before{content:""}.ri-archive-2-line:before{content:""}.ri-inbox-2-fill:before{content:""}.ri-inbox-2-line:before{content:""}.ri-shake-hands-fill:before{content:""}.ri-shake-hands-line:before{content:""}.ri-supabase-fill:before{content:""}.ri-supabase-line:before{content:""}.ri-water-percent-fill:before{content:""}.ri-water-percent-line:before{content:""}.ri-yuque-fill:before{content:""}.ri-yuque-line:before{content:""}.ri-crosshair-2-fill:before{content:""}.ri-crosshair-2-line:before{content:""}.ri-crosshair-fill:before{content:""}.ri-crosshair-line:before{content:""}.ri-file-close-fill:before{content:""}.ri-file-close-line:before{content:""}.ri-infinity-fill:before{content:""}.ri-infinity-line:before{content:""}.ri-rfid-fill:before{content:""}.ri-rfid-line:before{content:""}.ri-slash-commands-2:before{content:""}.ri-user-forbid-fill:before{content:""}.ri-user-forbid-line:before{content:""}.ri-beer-fill:before{content:""}.ri-beer-line:before{content:""}.ri-circle-fill:before{content:""}.ri-circle-line:before{content:""}.ri-dropdown-list:before{content:""}.ri-file-image-fill:before{content:""}.ri-file-image-line:before{content:""}.ri-file-pdf-2-fill:before{content:""}.ri-file-pdf-2-line:before{content:""}.ri-file-video-fill:before{content:""}.ri-file-video-line:before{content:""}.ri-folder-image-fill:before{content:""}.ri-folder-image-line:before{content:""}.ri-folder-video-fill:before{content:""}.ri-folder-video-line:before{content:""}.ri-hexagon-fill:before{content:""}.ri-hexagon-line:before{content:""}.ri-menu-search-fill:before{content:""}.ri-menu-search-line:before{content:""}.ri-octagon-fill:before{content:""}.ri-octagon-line:before{content:""}.ri-pentagon-fill:before{content:""}.ri-pentagon-line:before{content:""}.ri-rectangle-fill:before{content:""}.ri-rectangle-line:before{content:""}.ri-robot-2-fill:before{content:""}.ri-robot-2-line:before{content:""}.ri-shapes-fill:before{content:""}.ri-shapes-line:before{content:""}.ri-square-fill:before{content:""}.ri-square-line:before{content:""}.ri-tent-fill:before{content:""}.ri-tent-line:before{content:""}.ri-threads-fill:before{content:""}.ri-threads-line:before{content:""}.ri-tree-fill:before{content:""}.ri-tree-line:before{content:""}.ri-triangle-fill:before{content:""}.ri-triangle-line:before{content:""}.ri-twitter-x-fill:before{content:""}.ri-twitter-x-line:before{content:""}.ri-verified-badge-fill:before{content:""}.ri-verified-badge-line:before{content:""}.ri-armchair-fill:before{content:""}.ri-armchair-line:before{content:""}.ri-bnb-fill:before{content:""}.ri-bnb-line:before{content:""}.ri-bread-fill:before{content:""}.ri-bread-line:before{content:""}.ri-btc-fill:before{content:""}.ri-btc-line:before{content:""}.ri-calendar-schedule-fill:before{content:""}.ri-calendar-schedule-line:before{content:""}.ri-dice-1-fill:before{content:""}.ri-dice-1-line:before{content:""}.ri-dice-2-fill:before{content:""}.ri-dice-2-line:before{content:""}.ri-dice-3-fill:before{content:""}.ri-dice-3-line:before{content:""}.ri-dice-4-fill:before{content:""}.ri-dice-4-line:before{content:""}.ri-dice-5-fill:before{content:""}.ri-dice-5-line:before{content:""}.ri-dice-6-fill:before{content:""}.ri-dice-6-line:before{content:""}.ri-dice-fill:before{content:""}.ri-dice-line:before{content:""}.ri-drinks-fill:before{content:""}.ri-drinks-line:before{content:""}.ri-equalizer-2-fill:before{content:""}.ri-equalizer-2-line:before{content:""}.ri-equalizer-3-fill:before{content:""}.ri-equalizer-3-line:before{content:""}.ri-eth-fill:before{content:""}.ri-eth-line:before{content:""}.ri-flower-fill:before{content:""}.ri-flower-line:before{content:""}.ri-glasses-2-fill:before{content:""}.ri-glasses-2-line:before{content:""}.ri-glasses-fill:before{content:""}.ri-glasses-line:before{content:""}.ri-goggles-fill:before{content:""}.ri-goggles-line:before{content:""}.ri-image-circle-fill:before{content:""}.ri-image-circle-line:before{content:""}.ri-info-i:before{content:""}.ri-money-rupee-circle-fill:before{content:""}.ri-money-rupee-circle-line:before{content:""}.ri-news-fill:before{content:""}.ri-news-line:before{content:""}.ri-robot-3-fill:before{content:""}.ri-robot-3-line:before{content:""}.ri-share-2-fill:before{content:""}.ri-share-2-line:before{content:""}.ri-sofa-fill:before{content:""}.ri-sofa-line:before{content:""}.ri-svelte-fill:before{content:""}.ri-svelte-line:before{content:""}.ri-vk-fill:before{content:""}.ri-vk-line:before{content:""}.ri-xrp-fill:before{content:""}.ri-xrp-line:before{content:""}.ri-xtz-fill:before{content:""}.ri-xtz-line:before{content:""}.ri-archive-stack-fill:before{content:""}.ri-archive-stack-line:before{content:""}.ri-bowl-fill:before{content:""}.ri-bowl-line:before{content:""}.ri-calendar-view:before{content:""}.ri-carousel-view:before{content:""}.ri-code-block:before{content:""}.ri-color-filter-fill:before{content:""}.ri-color-filter-line:before{content:""}.ri-contacts-book-3-fill:before{content:""}.ri-contacts-book-3-line:before{content:""}.ri-contract-fill:before{content:""}.ri-contract-line:before{content:""}.ri-drinks-2-fill:before{content:""}.ri-drinks-2-line:before{content:""}.ri-export-fill:before{content:""}.ri-export-line:before{content:""}.ri-file-check-fill:before{content:""}.ri-file-check-line:before{content:""}.ri-focus-mode:before{content:""}.ri-folder-6-fill:before{content:""}.ri-folder-6-line:before{content:""}.ri-folder-check-fill:before{content:""}.ri-folder-check-line:before{content:""}.ri-folder-close-fill:before{content:""}.ri-folder-close-line:before{content:""}.ri-folder-cloud-fill:before{content:""}.ri-folder-cloud-line:before{content:""}.ri-gallery-view-2:before{content:""}.ri-gallery-view:before{content:""}.ri-hand:before{content:""}.ri-import-fill:before{content:""}.ri-import-line:before{content:""}.ri-information-2-fill:before{content:""}.ri-information-2-line:before{content:""}.ri-kanban-view-2:before{content:""}.ri-kanban-view:before{content:""}.ri-list-view:before{content:""}.ri-lock-star-fill:before{content:""}.ri-lock-star-line:before{content:""}.ri-puzzle-2-fill:before{content:""}.ri-puzzle-2-line:before{content:""}.ri-puzzle-fill:before{content:""}.ri-puzzle-line:before{content:""}.ri-ram-2-fill:before{content:""}.ri-ram-2-line:before{content:""}.ri-ram-fill:before{content:""}.ri-ram-line:before{content:""}.ri-receipt-fill:before{content:""}.ri-receipt-line:before{content:""}.ri-shadow-fill:before{content:""}.ri-shadow-line:before{content:""}.ri-sidebar-fold-fill:before{content:""}.ri-sidebar-fold-line:before{content:""}.ri-sidebar-unfold-fill:before{content:""}.ri-sidebar-unfold-line:before{content:""}.ri-slideshow-view:before{content:""}.ri-sort-alphabet-asc:before{content:""}.ri-sort-alphabet-desc:before{content:""}.ri-sort-number-asc:before{content:""}.ri-sort-number-desc:before{content:""}.ri-stacked-view:before{content:""}.ri-sticky-note-add-fill:before{content:""}.ri-sticky-note-add-line:before{content:""}.ri-swap-2-fill:before{content:""}.ri-swap-2-line:before{content:""}.ri-swap-3-fill:before{content:""}.ri-swap-3-line:before{content:""}.ri-table-3:before{content:""}.ri-table-view:before{content:""}.ri-text-block:before{content:""}.ri-text-snippet:before{content:""}.ri-timeline-view:before{content:""}.ri-blogger-fill:before{content:""}.ri-blogger-line:before{content:""}.ri-chat-thread-fill:before{content:""}.ri-chat-thread-line:before{content:""}.ri-discount-percent-fill:before{content:""}.ri-discount-percent-line:before{content:""}.ri-exchange-2-fill:before{content:""}.ri-exchange-2-line:before{content:""}.ri-git-fork-fill:before{content:""}.ri-git-fork-line:before{content:""}.ri-input-field:before{content:""}.ri-progress-1-fill:before{content:""}.ri-progress-1-line:before{content:""}.ri-progress-2-fill:before{content:""}.ri-progress-2-line:before{content:""}.ri-progress-3-fill:before{content:""}.ri-progress-3-line:before{content:""}.ri-progress-4-fill:before{content:""}.ri-progress-4-line:before{content:""}.ri-progress-5-fill:before{content:""}.ri-progress-5-line:before{content:""}.ri-progress-6-fill:before{content:""}.ri-progress-6-line:before{content:""}.ri-progress-7-fill:before{content:""}.ri-progress-7-line:before{content:""}.ri-progress-8-fill:before{content:""}.ri-progress-8-line:before{content:""}.ri-remix-run-fill:before{content:""}.ri-remix-run-line:before{content:""}.ri-signpost-fill:before{content:""}.ri-signpost-line:before{content:""}.ri-time-zone-fill:before{content:""}.ri-time-zone-line:before{content:""}.ri-arrow-down-wide-fill:before{content:""}.ri-arrow-down-wide-line:before{content:""}.ri-arrow-left-wide-fill:before{content:""}.ri-arrow-left-wide-line:before{content:""}.ri-arrow-right-wide-fill:before{content:""}.ri-arrow-right-wide-line:before{content:""}.ri-arrow-up-wide-fill:before{content:""}.ri-arrow-up-wide-line:before{content:""}.ri-bluesky-fill:before{content:""}.ri-bluesky-line:before{content:""}.ri-expand-height-fill:before{content:""}.ri-expand-height-line:before{content:""}.ri-expand-width-fill:before{content:""}.ri-expand-width-line:before{content:""}.ri-forward-end-fill:before{content:""}.ri-forward-end-line:before{content:""}.ri-forward-end-mini-fill:before{content:""}.ri-forward-end-mini-line:before{content:""}.ri-friendica-fill:before{content:""}.ri-friendica-line:before{content:""}.ri-git-pr-draft-fill:before{content:""}.ri-git-pr-draft-line:before{content:""}.ri-play-reverse-fill:before{content:""}.ri-play-reverse-line:before{content:""}.ri-play-reverse-mini-fill:before{content:""}.ri-play-reverse-mini-line:before{content:""}.ri-rewind-start-fill:before{content:""}.ri-rewind-start-line:before{content:""}.ri-rewind-start-mini-fill:before{content:""}.ri-rewind-start-mini-line:before{content:""}.ri-scroll-to-bottom-fill:before{content:""}.ri-scroll-to-bottom-line:before{content:""}.ri-add-large-fill:before{content:""}.ri-add-large-line:before{content:""}.ri-aed-electrodes-fill:before{content:""}.ri-aed-electrodes-line:before{content:""}.ri-aed-fill:before{content:""}.ri-aed-line:before{content:""}.ri-alibaba-cloud-fill:before{content:""}.ri-alibaba-cloud-line:before{content:""}.ri-align-item-bottom-fill:before{content:""}.ri-align-item-bottom-line:before{content:""}.ri-align-item-horizontal-center-fill:before{content:""}.ri-align-item-horizontal-center-line:before{content:""}.ri-align-item-left-fill:before{content:""}.ri-align-item-left-line:before{content:""}.ri-align-item-right-fill:before{content:""}.ri-align-item-right-line:before{content:""}.ri-align-item-top-fill:before{content:""}.ri-align-item-top-line:before{content:""}.ri-align-item-vertical-center-fill:before{content:""}.ri-align-item-vertical-center-line:before{content:""}.ri-apps-2-add-fill:before{content:""}.ri-apps-2-add-line:before{content:""}.ri-close-large-fill:before{content:""}.ri-close-large-line:before{content:""}.ri-collapse-diagonal-2-fill:before{content:""}.ri-collapse-diagonal-2-line:before{content:""}.ri-collapse-diagonal-fill:before{content:""}.ri-collapse-diagonal-line:before{content:""}.ri-dashboard-horizontal-fill:before{content:""}.ri-dashboard-horizontal-line:before{content:""}.ri-expand-diagonal-2-fill:before{content:""}.ri-expand-diagonal-2-line:before{content:""}.ri-expand-diagonal-fill:before{content:""}.ri-expand-diagonal-line:before{content:""}.ri-firebase-fill:before{content:""}.ri-firebase-line:before{content:""}.ri-flip-horizontal-2-fill:before{content:""}.ri-flip-horizontal-2-line:before{content:""}.ri-flip-horizontal-fill:before{content:""}.ri-flip-horizontal-line:before{content:""}.ri-flip-vertical-2-fill:before{content:""}.ri-flip-vertical-2-line:before{content:""}.ri-flip-vertical-fill:before{content:""}.ri-flip-vertical-line:before{content:""}.ri-formula:before{content:""}.ri-function-add-fill:before{content:""}.ri-function-add-line:before{content:""}.ri-goblet-2-fill:before{content:""}.ri-goblet-2-line:before{content:""}.ri-golf-ball-fill:before{content:""}.ri-golf-ball-line:before{content:""}.ri-group-3-fill:before{content:""}.ri-group-3-line:before{content:""}.ri-heart-add-2-fill:before{content:""}.ri-heart-add-2-line:before{content:""}.ri-id-card-fill:before{content:""}.ri-id-card-line:before{content:""}.ri-information-off-fill:before{content:""}.ri-information-off-line:before{content:""}.ri-java-fill:before{content:""}.ri-java-line:before{content:""}.ri-layout-grid-2-fill:before{content:""}.ri-layout-grid-2-line:before{content:""}.ri-layout-horizontal-fill:before{content:""}.ri-layout-horizontal-line:before{content:""}.ri-layout-vertical-fill:before{content:""}.ri-layout-vertical-line:before{content:""}.ri-menu-fold-2-fill:before{content:""}.ri-menu-fold-2-line:before{content:""}.ri-menu-fold-3-fill:before{content:""}.ri-menu-fold-3-line:before{content:""}.ri-menu-fold-4-fill:before{content:""}.ri-menu-fold-4-line:before{content:""}.ri-menu-unfold-2-fill:before{content:""}.ri-menu-unfold-2-line:before{content:""}.ri-menu-unfold-3-fill:before{content:""}.ri-menu-unfold-3-line:before{content:""}.ri-menu-unfold-4-fill:before{content:""}.ri-menu-unfold-4-line:before{content:""}.ri-mobile-download-fill:before{content:""}.ri-mobile-download-line:before{content:""}.ri-nextjs-fill:before{content:""}.ri-nextjs-line:before{content:""}.ri-nodejs-fill:before{content:""}.ri-nodejs-line:before{content:""}.ri-pause-large-fill:before{content:""}.ri-pause-large-line:before{content:""}.ri-play-large-fill:before{content:""}.ri-play-large-line:before{content:""}.ri-play-reverse-large-fill:before{content:""}.ri-play-reverse-large-line:before{content:""}.ri-police-badge-fill:before{content:""}.ri-police-badge-line:before{content:""}.ri-prohibited-2-fill:before{content:""}.ri-prohibited-2-line:before{content:""}.ri-shopping-bag-4-fill:before{content:""}.ri-shopping-bag-4-line:before{content:""}.ri-snowflake-fill:before{content:""}.ri-snowflake-line:before{content:""}.ri-square-root:before{content:""}.ri-stop-large-fill:before{content:""}.ri-stop-large-line:before{content:""}.ri-tailwind-css-fill:before{content:""}.ri-tailwind-css-line:before{content:""}.ri-tooth-fill:before{content:""}.ri-tooth-line:before{content:""}.ri-video-off-fill:before{content:""}.ri-video-off-line:before{content:""}.ri-video-on-fill:before{content:""}.ri-video-on-line:before{content:""}.ri-webhook-fill:before{content:""}.ri-webhook-line:before{content:""}.ri-weight-fill:before{content:""}.ri-weight-line:before{content:""}.ri-book-shelf-fill:before{content:""}.ri-book-shelf-line:before{content:""}.ri-brain-2-fill:before{content:""}.ri-brain-2-line:before{content:""}.ri-chat-search-fill:before{content:""}.ri-chat-search-line:before{content:""}.ri-chat-unread-fill:before{content:""}.ri-chat-unread-line:before{content:""}.ri-collapse-horizontal-fill:before{content:""}.ri-collapse-horizontal-line:before{content:""}.ri-collapse-vertical-fill:before{content:""}.ri-collapse-vertical-line:before{content:""}.ri-dna-fill:before{content:""}.ri-dna-line:before{content:""}.ri-dropper-fill:before{content:""}.ri-dropper-line:before{content:""}.ri-expand-diagonal-s-2-fill:before{content:""}.ri-expand-diagonal-s-2-line:before{content:""}.ri-expand-diagonal-s-fill:before{content:""}.ri-expand-diagonal-s-line:before{content:""}.ri-expand-horizontal-fill:before{content:""}.ri-expand-horizontal-line:before{content:""}.ri-expand-horizontal-s-fill:before{content:""}.ri-expand-horizontal-s-line:before{content:""}.ri-expand-vertical-fill:before{content:""}.ri-expand-vertical-line:before{content:""}.ri-expand-vertical-s-fill:before{content:""}.ri-expand-vertical-s-line:before{content:""}.ri-gemini-fill:before{content:""}.ri-gemini-line:before{content:""}.ri-reset-left-fill:before{content:""}.ri-reset-left-line:before{content:""}.ri-reset-right-fill:before{content:""}.ri-reset-right-line:before{content:""}.ri-stairs-fill:before{content:""}.ri-stairs-line:before{content:""}.ri-telegram-2-fill:before{content:""}.ri-telegram-2-line:before{content:""}.ri-triangular-flag-fill:before{content:""}.ri-triangular-flag-line:before{content:""}.ri-user-minus-fill:before{content:""}.ri-user-minus-line:before{content:""}.ri-account-box-2-fill:before{content:""}.ri-account-box-2-line:before{content:""}.ri-account-circle-2-fill:before{content:""}.ri-account-circle-2-line:before{content:""}.ri-alarm-snooze-fill:before{content:""}.ri-alarm-snooze-line:before{content:""}.ri-arrow-down-box-fill:before{content:""}.ri-arrow-down-box-line:before{content:""}.ri-arrow-left-box-fill:before{content:""}.ri-arrow-left-box-line:before{content:""}.ri-arrow-left-down-box-fill:before{content:""}.ri-arrow-left-down-box-line:before{content:""}.ri-arrow-left-up-box-fill:before{content:""}.ri-arrow-left-up-box-line:before{content:""}.ri-arrow-right-box-fill:before{content:""}.ri-arrow-right-box-line:before{content:""}.ri-arrow-right-down-box-fill:before{content:""}.ri-arrow-right-down-box-line:before{content:""}.ri-arrow-right-up-box-fill:before{content:""}.ri-arrow-right-up-box-line:before{content:""}.ri-arrow-up-box-fill:before{content:""}.ri-arrow-up-box-line:before{content:""}.ri-bar-chart-box-ai-fill:before{content:""}.ri-bar-chart-box-ai-line:before{content:""}.ri-brush-ai-fill:before{content:""}.ri-brush-ai-line:before{content:""}.ri-camera-ai-fill:before{content:""}.ri-camera-ai-line:before{content:""}.ri-chat-ai-fill:before{content:""}.ri-chat-ai-line:before{content:""}.ri-chat-smile-ai-fill:before{content:""}.ri-chat-smile-ai-line:before{content:""}.ri-chat-voice-ai-fill:before{content:""}.ri-chat-voice-ai-line:before{content:""}.ri-code-ai-fill:before{content:""}.ri-code-ai-line:before{content:""}.ri-color-filter-ai-fill:before{content:""}.ri-color-filter-ai-line:before{content:""}.ri-custom-size:before{content:""}.ri-fediverse-fill:before{content:""}.ri-fediverse-line:before{content:""}.ri-flag-off-fill:before{content:""}.ri-flag-off-line:before{content:""}.ri-home-9-fill:before{content:""}.ri-home-9-line:before{content:""}.ri-image-ai-fill:before{content:""}.ri-image-ai-line:before{content:""}.ri-image-circle-ai-fill:before{content:""}.ri-image-circle-ai-line:before{content:""}.ri-info-card-fill:before{content:""}.ri-info-card-line:before{content:""}.ri-landscape-ai-fill:before{content:""}.ri-landscape-ai-line:before{content:""}.ri-letter-spacing-2:before{content:""}.ri-line-height-2:before{content:""}.ri-mail-ai-fill:before{content:""}.ri-mail-ai-line:before{content:""}.ri-mic-2-ai-fill:before{content:""}.ri-mic-2-ai-line:before{content:""}.ri-mic-ai-fill:before{content:""}.ri-mic-ai-line:before{content:""}.ri-movie-ai-fill:before{content:""}.ri-movie-ai-line:before{content:""}.ri-music-ai-fill:before{content:""}.ri-music-ai-line:before{content:""}.ri-notification-snooze-fill:before{content:""}.ri-notification-snooze-line:before{content:""}.ri-php-fill:before{content:""}.ri-php-line:before{content:""}.ri-pix-fill:before{content:""}.ri-pix-line:before{content:""}.ri-pulse-ai-fill:before{content:""}.ri-pulse-ai-line:before{content:""}.ri-quill-pen-ai-fill:before{content:""}.ri-quill-pen-ai-line:before{content:""}.ri-speak-ai-fill:before{content:""}.ri-speak-ai-line:before{content:""}.ri-star-off-fill:before{content:""}.ri-star-off-line:before{content:""}.ri-translate-ai-2:before{content:""}.ri-translate-ai:before{content:""}.ri-user-community-fill:before{content:""}.ri-user-community-line:before{content:""}.ri-vercel-fill:before{content:""}.ri-vercel-line:before{content:""}.ri-video-ai-fill:before{content:""}.ri-video-ai-line:before{content:""}.ri-video-on-ai-fill:before{content:""}.ri-video-on-ai-line:before{content:""}.ri-voice-ai-fill:before{content:""}.ri-voice-ai-line:before{content:""}.ri-ai-generate-2:before{content:""}.ri-ai-generate-text:before{content:""}.ri-anthropic-fill:before{content:""}.ri-anthropic-line:before{content:""}.ri-apps-2-ai-fill:before{content:""}.ri-apps-2-ai-line:before{content:""}.ri-camera-lens-ai-fill:before{content:""}.ri-camera-lens-ai-line:before{content:""}.ri-clapperboard-ai-fill:before{content:""}.ri-clapperboard-ai-line:before{content:""}.ri-claude-fill:before{content:""}.ri-claude-line:before{content:""}.ri-closed-captioning-ai-fill:before{content:""}.ri-closed-captioning-ai-line:before{content:""}.ri-dvd-ai-fill:before{content:""}.ri-dvd-ai-line:before{content:""}.ri-film-ai-fill:before{content:""}.ri-film-ai-line:before{content:""}.ri-font-size-ai:before{content:""}.ri-mixtral-fill:before{content:""}.ri-mixtral-line:before{content:""}.ri-movie-2-ai-fill:before{content:""}.ri-movie-2-ai-line:before{content:""}.ri-mv-ai-fill:before{content:""}.ri-mv-ai-line:before{content:""}.ri-perplexity-fill:before{content:""}.ri-perplexity-line:before{content:""}.ri-poker-clubs-fill:before{content:""}.ri-poker-clubs-line:before{content:""}.ri-poker-diamonds-fill:before{content:""}.ri-poker-diamonds-line:before{content:""}.ri-poker-hearts-fill:before{content:""}.ri-poker-hearts-line:before{content:""}.ri-poker-spades-fill:before{content:""}.ri-poker-spades-line:before{content:""}.ri-safe-3-fill:before{content:""}.ri-safe-3-line:before{content:""}.ri-accessibility-fill:before{content:""}.ri-accessibility-line:before{content:""}.ri-alarm-add-fill:before{content:""}.ri-alarm-add-line:before{content:""}.ri-arrow-down-long-fill:before{content:""}.ri-arrow-down-long-line:before{content:""}.ri-arrow-left-down-long-fill:before{content:""}.ri-arrow-left-down-long-line:before{content:""}.ri-arrow-left-long-fill:before{content:""}.ri-arrow-left-long-line:before{content:""}.ri-arrow-left-up-long-fill:before{content:""}.ri-arrow-left-up-long-line:before{content:""}.ri-arrow-right-down-long-fill:before{content:""}.ri-arrow-right-down-long-line:before{content:""}.ri-arrow-right-long-fill:before{content:""}.ri-arrow-right-long-line:before{content:""}.ri-arrow-right-up-long-fill:before{content:""}.ri-arrow-right-up-long-line:before{content:""}.ri-arrow-up-long-fill:before{content:""}.ri-arrow-up-long-line:before{content:""}.ri-chess-fill:before{content:""}.ri-chess-line:before{content:""}.ri-diamond-fill:before{content:""}.ri-diamond-line:before{content:""}.ri-diamond-ring-fill:before{content:""}.ri-diamond-ring-line:before{content:""}.ri-figma-fill:before{content:""}.ri-figma-line:before{content:""}.ri-firefox-browser-fill:before{content:""}.ri-firefox-browser-line:before{content:""}.ri-jewelry-fill:before{content:""}.ri-jewelry-line:before{content:""}.ri-multi-image-fill:before{content:""}.ri-multi-image-line:before{content:""}.ri-no-credit-card-fill:before{content:""}.ri-no-credit-card-line:before{content:""}.ri-service-bell-fill:before{content:""}.ri-service-bell-line:before{content:""}:root,.dropdown,.base-surface{--baseFontFamily:"IBM Plex Sans", system-ui, sans-serif, emoji;--monospaceFontFamily:"IBM Plex Mono", ui-monospace, monospace, emoji;--iconFontFamily:"remixicon";--primaryColor:#25272d;--primaryAlt1Color:color-mix(in srgb, var(--primaryColor), white 8%);--primaryAlt2Color:color-mix(in srgb, var(--primaryColor), white 13%);--primaryTxtColor:#fff;--primaryTxtHintColor:color-mix(in srgb, var(--primaryTxtColor), transparent 35%);--primaryTxtDisabledColor:color-mix(in srgb, var(--primaryTxtColor), transparent 50%);--secondaryColor:#e4e8ec;--secondaryAlt1Color:#dce0e5;--secondaryAlt2Color:#d3d9df;--secondaryTxtColor:var(--primaryColor);--secondaryTxtHintColor:color-mix(in srgb, var(--secondaryTxtColor), transparent 35%);--secondaryTxtDisabledColor:color-mix(in srgb, var(--secondaryTxtColor), transparent 50%);--infoColor:#3376e5;--infoAlt1Color:color-mix(in srgb, var(--infoColor), white 10%);--infoAlt2Color:color-mix(in srgb, var(--infoColor), white 15%);--infoTxtColor:var(--primaryTxtColor);--infoTxtHintColor:var(--primaryTxtHintColor);--infoTxtDisabledColor:var(--primaryTxtDisabledColor);--successColor:#22a96d;--successAlt1Color:color-mix(in srgb, var(--successColor), white 10%);--successAlt2Color:color-mix(in srgb, var(--successColor), white 15%);--successTxtColor:var(--primaryTxtColor);--successTxtHintColor:var(--primaryTxtHintColor);--successTxtDisabledColor:var(--primaryTxtDisabledColor);--warningColor:#e57534;--warningAlt1Color:color-mix(in srgb, var(--warningColor), white 10%);--warningAlt2Color:color-mix(in srgb, var(--warningColor), white 15%);--warningTxtColor:var(--primaryTxtColor);--warningTxtHintColor:var(--primaryTxtHintColor);--warningTxtDisabledColor:var(--primaryTxtDisabledColor);--dangerColor:#c33751;--dangerAlt1Color:color-mix(in srgb, var(--dangerColor), white 10%);--dangerAlt2Color:color-mix(in srgb, var(--dangerColor), white 15%);--dangerTxtColor:var(--primaryTxtColor);--dangerTxtHintColor:var(--primaryTxtHintColor);--dangerTxtDisabledColor:var(--primaryTxtDisabledColor);--accentColor:#1055c9;--accentAlt1Color:color-mix(in srgb, var(--accentColor), white 10%);--accentAlt2Color:color-mix(in srgb, var(--accentColor), white 15%);--accentTxtColor:#fff;--accentTxtHintColor:color-mix(in srgb, var(--accentTxtColor), transparent 40%);--accentTxtDisabledColor:color-mix(in srgb, var(--accentTxtColor), transparent 50%);--surfaceColor:#fff;--surfaceAlt1Color:#f8f9fa;--surfaceAlt2Color:#e4e8ec;--surfaceAlt3Color:#dce0e5;--surfaceAlt4Color:#d3d9df;--surfaceAlt5Color:#ccd4db;--surfaceTxtColor:#25272d;--surfaceTxtHintColor:#687278;--surfaceTxtDisabledColor:#a2a9ae;--surfaceAccentColor:color-mix(in srgb, var(--accentColor), white 80%);--surfaceInfoColor:color-mix(in srgb, var(--infoColor), white 80%);--surfaceSuccessColor:color-mix(in srgb, var(--successColor), white 80%);--surfaceWarningColor:color-mix(in srgb, var(--warningColor), white 80%);--surfaceDangerColor:color-mix(in srgb, var(--dangerColor), white 80%);--selectionColor:#465a7340;--inputColor:var(--surfaceAlt2Color);--inputFocusColor:var(--surfaceAlt3Color);--inputBorderColor:var(--surfaceAlt4Color);scrollbar-color:var(--surfaceAlt4Color) transparent;--prismFunctionColor:#7c00aa;--prismStringColor:#116b00;--prismNumberColor:#246f86;--prismPropertyColor:#246f86;--prismKeywordColor:#a04900;--prismCommentColor:var(--surfaceTxtHintColor);--prismPunctuationColor:var(--surfaceTxtColor);--modalAnimationSpeed:.2s;--modalOverlayColor:#465a7340;--tooltipTxtColor:#fff;--tooltipSurfaceColor:#25272de6;--boxShadow:0px 8px 5px -5px #2224241a;--leftBoxShadow:-1px 0px 5px 0 #2224241a;--rightBoxShadow:1px 0px 5px 0 #2224241a;--topScrollShadow:0px 2px 8px 1px #22242426;--bottomScrollShadow:0px -2px 8px 1px #22242426;--leftScrollShadow:3px 0px 5px 0px #22242433;--rightScrollShadow:-3px 0px 5px 0px #22242433;--fontSize:14px;--smFontSize:13px;--lgFontSize:15px;--lineHeight:22px;--smLineHeight:16px;--lgLineHeight:24px;--btnHeight:45px;--smBtnHeight:35px;--lgBtnHeight:52px;--spacing:30px;--smSpacing:20px;--inputPadding:13px;--inputBorderRadius:var(--borderRadius);--borderRadius:5px;--lgBorderRadius:15px;--pageSidebarWidth:240px;--wrapperWidth:840px;--smWrapperWidth:430px;--lgWrapperWidth:1150px;--loaderIcon:"";--loaderAnimationSpeed:1.1s;--animationSpeed:.15s;--activeAnimationSpeed:70ms;--slideAnimationSpeed:.2s;accent-color:var(--primaryColor)}[data-color-scheme=dark],[data-color-scheme=dark] .dropdown{--surfaceColor:#1c1c1c;--surfaceAlt1Color:color-mix(in srgb, var(--surfaceColor), white 2%);--surfaceAlt2Color:color-mix(in srgb, var(--surfaceColor), white 7%);--surfaceAlt3Color:color-mix(in srgb, var(--surfaceColor), white 12%);--surfaceAlt4Color:color-mix(in srgb, var(--surfaceColor), white 14%);--surfaceAlt5Color:color-mix(in srgb, var(--surfaceColor), white 16%);--surfaceTxtColor:#ebebeb;--tooltipTxtColor:var(--surfaceTxtColor);--surfaceTxtHintColor:color-mix(in srgb, var(--surfaceTxtColor), transparent 45%);--surfaceTxtDisabledColor:color-mix(in srgb, var(--surfaceTxtColor), transparent 65%);--secondaryColor:var(--surfaceAlt2Color);--secondaryTxtColor:var(--surfaceTxtColor);--secondaryAlt1Color:var(--surfaceAlt3Color);--secondaryAlt2Color:var(--surfaceAlt4Color);--primaryColor:#121212;--primaryTxtColor:var(--surfaceTxtColor);--primaryAlt1Color:color-mix(in srgb, var(--primaryColor), black 12%);--primaryAlt2Color:color-mix(in srgb, var(--primaryColor), black 25%);--primaryTxtHintColor:color-mix(in srgb, var(--primaryTxtColor), transparent 35%);--primaryTxtDisabledColor:color-mix(in srgb, var(--primaryTxtColor), transparent 50%);--infoColor:#4273bd;--infoTxtColor:var(--surfaceTxtColor);--infoAlt1Color:color-mix(in srgb, var(--infoColor), black 10%);--infoAlt2Color:color-mix(in srgb, var(--infoColor), black 15%);--surfaceInfoColor:color-mix(in srgb, var(--infoColor), var(--surfaceColor) 45%);--successColor:#27865d;--successTxtColor:var(--surfaceTxtColor);--successAlt1Color:color-mix(in srgb, var(--successColor), black 10%);--successAlt2Color:color-mix(in srgb, var(--successColor), black 15%);--surfaceSuccessColor:color-mix(in srgb, var(--successColor), var(--surfaceColor) 45%);--warningColor:#cf4e17;--warningTxtColor:var(--surfaceTxtColor);--warningAlt1Color:color-mix(in srgb, var(--warningColor), black 10%);--warningAlt2Color:color-mix(in srgb, var(--warningColor), black 15%);--surfaceWarningColor:color-mix(in srgb, var(--warningColor), var(--surfaceColor) 45%);--dangerColor:#bb303e;--dangerTxtColor:var(--surfaceTxtColor);--dangerAlt1Color:color-mix(in srgb, var(--dangerColor), black 10%);--dangerAlt2Color:color-mix(in srgb, var(--dangerColor), black 15%);--surfaceDangerColor:color-mix(in srgb, var(--dangerColor), var(--surfaceColor) 45%);--selectionColor:#ffffff38;--inputColor:var(--surfaceAlt2Color);--inputFocusColor:var(--surfaceAlt3Color);--inputBorderColor:var(--surfaceAlt4Color);scrollbar-color:var(--surfaceAlt5Color) transparent;--modalOverlayColor:#12121280;--tooltipSurfaceColor:#161616e6;--boxShadow:0px 8px 5px -5px #0000004d;--leftBoxShadow:-1px 0px 5px 0 #0000004d;--rightBoxShadow:1px 0px 5px 0 #0000004d;--topScrollShadow:0px 2px 8px 1px #00000073;--bottomScrollShadow:0px -2px 8px 1px #00000073;--leftScrollShadow:3px 0px 5px 0px #0006;--rightScrollShadow:-3px 0px 5px 0px #0006;--prismFunctionColor:#ab7abd;--prismStringColor:#87b47e;--prismNumberColor:#86b9bb;--prismPropertyColor:#90c2d1;--prismKeywordColor:#b3703d;--prismCommentColor:var(--surfaceTxtHintColor);--prismPunctuationColor:var(--surfaceTxtColor)}html .accent-surface{--surfaceColor:var(--accentColor);--surfaceAlt1Color:color-mix(in srgb, var(--surfaceColor), white 5%);--surfaceAlt2Color:color-mix(in srgb, var(--surfaceColor), white 10%);--surfaceAlt3Color:color-mix(in srgb, var(--surfaceColor), white 20%);--surfaceAlt4Color:color-mix(in srgb, var(--surfaceColor), white 30%);--surfaceAlt5Color:color-mix(in srgb, var(--surfaceColor), white 40%);--surfaceTxtColor:#fff;--surfaceTxtHintColor:color-mix(in srgb, var(--surfaceTxtColor), transparent 35%);--surfaceTxtDisabledColor:color-mix(in srgb, var(--surfaceTxtColor), transparent 60%);--surfaceAccentColor:color-mix(in srgb, var(--surfaceColor), white 20%);--surfaceInfoColor:color-mix(in srgb, var(--infoColor), transparent 40%);--surfaceSuccessColor:color-mix(in srgb, var(--successColor), transparent 40%);--surfaceWarningColor:color-mix(in srgb, var(--warningColor), transparent 40%);--surfaceDangerColor:color-mix(in srgb, var(--dangerColor), transparent 40%);--inputColor:var(--surfaceAlt3Color);--inputFocusColor:var(--surfaceAlt4Color);--inputBorderColor:var(--surfaceAlt5Color);--selectionColor:var(--surfaceAccentColor);scrollbar-color:var(--surfaceAlt4Color) transparent}@media (width<=900px){:root,.dropdown,.base-surface{--spacing:20px;--smSpacing:15px;--btnHeight:40px;--smBtnHeight:30px;--lgBtnHeight:47px}}@keyframes rotate{0%{pointer-events:none}to{pointer-events:none;transform:rotate(360deg)}}@keyframes halfRotate{0%{pointer-events:none}to{pointer-events:none;transform:rotate(180deg)}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes slideTop{0%{opacity:0;transform:translateY(-3px)}to{opacity:1;transform:translateY(0)}}@keyframes scaleIn{0%{opacity:0;transform:scale(.85)}to{opacity:1;transform:scale(1)}}code[class*=language-],pre[class*=language-]{color:var(--surfaceTxtHintColor);font-family:var(--monospaceFontFamily);text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;tab-size:4;-webkit-hyphens:none;hyphens:none;font-size:1em;line-height:1.5}pre[class*=language-]{margin:.5em 0;padding:1em;overflow:auto}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:var(--prismCommentColor)}.token.punctuation{color:var(--prismPunctuationColor)}.token.namespace{opacity:.7}.token.constant,.token.boolean,.token.number{color:var(--prismNumberColor)}.token.deleted,.token.property,.token.symbol,.token.tag,.token.tag .token.punctuation{color:var(--prismPropertyColor)}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string,.token.url{color:var(--prismStringColor)}.token.atrule,.token.attr-value,.token.keyword{color:var(--prismKeywordColor)}.token.class-name,.token.function,.token.important,.token.regex,.token.variable{color:var(--prismFunctionColor)}.token.bold{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{font:inherit;vertical-align:baseline;border:0;margin:0;padding:0}*,:before,:after{box-sizing:border-box}[class^=ri-],[class*=\ ri-]{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-size:1.15em;font-style:normal;font-weight:400;font-family:var(--iconFontFamily)!important}:is([class^=ri-],[class*=\ ri-]):before{vertical-align:top}:focus-visible{outline:2px dotted var(--selectionColor)}::selection{background:var(--selectionColor)}:root{interpolate-size:allow-keywords}[data-dragover=true]{opacity:.5}html,body{font-optical-sizing:auto;font-size:var(--fontSize);font-family:var(--baseFontFamily);line-height:var(--lineHeight);color:var(--surfaceTxtColor);background:var(--surfaceColor);letter-spacing:.004em;height:100%}h1,h2,h3,h4,h5,h6{font-weight:400;display:block}h1{font-size:26px;line-height:32px}h2{font-size:24px;line-height:30px}h3{font-size:22px;line-height:28px}h4{font-size:20px;line-height:26px}h5{font-size:18px;line-height:24px}h6{font-size:16px;line-height:24px}em{font-style:italic}strong{font-weight:700}small{font-size:var(--smFontSize);line-height:var(--smLineHeight)}sub,sup{font-size:.8em;line-height:1;position:relative}sup{vertical-align:top}sub{vertical-align:bottom}p{margin:10px 0;display:block}p:first-child{margin-top:0}p:last-child{margin-bottom:0}a{color:inherit}a:hover{text-decoration:none}ol,ul{padding-left:var(--spacing);margin:10px 0}:is(ol,ul) li{margin-top:5px;margin-bottom:5px}:is(ol,ul) li:first-child{margin-top:0}:is(ol,ul) li:last-child{margin-bottom:0}ol{list-style:decimal}ul{list-style:outside}cite{font-style:italic}blockquote{quotes:none;color:var(--surfaceTxtHintColor);border-left:3px solid var(--surfaceAlt2Color);margin:10px 0;padding-left:10px;font-style:italic;display:block;position:relative}blockquote:before,blockquote:after{content:none;display:none}blockquote>:first-child{margin-top:0}blockquote>:last-child{margin-bottom:0}pre{white-space:pre-wrap;border-radius:var(--borderRadius)}pre[class*=language-]{background:0 0;margin:0;padding:0;overflow:visible}code,code[class*=language-]{letter-spacing:0;font-size:.93em;line-height:1.5;font-family:var(--monospaceFontFamily);background:var(--surfaceAlt2Color);border-radius:var(--borderRadius);tab-size:4;padding:1px 3px;display:inline-block}pre :is(code,code[class*=language-]),:is(code,code[class*=language-]).block{width:100%;padding:max(10px, calc(var(--inputPadding) - 5px)) max(10px, var(--inputPadding));white-space:pre-wrap;display:block}.code-wrapper{outline:0}.code-wrapper code{scrollbar-width:thin;overflow:auto}.code-wrapper .footnote{color:var(--surfaceTxtHintColor);font-size:var(--smFontSize);line-height:var(--smLineHeight);margin:5px 0 0}.code-wrapper .footnote:empty{display:none}hr{width:100%;height:1px;color:inherit;margin:var(--spacing) 0;background:var(--surfaceAlt2Color);border:0;flex-shrink:0;display:block}hr.sm{margin:var(--smSpacing) 0}.block,.full-width{width:100%}.flex{align-items:center;gap:var(--spacing);display:flex!important}.inline-flex{align-items:center;gap:var(--smSpacing);display:inline-flex!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-fill{flex:auto!important}.wrapper{width:var(--wrapperWidth);max-width:100%;margin:0 auto}.wrapper.sm{width:var(--smWrapperWidth)}.wrapper.lg{width:var(--lgWrapperWidth)}.app>.wrapper{max-width:calc(100% - var(--spacing))}[hidden],.hidden{display:none!important}.slide,[data-slide]{transition:margin var(--slideAnimationSpeed), padding var(--slideAnimationSpeed), height var(--slideAnimationSpeed), opacity var(--slideAnimationSpeed), display var(--slideAnimationSpeed), overflow var(--slideAnimationSpeed) step-end;transition-behavior:allow-discrete}@starting-style{.slide,[data-slide]{opacity:0!important;height:0!important;margin-top:0!important;margin-bottom:0!important;padding-top:0!important;padding-bottom:0!important;overflow:hidden!important}}:is(.slide,[data-slide]).hidden{opacity:0!important;height:0!important;transition:margin var(--slideAnimationSpeed), padding var(--slideAnimationSpeed), height var(--slideAnimationSpeed), opacity var(--slideAnimationSpeed), display var(--slideAnimationSpeed), overflow var(--slideAnimationSpeed) step-start!important;transition-behavior:allow-discrete!important;margin-top:0!important;margin-bottom:0!important;padding-top:0!important;padding-bottom:0!important;overflow:hidden!important}.txt-base{line-height:var(--lineHeight)!important;font-size:var(--fontSize)!important}.txt-sm{line-height:var(--smLineHeight)!important;font-size:var(--smFontSize)!important}.txt-lg{line-height:var(--lgLineHeight)!important;font-size:var(--lgFontSize)!important}.txt-center{text-align:center!important}.txt-left{text-align:left!important}.txt-right{text-align:right!important}.txt-warning{color:var(--warningColor)!important}.txt-success{color:var(--successColor)!important}.txt-danger{color:var(--dangerColor)!important}.txt-info{color:var(--infoColor)!important}.txt-accent{color:var(--accentColor)!important}.txt-primary{color:var(--surfaceTxtColor)!important}.txt-hint{color:var(--surfaceTxtHintColor)!important}.txt-disabled{color:var(--surfaceTxtDisabledColor)!important}.txt-bold{font-weight:700!important}.txt-normal{font-weight:400!important}.txt-strikethrough{text-decoration:line-through!important}.txt-code{letter-spacing:0;font-size:.93em!important;font-family:var(--monospaceFontFamily)!important;white-space:pre-wrap!important;text-align:left!important}.txt-nowrap{white-space:nowrap!important}.txt-ellipsis{vertical-align:top;flex-shrink:1;min-width:10px;max-width:100%;display:inline-block;overflow:hidden;white-space:nowrap!important;text-overflow:ellipsis!important}.faded{opacity:.6;transition:opacity var(--animationSpeed)}.link-faded{opacity:.6;cursor:pointer;transition:all var(--animationSpeed);text-decoration:none}.link-faded:hover,.link-faded:focus-visible,.link-faded[data-popover-state=true],.link-faded:active{opacity:1}.link-primary,.link-hint{--linkColor:var(--surfaceTxtHintColor);--linkFocusColor:var(--surfaceTxtColor);vertical-align:top;cursor:pointer;border-radius:var(--borderRadius);color:var(--linkColor);transition:color var(--animationSpeed), opacity var(--animationSpeed);-webkit-user-select:none;user-select:none;align-items:center;gap:5px;text-decoration:none;display:inline-flex}:is(.link-primary,.link-hint):hover,:is(.link-primary,.link-hint):focus-visible{color:var(--linkFocusColor)}:is(.link-primary,.link-hint)[data-popover-state=true],:is(.link-primary,.link-hint):active{color:var(--linkFocusColor);transition-duration:var(--activeAnimationSpeed)}.link-primary{--linkColor:var(--surfaceTxtColor);--linkFocusColor:var(--surfaceTxtColor)}.link-primary:hover,.link-primary:focus-visible,.link-primary[data-popover-state=true],.link-primary:active{opacity:.9}.gap-base{gap:var(--spacing)!important}.gap-sm{gap:var(--smSpacing)!important}.gap-auto{gap:auto!important}.gap-0{gap:0!important}.gap-5{gap:5px!important}.gap-10{gap:10px!important}.gap-15{gap:15px!important}.gap-20{gap:20px!important}.gap-25{gap:25px!important}.gap-30{gap:30px!important}.m-base{margin:var(--spacing)!important}.m-t-base{margin-top:var(--spacing)!important}.m-r-base{margin-right:var(--spacing)!important}.m-b-base{margin-bottom:var(--spacing)!important}.m-l-base{margin-left:var(--spacing)!important}.m-sm{margin:var(--smSpacing)!important}.m-t-sm{margin-top:var(--smSpacing)!important}.m-r-sm{margin-right:var(--smSpacing)!important}.m-b-sm{margin-bottom:var(--smSpacing)!important}.m-l-sm{margin-left:var(--smSpacing)!important}.m-auto{margin:auto!important}.m-t-auto{margin-top:auto!important}.m-r-auto{margin-right:auto!important}.m-b-auto{margin-bottom:auto!important}.m-l-auto{margin-left:auto!important}.m-0{margin:0!important}.m-t-0{margin-top:0!important}.m-r-0{margin-right:0!important}.m-b-0{margin-bottom:0!important}.m-l-0{margin-left:0!important}.m-5{margin:5px!important}.m-t-5{margin-top:5px!important}.m-r-5{margin-right:5px!important}.m-b-5{margin-bottom:5px!important}.m-l-5{margin-left:5px!important}.m-10{margin:10px!important}.m-t-10{margin-top:10px!important}.m-r-10{margin-right:10px!important}.m-b-10{margin-bottom:10px!important}.m-l-10{margin-left:10px!important}.m-15{margin:15px!important}.m-t-15{margin-top:15px!important}.m-r-15{margin-right:15px!important}.m-b-15{margin-bottom:15px!important}.m-l-15{margin-left:15px!important}.m-20{margin:20px!important}.m-t-20{margin-top:20px!important}.m-r-20{margin-right:20px!important}.m-b-20{margin-bottom:20px!important}.m-l-20{margin-left:20px!important}.m-t-25{margin-top:25px!important}.m-r-25{margin-right:25px!important}.m-b-25{margin-bottom:25px!important}.m-l-25{margin-left:25px!important}.m-t-30{margin-top:30px!important}.m-r-30{margin-right:30px!important}.m-b-30{margin-bottom:30px!important}.m-l-30{margin-left:30px!important}.p-base{padding:var(--spacing)!important}.p-t-base{padding-top:var(--spacing)!important}.p-r-base{padding-right:var(--spacing)!important}.p-b-base{padding-bottom:var(--spacing)!important}.p-l-base{padding-left:var(--spacing)!important}.p-sm{padding:var(--smSpacing)!important}.p-t-sm{padding-top:var(--smSpacing)!important}.p-r-sm{padding-right:var(--smSpacing)!important}.p-b-sm{padding-bottom:var(--smSpacing)!important}.p-l-sm{padding-left:var(--smSpacing)!important}.p-0{padding:0!important}.p-t-0{padding-top:0!important}.p-r-0{padding-right:0!important}.p-b-0{padding-bottom:0!important}.p-l-0{padding-left:0!important}.p-5{padding:5px!important}.p-t-5{padding-top:5px!important}.p-r-5{padding-right:5px!important}.p-b-5{padding-bottom:5px!important}.p-l-5{padding-left:5px!important}.p-10{padding:10px!important}.p-t-10{padding-top:10px!important}.p-r-10{padding-right:10px!important}.p-b-10{padding-bottom:10px!important}.p-l-10{padding-left:10px!important}.p-15{padding:15px!important}.p-t-15{padding-top:15px!important}.p-r-15{padding-right:15px!important}.p-b-15{padding-bottom:15px!important}.p-l-15{padding-left:15px!important}.p-20{padding:20px!important}.p-t-20{padding-top:20px!important}.p-r-20{padding-right:20px!important}.p-b-20{padding-bottom:20px!important}.p-l-20{padding-left:20px!important}.p-t-25{padding-top:25px!important}.p-r-25{padding-right:25px!important}.p-b-25{padding-bottom:25px!important}.p-l-25{padding-left:25px!important}.p-t-30{padding-top:30px!important}.p-r-30{padding-right:30px!important}.p-b-30{padding-bottom:30px!important}.p-l-30{padding-left:30px!important}.loader{--loaderSize:24px;vertical-align:top;text-align:center;color:var(--surfaceAlt5Color);opacity:0;animation:scaleIn var(--animationSpeed) .1s forwards;align-items:center;gap:5px;line-height:1;display:inline-flex}.loader:before{content:var(--loaderIcon);-webkit-user-select:none;user-select:none;text-align:left;color:currentColor;font-weight:400;line-height:var(--loaderSize);font-size:var(--loaderSize);font-family:var(--iconFontFamily);animation:rotate var(--loaderAnimationSpeed) infinite linear;align-content:center;display:block}.loader.sm{--loaderSize:20px;font-size:var(--smFontSize)}.loader.lg{--loaderSize:33px;font-size:var(--lgFontSize)}@keyframes shine{to{background-position-x:-200%}}.skeleton-loader{border-radius:var(--borderRadius);background:var(--surfaceAlt1Color);background:linear-gradient(90deg, var(--surfaceAlt1Color) 10%, var(--surfaceAlt2Color) 25%, var(--surfaceAlt1Color) 60%);width:100%;height:12px;animation:fadeIn var(--animationSpeed), shine 1s linear infinite;background-size:200% 100%;margin:5px 0;display:block;position:relative}.skeleton-loader.sm{height:8px}.skeleton-loader.lg{height:18px}.alert{width:100%;min-height:var(--lgBtnHeight);padding:max(5px, calc(var(--inputPadding) - 5px)) max(10px, var(--inputPadding));border-radius:var(--borderRadius);color:var(--alertTxtColor);background:var(--surfaceAlt2Color);border:2px solid var(--surfaceAlt4Color);flex-shrink:0;align-content:center;display:block}.alert code,.alert .label,.alert hr{background:color-mix(in srgb, currentColor, transparent 90%)}.alert p{margin:5px 0}.alert p:first-child{margin-top:0}.alert p:last-child{margin-bottom:0}.alert.info{border-color:var(--infoColor);background:var(--surfaceInfoColor)}.alert.success{border-color:var(--successColor);background:var(--surfaceSuccessColor)}.alert.warning{border-color:var(--warningColor);background:var(--surfaceWarningColor)}.alert.danger{border-color:var(--dangerColor);background:var(--surfaceDangerColor)}.label{text-align:center;vertical-align:middle;word-break:break-word;width:auto;min-width:0;max-width:100%;min-height:25px;font-weight:400;font-size:var(--smFontSize);line-height:calc(var(--smFontSize) + 2px);border-radius:var(--borderRadius);color:var(--surfaceTxtColor);background:var(--surfaceAlt3Color);border:0px solid var(--surfaceAlt5Color);flex-shrink:0;justify-content:center;align-items:center;gap:5px;padding:5px 7px;text-decoration:none;display:inline-flex}.label .label{border-width:1px;border-color:var(--surfaceAlt5Color)}.label.sm{min-height:20px;font-size:calc(var(--smFontSize) - 2px);padding:3px 5px}.label.info{background:var(--surfaceInfoColor)}.label.info .label{border-color:var(--infoColor)}.label.success{background:var(--surfaceSuccessColor)}.label.success .label{border-color:var(--successColor)}.label.warning{background:var(--surfaceWarningColor)}.label.warning .label{border-color:var(--warningColor)}.label.danger{background:var(--surfaceDangerColor)}.label.danger .label{border-color:var(--dangerColor)}.label.handle{cursor:pointer;-webkit-user-select:none;user-select:none;transition:opacity var(--animationSpeed);outline:0}.label.handle:hover,.label.handle:focus-visible{opacity:.9}.label.handle:active{opacity:.8;transition-duration:var(--activeAnimationSpeed)}.breadcrumbs{min-width:0;max-width:calc(100% - 50px);color:var(--surfaceTxtHintColor);align-items:center;gap:30px;font-size:1.286rem;font-weight:400;display:inline-flex}.breadcrumbs>div,.breadcrumbs>span,.breadcrumbs>a,.breadcrumbs>button{position:relative}:is(.breadcrumbs>div,.breadcrumbs>span,.breadcrumbs>a,.breadcrumbs>button):after{content:"/";vertical-align:top;cursor:default;-webkit-user-select:none;user-select:none;pointer-events:none;height:100%;color:var(--surfaceTxtDisabledColor);align-content:center;font-size:.85em;line-height:1;display:inline-block;position:absolute;top:0;right:-18px}:is(.breadcrumbs>div,.breadcrumbs>span,.breadcrumbs>a,.breadcrumbs>button):last-child{color:var(--surfaceTxtColor);white-space:nowrap;text-overflow:ellipsis;flex-shrink:1;min-width:0;max-width:400px;position:relative;overflow:hidden}:is(.breadcrumbs>div,.breadcrumbs>span,.breadcrumbs>a,.breadcrumbs>button):last-child:after{content:none;display:none}@media (width<=800px){.breadcrumbs>div,.breadcrumbs>span,.breadcrumbs>a,.breadcrumbs>button{display:none}:is(.breadcrumbs>div,.breadcrumbs>span,.breadcrumbs>a,.breadcrumbs>button):last-child{display:block}}.breadcrumbs a{cursor:pointer;color:var(--surfaceTxtHintColor);transition:color var(--animationSpeed);outline:0;text-decoration:none}.breadcrumbs a:hover,.breadcrumbs a:focus-visible{color:var(--surfaceTxtColor)}.copy-to-clipboard{cursor:pointer;-webkit-user-select:none;user-select:none;vertical-align:middle;color:var(--surfaceTxtHintColor);transition:color var(--animationSpeed);outline:0;align-items:center;gap:5px;line-height:1;text-decoration:none;display:inline-flex}.copy-to-clipboard .copy-icon{font-size:var(--smFontSize)}.copy-to-clipboard:hover,.copy-to-clipboard:active{color:var(--surfaceTxtColor)}.copy-to-clipboard.active{color:var(--successColor)}.label .copy-to-clipboard{align-self:stretch;margin:-5px;padding:5px}.thumb{--thumbSize:var(--btnHeight);width:var(--thumbSize);aspect-ratio:1;background:var(--surfaceAlt1Color);border-radius:var(--borderRadius);text-align:center;color:var(--surfaceTxtHintColor);font-size:var(--fontSize);line-height:var(--lineHeight);transition:border var(--animationSpeed), opacity var(--animationSpeed);border:1px solid var(--surfaceAlt3Color);outline:0;flex-shrink:0;justify-content:center;align-items:center;text-decoration:none;display:inline-flex;overflow:hidden}.thumb img{width:100%;max-width:100%;height:auto;transition:opacity var(--animationSpeed)}.thumb.loading{background:linear-gradient(90deg, var(--surfaceAlt1Color) 0%, var(--surfaceColor) 25%, var(--surfaceAlt1Color) 50%);background-size:200% 100%;animation:.85s linear .2s infinite shine}.thumb.loading img{opacity:0}.thumb.lg{--thumbSize:80px;font-size:var(--lgFontSize)}.thumb.sm{--thumbSize:var(--smBtnHeight);font-size:var(--smFontSize);line-height:var(--smLineHeight)}.thumb.info{border:1px solid var(--infoColor)}.thumb.success{border:1px solid var(--successColor)}.thumb.warning{border:1px solid var(--warningColor)}.thumb.danger{border:1px solid var(--dangerColor)}.hanlde.thumb,button.thumb,a.thumb{cursor:pointer}:is(.hanlde.thumb,button.thumb,a.thumb):hover,:is(.hanlde.thumb,button.thumb,a.thumb):active,:is(.hanlde.thumb,button.thumb,a.thumb):focus-visible{opacity:.8}:is(.hanlde.thumb,button.thumb,a.thumb):active{opacity:.6;transition-duration:var(--activeAnimationSpeed)}.animate-fadeIn{animation:fadeIn var(--animationSpeed)}.animate-delayed-fadeIn{opacity:0;animation:fadeIn var(--animationSpeed) var(--animationSpeed) forwards}.formatted-date .missing-value,.formatted-date .primary-date,.formatted-date .secondary-date{width:100%;display:block}.formatted-date .secondary-date{color:var(--surfaceTxtHintColor);font-size:var(--smFontSize);line-height:var(--smLineHeight);margin-top:3px}.formatted-date .missing-value{text-align:left}.formatted-date.short{text-align:center;white-space:nowrap}.formatted-date.short .secondary-date{margin-top:0;font-size:.87em}.section-heading{color:var(--surfaceTxtHintColor);margin:0 0 var(--smSpacing);align-items:center;gap:10px;display:flex}.section-heading .field{width:auto}.main-logo{max-height:40px}.dragline{z-index:9;cursor:ew-resize;box-sizing:content-box;-webkit-user-select:none;user-select:none;width:2px;height:100%;transition:background var(--animationSpeed);background:0 0;flex-shrink:0;margin:0;padding:0;position:relative}.dragline:hover,.dragline:active,.dragline.dragging{z-index:101;background:var(--surfaceAlt3Color)}.pb-tooltip{z-index:99999;text-align:center;white-space:pre-line;word-break:break-word;background:var(--tooltipSurfaceColor);border-radius:var(--borderRadius);min-width:30px;max-width:300px;max-height:90%;color:var(--tooltipTxtColor);font-weight:400;font-size:var(--smFontSize);line-height:var(--smLineHeight);font-family:var(--baseFontFamily);scrollbar-width:thin;pointer-events:none;opacity:0;transition:opacity var(--animationSpeed), display var(--animationSpeed) allow-discrete;padding:3px 5px;position:fixed}.pb-tooltip:popover-open{opacity:1;display:block}@starting-style{.pb-tooltip:popover-open{opacity:0}}.pb-tooltip[data-style=code]{text-align:left;white-space:pre-wrap;max-width:350px;font-family:var(--monospaceFontFamily)}@keyframes toastEntrance{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}@keyframes toastContentEntrance{0%{overflow:hidden}}.toast{--toastWidth:420px;--toastSurfaceColor:var(--surfaceInfoColor);--toastSurfaceTxtColor:var(--surfaceTxtColor);--toastAccentColor:var(--infoColor);width:var(--toastWidth);max-width:95%;margin:0 0 var(--smSpacing);animation:toastEntrance var(--animationSpeed);grid-template-rows:1fr;display:grid;position:sticky;top:0}.toast .toast-container{border-radius:var(--borderRadius);width:100%;line-height:var(--lineHeight);color:var(--toastSurfaceTxtColor);background:var(--toastSurfaceColor);box-shadow:var(--boxShadow);animation:toastContentEntrance var(--animationSpeed);align-items:stretch;display:flex}.toast .toast-content{text-align:left;word-break:break-word;width:100%;min-height:var(--lgBtnHeight);padding:max(5px, calc(var(--inputPadding) - 5px)) var(--inputPadding);align-items:center;gap:10px;display:flex}.toast .toast-icon{min-height:0;color:var(--toastAccentColor);border-top-left-radius:var(--borderRadius);border-bottom-left-radius:var(--borderRadius);border-right:1px solid color-mix(in srgb, var(--toastAccentColor), transparent 90%);align-content:center;margin-left:-1px;padding:5px 10px;font-size:1.2em}[data-color-scheme=dark] :is(.toast .toast-icon){border-color:color-mix(in srgb, var(--toastAccentColor), transparent 75%)}.toast .toast-icon:before{content:"";font-family:var(--iconFontFamily)}.toast .toast-remove{margin:-5px -10px -5px 0}.toast.success{--toastSurfaceColor:var(--surfaceSuccessColor);--toastAccentColor:var(--successColor)}.toast.success .toast-icon:before{content:""}.toast.error{--toastSurfaceColor:var(--surfaceDangerColor);--toastAccentColor:var(--dangerColor)}.toast.error .toast-icon:before{content:""}.toast.removing{transition:opacity var(--animationSpeed), margin var(--animationSpeed), grid-template-rows var(--animationSpeed);opacity:0;grid-template-rows:0fr;margin:0}.toast.removing .toast-container{overflow:hidden}.toasts-container{z-index:9999;bottom:var(--spacing);width:100%;display:block;position:fixed;left:0;pointer-events:none!important}.toasts-container .toast{pointer-events:auto;margin-left:auto;margin-right:auto}.list{--borderColor:var(--surfaceAlt3Color);width:100%;color:var(--surfaceTxtColor);background:var(--surfaceColor);border:1px solid var(--borderColor);border-radius:var(--borderRadius);scrollbar-width:thin;display:block;position:relative;overflow:auto}.list:empty{display:none}.list .list-content{scrollbar-width:thin;width:100%;max-height:425px;display:block;overflow:auto}.list .list-item{word-break:break-word;width:100%;padding:10px var(--inputPadding);min-height:54px;box-shadow:0px -1px 0 0 var(--borderColor);transition:background var(--animationSpeed);outline:0;align-items:center;gap:10px;display:flex;position:relative}.list .list-item .content{-webkit-user-select:text;user-select:text;align-items:center;gap:5px;width:100%;min-width:0;max-width:100%;line-height:1.5;display:flex}.list .list-item .actions{flex-shrink:0;align-items:center;gap:10px;margin:-1px -5px -1px 0;display:inline-flex}.list .list-item .actions.autohide{opacity:0;transition:transform var(--animationSpeed), opacity var(--animationSpeed), visibility var(--animationSpeed);transform:translate(5px)}@media only screen and (hover:none){.list .list-item .actions.autohide{opacity:1;transform:translate(0)}}.list .list-item>.btn.block{border-radius:0;width:100%}.list .list-item>.btn.block.sm{border-radius:var(--borderRadius)}.list .list-item:has(>.btn.block){min-height:0;padding:0}.list .list-item:has(>.btn.sm.block){padding:5px}:is(.list .list-item:hover,.list .list-item:focus-visible,.list .list-item:focus-within,.list .list-item:active) .actions.autohide{opacity:1;transform:translate(0)}.list .list-item[data-dragstart=true],.list .list-item.active{background:var(--surfaceAlt1Color)}.list .list-item[data-dragstart=true] .actions.autohide{opacity:0}:is(.list .list-item:not(.disabled).highlight,.list .list-item:not(.disabled).handle):hover,:is(.list .list-item:not(.disabled).highlight,.list .list-item:not(.disabled).handle):focus-visible{background:var(--surfaceAlt1Color)}:is(.list .list-item:not(.disabled).highlight,.list .list-item:not(.disabled).handle):active{background:var(--surfaceAlt2Color);transition-duration:var(--activeAnimationSpeed)}.list .list-item.handle{cursor:pointer;-webkit-user-select:none;user-select:none}.list .list-item.deleted{cursor:default}.list .list-item.deleted .content{opacity:.6}.list .list-item.deleted .content .txt{text-decoration:line-through}.list .list-item.disabled:not(.active){cursor:default;opacity:.6}.list.sm .list-item{min-height:var(--btnHeight);padding:5px 10px}.grid{--gridGap:var(--spacing);row-gap:var(--gridGap);margin:0 calc(-.5 * var(--gridGap));flex-wrap:wrap;flex-grow:1;display:flex;position:relative;container-type:inline-size}.grid.center{align-items:center}.grid.sm{--gridGap:var(--smSpacing)}.grid>[class^=col-]{width:100%;min-width:0;min-height:1px;margin:0 calc(.5 * var(--gridGap));position:relative}.grid>[class^=col-]>hr:first-child{margin-top:0}.grid>[class^=col-]>hr:last-child{margin-bottom:0}.grid .col-12{width:calc(100% - var(--gridGap))}.grid .col-11{width:calc(91.6667% - var(--gridGap))}.grid .col-10{width:calc(83.3333% - var(--gridGap))}.grid .col-9{width:calc(75% - var(--gridGap))}.grid .col-8{width:calc(66.6667% - var(--gridGap))}.grid .col-7{width:calc(58.3333% - var(--gridGap))}.grid .col-6{width:calc(50% - var(--gridGap))}.grid .col-5{width:calc(41.6667% - var(--gridGap))}.grid .col-4{width:calc(33.3333% - var(--gridGap))}.grid .col-3{width:calc(25% - var(--gridGap))}.grid .col-2{width:calc(16.6667% - var(--gridGap))}.grid .col-1{width:calc(8.33333% - var(--gridGap))}@container (width>=380px){.grid .col-sm-12{width:calc(100% - var(--gridGap))}.grid .col-sm-11{width:calc(91.6667% - var(--gridGap))}.grid .col-sm-10{width:calc(83.3333% - var(--gridGap))}.grid .col-sm-9{width:calc(75% - var(--gridGap))}.grid .col-sm-8{width:calc(66.6667% - var(--gridGap))}.grid .col-sm-7{width:calc(58.3333% - var(--gridGap))}.grid .col-sm-6{width:calc(50% - var(--gridGap))}.grid .col-sm-5{width:calc(41.6667% - var(--gridGap))}.grid .col-sm-4{width:calc(33.3333% - var(--gridGap))}.grid .col-sm-3{width:calc(25% - var(--gridGap))}.grid .col-sm-2{width:calc(16.6667% - var(--gridGap))}.grid .col-sm-1{width:calc(8.33333% - var(--gridGap))}}@container (width>=550px){.grid .col-md-12{width:calc(100% - var(--gridGap))}.grid .col-md-11{width:calc(91.6667% - var(--gridGap))}.grid .col-md-10{width:calc(83.3333% - var(--gridGap))}.grid .col-md-9{width:calc(75% - var(--gridGap))}.grid .col-md-8{width:calc(66.6667% - var(--gridGap))}.grid .col-md-7{width:calc(58.3333% - var(--gridGap))}.grid .col-md-6{width:calc(50% - var(--gridGap))}.grid .col-md-5{width:calc(41.6667% - var(--gridGap))}.grid .col-md-4{width:calc(33.3333% - var(--gridGap))}.grid .col-md-3{width:calc(25% - var(--gridGap))}.grid .col-md-2{width:calc(16.6667% - var(--gridGap))}.grid .col-md-1{width:calc(8.33333% - var(--gridGap))}}@container (width>=650px){.grid .col-lg-12{width:calc(100% - var(--gridGap))}.grid .col-lg-11{width:calc(91.6667% - var(--gridGap))}.grid .col-lg-10{width:calc(83.3333% - var(--gridGap))}.grid .col-lg-9{width:calc(75% - var(--gridGap))}.grid .col-lg-8{width:calc(66.6667% - var(--gridGap))}.grid .col-lg-7{width:calc(58.3333% - var(--gridGap))}.grid .col-lg-6{width:calc(50% - var(--gridGap))}.grid .col-lg-5{width:calc(41.6667% - var(--gridGap))}.grid .col-lg-4{width:calc(33.3333% - var(--gridGap))}.grid .col-lg-3{width:calc(25% - var(--gridGap))}.grid .col-lg-2{width:calc(16.6667% - var(--gridGap))}.grid .col-lg-1{width:calc(8.33333% - var(--gridGap))}}table{--tableSpacing:var(--inputPadding);border-collapse:separate;border-spacing:0;width:100%}table td,table th{padding:10px max(var(--tableSpacing), 10px);vertical-align:middle;text-align:left;height:var(--btnHeight);background:var(--surfaceColor);border:1px solid var(--surfaceAlt2Color);border-bottom-width:0}:is(table td,table th):not(:first-child){border-left:0}:is(table td,table th):not(:last-child){border-right:0}@media (width<=600px){table td,table th{padding:5px}}table>:first-child>tr:first-child>:first-child{border-top-left-radius:var(--borderRadius)}table>:first-child>tr:first-child>:last-child{border-top-right-radius:var(--borderRadius)}table>:last-child>tr:last-child>:first-child{border-bottom-left-radius:var(--borderRadius)}table>:last-child>tr:last-child>:last-child{border-bottom-right-radius:var(--borderRadius)}table>:last-child>tr:last-child>td,table>:last-child>tr:last-child>th{border-width:1px}table th{color:var(--surfaceTxtHintColor);font-size:.98em;font-weight:700;line-height:1.4}table th.sort-handle{cursor:pointer;-webkit-user-select:none;user-select:none;outline:0;padding-right:40px;position:relative}:is(table th.sort-handle.asc,table th.sort-handle.desc):before{content:"";right:var(--tableSpacing);color:inherit;opacity:.5;font-weight:400;font-family:var(--iconFontFamily);transition:opacity var(--animationSpeed);display:block;position:absolute;top:50%;transform:translateY(-50%)}table th.sort-handle.desc:before{content:""}table th.sort-handle:hover,table th.sort-handle:focus-visible{background:var(--surfaceAlt1Color)}:is(table th.sort-handle:hover,table th.sort-handle:focus-visible):before{opacity:1}table th.sort-handle:active{background:var(--surfaceAlt2Color)}:is(table td,table th).min-width{white-space:nowrap;width:1%!important;min-width:0!important}:is(table td,table th).col-bulk-select,:is(table td,table th).col-meta{z-index:2;word-break:normal;white-space:nowrap;position:sticky}:is(table td,table th).col-bulk-select{width:1%;min-width:62px;padding-right:5px;left:0}:is(table td,table th).col-bulk-select .field{width:auto}:is(table td,table th).col-bulk-select .loader{margin-left:-2px;position:absolute;top:10px}:is(table td,table th).col-bulk-select:after{content:"";opacity:0;pointer-events:none;width:1px;height:100%;box-shadow:var(--leftScrollShadow);display:block;position:absolute;top:0;right:0}@container scroll-state(scrollable:left){:is(table td,table th).col-bulk-select:after{opacity:1;transition:opacity var(--animationSpeed)}}@media (width<=600px){:is(table td,table th).col-bulk-select{min-width:50px;padding-right:0}}:is(table td,table th).col-meta{text-align:right;width:1%;padding-top:4px;padding-bottom:5px;padding-left:10px;right:0}:is(table td,table th).col-meta:before{content:"";opacity:0;pointer-events:none;width:1px;height:100%;box-shadow:var(--rightScrollShadow);display:block;position:absolute;top:0;left:0}@container scroll-state(scrollable:right){:is(table td,table th).col-meta:before{opacity:1;transition:opacity var(--animationSpeed)}}:is(table td,table th).col-meta>i{vertical-align:top;color:var(--surfaceTxtDisabledColor);transition:color var(--animationSpeed), transform var(--animationSpeed);will-change:transform, color;display:inline-block}@media (width<=600px){:is(table td,table th).col-meta{padding-left:5px}}:is(table td,table th).col-field-type-date,:is(table td,table th).col-field-type-autodate,:is(table td,table th).col-field-type-geopoint{white-space:nowrap;width:1%}:is(table td,table th).col-field-type-relation{min-width:190px;max-width:500px}:is(table td,table th).col-field-type-text{max-width:300px}:is(table td,table th).col-field-type-editor{min-width:400px}:is(table td,table th).col-field-type-select{min-width:180px}:is(table td,table th).col-field-type-url{max-width:300px}:is(table td,table th).col-field-type-email{white-space:nowrap;min-width:120px}:is(table td,table th).col-field-type-number{white-space:nowrap}:is(table td,table th).col-field-type-file{min-width:120px}:is(table td,table th).col-field-type-json{max-width:300px}:is(table td,table th).col-field-name-id{white-space:nowrap;width:190px}table .col-copy{padding-left:0}table .col-copy .copy-to-clipboard{opacity:0;transition:color var(--animationSpeed), opacity var(--animationSpeed), transform var(--animationSpeed);transform:translate(3px)}table tr:hover .col-copy .copy-to-clipboard{opacity:1;transform:translate(0)}table thead th{background:var(--surfaceAlt1Color)}table thead.sticky{z-index:3;position:sticky;top:0}table thead.sticky:after{content:"";z-index:-1;opacity:0;pointer-events:none;width:100%;height:1px;box-shadow:var(--topScrollShadow);display:block;position:absolute;bottom:0;left:0}@container scroll-state(scrollable:top){table thead.sticky:after{opacity:1;transition:opacity var(--animationSpeed)}}table tr.handle{cursor:pointer;outline:0}table tr.handle td,table tr.handle th{transition:background var(--animationSpeed);will-change:background}:is(table tr.handle:hover,table tr.handle:focus-within) td,:is(table tr.handle:hover,table tr.handle:focus-within) th{background:var(--surfaceAlt1Color)}:is(table tr.handle:hover,table tr.handle:focus-within) .col-meta>i{color:var(--surfaceTxtColor);transform:translate(3px)}table tr.handle.active td,table tr.handle.active th{background:var(--surfaceAlt2Color)}table.optimize td,table.optimize th,table.optimize .col-bulk-select label:before,table.optimize .col-bulk-select label:after,table.optimize .col-meta i{transition:none!important}table.optimize .col-meta i{transform:none!important}.page-table-wrapper{width:auto;min-height:130px;margin-left:calc(-1 * var(--spacing));margin-right:calc(-1 * var(--spacing));container-type:inline-size scroll-state;display:block;overflow:auto}.page-table-wrapper .label{word-break:normal}.page-table-wrapper .load-more-btn,.page-table-wrapper .sticky-content{z-index:3;width:100%;left:var(--spacing);right:var(--spacing);max-width:calc(100cqw - (2 * var(--spacing)));position:sticky}.page-table-wrapper table{border-radius:0;min-width:900px}.page-table-wrapper table>:first-child>tr:first-child>:first-child{border-top-left-radius:0}.page-table-wrapper table>:first-child>tr:first-child>:last-child{border-top-right-radius:0}.page-table-wrapper table>:last-child>tr:last-child>:first-child{border-bottom-left-radius:0}.page-table-wrapper table>:last-child>tr:last-child>:last-child{border-bottom-right-radius:0}.page-table-wrapper td,.page-table-wrapper th{border:0;border-bottom:1px solid var(--surfaceAlt2Color);border-radius:0}:is(.page-table-wrapper td,.page-table-wrapper th):first-child{padding-left:var(--spacing)}:is(.page-table-wrapper td,.page-table-wrapper th):last-child{padding-right:var(--spacing)}.page-table-wrapper th{background:var(--surfaceColor)}.page-table-wrapper th.sort-handle{border-radius:var(--borderRadius) var(--borderRadius) 0 0}.page-table-wrapper tbody td,.page-table-wrapper tbody th{border-radius:0;height:59px}.page-table-wrapper:before,.page-table-wrapper:after{content:"";z-index:9;opacity:0;pointer-events:none;width:100%;height:1px;display:block;position:sticky;left:0}.page-table-wrapper:before{box-shadow:var(--topScrollShadow);top:0}@container scroll-state(scrollable:top){.page-table-wrapper:before{opacity:1;transition:opacity var(--animationSpeed)}}.page-table-wrapper:after{box-shadow:var(--bottomScrollShadow);margin-top:-1px;bottom:0}@container scroll-state(scrollable:bottom){.page-table-wrapper:after{opacity:1;transition:opacity var(--animationSpeed)}}.page-table-wrapper:has(thead.sticky):before{content:none;display:none}@media (width<=600px){table.responsive-table{border-collapse:collapse;table-layout:fixed;min-width:0}table.responsive-table tr{border:1px solid var(--surfaceAlt2Color);flex-flow:wrap;gap:5px;width:100%;padding:10px;display:flex}table.responsive-table tr~tr{border-top:0}table.responsive-table .col-copy{display:none}table.responsive-table td,table.responsive-table th{word-break:break-word;margin:0;display:block;white-space:normal!important;background:0 0!important;border:0!important;width:100%!important;min-width:0!important;max-width:none!important;height:auto!important;min-height:0!important;padding:0!important}:is(table.responsive-table td,table.responsive-table th):before{content:attr(data-name);width:100%;color:var(--surfaceTxtHintColor);font-weight:700;font-size:var(--smFontSize);line-height:var(--lineHeight);display:block}:is(table.responsive-table td,table.responsive-table th).col-bulk-select{align-content:center;margin-top:0;padding:0}:is(table.responsive-table td,table.responsive-table th).col-meta{display:none}:is(:is(table.responsive-table td,table.responsive-table th).col-bulk-select,:is(table.responsive-table td,table.responsive-table th).col-meta):before,:is(:is(table.responsive-table td,table.responsive-table th).col-bulk-select,:is(table.responsive-table td,table.responsive-table th).col-meta):after{display:none}:is(table.responsive-table td,table.responsive-table th) .formatted-date.short{align-items:center;gap:5px;display:inline-flex}:is(table.responsive-table td,table.responsive-table th) .formatted-date.short .secondary-date,:is(table.responsive-table td,table.responsive-table th) .formatted-date.short .primary-date{width:auto;margin:0}:is(table.responsive-table td,table.responsive-table th) .record-field-view{text-align:left}table.responsive-table td.col-bulk-select{width:auto!important}table.responsive-table td.col-field-name-id{flex-grow:1;width:auto!important}table.responsive-table td.col-field-name-id:before{display:none}table.responsive-table td.col-field-name-id .label{width:100%;font-weight:700}table.responsive-table thead tr{background:var(--surfaceColor);border:0;flex-direction:row;padding-top:0}table.responsive-table thead th{display:none}table.responsive-table thead th.col-meta,table.responsive-table thead th.col-bulk-select{margin:0;display:inline-block;width:auto!important}table.responsive-table thead th.col-meta{margin-left:auto;padding:0}table.responsive-table .label{word-break:break-word}.page-table-wrapper .responsive-table{border:0}.page-table-wrapper .responsive-table tr{padding-left:var(--spacing);padding-right:var(--spacing);border-left:0;border-right:0}}button{color:inherit;text-align:left;font-family:inherit;font-size:inherit;font-weight:inherit;line-height:inherit;letter-spacing:inherit;background:0 0;border:0;outline:0;padding:0}.btn{--btnGap:8px;cursor:pointer;justify-content:center;align-items:center;gap:var(--btnGap);-webkit-user-select:none;user-select:none;min-height:var(--btnHeight);text-align:center;min-width:0;max-width:100%;color:var(--primaryTxtColor);font-weight:700;line-height:1;font-size:var(--fontSize);font-family:var(--baseFontFamily);border-radius:var(--borderRadius);background:var(--primaryColor);transition:color var(--animationSpeed), border-color var(--animationSpeed), background var(--animationSpeed);border:0;outline:0;flex-shrink:0;margin:0;padding:5px 25px;text-decoration:none;display:inline-flex;position:relative}.btn:hover,.btn:focus-visible{background:var(--primaryAlt1Color)}.btn[data-popover-state=true],.btn:active{background:var(--primaryAlt2Color);transition-duration:var(--activeAnimationSpeed)}.btn i{font-size:1.25em}.btn.secondary{color:var(--secondaryTxtColor);background:var(--secondaryColor)}.btn.secondary:hover,.btn.secondary:focus-visible{background:var(--secondaryAlt1Color)}.btn.secondary[data-popover-state=true],.btn.secondary:active{background:var(--secondaryAlt2Color)}.btn.info{color:var(--infoTxtColor);background:var(--infoColor)}.btn.info:hover,.btn.info:focus-visible{background:var(--infoAlt1Color)}.btn.info[data-popover-state=true],.btn.info:active{background:var(--infoAlt2Color)}.btn.success{color:var(--successTxtColor);background:var(--successColor)}.btn.success:hover,.btn.success:focus-visible{background:var(--successAlt1Color)}.btn.success[data-popover-state=true],.btn.success:active{background:var(--successAlt2Color)}.btn.warning{color:var(--warningTxtColor);background:var(--warningColor)}.btn.warning:hover,.btn.warning:focus-visible{background:var(--warningAlt1Color)}.btn.warning[data-popover-state=true],.btn.warning:active{background:var(--warningAlt2Color)}.btn.danger{color:var(--dangerTxtColor);background:var(--dangerColor)}.btn.danger:hover,.btn.danger:focus-visible{background:var(--dangerAlt1Color)}.btn.danger[data-popover-state=true],.btn.danger:active{background:var(--dangerAlt2Color)}.btn.outline{border:2px solid}.btn.outline,.btn.transparent{color:var(--surfaceTxtColor);background:var(--surfaceColor)}[data-color-scheme=dark] :is(.btn.outline,.btn.transparent){border-color:var(--surfaceAlt3Color)}[data-color-scheme=dark] :is(.btn.outline,.btn.transparent):hover,[data-color-scheme=dark] :is(.btn.outline,.btn.transparent):focus-visible{background:var(--surfaceAlt4Color)}[data-color-scheme=dark] :is(.btn.outline,.btn.transparent)[data-popover-state=true],[data-color-scheme=dark] :is(.btn.outline,.btn.transparent):active{background:var(--surfaceAlt5Color)}:is(.btn.outline,.btn.transparent):hover,:is(.btn.outline,.btn.transparent):focus-visible{background:var(--surfaceAlt1Color)}:is(.btn.outline,.btn.transparent)[data-popover-state=true],:is(.btn.outline,.btn.transparent):active{background:var(--surfaceAlt2Color)}:is(.btn.outline,.btn.transparent).secondary{color:var(--surfaceTxtHintColor)}:is(.btn.outline,.btn.transparent).info{color:var(--infoColor)}:is(.btn.outline,.btn.transparent).success{color:var(--successColor)}:is(.btn.outline,.btn.transparent).warning{color:var(--warningColor)}:is(.btn.outline,.btn.transparent).danger{color:var(--dangerColor)}:is(:is(.btn.outline,.btn.transparent).secondary,:is(.btn.outline,.btn.transparent).info,:is(.btn.outline,.btn.transparent).success,:is(.btn.outline,.btn.transparent).warning,:is(.btn.outline,.btn.transparent).danger):hover,:is(:is(.btn.outline,.btn.transparent).secondary,:is(.btn.outline,.btn.transparent).info,:is(.btn.outline,.btn.transparent).success,:is(.btn.outline,.btn.transparent).warning,:is(.btn.outline,.btn.transparent).danger):focus-visible{background:color-mix(in srgb, currentColor, transparent 92%)}:is(:is(.btn.outline,.btn.transparent).secondary,:is(.btn.outline,.btn.transparent).info,:is(.btn.outline,.btn.transparent).success,:is(.btn.outline,.btn.transparent).warning,:is(.btn.outline,.btn.transparent).danger)[data-popover-state=true],:is(:is(.btn.outline,.btn.transparent).secondary,:is(.btn.outline,.btn.transparent).info,:is(.btn.outline,.btn.transparent).success,:is(.btn.outline,.btn.transparent).warning,:is(.btn.outline,.btn.transparent).danger):active{background:color-mix(in srgb, currentColor, transparent 85%)}.btn.transparent{background:0 0}.btn.transparent:hover,.btn.transparent:focus-visible{background:var(--surfaceAlt2Color)}.btn.transparent[data-popover-state=true],.btn.transparent:active{background:var(--surfaceAlt4Color)}.btn.pill{border-radius:30px}.btn.expanded{min-width:130px}.btn.expanded-sm{min-width:100px}.btn.expanded-lg{min-width:160px}.btn.sm{--btnGap:5px;min-height:var(--smBtnHeight);font-size:var(--smFontSize);line-height:var(--smLineHeight);padding-left:12px;padding-right:12px}.btn.sm i{font-size:1.15em}.btn.sm.expanded{min-width:110px}.btn.sm.expanded-sm{min-width:85px}.btn.sm.expanded-lg{min-width:130px}.btn.lg{min-height:var(--lgBtnHeight);font-size:var(--lgFontSize)}.btn.lg i{font-size:1.4em}.btn.lg.expanded{min-width:150px}.btn.lg.expanded-sm{min-width:120px}.btn.lg.expanded-lg{min-width:190px}.btn.circle{--circleSize:var(--btnHeight);min-height:0;width:var(--circleSize);height:var(--circleSize);text-align:center;border-radius:50%;flex-shrink:0;align-content:center;padding:0!important}.btn.circle i{vertical-align:top;display:inline-block}.btn.circle i:before{vertical-align:top;line-height:26px;display:inline-block}.btn.circle.sm{--circleSize:var(--smBtnHeight)}.btn.circle.lg{--circleSize:var(--lgBtnHeight)}.btn.next i{will-change:transform;transition:transform var(--animationSpeed)}:is(.btn.next:hover,.btn.next:active,.btn.next:focus-visible) i{transform:translate(3px)}:is(.btn[data-rotate=true],.btn.rotate) i{animation:.3s halfRotate}.btn[data-loading=true],.btn.loading{cursor:default}:is(.btn[data-loading=true],.btn.loading):after{--size:24px;content:var(--loaderIcon);width:var(--size);height:var(--size);line-height:var(--size);margin-top:calc(var(--size) / -2);margin-left:calc(var(--size) / -2);color:inherit;text-align:center;font-size:1.5em;font-weight:400;font-family:var(--iconFontFamily);will-change:transform;animation:fadeIn var(--animationSpeed), rotate var(--loaderAnimationSpeed) infinite linear;flex-shrink:0;padding:0;display:block;position:absolute;top:50%;left:50%}:is(.btn[data-loading=true],.btn.loading)>*{opacity:0}.btn.disabled,.btn[disabled]{cursor:default;color:var(--surfaceTxtDisabledColor)!important;background:var(--surfaceAlt2Color)!important}:is(.btn.disabled,.btn[disabled]).outline,:is(.btn.disabled,.btn[disabled]).transparent{color:var(--surfaceTxtDisabledColor);background:0 0!important}:is(.btn.disabled,.btn[disabled]).outline{border-color:var(--surfaceAlt2Color)}.btns{align-items:stretch;min-width:0;display:inline-flex}.btns>.btn~.btn{border-left:1px solid var(--primaryAlt1Color);border-top-left-radius:0;border-bottom-left-radius:0}.btns>.btn~.btn.outline{border-left:0}.btns>.btn~.btn.secondary{border-left-color:var(--secondaryAlt1Color)}.btns>.btn~.btn.info{border-left-color:var(--infoAlt1Color)}.btns>.btn~.btn.success{border-left-color:var(--successAlt1Color)}.btns>.btn~.btn.warning{border-left-color:var(--warningAlt1Color)}.btns>.btn~.btn.danger{border-left-color:var(--dangerAlt1Color)}.btns>.btn~.btn.disabled,.btns>.btn~.btn[disabled]{border-left-color:var(--surfaceAlt3Color)}.btns>.btn:has(~.btn){border-top-right-radius:0;border-bottom-right-radius:0}.btns.full-width{width:100%}.btns.full-width>.btn{flex:1 1 0;min-width:0}.field:after{content:"";clear:both;display:table}.field input,.field textarea,.field select,.field .input{vertical-align:top;width:100%;letter-spacing:inherit;padding:10px var(--inputPadding);color:var(--surfaceTxtColor);font-weight:400;line-height:1;font-size:var(--fontSize);font-family:var(--baseFontFamily);scrollbar-width:thin;scrollbar-color:var(--surfaceAlt5Color) transparent;background:0 0;border:0;outline:0;align-self:stretch;margin:0;display:inline-block}[data-color-scheme=dark] :is(.field input,.field textarea,.field select,.field .input){--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}:is(.field input,.field textarea,.field select,.field .input)::placeholder{-webkit-user-select:none;user-select:none;color:var(--surfaceTxtDisabledColor);font-weight:inherit;font-family:inherit}:is(.field input,.field textarea,.field select,.field .input):autofill{-webkit-text-fill-color:var(--surfaceTxtColor);box-shadow:0 0 0 50px var(--inputColor) inset;transition:box-shadow var(--animationSpeed);background:0 0}:is(.field input,.field textarea,.field select,.field .input):focus,:is(.field input,.field textarea,.field select,.field .input):focus-visible,:is(.field input,.field textarea,.field select,.field .input):focus-within{outline:0}:is(:is(.field input,.field textarea,.field select,.field .input):focus,:is(.field input,.field textarea,.field select,.field .input):focus-visible,:is(.field input,.field textarea,.field select,.field .input):focus-within):autofill{box-shadow:0 0 0 50px var(--inputFocusColor) inset}:is(.field input,.field textarea,.field select,.field .input).readonly,:is(.field input,.field textarea,.field select,.field .input)[readonly]{cursor:default;color:var(--surfaceTxtHintColor)}:is(.field input,.field textarea,.field select,.field .input).disabled,:is(.field input,.field textarea,.field select,.field .input)[disabled]{cursor:default;color:var(--surfaceTxtDisabledColor)}:is(.field input,.field textarea,.field select,.field .input):only-child{height:100%}.field input[type=range]{-webkit-appearance:none;left:var(--inputPadding);width:calc(100% - 2 * var(--inputPadding));background:var(--surfaceAlt5Color);height:6px;min-height:0;accent-color:var(--surfaceTxtHintColor);margin-top:16px;margin-bottom:16px;padding:0;position:relative;border-radius:var(--borderRadius)!important}.field input[type=color]{-webkit-appearance:none;border:0;height:36px;min-height:0;margin:1px 0}.field input[type=color]::-webkit-color-swatch-wrapper{border-radius:var(--borderRadius);padding:0}.field input[type=color]::-webkit-color-swatch{border-radius:var(--borderRadius);border:none}.field input[type=color]::-moz-color-swatch{border-radius:var(--borderRadius);border:none}.field .autoexpand-wrapper>.input,.field textarea{resize:vertical;min-height:38px;max-height:300px;padding-top:8px;padding-bottom:9px;line-height:1.5}@supports (field-sizing:content){:is(.field .autoexpand-wrapper>.input,.field textarea).autoexpand{field-sizing:content;resize:none}}.field .autoexpand-wrapper{display:grid}.field .autoexpand-wrapper>textarea{resize:none}.field .autoexpand-wrapper>.input{white-space:pre-wrap;visibility:hidden;overflow:auto}.field .autoexpand-wrapper>.input,.field .autoexpand-wrapper>textarea{grid-area:1/1/2/2}.field.addon{width:auto;color:var(--surfaceTxtHintColor);flex-shrink:0;align-items:center;display:inline-flex}.field.addon:after{content:none;display:none}.field:focus-within:not(.addon){background:var(--inputFocusColor)}.field:focus-within:not(.addon) label{color:var(--surfaceTxtColor)}.field-list,.field{border-radius:var(--inputBorderRadius);background:var(--inputColor);width:100%;min-width:0;transition:background var(--animationSpeed);outline:0;display:block;position:relative}:is(.field-list,.field):not(.addon)>:first-child:not(.btn){border-top-left-radius:inherit;border-top-right-radius:inherit}:is(.field-list,.field):not(.addon)>:last-child:not(.btn){border-bottom-left-radius:inherit;border-bottom-right-radius:inherit}:is(.field-list,.field) label{width:100%;min-height:24px;padding:9px var(--inputPadding) 1px;white-space:normal;color:var(--surfaceTxtHintColor);font-weight:700;line-height:1;font-size:var(--smFontSize);transition:color var(--animationSpeed);align-self:center;align-items:center;gap:5px;display:flex}:is(.field-list,.field) label i{padding:0;font-size:1em}:is(.field-list,.field) label:has(~.autoexpand-wrapper textarea[required]):after{vertical-align:top;content:"*";color:var(--dangerColor);margin:-5px 0 0 -2px;font-size:.75em;line-height:1}:is(.field-list,.field) label:has(~[required]):after{vertical-align:top;content:"*";color:var(--dangerColor);margin:-5px 0 0 -2px;font-size:.75em;line-height:1}:is(.field-list,.field) label:has(~.input.required):after{vertical-align:top;content:"*";color:var(--dangerColor);margin:-5px 0 0 -2px;font-size:.75em;line-height:1}:is(:is(.field-list,.field).required label,:is(.field-list,.field) label.required,:is(.field-list,.field) .required>label):after{vertical-align:top;content:"*";color:var(--dangerColor);margin:-5px 0 0 -2px;font-size:.75em;line-height:1}:is(.field-list,.field).disabled label,:is(.field-list,.field) label.disabled,:is(.field-list,.field) .disabled>label{color:var(--surfaceTxtDisabledColor)}:is(.field-list,.field) label:has(~.autoexpand-wrapper textarea[disabled]){color:var(--surfaceTxtDisabledColor)}:is(.field-list,.field) label:has(~.autoexpand-wrapper textarea[disabled]):after{color:inherit}:is(.field-list,.field) label:has(~[disabled]){color:var(--surfaceTxtDisabledColor)}:is(.field-list,.field) label:has(~[disabled]):after{color:inherit}:is(.field-list,.field) label:has(~.input.disabled){color:var(--surfaceTxtDisabledColor)}:is(.field-list,.field) label:has(~.input.disabled):after{color:inherit}:is(:is(.field-list,.field).disabled label,:is(.field-list,.field) label.disabled,:is(.field-list,.field) .disabled>label):after{color:inherit}:is(.field-list,.field).addon label{padding:0}:is(.field-list,.field) hr{background:var(--inputBorderColor);height:1px}:is(.field-list,.field).error,.error :is(.field-list,.field){color:var(--dangerColor)}:is(:is(.field-list,.field).error,.error :is(.field-list,.field)) label,:is(:is(.field-list,.field).error,.error :is(.field-list,.field)) input,:is(:is(.field-list,.field).error,.error :is(.field-list,.field)) textarea,:is(:is(.field-list,.field).error,.error :is(.field-list,.field)) select,:is(:is(.field-list,.field).error,.error :is(.field-list,.field)) .input{color:var(--dangerColor)!important}:is(.field-list,.field).error,.error :is(.field-list,.field){--inputColor:color-mix(in srgb, var(--dangerColor), var(--surfaceColor) 80%);--inputFocusColor:color-mix(in srgb, var(--dangerColor), var(--surfaceColor) 75%);--inputBorderColor:color-mix(in srgb, var(--dangerColor), var(--surfaceColor) 70%)}.field-content{flex-direction:column;gap:3px;margin-top:4px;padding:3px;display:flex;position:relative}.field-content .list{border:1px solid var(--inputBorderColor);background:0 0;border-left:0;border-right:0;border-radius:0;gap:0;width:auto;max-height:300px;margin:0 -3px}.field-content .list .list-item{box-shadow:none;border-top:1px solid var(--inputBorderColor);margin:0}.field-content .list .list-item:first-child{border-top:0}.field-content .list .list-item:not(.disabled)[data-dragstart=true],.field-content .list .list-item:not(.disabled).active,.field-content .list .list-item:not(.disabled).handle:hover,.field-content .list .list-item:not(.disabled).handle:active,.field-content .list .list-item:not(.disabled).highlight:hover,.field-content .list .list-item:not(.disabled).highlight:active{background:var(--inputFocusColor)}.field-list .field-list-content{scrollbar-width:thin;max-height:500px;color:var(--surfaceTxtColor);overflow:auto}.field-list label+.field-list-content{margin-top:8px}.field-list .field-list-item{width:100%;padding:var(--inputPadding);min-height:var(--btnHeight);border-top:1px solid var(--inputBorderColor);align-items:center;gap:10px;display:flex}.field-list .field-list-item.p-0:first-child>.field,.field-list .field-list-item.p-0:first-child>.fields .field{border-radius:0}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])){--checkboxSize:20px;--checkboxActiveColor:var(--successColor);--checkboxBorderColor:var(--surfaceAlt3Color);--checkboxBorderFocusColor:var(--surfaceAlt5Color);text-align:left;background:0 0;align-items:center;gap:10px;padding:0;display:flex}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])):focus-visible,:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])):focus-within{background:0 0}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])):after{content:none;display:none}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input{opacity:0;width:1px;height:1px;position:absolute}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) label{padding:0 0 0 calc(var(--checkboxSize) + 7px);-webkit-user-select:none;user-select:none;cursor:pointer;width:auto;color:var(--surfaceTxtColor);font-weight:400;font-size:var(--fontSize);line-height:var(--smLineHeight);min-height:var(--checkboxSize);background:0 0;margin:0}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) label:before{content:"";width:var(--checkboxSize);height:var(--checkboxSize);border-radius:var(--borderRadius);background:var(--surfaceColor);border:2px solid var(--checkboxBorderColor);transition:border-color var(--animationSpeed), box-shadow var(--animationSpeed), background var(--animationSpeed);display:block;position:absolute;top:0;left:0}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) label:after{content:"";z-index:1;width:var(--checkboxSize);height:var(--checkboxSize);text-align:center;font-size:1.15rem;font-weight:400;font-family:var(--iconFontFamily);color:var(--checkboxActiveColor);opacity:0;transition:transform var(--activeAnimationSpeed), opacity var(--activeAnimationSpeed);justify-content:center;align-items:center;display:flex;position:absolute;top:0;left:0;transform:translateY(2px)}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input:focus-visible~label,:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) label:hover,:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) label:focus-within{--checkboxBorderColor:var(--checkboxBorderFocusColor);background:0 0}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input[type=radio]~label:before{border-radius:50%}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input[type=radio]~label:after{content:"";font-size:.7em}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input:checked~label{--checkboxBorderColor:var(--checkboxActiveColor)}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input:checked~label:after{opacity:1;transform:scale(1)}:is(:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input:checked:focus-visible~label,:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input:checked~label:hover,:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input:checked~label:focus-within):before{box-shadow:0px 0px 0px 3px color-mix(in srgb, var(--checkboxActiveColor), transparent 85%)}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input.sm~label{--checkboxSize:18px;font-size:var(--smFontSize);padding-left:calc(var(--checkboxSize) + 5px);gap:3px}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input.sm~label:after{font-size:1.12em}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])){--switchH:24px;--switchW:41px;--switchTranslateX:0}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input.switch~label{--switchTranslateX:0;min-height:var(--switchH);padding-left:calc(var(--switchW) + 8px)}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input.switch~label:before{width:var(--switchW);height:var(--switchH);background:var(--checkboxBorderColor);border:0;border-radius:7px}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input.switch~label:after{content:"";height:calc(var(--switchH) - 8px);width:calc(var(--switchH) - 5px);opacity:1;transform:translateX(var(--switchTranslateX));background:#fff;border-radius:5px;top:4px;left:4px;box-shadow:1px 0 2px #0003}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input.switch~label:active:after{transform:scale(.92)}@supports (corner-shape:squircle){:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input.switch~label:before{corner-shape:squircle;border-radius:11px}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input.switch~label:after{corner-shape:squircle;border-radius:7px}}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input.switch:checked~label{--switchTranslateX:calc(var(--switchW) - var(--switchH) - 3px)}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input.switch:checked~label:before{background:var(--checkboxActiveColor)}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input.switch:checked~label:after{left:4px;box-shadow:-1px 0 2px #0003}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input.switch:checked~label:active:after{transform:scale(.92) translateX(var(--switchTranslateX))}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input.switch.sm~label{--switchH:21px;--switchW:35px}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input[disabled]~label{cursor:default;color:var(--surfaceTxtHintColor)}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input[disabled]~label:before{opacity:.6}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])) input[disabled]~label:after{box-shadow:none!important}:is(.field:has(input[type=checkbox]),.field:has(input[type=radio])).error label{--checkboxActiveColor:var(--dangerColor);color:var(--dangerColor)}.flex>.field,.inline-flex>.field{width:auto}.input.select{align-items:center;gap:10px;min-height:38px;padding:5px 0;display:flex}.input.select .selected-container{width:100%;padding:1px var(--inputPadding);color:var(--surfaceTxtHintColor);cursor:pointer;outline:0;flex-wrap:wrap;align-self:stretch;align-items:center;gap:5px;margin:0;display:flex}.input.select .selected-container:after{content:"";text-align:right;font-size:1.2rem;font-family:var(--iconFontFamily);transition:color var(--animationSpeed);align-content:center;margin-left:auto;margin-right:-2px;display:block}.input.select .selected-item{color:var(--surfaceTxtColor);border-radius:var(--borderRadius);background:0 0;align-items:center;gap:5px;padding:0;display:inline-flex}.input.select .selected-item .link-hint{font-size:var(--smFontSize);padding:1px}.input.select .dropdown{border-width:2px;border-color:var(--inputFocusColor);min-width:100px;margin:-2px 0}.input.select.single .selected-item{flex-grow:1}.input.select.single .selected-item .btn-option-unset{margin-left:auto}.input.select.single .selected-container{flex-wrap:nowrap}.input.select.multiple .selected-item{color:var(--surfaceTxtColor);background:var(--surfaceColor);padding:3px 4px}.input.select:has(.dropdown:popover-open) .selected-container:after{content:"";color:var(--surfaceTxtColor)}.input.select.disabled .selected-container{cursor:default}.input.select.disabled .selected-container:after{color:var(--surfaceTxtDisabledColor)}.input.select.disabled .selected-item{color:var(--surfaceTxtHintColor)}.fields{align-items:stretch;width:100%;display:flex}.fields .field:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.fields .field:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.fields .delimiter{background:var(--inputFocusColor);flex-shrink:0;width:1px;min-height:0}.fields .addon{padding:0 var(--inputPadding);align-items:center;gap:5px;display:inline-flex}.fields .addon:has(+.field){padding-right:0}.fields .field+.addon{padding-left:0}.fields .field:focus-within+.addon{background:var(--inputFocusColor)}.fields .addon:has(+.field:focus-within){background:var(--inputFocusColor)}.fields.error .delimiter{background:color-mix(in srgb, var(--dangerColor), var(--surfaceColor) 70%)}.field-help{width:100%;font-size:var(--smFontSize);line-height:var(--smLineHeight);color:var(--surfaceTxtHintColor);margin:7px 0 0;display:block}.field-help.error{color:var(--dangerColor)}.field-help code{font-size:.9em;line-height:1.4}.field-help p{margin:5px 0}.searchbar{min-height:var(--btnHeight);max-width:100%}.searchbar input,.searchbar .input{height:100%}.searchbar .field{--inputBorderRadius:30px}.searchbar .field:not(.addon){flex-grow:1;width:0}.pb-tinymce{width:100%;display:block}.field .pb-tinymce .tox-tinymce{border-width:2px;border-color:#0000}.field label~.pb-tinymce{margin-top:7px}.input.code-editor{cursor:text;scrollbar-width:thin;flex-shrink:0;align-items:stretch;width:100%;min-height:150px;max-height:350px;display:flex;position:relative;overflow:auto}.input.code-editor .code-editor-container{width:100%;position:relative}.input.code-editor .editor-content,.input.code-editor .highlight-overlay{tab-size:4;-webkit-hyphens:none;hyphens:none;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;text-align:left;letter-spacing:normal;width:100%;min-height:100%;font-size:.96rem;font-weight:400;line-height:1.5;font-family:var(--monospaceFontFamily);border:0;outline:0;margin:0;padding:0;display:block}.input.code-editor .highlight-overlay{pointer-events:none;position:absolute;top:0;left:0}.input.code-editor .editor-content{color:#0000;resize:none;caret-color:var(--surfaceTxtHintColor)}.input.code-editor .editor-content:empty:before{content:attr(data-placeholder);color:var(--surfaceTxtDisabledColor);white-space:normal}.input.code-editor.single-line{align-items:center;min-height:0;padding-top:9px;padding-bottom:9px}.input.code-editor.pre-wrap .editor-content,.input.code-editor.pre-wrap .highlight-overlay{white-space:pre-wrap}.dropdown{width:anchor-size(width);text-align:left;min-width:150px;max-height:330px;font-weight:400;font-size:var(--fontSize);line-height:var(--smLineHeight);color:var(--surfaceTxtColor);background:var(--surfaceColor);box-shadow:var(--boxShadow);scrollbar-width:thin;border-radius:var(--borderRadius);border:1px solid var(--surfaceAlt3Color);outline:0;flex-direction:column;gap:5px;margin:5px 0;padding:5px;display:inline-flex;overflow:auto}.dropdown[popover]{position-area:bottom span-left;position-try-fallbacks:flip-block;opacity:0;transition:opacity var(--animationSpeed), transform var(--animationSpeed), display var(--animationSpeed) allow-discrete, overlay var(--animationSpeed) allow-discrete;display:none;transform:translateY(3px)}.dropdown[popover]:popover-open{opacity:1;display:inline-flex;transform:translateY(0)}@starting-style{.dropdown[popover]:popover-open{opacity:0;transform:translateY(3px)}}@supports not (fill:context-stroke){.dropdown[popover]{position-try-fallbacks:none!important;transition:none!important}}.dropdown[popover].left{position-area:bottom span-right}.dropdown hr{margin:0}.dropdown .dropdown-item{cursor:pointer;-webkit-user-select:none;user-select:none;word-break:break-word;width:100%;color:inherit;transition:background var(--animationSpeed);border-radius:var(--borderRadius);outline:0;align-items:center;gap:8px;padding:10px 12px;text-decoration:none;display:flex}.dropdown .dropdown-item:hover{background:var(--surfaceAlt1Color)}.dropdown .dropdown-item:focus-visible{outline:2px dotted var(--surfaceAlt3Color)}.dropdown .dropdown-item:active{background:var(--surfaceAlt2Color);transition-duration:var(--activeAnimationSpeed)}.dropdown .dropdown-item.active{background:var(--surfaceAlt2Color);transition-duration:var(--activeAnimationSpeed)}.dropdown .dropdown-item.active code{background:var(--surfaceAlt1Color)}.dropdown .dropdown-item.active:hover{background:var(--surfaceAlt3Color);transition-duration:var(--animationSpeed)}.dropdown .dropdown-item[disabled],.dropdown .dropdown-item.disabled{cursor:default;color:var(--surfaceTxtDisabledColor);background:0 0}:is(.dropdown .dropdown-item input[type=checkbox],.dropdown .dropdown-item input[type=radio])~label{width:100%}.dropdown>.field,.dropdown>.fields-group{margin:0}:is(.dropdown .field:has(.search),.dropdown .fields-group:has(.search)){border-bottom:1px solid var(--surfaceAlt1Color);background:0 0;margin:0}:is(.dropdown .field:has(.search),.dropdown .fields-group:has(.search)) .field,:is(.dropdown .field:has(.search),.dropdown .fields-group:has(.search)) input.search{background:0 0;border-bottom:0}.dropdown .dropdown-search{border-bottom:1px solid var(--surfaceAlt2Color)}.dropdown .dropdown-search .field{background:0 0!important}.dropdown.nowrap,.dropdown.txt-nowrap{white-space:nowrap;width:auto}.dropdown.sm{min-width:100px;font-size:var(--smFontSize)}.dropdown.sm .dropdown-item{gap:5px;padding:7px}.dropdown.autocomplete{z-index:99999;word-break:break-word;max-width:350px;max-height:200px;font-size:var(--smFontSize);gap:3px;padding:3px;position:fixed}.dropdown.autocomplete .dropdown-item{padding:2px 4px;line-height:1.2}.dropdown.history-searchbar-dropdown[popover]{min-width:300px;max-width:450px}.accordion{--siblingSpacing:var(--smSpacing);--contentPadding:var(--smSpacing);width:100%;color:var(--surfaceTxtColor);border-radius:var(--borderRadius);background:var(--surfaceAlt1Color);border:1px solid var(--surfaceAlt3Color);transition:border var(--animationSpeed), border-radius var(--animationSpeed), box-shadow var(--animationSpeed), margin var(--animationSpeed);display:block;position:relative}.accordion summary{width:100%;min-height:var(--lgBtnHeight);cursor:pointer;padding:10px var(--smSpacing);padding-right:calc(var(--smSpacing) + 30px);background:var(--surfaceColor);border-radius:var(--borderRadius);-webkit-user-select:none;user-select:none;transition:background var(--animationSpeed), border var(--animationSpeed), border-radius var(--animationSpeed);align-items:center;gap:10px;display:flex;position:relative}.accordion summary::marker{content:"";display:none}.accordion summary:before{right:calc(var(--smSpacing) - 3px);content:"";width:20px;height:20px;font-size:1.3rem;font-weight:400;line-height:20px;font-family:var(--iconFontFamily);color:var(--surfaceTxtHintColor);transition:color var(--animationSpeed);flex-shrink:0;align-content:center;margin-top:-10px;display:block;position:absolute;top:50%}.accordion summary:not([tabindex="-1"]):hover,.accordion summary:not([tabindex="-1"]):focus-visible{background:var(--surfaceAlt1Color);outline:0}:is(.accordion summary:not([tabindex="-1"]):hover,.accordion summary:not([tabindex="-1"]):focus-visible):before{color:var(--surfaceTxtColor)}.accordion summary[tabindex="-1"]{cursor:default;-webkit-user-select:auto;user-select:auto;padding-right:var(--smSpacing);outline:0}.accordion summary[tabindex="-1"]:before{content:none;display:none}.accordion::details-content{height:0;padding:0 var(--contentPadding);background:var(--surfaceColor);border-bottom-left-radius:var(--borderRadius);border-bottom-right-radius:var(--borderRadius);transition:border var(--slideAnimationSpeed) step-end, height var(--slideAnimationSpeed), padding var(--slideAnimationSpeed), content-visibility var(--slideAnimationSpeed), overflow var(--slideAnimationSpeed) step-start;transition-behavior:allow-discrete;overflow:clip}.accordion:has(+.accordion){border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion:has(+.accordion) summary{border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion:not([open]):has(+.accordion[open]){border-bottom-left-radius:var(--borderRadius);border-bottom-right-radius:var(--borderRadius);border-bottom-width:1px}.accordion:not([open]):has(+.accordion[open]) summary{border-bottom-left-radius:var(--borderRadius);border-bottom-right-radius:var(--borderRadius)}.accordion+.accordion{border-top-width:0;border-top-left-radius:0;border-top-right-radius:0}.accordion+.accordion summary{border-top-left-radius:0;border-top-right-radius:0}.accordion+.accordion[open]{margin-top:var(--siblingSpacing)}.accordion[open]{border-radius:var(--borderRadius);box-shadow:var(--boxShadow);border-width:1px}.accordion[open] summary{background:var(--surfaceAlt1Color);border-radius:var(--borderRadius) var(--borderRadius) 0 0}.accordion[open] summary:before{content:""}.accordion[open]::details-content{border-top:1px solid var(--surfaceAlt3Color);padding-top:var(--contentPadding);padding-bottom:var(--contentPadding);height:auto;transition:height var(--slideAnimationSpeed), padding var(--slideAnimationSpeed), content-visibility var(--slideAnimationSpeed), overflow var(--slideAnimationSpeed) step-end;transition-behavior:allow-discrete;overflow:visible}.accordion[open]+.accordion{margin-top:var(--siblingSpacing);border-top-left-radius:var(--borderRadius);border-top-right-radius:var(--borderRadius);border-top-width:1px}.accordion[open]+.accordion summary{border-top-left-radius:var(--borderRadius);border-top-right-radius:var(--borderRadius)}.accordion.noanimation::details-content{transition:none!important}.bulkbar{z-index:11;bottom:var(--spacing);opacity:1;width:450px;max-width:95%;padding:5px var(--smSpacing);min-height:var(--lgBtnHeight);background:var(--surfaceColor);box-shadow:var(--boxShadow);border:1px solid var(--surfaceAlt3Color);transition-behavior:allow-discrete;transition-property:opacity,transform,display;transition-duration:var(--animationSpeed);border-radius:50px;flex-wrap:wrap;align-items:center;gap:10px;display:flex;position:fixed;left:50%;transform:translate(-50%,10px)}@starting-style{.bulkbar{opacity:0;transform:translate(-50%,10px)}}.bulkbar[hidden],.bulkbar.hidden{opacity:0;transform:translate(-50%,10px)}.bulkbar>.btn:first-child{margin-left:-5px}.bulkbar>.btn:last-child{margin-right:-5px}@media (width<=480px){.bulkbar{border-radius:var(--borderRadius);padding:min(10px, var(--smSpacing)) var(--smSpacing)}.bulkbar>.txt,.bulkbar>.content{width:100%}}.bulkbar-wrapper{z-index:11;width:100%;left:0;bottom:var(--smSpacing);pointer-events:none;position:sticky;right:0}.bulkbar-wrapper .bulkbar{margin:var(--smSpacing) auto;pointer-events:auto;position:relative;bottom:0;left:auto;right:auto;transform:translateY(0)}@starting-style{.bulkbar-wrapper .bulkbar{transform:translateY(10px)}}.bulkbar-wrapper .bulkbar[hidden],.bulkbar-wrapper .bulkbar.hidden{transform:translateY(10px)}.tabs-header{z-index:0;border-bottom:2px solid var(--surfaceAlt2Color);align-items:center;gap:10px;width:100%;display:flex;position:relative}.tabs-header .tab-item{cursor:pointer;text-align:center;-webkit-user-select:none;user-select:none;min-width:0;min-height:var(--btnHeight);padding:5px var(--smSpacing);border-top-left-radius:var(--borderRadius);border-top-right-radius:var(--borderRadius);color:var(--surfaceTxtHintColor);transition:color var(--animationSpeed), background var(--animationSpeed);outline:0;justify-content:center;align-items:center;gap:5px;display:inline-flex}.tabs-header .tab-item:hover,.tabs-header .tab-item:focus-visible{color:var(--surfaceTxtColor);background:var(--surfaceAlt1Color)}.tabs-header .tab-item.active,.tabs-header .tab-item:active{color:var(--surfaceTxtColor);background:var(--surfaceAlt2Color);transition-duration:var(--activeAnimationSpeed)}.tabs-header.equal-width .tab-item{flex:1 0 0;padding:5px}.code-block-tabs-content{z-index:1;margin-top:calc(-1 * (var(--borderRadius)));position:relative}.code-block-tabs-content code.block{padding:max(10px, calc(var(--smSpacing) - 5px)) max(10px, var(--smSpacing))}.modal{z-index:1000;opacity:0;word-break:break-word;width:620px;max-width:100%;height:100%;color:var(--surfaceTxtColor);background:var(--surfaceColor);backface-visibility:hidden;transition-property:opacity,transform,width,display;transition-duration:var(--modalAnimationSpeed);transition-behavior:allow-discrete;box-shadow:var(--leftBoxShadow);overscroll-behavior:none;border:0;outline:0;flex-direction:column;margin:0;display:none;position:fixed;top:0;right:0;transform:translate(30px)}.modal[data-modal-state=open]{opacity:1;display:flex;transform:translate(0)}@starting-style{.modal[data-modal-state=open]{opacity:0;transform:translate(30px)}}.modal:before{content:"";z-index:-2;background:var(--modalOverlayColor);width:200vw;height:200vh;display:block;position:fixed;top:-100vh;left:-100vw}.modal:after{content:"";z-index:-1;border-radius:inherit;background:inherit;width:100%;height:100%;display:block;position:absolute;top:0;left:0}.modal .modal-header,.modal .modal-content,.modal .modal-footer{padding:var(--spacing);outline:0}.modal .modal-header,.modal .modal-footer{align-items:center;gap:var(--smSpacing);width:100%;padding-top:calc(var(--spacing) - 10px);padding-bottom:calc(var(--spacing) - 10px);display:flex}.modal .modal-footer{border-top:1px solid var(--surfaceAlt2Color);margin-top:auto}.modal .modal-content{scrollbar-width:thin;flex-grow:1;position:relative;overflow:auto}.modal .modal-content>:first-child{margin-top:0}.modal .modal-content>:last-child{margin-bottom:0}.modal .modal-header .modal-close-btn.transparent{margin-right:-10px}.modal .modal-header:not(.isolated)~.modal-content{padding-top:5px}.modal .modal-header.isolated{background:var(--surfaceAlt1Color);border-bottom:1px solid var(--surfaceAlt2Color);border-top-left-radius:inherit;border-top-right-radius:inherit}.modal .modal-title{align-content:center}.modal.sm{width:480px}.modal.lg{width:880px}.modal.full{width:100%}.modal.preview,.modal.popup{max-width:95%;height:min-content;max-height:95%;box-shadow:var(--boxShadow);border-radius:var(--lgBorderRadius);margin:auto;inset:0;transform:scale(.98)}:is(.modal.preview,.modal.popup).full{width:95%}:is(.modal.preview,.modal.popup) .modal-header,:is(.modal.preview,.modal.popup) .modal-content,:is(.modal.preview,.modal.popup) .modal-footer{border-radius:inherit}:is(.modal.preview,.modal.popup) .modal-header,:is(.modal.preview,.modal.popup) .modal-footer{padding-top:calc(var(--spacing) - 5px);padding-bottom:calc(var(--spacing) - 5px)}:is(:is(.modal.preview,.modal.popup) .modal-header:has(~.modal-content),:is(.modal.preview,.modal.popup) .modal-header:has(~.modal-footer),:is(.modal.preview,.modal.popup) .modal-content:has(~.modal-footer)){border-bottom-right-radius:0;border-bottom-left-radius:0}:is(.modal.preview,.modal.popup) .modal-header~.modal-content,:is(.modal.preview,.modal.popup) .modal-content~.modal-footer,:is(.modal.preview,.modal.popup) .modal-header~.modal-footer{border-top-left-radius:0;border-top-right-radius:0}:is(.modal.preview,.modal.popup) .modal-footer{background:var(--surfaceAlt1Color)}:is(.modal.preview,.modal.popup)[data-modal-state=open]{transform:scale(1)}@starting-style{:is(.modal.preview,.modal.popup)[data-modal-state=open]{transform:scale(.98)}}.modal.preview{width:min-content;min-width:400px;min-height:300px}.modal.preview .modal-footer{padding:var(--smSpacing)}.modal.preview .modal-content{text-align:center;justify-content:center;align-items:flex-start;width:100%;max-width:100%;margin:auto;padding:0;display:flex}.modal.preview .modal-content:has(img){width:max-content}.modal.preview .modal-content:before,.modal.preview .modal-content:after{content:none}.modal.preview .modal-content img{width:100%;margin:auto}.modal.preview .modal-content object{width:100%;height:100%;margin:auto}.modal.preview:has(object){width:75%;height:85%}@media (width<=900px){.modal.preview:has(object){width:90%;height:90%}}.modal.preview.preview-audio{width:500px;height:400px}.tox-fullscreen .modal{transition:none;width:100%!important}.modal-content{--topShadowOffset:calc(var(--spacing) + 1px);--bottomShadowOffset:calc(var(--spacing) + 1px);min-height:92px;container-type:scroll-state}.modal-content:before,.modal-content:after{content:"";z-index:9;width:auto;height:1px;margin-left:calc(-1 * var(--spacing));margin-right:calc(-1 * var(--spacing));pointer-events:none;opacity:0;display:block;position:sticky;left:0}.modal-content:before{top:calc(100% + var(--bottomShadowOffset));margin-bottom:calc(-1 * var(--bottomShadowOffset));margin-top:var(--bottomShadowOffset);box-shadow:var(--bottomScrollShadow)}@container scroll-state(scrollable:bottom){.modal-content:before{opacity:1;transition:opacity var(--animationSpeed)}}.modal-content:after{top:auto;bottom:calc(100% + var(--topShadowOffset));margin-top:calc(-1 * var(--topShadowOffset));margin-bottom:var(--topShadowOffset);box-shadow:var(--topScrollShadow)}@container scroll-state(scrollable:top){.modal-content:after{opacity:1;transition:opacity var(--animationSpeed)}}.modal-header:not(.isolated)~.modal-content:after{--topShadowOffset:6px}.modal-header .tabs-header{flex-grow:1;width:auto}.modal-header .tabs-header .tab-item{font-size:var(--lgFontSize)}.modal-header.isolated :is(.modal-header .tabs-header):last-child{padding:0 var(--spacing);margin-left:calc(-1 * var(--spacing));margin-right:calc(-1 * var(--spacing));margin-bottom:calc(-1 * var(--spacing) + 9px);border-bottom:0}.modal-header.isolated :is(.modal-header .tabs-header):last-child .tab-item{border:1px solid #0000;border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.modal-header.isolated :is(.modal-header .tabs-header):last-child .tab-item:hover,.modal-header.isolated :is(.modal-header .tabs-header):last-child .tab-item:focus-visible{background:var(--surfaceAlt2Color)}.modal-header.isolated :is(.modal-header .tabs-header):last-child .tab-item:active{background:var(--surfaceAlt3Color)}.modal-header.isolated :is(.modal-header .tabs-header):last-child .tab-item.active{background:var(--surfaceColor);border-color:var(--surfaceAlt2Color)}.popup .modal-header.isolated :is(.modal-header .tabs-header):last-child{margin-bottom:calc(-1 * var(--spacing) + 4px)}.app{flex-direction:column;width:100%;height:100%;padding:0;display:flex}.app:has(.accent-surface){background:var(--accentColor);color:var(--accentTxtColor)}.app-header{--headerItemsGap:var(--inputPadding);align-items:center;gap:var(--headerItemsGap);width:100%;min-height:38px;font-size:var(--smFontSize);line-height:var(--smLineHeight);flex-shrink:0;padding:2px 10px;display:flex}@media (display-mode:minimal-ui) or (display-mode:standalone){.app-header{border-top:1px solid var(--accentAlt1Color);border-bottom:1px solid #0000}}.app-header .logo{vertical-align:top;border-radius:var(--borderRadius);justify-content:center;align-items:center;gap:5px;text-decoration:none;display:inline-flex}.app-header .logo img{vertical-align:top;max-height:26px}@media (width<=400px){.app-header{gap:7px;padding-left:5px;padding-right:5px}}.app-main-nav{align-items:center;gap:max(var(--headerItemsGap), 5px);scrollbar-width:thin;overflow:auto;overflow:overlay;margin:0 -3px;padding:3px;display:inline-flex;position:relative;container-type:scroll-state}.app-main-nav:before,.app-main-nav:after{content:"";z-index:9;opacity:0;pointer-events:none;flex-shrink:0;align-self:stretch;width:1px;height:auto;margin:0;display:block;position:sticky}.app-main-nav:before{margin-right:calc(-1 * (var(--headerItemsGap) + 1px));left:-5px;box-shadow:2px 0 4px 1px #0006}@container scroll-state(scrollable:left){.app-main-nav:before{opacity:1;transition:opacity var(--animationSpeed)}}.app-main-nav:after{margin-left:calc(-1 * (var(--headerItemsGap) + 1px));right:-5px;box-shadow:-2px 0 4px 1px #0006}@container scroll-state(scrollable:right){.app-main-nav:after{opacity:1;transition:opacity var(--animationSpeed)}}@media (width<=500px){.app-main-nav{--headerItemsGap:5px}}.app-header .header-link{-webkit-user-select:none;user-select:none;cursor:pointer;color:var(--accentTxtHintColor);border-radius:var(--borderRadius);transition:color var(--animationSpeed), background var(--animationSpeed);align-items:center;gap:5px;padding:4px 5px;font-weight:700;text-decoration:none;display:inline-flex}.app-header .header-link[data-popover-state=true],.app-header .header-link.active,.app-header .header-link:hover,.app-header .header-link:focus-visible{color:var(--accentTxtColor);background:var(--surfaceAlt2Color)}.app-header .header-link:active{background:var(--surfaceAlt3Color);transition-duration:var(--activeAnimationSpeed)}.app-header .logged-user{max-width:300px}@media (width<=600px){.app-header .logged-user .superuser-name{display:none}}.page{height:100%;color:var(--surfaceTxtColor);background:var(--surfaceColor);border-radius:var(--lgBorderRadius);display:flex;position:relative;overflow:auto}.page:first-child{border-top-left-radius:0;border-top-right-radius:0}.page:last-child{border-bottom-right-radius:0;border-bottom-left-radius:0}.page-content{z-index:0;width:100%;min-width:0;padding:var(--spacing);flex-grow:1;overflow:auto}.page-header{column-gap:var(--smSpacing);row-gap:calc(var(--smSpacing) - 5px);width:100%;min-height:var(--btnHeight);margin:-10px 0 calc(var(--spacing) - 10px);flex-wrap:wrap;flex-shrink:0;align-items:center;display:flex}.page-header .searchbar{flex-grow:1;width:auto}.page-header .page-header-secondary-btns,.page-header .page-header-primary-btns{flex-shrink:0;justify-content:flex-end;display:inline-flex}.page-header-secondary-btns{gap:10px}@media (width<=550px){.page-header-secondary-btns{gap:5px}}.page-header-primary-btns{gap:var(--smSpacing);margin-left:auto}.page-header-primary-btns .btn{white-space:nowrap;flex-wrap:nowrap}@media (width<=950px){.page-header-primary-btns{gap:5px}.page-header-primary-btns .btn{flex-grow:1;flex-basis:0;padding-left:10px;padding-right:10px}}@media (width<=550px){.page-header-primary-btns{gap:5px}.page-header-primary-btns .api-preview-btn{display:none}.page-header-primary-btns .btn:not(.circle){aspect-ratio:1;border-radius:50%;width:auto;min-width:0;padding:0}.page-header-primary-btns .btn:not(.circle) i+.txt{display:none}}.page-footer{z-index:10;align-items:center;gap:var(--smSpacing);width:auto;color:var(--surfaceTxtHintColor);font-size:var(--smFontSize);line-height:var(--smLineHeight);box-shadow:0px -1px 0px 0px var(--surfaceAlt2Color);padding:10px var(--spacing);margin:auto calc(-1 * var(--spacing)) calc(-1 * var(--spacing));flex-wrap:wrap;display:flex}.page-footer a{transition:color var(--animationSpeed);border-radius:var(--borderRadius);text-decoration:none}.page-footer a:hover,.page-footer a:focus-visible{color:var(--surfaceTxtColor)}.page-footer .credits{--creditsGap:20px;align-items:center;gap:var(--creditsGap);margin:0 0 0 auto;display:inline-flex}.page-footer .credits .credit-item{color:var(--surfaceTxtDisabledColor);align-items:center;gap:5px;display:inline-flex;position:relative}.page-footer .credits .credit-item:before{content:"";pointer-events:none;-webkit-user-select:none;user-select:none;top:50%;left:calc(var(--creditsGap) / -2);opacity:.5;background:var(--surfaceTxtDisabledColor);width:1px;height:10px;margin-top:-5px;position:absolute}.page-footer .credits .credit-item:first-child:before{content:none;display:none}.page-footer .credits .credit-item:hover,.page-footer .credits .credit-item:active,.page-footer .credits .credit-item:focus-within{color:var(--surfaceTxtColor)}.page-sidebar{--sidebarSpacing:calc(var(--spacing) / 2);width:var(--pageSidebarWidth);min-width:var(--pageSidebarWidth);background:var(--surfaceAlt1Color);border-right:1px solid var(--surfaceAlt2Color);backface-visibility:hidden;flex-direction:column;flex-shrink:0;max-width:min(400px,80%);display:flex;position:relative;overflow:auto}.page-sidebar .sidebar-content{padding:var(--sidebarSpacing);position:relative}.page-sidebar .sidebar-content.scrollable{scrollbar-width:thin;--topShadowOffset:calc(var(--sidebarSpacing) + 1px);--bottomShadowOffset:calc(var(--sidebarSpacing) + 1px);overflow:auto;container-type:scroll-state}.page-sidebar .sidebar-content.scrollable:before,.page-sidebar .sidebar-content.scrollable:after{content:"";z-index:9;width:auto;height:1px;margin-left:calc(-1 * var(--sidebarSpacing));margin-right:calc(-1 * var(--sidebarSpacing));pointer-events:none;opacity:0;display:block;position:sticky;left:0}.page-sidebar .sidebar-content.scrollable:before{top:calc(100% + var(--bottomShadowOffset));margin-bottom:calc(-1 * var(--bottomShadowOffset));margin-top:var(--bottomShadowOffset);box-shadow:var(--bottomScrollShadow)}@container scroll-state(scrollable:bottom){.page-sidebar .sidebar-content.scrollable:before{opacity:1;transition:opacity var(--animationSpeed)}}.page-sidebar .sidebar-content.scrollable:after{top:auto;bottom:calc(100% + var(--topShadowOffset));margin-top:calc(-1 * var(--topShadowOffset));margin-bottom:var(--topShadowOffset);box-shadow:var(--topScrollShadow)}@container scroll-state(scrollable:top){.page-sidebar .sidebar-content.scrollable:after{opacity:1;transition:opacity var(--animationSpeed)}}.page-sidebar .sidebar-content:has(+.sidebar-content){--bottomShadowOffset:1px;padding-bottom:0}.page-sidebar .sidebar-search{--inputPadding:var(--sidebarSpacing);border-bottom:1px solid var(--surfaceAlt2Color);--inputColor:var(--surfaceAlt1Color);--inputFocusColor:var(--surfaceAlt2Color);--inputBorderColor:var(--surfaceAlt3Color);align-items:center;gap:5px;min-height:50px;display:flex}.page-sidebar .sidebar-search .fields{height:100%}.page-sidebar .sidebar-search .field{border-radius:0;height:100%}.page-sidebar .sidebar-search .field input{height:100%}.page-sidebar hr{margin:var(--sidebarSpacing) 0}.page-sidebar .nav-group{box-shadow:none;margin:0 0 var(--sidebarSpacing);background:0 0;border:0;border-radius:0;padding:0;transition:none}.page-sidebar .nav-group:last-child{margin:0}.page-sidebar .nav-group summary{-webkit-user-select:none;user-select:none;min-height:30px;font-weight:700;font-size:var(--smFontSize);color:var(--surfaceTxtHintColor);transition:color var(--animationSpeed);background:0 0;border:0;border-radius:0;align-items:center;gap:5px;margin:0;padding:0;display:flex}.page-sidebar .nav-group summary::marker{content:"";display:none}.page-sidebar .nav-group summary:before{content:"";width:20px;height:20px;font-size:1em;font-weight:400;line-height:20px;font-family:var(--iconFontFamily);flex-shrink:0;order:1;align-content:center;margin:0;padding:0;display:block;position:relative;inset:auto}.page-sidebar .nav-group summary .actions{opacity:0;transition:transform var(--animationSpeed), opacity var(--animationSpeed);order:100;align-items:center;gap:5px;margin:0 0 0 auto;display:inline-flex;transform:translate(3px)}.page-sidebar .nav-group summary .actions:focus-within{opacity:1;transform:translate(0)}.page-sidebar .nav-group summary:hover,.page-sidebar .nav-group summary:active,.page-sidebar .nav-group summary:focus-visible{cursor:pointer;color:var(--surfaceTxtColor)}:is(.page-sidebar .nav-group summary:hover,.page-sidebar .nav-group summary:active,.page-sidebar .nav-group summary:focus-visible) .actions{opacity:1;transform:translate(0)}.page-sidebar .nav-group summary[tabindex="-1"]{cursor:default;color:var(--surfaceTxtHintColor)}.page-sidebar .nav-group summary[tabindex="-1"]:before{content:none;display:none}.page-sidebar .nav-group summary:empty{display:none}.page-sidebar .nav-group::details-content{background:0 0;border:0;flex-direction:column;gap:5px;margin:0;padding:0;display:flex}.page-sidebar .nav-group[open] summary:before{content:""}.compact :is(.page-sidebar .nav-group)::details-content{gap:2px}.page-sidebar .nav-group.compact::details-content{gap:2px}.page-sidebar .nav-group .nav-item{cursor:pointer;width:100%;font-size:var(--lgFontSize);color:var(--surfaceTxtHintColor);min-height:var(--btnHeight);border-radius:var(--borderRadius);transition:color var(--animationSpeed), background var(--animationSpeed);outline:0;align-items:center;gap:10px;padding:5px 10px;text-decoration:none;display:flex}.page-sidebar .nav-group .nav-item .txt{text-overflow:ellipsis;overflow:hidden}.page-sidebar .nav-group .nav-item .pin{color:var(--surfaceTxtDisabledColor);border-radius:var(--borderRadius);opacity:0;transition:color var(--animationSpeed), opacity var(--animationSpeed), transform var(--animationSpeed);margin-left:auto;margin-right:-5px;padding:2px;font-size:.8em;transform:translate(3px)}.page-sidebar .nav-group .nav-item .pin:hover,.page-sidebar .nav-group .nav-item .pin:focus-visible{color:var(--surfaceTxtColor)}.page-sidebar .nav-group .nav-item:hover .pin,.page-sidebar .nav-group .nav-item:focus-visible .pin,.page-sidebar .nav-group .nav-item:active .pin,.page-sidebar .nav-group .nav-item .pin:hover,.page-sidebar .nav-group .nav-item .pin:focus-visible,.page-sidebar .nav-group .nav-item .pin:active,.page-sidebar .nav-group .nav-item .pin.active{opacity:1;transform:translate(0)}.page-sidebar .nav-group .nav-item:hover,.page-sidebar .nav-group .nav-item:focus-visible,.page-sidebar .nav-group .nav-item:active,.page-sidebar .nav-group .nav-item.active{background:var(--surfaceAlt2Color)}.page-sidebar .nav-group .nav-item.active{color:var(--surfaceTxtColor)}.page-sidebar .nav-group .nav-item:active{background:var(--surfaceAlt3Color);transition-duration:var(--activeAnimationSpeed)}.page-sidebar .dragline{position:absolute;top:0;right:0}.page-sidebar[data-responsive=true]{z-index:9;opacity:0;visibility:hidden;height:100%;transition:opacity var(--animationSpeed), visibility var(--animationSpeed), transform var(--animationSpeed);position:absolute;top:0;left:0;transform:translate(-30px)}.page-sidebar[data-responsive=true] .dragline{display:none}.page-sidebar[data-responsive=true].active{opacity:1;visibility:visible;box-shadow:100px 0px 0px 1000px var(--modalOverlayColor);transform:translate(0)}.btn.responsive-sidebar-btn{min-height:var(--smBtnHeight);align-self:stretch;height:auto;margin:0 -5px;padding:0 10px}.btn.responsive-sidebar-btn i{margin-top:2px}.collections-sidebar .sidebar-content.collections-list{--topShadowOffset:6px;--bottomShadowOffset:6px;padding-top:5px;padding-bottom:5px}.page-content.full-height{flex-direction:column;align-items:normal;gap:0;display:flex}@supports not (fill:context-stroke){.page-content.full-height{overflow-x:hidden}}.rule-field{min-height:67px}.rule-field .code-editor{z-index:0;min-height:41px}.rule-field .code-editor .editor-content:empty:before{font-family:var(--baseFontFamily)}.rule-field .code-editor .highlight-overlay{color:var(--surfaceTxtColor)}.rule-field .code-editor .editor-content,.rule-field .code-editor .highlight-overlay{white-space:pre-wrap}.rule-field .superuser-toggle,.rule-field .superuser-toggle:last-child:not(.btn){border-radius:0;border-bottom-left-radius:var(--borderRadius);border-top-right-radius:var(--borderRadius)}.rule-field .superuser-toggle{z-index:3;cursor:pointer;-webkit-user-select:none;user-select:none;font-weight:700;line-height:1;font-size:var(--smFontSize);color:var(--surfaceTxtHintColor);background:var(--inputFocusColor);border:1px solid var(--surfaceAlt5Color);transition:color var(--animationSpeed), background var(--animationSpeed);border-top:0;border-right:0;outline:0;align-items:center;gap:5px;padding:8px 10px;display:inline-flex;position:absolute;top:0;right:0}.rule-field .superuser-toggle:hover,.rule-field .superuser-toggle:active,.rule-field .superuser-toggle:focus-visible{color:var(--successColor);background:var(--inputBorderColor)}.rule-field .superuser-toggle[disabled]{cursor:not-allowed;color:var(--surfaceTxtDisabledColor);background:var(--surfaceAlt3Color)}.rule-field .input:focus-within~.superuser-toggle{background:var(--inputBorderColor)}.rule-field label{z-index:2;position:relative}.rule-field label .superusers-label{opacity:1;visibility:visible;transition:opacity var(--animationSpeed), visibility var(--animationSpeed), transform var(--animationSpeed);transform:translate(0)}.rule-field label .superusers-label.hidden,.rule-field label .superusers-label[hidden]{opacity:0;visibility:hidden;transform:translate(3px);display:inline!important}.rule-field .unlock-overlay{z-index:1;cursor:pointer;-webkit-user-select:none;user-select:none;width:100%;height:100%;font-weight:700;font-size:var(--smFontSize);color:var(--successColor);padding:5px var(--smSpacing);border-radius:var(--borderRadius);background:var(--surfaceAlt1Color);border:2px solid var(--surfaceAlt2Color);transition:border-color var(--animationSpeed);justify-content:flex-end;align-items:center;gap:10px;display:flex;position:absolute;top:0;left:0}.rule-field .unlock-overlay .txt{opacity:0;transition:opacity var(--animationSpeed), transform var(--animationSpeed);transform:translate(3px)}.rule-field .unlock-overlay i{font-size:1.4em}.rule-field .unlock-overlay:hover,.rule-field .unlock-overlay:active,.rule-field .unlock-overlay:focus-visible{border-color:var(--surfaceAlt4Color)}:is(.rule-field .unlock-overlay:hover,.rule-field .unlock-overlay:active,.rule-field .unlock-overlay:focus-visible) .txt{opacity:1;transform:translate(0)}.rule-field .unlock-overlay:active{border-color:var(--surfaceAlt5Color);transition-duration:var(--activeAnimationSpeed)}.rule-field .unlock-overlay[disabled]{cursor:not-allowed}.rule-field .unlock-overlay[disabled] .txt{display:none}.rule-field.locked label{pointer-events:none}.rate-limit-table{background:0 0}.rate-limit-table tr,.rate-limit-table td,.rate-limit-table th{background:0 0;border:0;height:auto;min-height:0;padding:0}.rate-limit-table th{font-size:var(--smFontSize);line-height:var(--smLineHeight);padding:0 0 10px}.rate-limit-table .rate-limit-row .fields,.rate-limit-table .rate-limit-row .field,.rate-limit-table .rate-limit-row input,.rate-limit-table .rate-limit-row select,.rate-limit-table .rate-limit-row .input,.rate-limit-table .rate-limit-row .select{background:0 0;border-radius:0;height:100%}.rate-limit-table .rate-limit-row td{background:var(--inputColor);border-left:1px solid var(--inputBorderColor);border-top:1px solid var(--inputBorderColor);transition:background var(--animationSpeed)}.rate-limit-table .rate-limit-row td:first-child{border-left:0}.rate-limit-table .rate-limit-row td:focus-within{background:var(--inputFocusColor)}.rate-limit-table .rate-limit-row td:has(.error:not(.hidden)){background:var(--surfaceDangerColor)}.rate-limit-table .rate-limit-row:first-child td{border-top:0}.rate-limit-table .rate-limit-row:first-child td:first-child{border-top-left-radius:var(--borderRadius)}.rate-limit-table .rate-limit-row:first-child td:last-child{border-top-right-radius:var(--borderRadius)}.rate-limit-table .rate-limit-row:last-child td:first-child{border-bottom-left-radius:var(--borderRadius)}.rate-limit-table .rate-limit-row:last-child td:last-child{border-bottom-right-radius:var(--borderRadius)}.rate-limit-table td.col-new-btn{padding:3px}.rate-limit-table td.col-new-btn:focus-within{background:var(--inputColor)}.rate-limit-table .col-label{width:60%}.rate-limit-table .col-requests,.rate-limit-table .col-duration{width:15%}.rate-limit-table .col-audience{white-space:nowrap;width:1px;min-width:130px}.rate-limit-table .col-audience .selected-container{flex-wrap:nowrap}.rate-limit-table .col-action{width:1px;min-width:0;padding:0 3px}.rate-limit-table-wrapper{overflow:auto}.rate-limit-table-wrapper .rate-limit-table{min-width:550px}.export-panel{background:var(--surfaceAlt1Color);border-radius:var(--borderRadius);align-items:stretch;width:100%;height:550px;display:flex}.export-panel .export-list{gap:var(--smSpacing);scrollbar-width:thin;width:230px;min-height:0;padding:var(--smSpacing);word-break:break-word;flex-direction:column;flex-shrink:0;display:flex;overflow:auto}.export-panel .export-list .list-item{width:100%;margin:0}.export-panel .export-preview{flex-grow:1;position:relative;overflow:hidden}.export-panel .export-preview .ctrls{right:var(--smSpacing);top:calc(var(--smSpacing) - 5px);align-items:center;gap:10px;display:flex;position:absolute}.export-panel .export-preview .code-wrapper{width:100%;height:100%}.export-panel .export-preview .code-wrapper code{white-space:pre;background:0 0;border-radius:0;height:100%}.export-panel>*{border-left:1px solid var(--surfaceAlt2Color)}.export-panel>:first-child{border-top-left-radius:var(--borderRadius);border-bottom-left-radius:var(--borderRadius);border-left:0}.export-panel>:last-child{border-top-right-radius:var(--borderRadius);border-bottom-right-radius:var(--borderRadius)}@media (width<=700px){.export-panel{flex-flow:wrap;height:auto}.export-panel>*{border:0;border-top:1px solid var(--surfaceAlt2Color);border-radius:0}.export-panel>:first-child{border-top-left-radius:var(--borderRadius);border-top-right-radius:var(--borderRadius);border-top:0}.export-panel>:last-child{border-bottom-left-radius:var(--borderRadius);border-bottom-right-radius:var(--borderRadius)}.export-panel .export-list{width:100%;max-height:200px}.export-panel .export-preview{max-height:300px}}.import-change-label{min-width:65px}.collections-diff-table-title{width:100%;margin:0 0 var(--smSpacing);flex-wrap:wrap;align-items:center;gap:10px;display:flex}.collections-diff-table-title .import-change-label{font-weight:700}.collections-diff-table{color:var(--surfaceTxtHintColor);border-radius:var(--borderRadius)}.collections-diff-table tr{background:0 0}.collections-diff-table th,.collections-diff-table td{height:auto;padding:2px 10px}.collections-diff-table th{color:var(--surfaceTxtColor);padding:4px 10px}.collections-diff-table thead tr{background:var(--surfaceAlt2Color)}.collections-diff-table thead tr th{background:0 0}.collections-diff-table .changed-none-col{color:var(--surfaceTxtDisabledColor);background:var(--surfaceAlt1Color)}.collections-diff-table .changed-old-col{color:var(--surfaceTxtColor);background:var(--surfaceDangerColor)}.collections-diff-table .changed-new-col{color:var(--surfaceTxtColor);background:var(--surfaceSuccessColor)}.collections-diff-table .field-key-col{padding-left:var(--spacing);white-space:nowrap}.collections-diff-table .diff-value{white-space:break-spaces}.collections-diff-table tbody th{background:var(--surfaceAlt1Color)}.collections-diff-table-wrapper+.collections-diff-table-wrapper{margin-top:var(--spacing)}.logs-chart{text-align:center;touch-action:none;width:100%;height:0;max-height:0;transition:opacity var(--animationSpeed), max-height var(--slideAnimationSpeed), height var(--slideAnimationSpeed);flex-shrink:0;align-content:center;margin:0;display:block;position:relative;overflow:hidden;transform:translateZ(0)}.logs-chart .chart-tooltip{width:;z-index:3;white-space:nowrap;text-align:center;font-size:var(--smFontSize);border-radius:var(--borderRadius);color:var(--tooltipTxtColor);background:var(--tooltipSurfaceColor);pointer-events:none;box-shadow:var(--boxShadow);padding:5px;line-height:1;position:absolute}.logs-chart .chart-tooltip .content-primary{font-weight:700}.logs-chart .chart-tooltip .content-secondary{opacity:.6;margin:3px 0 1px;font-size:.85em}.logs-chart:has(.uplot){height:auto;max-height:180px}.logs-chart .uplot{padding-bottom:var(--smSpacing)}.logs-chart.loading{opacity:.7}.logs-chart.pending,.logs-chart.empty-list:not(.zoomed),.logs-chart.nodata:not(.nonempty-list){height:0;max-height:0}.logs-chart.zoomed .u-over+.u-axis{cursor:ew-resize}.logs-reset-zoom-ctrl{z-index:2;vertical-align:top;text-align:center;cursor:pointer;opacity:.7;font-weight:700;font-size:var(--smFontSize);border-radius:var(--borderRadius);color:var(--surfaceTxtColor);background:var(--surfaceAlt3Color);transition:opacity var(--animationSpeed);outline:0;gap:5px;padding:5px 8px;line-height:1;display:inline-block;position:absolute;top:0;left:50%;transform:translate(-50%)}.logs-reset-zoom-ctrl .content-secondary{margin-top:5px;font-size:.85em;font-weight:400}.logs-reset-zoom-ctrl:hover,.logs-reset-zoom-ctrl:active,.logs-reset-zoom-ctrl:focus-visible{opacity:1}.logs-chart-loader{z-index:1;position:absolute;top:50%;left:50%;transform:translate(-50%,-80%)}.logs-chart-container{width:100%;display:block}.logs-table .col-field-name-level{white-space:nowrap;width:1px}.logs-table .col-field-name-message{word-break:break-word}.logs-table .col-field-name-message .content-primary{-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.logs-table .col-field-name-message .content-secondary{flex-wrap:wrap;gap:10px;margin-top:7px;display:flex}.logs-table .log-request .col-field-name-message .content-primary{word-break:break-all}.logs-include-superuser-requests{width:150px}.logs-include-superuser-requests .txt{margin-top:-3px;line-height:1}.log-level-label{justify-content:center;min-width:80px;font-size:.8em;font-weight:700}.log-level-label:before{content:"";background:var(--surfaceAlt5Color);border-radius:6px;width:6px;height:6px}.log-level-label.level-0:before{background:var(--successColor)}.log-level-label.level-4:before{background:var(--warningColor)}.log-level-label.level-8:before{background:var(--dangerColor)}.page-logs .page-content{padding-top:0}.page-logs .page-header{margin:10px 0}@media (width<=750px){.page-header .logs-searchbar{order:99;width:100%}}@media (width<=600px){.page-table-wrapper .logs-table{table-layout:fixed;width:100%;min-width:0}.page-table-wrapper .logs-table thead{display:none}.page-table-wrapper .logs-table tbody tr{border-top:1px solid var(--surfaceAlt2Color);flex-wrap:wrap;width:100%;padding:10px 0;display:flex}.page-table-wrapper .logs-table tbody td{height:auto;padding:5px var(--spacing);white-space:normal;border:0;width:100%;min-height:0;display:block;background:0 0!important}.page-table-wrapper .logs-table tbody td.col-bulk-select{width:auto;padding:0 0 0 var(--spacing);align-content:center}.page-table-wrapper .logs-table tbody td.col-field-name-level{flex-grow:1;width:auto;padding-left:0}.page-table-wrapper .logs-table .log-level-label{width:100%}.page-table-wrapper .logs-table .col-field-name-message .content-secondary{gap:5px}.page-table-wrapper .logs-table .col-field-name-message .content-secondary .label{word-break:break-word}.page-table-wrapper .logs-table .col-meta{display:none}.page-table-wrapper .logs-table~.bulkbar{margin-left:auto;margin-right:auto;position:sticky;left:0;transform:none!important}}.map-container{flex-direction:column;width:100%;height:100%;min-height:200px;display:flex;position:relative}.map-box{z-index:1;flex-grow:1;width:100%;height:100%;min-height:0}.map-search{--searchWidth:400px;z-index:2;top:10px;left:calc((100% - var(--searchWidth)) / 2);height:auto;width:var(--searchWidth);opacity:.8;max-width:90%;transition:opacity var(--animationSpeed);position:absolute}.map-search input{background:inherit}.map-search .field{box-shadow:var(--boxShadow);background:var(--surfaceColor)!important}.map-search .dropdown{border:0;max-height:180px}.map-search:focus-within{opacity:1}.record-summary{flex-shrink:1;justify-content:flex-start;padding:3px 5px}.record-summary .thumb{--thumbSize:28px;border-width:1px;font-size:75%}.record-summary i{font-size:1em}.record-summary .record-preview-icon{align-content:center;height:100%;margin:0 -5px;padding:0 5px}.record-summary .record-summary .thumb{--thumbSize:24px}.record-summary .record-summary .record-summary .thumb{--thumbSize:20px}.field-content .record-summary{border-width:1px}.record-summary .record-field-view{vertical-align:top;display:inline-block}td .record-summary .field-type-text,td .record-summary .field-type-editor,td .record-summary .field-type-json{max-width:250px}.record-summary-dropdown{font-family:var(--monospaceFontFamily);font-size:var(--smFontSize);line-height:var(--smLineHeight);width:100%;max-width:350px;max-height:305px;min-width:anchor-size(width);-webkit-user-select:text;user-select:text;cursor:default}.record-summary-dropdown[popover]{position-area:bottom span-right;position-try-fallbacks:flip-block}.record-summary-dropdown .record-header{width:100%;font-family:var(--baseFontFamily);align-items:center;gap:10px;padding:5px;display:flex}.record-summary-dropdown .record-json{scrollbar-width:thin;overflow:auto}.record-file-picker-collection-select-btn{padding-left:var(--smSpacing);padding-right:calc(var(--smSpacing) - 5px);white-space:nowrap;width:27%}.record-file-picker-collection-select-btn .collection-name{white-space:nowrap;text-overflow:ellipsis;max-width:100%;overflow:hidden}.record-file-picker-thumb-select{min-width:170px}.record-file-picker-list{grid-gap:var(--smSpacing);grid-template-columns:1fr 1fr 1fr 1fr 1fr 1fr 1fr;display:grid}.record-file-picker-list .list-item{--thumbSize:100%;border-width:2px}@media only screen and (width<=700px){.record-file-picker-list{grid-template-columns:1fr 1fr 1fr 1fr 1fr}}@media only screen and (width<=450px){.record-file-picker-list{grid-template-columns:1fr 1fr 1fr}}.record-file-picker-modal{width:var(--wrapperWidth)}@media only screen and (width<=550px){.record-file-picker-modal .modal-header{flex-wrap:wrap}.record-file-picker-modal .record-file-picker-collection-select-btn{flex-grow:1}.record-file-picker-modal .records-searchbar-wrapper{flex-grow:1;order:99}.record-file-picker-modal .record-file-picker-thumb-select{min-width:100px}.record-file-picker-modal .modal-footer .btn.expanded,.record-file-picker-modal .modal-footer .btn.expanded-sm,.record-file-picker-modal .modal-footer .btn.expanded-lg{min-width:50px}}.records-picker-list{max-height:400px}.records-picker-selected-list{flex-wrap:wrap;align-items:stretch;gap:10px;max-width:100%;display:flex}.records-picker-modal .modal-header .collection-name{text-overflow:ellipsis;white-space:nowrap;flex-shrink:0;min-width:0;max-width:250px;overflow:hidden}@media only screen and (width<=550px){.records-picker-modal .modal-header{flex-wrap:wrap}.records-picker-modal .modal-header .collection-name{flex-grow:1;flex-shrink:1;max-width:80%;margin-right:auto}.records-picker-modal .searchbar{flex-grow:1;order:99}}.accordion.record-field-settings{--fieldsGap:10px;border-color:var(--surfaceAlt4Color)}.accordion.record-field-settings summary{align-items:center;gap:var(--fieldsGap);background:var(--inputColor);min-height:0;padding:4px;display:flex;container-type:inline-size}.accordion.record-field-settings summary .header-fields{gap:var(--fieldsGap);align-content:center;align-items:stretch;width:100%;display:flex}.accordion.record-field-settings summary .header-fields .header-select{max-width:150px}.accordion.record-field-settings summary .header-fields>*{position:relative}.accordion.record-field-settings summary .header-fields>*:before{content:"";background:var(--surfaceAlt4Color);flex-shrink:0;width:1px;height:auto;min-height:0;position:absolute;top:-4px;bottom:-4px;right:-5px}.accordion.record-field-settings summary .btn i.ri-settings-3-line{transition:color var(--animationSpeed), transform var(--animationSpeed)}.accordion.record-field-settings summary .sort-handle{z-index:2;text-align:left;cursor:pointer;width:30px;height:100%;color:var(--surfaceTxtHintColor);opacity:0;transition:color var(--animationSpeed), opacity var(--animationSpeed), transform var(--animationSpeed);flex-shrink:0;align-content:center;padding-left:7px;position:absolute;top:0;left:-30px;transform:translate(3px)}.accordion.record-field-settings summary .sort-handle:hover,.accordion.record-field-settings summary .sort-handle:active{opacity:1;color:var(--surfaceTxtColor);transform:translate(0)}@container (width<=480px){.accordion.record-field-settings summary .header-fields{border-radius:var(--borderRadius);background:var(--surfaceColor);flex-direction:column;gap:5px;padding:5px}.accordion.record-field-settings summary .header-fields>*{flex-grow:1;max-width:none!important}.accordion.record-field-settings summary .header-fields>*:before{content:none;display:none}}.accordion.record-field-settings:hover summary .sort-handle{opacity:1;transform:translate(0)}.accordion.record-field-settings .record-field-settings-footer{align-items:center;gap:var(--smSpacing);margin-top:var(--smSpacing);flex-wrap:wrap;display:flex}.accordion.record-field-settings .record-field-settings-footer>.field,.accordion.record-field-settings .record-field-settings-footer>.fields{width:auto}.accordion.record-field-settings summary+.record-field-settings-footer{margin-top:0}.accordion.record-field-settings .field-labels{z-index:2;pointer-events:none;-webkit-user-select:none;user-select:none;align-items:center;gap:2px;display:flex;position:absolute;top:2px;right:2px}.accordion.record-field-settings .field-labels .label{min-height:0;padding:2px 3px;font-size:.7em;line-height:1.2}.accordion.record-field-settings[open]{box-shadow:0px 0px 0px 1px var(--surfaceAlt3Color)}.accordion.record-field-settings[open] summary{outline:0}.accordion.record-field-settings[open] summary .btn i.ri-settings-3-line{color:var(--surfaceTxtColor);transform:rotate(-30deg)}.accordion.record-field-settings.deleted summary{background:var(--surfaceAlt1Color)}.accordion.record-field-settings.deleted summary .field,.accordion.record-field-settings.deleted summary .fields{background:0 0!important}.accordion.record-field-settings.deleted .header-fields{opacity:.7}.accordion.record-field-settings.deleted .header-fields>:after{background:var(--surfaceAlt2Color)}.header-select.field-select-choices-input,.header-select.collections-select{min-width:200px}.record-field-view{vertical-align:top;text-align:left;align-content:center;min-width:0;max-width:100%;display:block}.record-field-view:empty{display:none!important}.record-field-view .missing-value{vertical-align:top;color:var(--surfaceTxtDisabledColor);font-size:var(--smFontSize);align-content:center;display:inline-block}.record-field-view .missing-value:before{content:"N/A"}.record-field-view .delimiter{line-height:1;font-size:var(--smFontSize);color:var(--surfaceTxtDisabledColor)}.record-field-view .marker{line-height:1;font-size:var(--smFontSize);color:var(--surfaceTxtHintColor);align-content:center;padding-right:5px}.field-type-bool.record-field-view{min-width:auto}.field-type-bool.record-field-view .label{min-width:48px}.field-type-json.record-field-view{white-space:pre-wrap;scrollbar-width:thin;max-height:400px;overflow:auto}.field-type-json .input.code-editor{min-height:38px}.field-type-json .json-state{position:absolute;top:10px;right:10px}.field-type-geoPoint.record-field-view{white-space:nowrap}.field-type-geoPoint.record-field-input .field-list-item label:after{content:none;display:none}.more-marker{color:var(--surfaceTxtHintColor);align-self:flex-end}.field-type-select .field-select-choices-input>input{font-size:var(--smFontSize)}.field-type-select .field-select-choices-dropdown{max-height:400px}.field-type-select .field-select-choices-dropdown textarea{font-size:var(--smFontSize);max-height:300px}:is(.field-type-select,.field-type-file).record-field-view{flex-wrap:wrap;align-items:center;gap:5px;min-width:auto;display:inline-flex}.field-type-editor.record-field-view{width:100%}.record-summary .field-type-editor.record-field-view{width:auto}.field-type-editor.record-field-input .field{min-height:296px}.field-type-editor.record-field-input .loading-textarea{resize:none}.field-type-editor.record-field-input .pb-tinymce{animation:fadeIn var(--animationSpeed)}.field-type-relation.record-field-view{flex-wrap:wrap;align-items:stretch;gap:10px;display:inline-flex}.record-summary .field-type-relation.record-field-view{gap:5px}:is(.field-type-autodate,.field-type-date).record-field-view{flex-shrink:0}.hidden-field-blur{filter:blur(2.5px);transition:filter var(--animationSpeed)}.hidden-field-blur:hover,.hidden-field-blur:active,.hidden-field-blur:focus-within{filter:none;transition-delay:0s}.records-list-columns-dropdown{max-height:230px}.record-upsert-modal{width:720px}.record-upsert-modal .modal-title{min-height:var(--smBtnHeight);align-content:center}.record-upsert-modal .modal-content{scrollbar-gutter:stable}:is(.record-upsert-modal,.record-preview-modal):has(.large-modal){width:832px}.collection-upsert-modal{width:720px}.collection-upsert-modal .modal-content{scrollbar-gutter:stable both-edges}.collection-upsert-modal .modal-title{min-height:var(--smBtnHeight)}.collection-upsert-modal .modal-title .collection-name{max-width:220px}.new-collection-field-btn-wrapper{margin:var(--smSpacing) 0}.field-types-dropdown{border:2px solid var(--primaryColor);flex-flow:wrap;gap:0;margin:-5px 0}.field-types-dropdown .dropdown-item{width:25%;margin:2px 0;padding:10px}@media (width<=600px){.field-types-dropdown .dropdown-item{width:50%}}.indexes-list{flex-wrap:wrap;gap:10px;display:flex}.code-editor.collection-index-input{min-height:101px}.query-caveats-dropdown{width:300px;padding:10px}.query-caveats-dropdown ul{margin:0;padding-left:20px}.query-state{position:absolute;top:10px;right:10px}.collection-changes-list{margin:10px 0}.collection-changes-list li{line-height:var(--smLineHeight);align-content:center;margin:10px 0}.collection-changes-list li i{display:inline-block}.collection-changes-list li .label{vertical-align:baseline}.collection-changes-list .rule-content{font-family:var(--monospaceFontFamily);font-size:var(--smFontSize);line-height:var(--smLineHeight);white-space:pre-wrap;text-align:left;margin:5px 0}.collection-changes-list .rule-content.old-rule{color:var(--surfaceTxtHintColor)}.collection-changes-list .rule-content.new-rule{color:var(--successColor)}.oidc-userinfo-options{border:2px solid var(--inputFocusColor);border-radius:var(--borderRadius);padding:var(--smSpacing)}.view-query-sample-wrapper{position:relative}.view-query-sample-wrapper .loader{z-index:1;position:absolute;top:10px;right:10px}.view-query-sample code{z-index:0;border:1px solid var(--surfaceAlt2Color);background:0 0;max-height:300px}.provider-logo{--size:40px;text-align:center;width:var(--size);height:var(--size);line-height:1;font-size:var(--fontSize);color:var(--surfaceTxtDisabledColor);background:var(--surfaceAlt2Color);border-radius:var(--borderRadius);transition:background var(--animationSpeed);flex-shrink:0;justify-content:center;align-items:center;padding:8px;display:flex}.provider-logo:empty:before{content:"";font-family:var(--iconFontFamily)}.provider-logo img{width:100%;height:auto}.handle:hover .provider-logo,.handle:focus-visible .provider-logo{background:var(--surfaceAlt3Color)}.handle.active .provider-logo,.handle:active .provider-logo{transition-duration:var(--activeAnimationSpeed);background:var(--surfaceAlt4Color)}.provider-card{width:100%;min-height:var(--lgBtnHeight);border-radius:var(--borderRadius);border:1px solid var(--surfaceAlt2Color);align-items:center;gap:10px;padding:5px;display:flex}.provider-card .content{word-break:break-word;font-size:var(--fontSize);flex-grow:1;line-height:1.1}.provider-card .content p{margin:5px 0}.provider-card .content p:first-child{margin-top:0}.provider-card .content p:last-child{margin-bottom:0}.provider-card .content .primary-txt,.provider-card .content .secondary-txt{display:block}.provider-card .content .secondary-txt{color:var(--surfaceTxtHintColor);font-size:var(--smFontSize);margin:2px 0}.provider-card.handle{cursor:pointer;-webkit-user-select:none;user-select:none;transition:background var(--animationSpeed);outline:0;text-decoration:none}.provider-card.handle:hover,.provider-card.handle:focus-visible{background:var(--surfaceAlt1Color)}.provider-card.error{border-color:var(--dangerColor);background:var(--surfaceDangerColor)}.btn.add-provider-btn{font-size:var(--fontSize);line-height:var(--lineHeight);height:100%}.api-preview-content{scrollbar-width:thin;padding:calc(var(--smSpacing) + 5px) var(--spacing) calc(var(--spacing) - 5px);padding:calc(var(--spacing) - 5px) var(--spacing) calc(var(--spacing) - 5px);flex-grow:1;overflow:auto}.api-preview-content-header{width:100%;margin:0 0 calc(var(--smSpacing) + 5px);align-items:center;display:flex}.api-preview-content-header .preview-close-btn{margin:-10px 0}.api-preview-sidebar{scrollbar-width:thin;background:var(--surfaceAlt1Color);width:195px;padding:var(--inputPadding);border-right:1px solid var(--surfaceAlt2Color);flex-shrink:0;overflow:auto}.api-preview-nav{flex-direction:column;gap:5px;display:flex}.api-preview-nav hr{margin:var(--inputPadding) 0}.api-preview-nav .nav-item{width:100%;padding:5px var(--inputPadding);cursor:pointer;font-size:var(--lgFontSize);min-height:var(--btnHeight);border-radius:var(--borderRadius);color:var(--surfaceTxtHintColor);transition:color var(--animationSpeed), background var(--animationSpeed);outline:0;align-items:center;gap:5px;display:flex}.api-preview-nav .nav-item:hover,.api-preview-nav .nav-item:focus-visible,.api-preview-nav .nav-item.active{background:var(--surfaceAlt2Color)}.api-preview-nav .nav-item.active{color:var(--surfaceTxtColor)}.api-preview-nav .nav-item:active{background:var(--surfaceAlt3Color);transition-duration:var(--activeAnimationSpeed)}.api-preview-nav .nav-item[disabled]{cursor:not-allowed;color:var(--surfaceTxtDisabledColor);background:0 0}.api-preview-alert{margin:var(--smSpacing) 0;align-items:center;gap:10px;display:flex}.api-preview-alert .method{flex-shrink:0;font-weight:700}.api-preview-alert .path{flex-grow:1}.api-preview-alert .extra{color:var(--surfaceTxtHintColor);text-align:right;max-width:180px;margin:-3px 0 -3px auto;font-size:.85em}.api-preview-table{width:100%;margin:var(--smSpacing) 0}code.filter-op{text-align:center;min-width:30px}.api-preview-modal{width:900px;max-width:95%}.api-preview-modal .modal-content{align-items:stretch;padding:0;display:flex}.api-preview-modal .modal-content:before,.api-preview-modal .modal-content:after{content:none;display:none}@media (width<=550px){.api-preview-modal .modal-content{flex-direction:column}.api-preview-modal .api-preview-content>.content{min-width:550px}.api-preview-modal .api-preview-sidebar{border:0;border-bottom:2px solid var(--surfaceAlt3Color);width:100%;max-height:150px}}.color-picker{border-radius:var(--borderRadius);background:0 0;background:var(--surfaceColor);align-items:stretch;gap:3px;width:100%;min-height:28px;padding:3px;display:flex}.color-picker .predefined-colors-btn{padding:0 3px}.color-picker .color-picker-input-wrapper{flex-grow:1;height:auto;display:block;position:relative}.color-picker .color-picker-input-wrapper input[type=color]{-webkit-appearance:none;cursor:cell;border-radius:var(--borderRadius);border:0;width:100%;height:100%;margin:0;padding:0}.color-picker .color-picker-input-wrapper input[type=color]::-webkit-color-swatch-wrapper{padding:0}.color-picker .color-picker-input-wrapper input[type=color]::-webkit-color-swatch{border:none;border-radius:0}.color-picker .color-picker-input-wrapper .result{text-align:center;width:100%;height:100%;font-size:.85rem;line-height:1;font-family:var(--monospaceFontFamily);pointer-events:none;white-space:nowrap;text-overflow:ellipsis;align-content:center;padding:0 2px;position:absolute;top:0;left:0;overflow:hidden}.predefined-colors-list{-webkit-user-select:none;user-select:none;flex-wrap:wrap;gap:5px;display:flex}.predefined-colors-list .color{cursor:pointer;border-radius:var(--borderRadius);width:22px;height:22px;transition:opacity var(--animationSpeed);flex-shrink:0}.predefined-colors-list .color:focus-visible{outline-color:var(--surfaceAlt5Color)}.predefined-colors-list .color:hover,.predefined-colors-list .color:active{opacity:.8}.predefined-colors-list .color:active{opacity:.7;transition-duration:var(--activeAnimationSpeed)}.predefined-colors-dropdown{width:auto;min-width:25px;max-width:170px}.field .color-picker{width:auto;margin:4px}.field label~.color-picker{margin-top:6px}.modal.collections-overview-modal{width:1500px;max-width:90%;min-height:90%;max-height:90%;transform:translateY(3px)}.modal.collections-overview-modal[data-modal-state=open]{transform:translateY(0)}@starting-style{.modal.collections-overview-modal[data-modal-state=open]{transform:translateY(3px)}}.modal.collections-overview-modal .modal-header{padding-top:var(--smSpacing)}.modal.collections-overview-modal .modal-content.erd-tab{align-items:stretch;padding:0;display:flex}.modal.collections-overview-modal .modal-content.erd-tab:before,.modal.collections-overview-modal .modal-content.erd-tab:after{content:none;display:none}.modal.collections-overview-modal .modal-content.rules-tab{scrollbar-gutter:stable;padding-top:0}.modal.collections-overview-modal .rules-table{word-break:break-word;min-width:700px;margin-top:calc(var(--spacing) - 5px)}.modal.collections-overview-modal .rules-table .collection-name{text-overflow:ellipsis;width:200px;overflow:hidden}.modal.collections-overview-modal .rules-table thead{top:-3px}.modal.collections-overview-modal .rule-btns{gap:5px;width:100%;display:flex}.modal.collections-overview-modal .rule-btns .btn{flex:1 1 0;padding:5px}.modal.collections-overview-modal .rule-btns .btn.outline{padding:3px}@media (width<=700px){.modal.collections-overview-modal{max-width:95%;min-height:95%;max-height:95%}.modal.collections-overview-modal .rules-table .collection-name{max-width:130px}}.erd-table-row{width:100%;min-height:var(--smBtnHeight);background:var(--surfaceColor);color:var(--surfaceTxtColor);border:0;border-top:1px solid var(--surfaceAlt2Color);align-items:center;gap:5px;padding:2px 5px;font-weight:400;display:flex}.erd-table-row .txt-ellipsis{min-width:0}.erd-table-row.header{color:inherit;background:inherit;text-align:center;border:0;justify-content:center;font-weight:700}.erd-table-row.type-relation{background:var(--surfaceAlt1Color)}.erd-table-row:first-child,.erd-table-row.header+.erd-table-row{border-top-left-radius:var(--borderRadius);border-top-right-radius:var(--borderRadius);border-top:0}.erd-table-row:last-child{border-bottom-left-radius:var(--borderRadius);border-bottom-right-radius:var(--borderRadius)}.erd-table-row i{font-size:1em}.erd-table-row .field-icon{color:var(--surfaceTxtHintColor)}.erd-table-row .field-name{vertical-align:top;white-space:nowrap;text-overflow:ellipsis;flex-shrink:1;min-width:10px;max-width:100%;display:inline-block;overflow:hidden}.erd-table-row .field-hidden-label{min-height:0;padding:3px;font-size:.78em;line-height:1}.erd-table-row .meta{white-space:nowrap;color:var(--surfaceTxtHintColor);flex-grow:0;flex-shrink:0;margin-left:auto;font-size:.9em;display:flex}.erd-table{z-index:2;text-align:left;-webkit-user-select:none;user-select:none;word-break:break-word;background:var(--surfaceAlt4Color);width:200px;color:var(--surfaceTxtHintColor);border-radius:var(--borderRadius);font-size:var(--smFontSize);line-height:var(--smLineHeight);transition:opacity var(--animationSpeed), background var(--animationSpeed), color var(--animationSpeed), box-shadow var(--animationSpeed);padding:2px;display:block;position:relative}.erd-table.active{color:var(--surfaceTxtColor);background:var(--surfaceAlt5Color)}.erd-table.system{color:var(--dangerTxtColor);background:var(--dangerColor)}.erd-table.active,.erd-table:hover{box-shadow:var(--boxShadow)}.erd{cursor:grab;background-image:radial-gradient(var(--surfaceAlt2Color) 1px, transparent 1px);border-radius:var(--borderRadius);touch-action:none;background-size:20px 20px;width:100%;height:auto;min-height:300px;display:block;position:relative;overflow:hidden}.erd .erd-area{-webkit-user-select:none;user-select:none;transform-origin:0 0;position:relative;top:0;left:0}.erd .erd-table{position:absolute;top:0;left:0}.erd .erd-paths{--pathColor:var(--surfaceAlt2Color);z-index:0;pointer-events:none;width:2000px;height:8000px;position:absolute;top:0;left:0}.erd .paths-group{fill:none;stroke-width:2px;stroke:var(--pathColor)}.erd .erd-paths.front{--pathColor:var(--surfaceTxtDisabledColor);z-index:2}.erd .erd-paths.front .active-from{stroke:var(--surfaceTxtHintColor)}.erd .erd-nav{z-index:3;right:var(--smSpacing);bottom:var(--smSpacing);flex-direction:column;align-items:center;gap:10px;display:flex;position:absolute}.erd.updating .erd-paths,.erd.updating .erd-table{opacity:0}.erd.panning{cursor:grabbing}.erd.active .erd-table{opacity:.7}.erd.active .erd-table.active{opacity:1}.erd.active .erd-paths.back{opacity:.3}.app.hide-controls .btn-collection-settings,.app.hide-controls .sidebar-content.new-collection,.app.hide-controls .settings-sidebar [data-group=Sync]{display:none}.field.sql-console-field .input{resize:vertical;height:250px;min-height:150px;max-height:400px}@media (height<=650px){.field.sql-console-field .input{height:150px}}.sql-console-history-dropdown{width:450px}.sql-console-history-dropdown.no-items{width:300px}.sql-console-history-dropdown .dropdown-item{font-size:.9em;line-height:1.2;font-family:var(--monospaceFontFamily);gap:5px;padding:5px}.sql-console-history-dropdown .dropdown-item .query{-webkit-line-clamp:3;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.leaflet-pane,.leaflet-tile,.leaflet-marker-icon,.leaflet-marker-shadow,.leaflet-tile-container,.leaflet-pane>svg,.leaflet-pane>canvas,.leaflet-zoom-box,.leaflet-image-layer,.leaflet-layer{position:absolute;top:0;left:0}.leaflet-container{overflow:hidden}.leaflet-tile,.leaflet-marker-icon,.leaflet-marker-shadow{-webkit-user-select:none;user-select:none;-webkit-user-drag:none}.leaflet-tile::selection{background:0 0}.leaflet-safari .leaflet-tile{image-rendering:-webkit-optimize-contrast}.leaflet-safari .leaflet-tile-container{-webkit-transform-origin:0 0;width:1600px;height:1600px}.leaflet-marker-icon,.leaflet-marker-shadow{display:block}.leaflet-container .leaflet-overlay-pane svg{max-width:none!important;max-height:none!important}.leaflet-container .leaflet-marker-pane img,.leaflet-container .leaflet-shadow-pane img,.leaflet-container .leaflet-tile-pane img,.leaflet-container img.leaflet-image-layer,.leaflet-container .leaflet-tile{width:auto;padding:0;max-width:none!important;max-height:none!important}.leaflet-container img.leaflet-tile{mix-blend-mode:plus-lighter}.leaflet-container.leaflet-touch-zoom{-ms-touch-action:pan-x pan-y;touch-action:pan-x pan-y}.leaflet-container.leaflet-touch-drag{-ms-touch-action:pinch-zoom;touch-action:none;touch-action:pinch-zoom}.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom{-ms-touch-action:none;touch-action:none}.leaflet-container{-webkit-tap-highlight-color:transparent}.leaflet-container a{-webkit-tap-highlight-color:#33b5e566}.leaflet-tile{filter:inherit;visibility:hidden}.leaflet-tile-loaded{visibility:inherit}.leaflet-zoom-box{box-sizing:border-box;z-index:800;width:0;height:0}.leaflet-overlay-pane svg{-moz-user-select:none}.leaflet-pane{z-index:400}.leaflet-tile-pane{z-index:200}.leaflet-overlay-pane{z-index:400}.leaflet-shadow-pane{z-index:500}.leaflet-marker-pane{z-index:600}.leaflet-tooltip-pane{z-index:650}.leaflet-popup-pane{z-index:700}.leaflet-map-pane canvas{z-index:100}.leaflet-map-pane svg{z-index:200}.leaflet-vml-shape{width:1px;height:1px}.lvml{behavior:url(#default#VML);display:inline-block;position:absolute}.leaflet-control{z-index:800;pointer-events:visiblePainted;pointer-events:auto;position:relative}.leaflet-top,.leaflet-bottom{z-index:1000;pointer-events:none;position:absolute}.leaflet-top{top:0}.leaflet-right{right:0}.leaflet-bottom{bottom:0}.leaflet-left{left:0}.leaflet-control{float:left;clear:both}.leaflet-right .leaflet-control{float:right}.leaflet-top .leaflet-control{margin-top:10px}.leaflet-bottom .leaflet-control{margin-bottom:10px}.leaflet-left .leaflet-control{margin-left:10px}.leaflet-right .leaflet-control{margin-right:10px}.leaflet-fade-anim .leaflet-popup{opacity:0;transition:opacity .2s linear}.leaflet-fade-anim .leaflet-map-pane .leaflet-popup{opacity:1}.leaflet-zoom-animated{transform-origin:0 0}svg.leaflet-zoom-animated{will-change:transform}.leaflet-zoom-anim .leaflet-zoom-animated{-webkit-transition:-webkit-transform .25s cubic-bezier(0,0,.25,1);-moz-transition:-moz-transform .25s cubic-bezier(0,0,.25,1);transition:transform .25s cubic-bezier(0,0,.25,1)}.leaflet-zoom-anim .leaflet-tile,.leaflet-pan-anim .leaflet-tile{transition:none}.leaflet-zoom-anim .leaflet-zoom-hide{visibility:hidden}.leaflet-interactive{cursor:pointer}.leaflet-grab{cursor:-webkit-grab;cursor:-moz-grab;cursor:grab}.leaflet-crosshair,.leaflet-crosshair .leaflet-interactive{cursor:crosshair}.leaflet-popup-pane,.leaflet-control{cursor:auto}.leaflet-dragging .leaflet-grab,.leaflet-dragging .leaflet-grab .leaflet-interactive,.leaflet-dragging .leaflet-marker-draggable{cursor:move;cursor:-webkit-grabbing;cursor:-moz-grabbing;cursor:grabbing}.leaflet-marker-icon,.leaflet-marker-shadow,.leaflet-image-layer,.leaflet-pane>svg path,.leaflet-tile-container{pointer-events:none}.leaflet-marker-icon.leaflet-interactive,.leaflet-image-layer.leaflet-interactive,.leaflet-pane>svg path.leaflet-interactive,svg.leaflet-image-layer.leaflet-interactive path{pointer-events:visiblePainted;pointer-events:auto}.leaflet-container{outline-offset:1px;background:#ddd}.leaflet-container a{color:#0078a8}.leaflet-zoom-box{background:#ffffff80;border:2px dotted #38f}.leaflet-container{font-family:Helvetica Neue,Arial,Helvetica,sans-serif;font-size:.75rem;line-height:1.5}.leaflet-bar{border-radius:4px;box-shadow:0 1px 5px #000000a6}.leaflet-bar a{text-align:center;color:#000;background-color:#fff;border-bottom:1px solid #ccc;width:26px;height:26px;line-height:26px;text-decoration:none;display:block}.leaflet-bar a,.leaflet-control-layers-toggle{background-position:50%;background-repeat:no-repeat;display:block}.leaflet-bar a:hover,.leaflet-bar a:focus{background-color:#f4f4f4}.leaflet-bar a:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.leaflet-bar a:last-child{border-bottom:none;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.leaflet-bar a.leaflet-disabled{cursor:default;color:#bbb;background-color:#f4f4f4}.leaflet-touch .leaflet-bar a{width:30px;height:30px;line-height:30px}.leaflet-touch .leaflet-bar a:first-child{border-top-left-radius:2px;border-top-right-radius:2px}.leaflet-touch .leaflet-bar a:last-child{border-bottom-right-radius:2px;border-bottom-left-radius:2px}.leaflet-control-zoom-in,.leaflet-control-zoom-out{text-indent:1px;font:700 18px Lucida Console,Monaco,monospace}.leaflet-touch .leaflet-control-zoom-in,.leaflet-touch .leaflet-control-zoom-out{font-size:22px}.leaflet-control-layers{background:#fff;border-radius:5px;box-shadow:0 1px 5px #0006}.leaflet-control-layers-toggle{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAQAAAADQ4RFAAACf0lEQVR4AY1UM3gkARTePdvdoTxXKc+qTl3aU5U6b2Kbkz3Gtq3Zw6ziLGNPzrYx7946Tr6/ee/XeCQ4D3ykPtL5tHno4n0d/h3+xfuWHGLX81cn7r0iTNzjr7LrlxCqPtkbTQEHeqOrTy4Yyt3VCi/IOB0v7rVC7q45Q3Gr5K6jt+3Gl5nCoDD4MtO+j96Wu8atmhGqcNGHObuf8OM/x3AMx38+4Z2sPqzCxRFK2aF2e5Jol56XTLyggAMTL56XOMoS1W4pOyjUcGGQdZxU6qRh7B9Zp+PfpOFlqt0zyDZckPi1ttmIp03jX8gyJ8a/PG2yutpS/Vol7peZIbZcKBAEEheEIAgFbDkz5H6Zrkm2hVWGiXKiF4Ycw0RWKdtC16Q7qe3X4iOMxruonzegJzWaXFrU9utOSsLUmrc0YjeWYjCW4PDMADElpJSSQ0vQvA1Tm6/JlKnqFs1EGyZiFCqnRZTEJJJiKRYzVYzJck2Rm6P4iH+cmSY0YzimYa8l0EtTODFWhcMIMVqdsI2uiTvKmTisIDHJ3od5GILVhBCarCfVRmo4uTjkhrhzkiBV7SsaqS+TzrzM1qpGGUFt28pIySQHR6h7F6KSwGWm97ay+Z+ZqMcEjEWebE7wxCSQwpkhJqoZA5ivCdZDjJepuJ9IQjGGUmuXJdBFUygxVqVsxFsLMbDe8ZbDYVCGKxs+W080max1hFCarCfV+C1KATwcnvE9gRRuMP2prdbWGowm1KB1y+zwMMENkM755cJ2yPDtqhTI6ED1M/82yIDtC/4j4BijjeObflpO9I9MwXTCsSX8jWAFeHr05WoLTJ5G8IQVS/7vwR6ohirYM7f6HzYpogfS3R2OAAAAAElFTkSuQmCC);width:36px;height:36px}.leaflet-retina .leaflet-control-layers-toggle{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAA0CAQAAABvcdNgAAAEsklEQVR4AWL4TydIhpZK1kpWOlg0w3ZXP6D2soBtG42jeI6ZmQTHzAxiTbSJsYLjO9HhP+WOmcuhciVnmHVQcJnp7DFvScowZorad/+V/fVzMdMT2g9Cv9guXGv/7pYOrXh2U+RRR3dSd9JRx6bIFc/ekqHI29JC6pJ5ZEh1yWkhkbcFeSjxgx3L2m1cb1C7bceyxA+CNjT/Ifff+/kDk2u/w/33/IeCMOSaWZ4glosqT3DNnNZQ7Cs58/3Ce5HL78iZH/vKVIaYlqzfdLu8Vi7dnvUbEza5Idt36tquZFldl6N5Z/POLof0XLK61mZCmJSWjVF9tEjUluu74IUXvgttuVIHE7YxSkaYhJZam7yiM9Pv82JYfl9nptxZaxMJE4YSPty+vF0+Y2up9d3wwijfjZbabqm/3bZ9ecKHsiGmRflnn1MW4pjHf9oLufyn2z3y1D6n8g8TZhxyzipLNPnAUpsOiuWimg52psrTZYnOWYNDTMuWBWa0tJb4rgq1UvmutpaYEbZlwU3CLJm/ayYjHW5/h7xWLn9Hh1vepDkyf7dE7MtT5LR4e7yYpHrkhOUpEfssBLq2pPhAqoSWKUkk7EDqkmK6RrCEzqDjhNDWNE+XSMvkJRDWlZTmCW0l0PHQGRZY5t1L83kT0Y3l2SItk5JAWHl2dCOBm+fPu3fo5/3v61RMCO9Jx2EEYYhb0rmNQMX/vm7gqOEJLcXTGw3CAuRNeyaPWwjR8PRqKQ1PDA/dpv+on9Shox52WFnx0KY8onHayrJzm87i5h9xGw/tfkev0jGsQizqezUKjk12hBMKJ4kbCqGPVNXudyyrShovGw5CgxsRICxF6aRmSjlBnHRzg7Gx8fKqEubI2rahQYdR1YgDIRQO7JvQyD52hoIQx0mxa0ODtW2Iozn1le2iIRdzwWewedyZzewidueOGqlsn1MvcnQpuVwLGG3/IR1hIKxCjelIDZ8ldqWz25jWAsnldEnK0Zxro19TGVb2ffIZEsIO89EIEDvKMPrzmBOQcKQ+rroye6NgRRxqR4U8EAkz0CL6uSGOm6KQCdWjvjRiSP1BPalCRS5iQYiEIvxuBMJEWgzSoHADcVMuN7IuqqTeyUPq22qFimFtxDyBBJEwNyt6TM88blFHao/6tWWhuuOM4SAK4EI4QmFHA+SEyWlp4EQoJ13cYGzMu7yszEIBOm2rVmHUNqwAIQabISNMRstmdhNWcFLsSm+0tjJH1MdRxO5Nx0WDMhCtgD6OKgZeljJqJKc9po8juskR9XN0Y1lZ3mWjLR9JCO1jRDMd0fpYC2VnvjBSEFg7wBENc0R9HFlb0xvF1+TBEpF68d+DHR6IOWVv2BECtxo46hOFUBd/APU57WIoEwJhIi2CdpyZX0m93BZicktMj1AS9dClteUFAUNUIEygRZCtik5zSxI9MubTBH1GOiHsiLJ3OCoSZkILa9PxiN0EbvhsAo8tdAf9Seepd36lGWHmtNANTv5Jd0z4QYyeo/UEJqxKRpg5LZx6btLPsOaEmdMyxYdlc8LMaJnikDlhclqmPiQnTEpLUIZEwkRagjYkEibQErwhkTAKCLQEbUgkzJQWc/0PstHHcfEdQ+UAAAAASUVORK5CYII=);background-size:26px 26px}.leaflet-touch .leaflet-control-layers-toggle{width:44px;height:44px}.leaflet-control-layers .leaflet-control-layers-list,.leaflet-control-layers-expanded .leaflet-control-layers-toggle{display:none}.leaflet-control-layers-expanded .leaflet-control-layers-list{display:block;position:relative}.leaflet-control-layers-expanded{color:#333;background:#fff;padding:6px 10px 6px 6px}.leaflet-control-layers-scrollbar{padding-right:5px;overflow:hidden scroll}.leaflet-control-layers-selector{margin-top:2px;position:relative;top:1px}.leaflet-control-layers label{font-size:1.08333em;display:block}.leaflet-control-layers-separator{border-top:1px solid #ddd;height:0;margin:5px -10px 5px -6px}.leaflet-default-icon-path{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAApCAYAAADAk4LOAAAFgUlEQVR4Aa1XA5BjWRTN2oW17d3YaZtr2962HUzbDNpjszW24mRt28p47v7zq/bXZtrp/lWnXr337j3nPCe85NcypgSFdugCpW5YoDAMRaIMqRi6aKq5E3YqDQO3qAwjVWrD8Ncq/RBpykd8oZUb/kaJutow8r1aP9II0WmLKLIsJyv1w/kqw9Ch2MYdB++12Onxee/QMwvf4/Dk/Lfp/i4nxTXtOoQ4pW5Aj7wpici1A9erdAN2OH64x8OSP9j3Ft3b7aWkTg/Fm91siTra0f9on5sQr9INejH6CUUUpavjFNq1B+Oadhxmnfa8RfEmN8VNAsQhPqF55xHkMzz3jSmChWU6f7/XZKNH+9+hBLOHYozuKQPxyMPUKkrX/K0uWnfFaJGS1QPRtZsOPtr3NsW0uyh6NNCOkU3Yz+bXbT3I8G3xE5EXLXtCXbbqwCO9zPQYPRTZ5vIDXD7U+w7rFDEoUUf7ibHIR4y6bLVPXrz8JVZEql13trxwue/uDivd3fkWRbS6/IA2bID4uk0UpF1N8qLlbBlXs4Ee7HLTfV1j54APvODnSfOWBqtKVvjgLKzF5YdEk5ewRkGlK0i33Eofffc7HT56jD7/6U+qH3Cx7SBLNntH5YIPvODnyfIXZYRVDPqgHtLs5ABHD3YzLuespb7t79FY34DjMwrVrcTuwlT55YMPvOBnRrJ4VXTdNnYug5ucHLBjEpt30701A3Ts+HEa73u6dT3FNWwflY86eMHPk+Yu+i6pzUpRrW7SNDg5JHR4KapmM5Wv2E8Tfcb1HoqqHMHU+uWDD7zg54mz5/2BSnizi9T1Dg4QQXLToGNCkb6tb1NU+QAlGr1++eADrzhn/u8Q2YZhQVlZ5+CAOtqfbhmaUCS1ezNFVm2imDbPmPng5wmz+gwh+oHDce0eUtQ6OGDIyR0uUhUsoO3vfDmmgOezH0mZN59x7MBi++WDL1g/eEiU3avlidO671bkLfwbw5XV2P8Pzo0ydy4t2/0eu33xYSOMOD8hTf4CrBtGMSoXfPLchX+J0ruSePw3LZeK0juPJbYzrhkH0io7B3k164hiGvawhOKMLkrQLyVpZg8rHFW7E2uHOL888IBPlNZ1FPzstSJM694fWr6RwpvcJK60+0HCILTBzZLFNdtAzJaohze60T8qBzyh5ZuOg5e7uwQppofEmf2++DYvmySqGBuKaicF1blQjhuHdvCIMvp8whTTfZzI7RldpwtSzL+F1+wkdZ2TBOW2gIF88PBTzD/gpeREAMEbxnJcaJHNHrpzji0gQCS6hdkEeYt9DF/2qPcEC8RM28Hwmr3sdNyht00byAut2k3gufWNtgtOEOFGUwcXWNDbdNbpgBGxEvKkOQsxivJx33iow0Vw5S6SVTrpVq11ysA2Rp7gTfPfktc6zhtXBBC+adRLshf6sG2RfHPZ5EAc4sVZ83yCN00Fk/4kggu40ZTvIEm5g24qtU4KjBrx/BTTH8ifVASAG7gKrnWxJDcU7x8X6Ecczhm3o6YicvsLXWfh3Ch1W0k8x0nXF+0fFxgt4phz8QvypiwCCFKMqXCnqXExjq10beH+UUA7+nG6mdG/Pu0f3LgFcGrl2s0kNNjpmoJ9o4B29CMO8dMT4Q5ox8uitF6fqsrJOr8qnwNbRzv6hSnG5wP+64C7h9lp30hKNtKdWjtdkbuPA19nJ7Tz3zR/ibgARbhb4AlhavcBebmTHcFl2fvYEnW0ox9xMxKBS8btJ+KiEbq9zA4RthQXDhPa0T9TEe69gWupwc6uBUphquXgf+/FrIjweHQS4/pduMe5ERUMHUd9xv8ZR98CxkS4F2n3EUrUZ10EYNw7BWm9x1GiPssi3GgiGRDKWRYZfXlON+dfNbM+GgIwYdwAAAAASUVORK5CYII=)}.leaflet-container .leaflet-control-attribution{background:#fffc;margin:0}.leaflet-control-attribution,.leaflet-control-scale-line{color:#333;padding:0 5px;line-height:1.4}.leaflet-control-attribution a{text-decoration:none}.leaflet-control-attribution a:hover,.leaflet-control-attribution a:focus{text-decoration:underline}.leaflet-attribution-flag{width:1em;height:.6669em;vertical-align:baseline!important;display:inline!important}.leaflet-left .leaflet-control-scale{margin-left:5px}.leaflet-bottom .leaflet-control-scale{margin-bottom:5px}.leaflet-control-scale-line{white-space:nowrap;box-sizing:border-box;text-shadow:1px 1px #fff;background:#fffc;border:2px solid #777;border-top:none;padding:2px 5px 1px;line-height:1.1}.leaflet-control-scale-line:not(:first-child){border-top:2px solid #777;border-bottom:none;margin-top:-2px}.leaflet-control-scale-line:not(:first-child):not(:last-child){border-bottom:2px solid #777}.leaflet-touch .leaflet-control-attribution,.leaflet-touch .leaflet-control-layers,.leaflet-touch .leaflet-bar{box-shadow:none}.leaflet-touch .leaflet-control-layers,.leaflet-touch .leaflet-bar{background-clip:padding-box;border:2px solid #0003}.leaflet-popup{text-align:center;margin-bottom:20px;position:absolute}.leaflet-popup-content-wrapper{text-align:left;border-radius:12px;padding:1px}.leaflet-popup-content{min-height:1px;margin:13px 24px 13px 20px;font-size:1.08333em;line-height:1.3}.leaflet-popup-content p{margin:1.3em 0}.leaflet-popup-tip-container{pointer-events:none;width:40px;height:20px;margin-top:-1px;margin-left:-20px;position:absolute;left:50%;overflow:hidden}.leaflet-popup-tip{pointer-events:auto;width:17px;height:17px;margin:-10px auto 0;padding:1px;transform:rotate(45deg)}.leaflet-popup-content-wrapper,.leaflet-popup-tip{color:#333;background:#fff;box-shadow:0 3px 14px #0006}.leaflet-container a.leaflet-popup-close-button{text-align:center;color:#757575;background:0 0;border:none;width:24px;height:24px;font:16px/24px Tahoma,Verdana,sans-serif;text-decoration:none;position:absolute;top:0;right:0}.leaflet-container a.leaflet-popup-close-button:hover,.leaflet-container a.leaflet-popup-close-button:focus{color:#585858}.leaflet-popup-scrolled{overflow:auto}.leaflet-oldie .leaflet-popup-content-wrapper{-ms-zoom:1}.leaflet-oldie .leaflet-popup-tip{-ms-filter:"progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";width:24px;filter:progid:DXImageTransform.Microsoft.Matrix(M11=.707107, M12=.707107, M21=-.707107, M22=.707107);margin:0 auto}.leaflet-oldie .leaflet-control-zoom,.leaflet-oldie .leaflet-control-layers,.leaflet-oldie .leaflet-popup-content-wrapper,.leaflet-oldie .leaflet-popup-tip{border:1px solid #999}.leaflet-div-icon{background:#fff;border:1px solid #666}.leaflet-tooltip{color:#222;white-space:nowrap;-webkit-user-select:none;user-select:none;pointer-events:none;background-color:#fff;border:1px solid #fff;border-radius:3px;padding:6px;position:absolute;box-shadow:0 1px 3px #0006}.leaflet-tooltip.leaflet-interactive{cursor:pointer;pointer-events:auto}.leaflet-tooltip-top:before,.leaflet-tooltip-bottom:before,.leaflet-tooltip-left:before,.leaflet-tooltip-right:before{pointer-events:none;content:"";background:0 0;border:6px solid #0000;position:absolute}.leaflet-tooltip-bottom{margin-top:6px}.leaflet-tooltip-top{margin-top:-6px}.leaflet-tooltip-bottom:before,.leaflet-tooltip-top:before{margin-left:-6px;left:50%}.leaflet-tooltip-top:before{border-top-color:#fff;margin-bottom:-12px;bottom:0}.leaflet-tooltip-bottom:before{border-bottom-color:#fff;margin-top:-12px;margin-left:-6px;top:0}.leaflet-tooltip-left{margin-left:-6px}.leaflet-tooltip-right{margin-left:6px}.leaflet-tooltip-left:before,.leaflet-tooltip-right:before{margin-top:-6px;top:50%}.leaflet-tooltip-left:before{border-left-color:#fff;margin-right:-12px;right:0}.leaflet-tooltip-right:before{border-right-color:#fff;margin-left:-12px;left:0}@media print{.leaflet-control{-webkit-print-color-adjust:exact;print-color-adjust:exact}} diff --git a/ui/dist/assets/index-BMShE0dZ.js b/ui/dist/assets/index-DiLDboS9.js similarity index 76% rename from ui/dist/assets/index-BMShE0dZ.js rename to ui/dist/assets/index-DiLDboS9.js index 8e6b8565aa..c7778cfa2c 100644 --- a/ui/dist/assets/index-BMShE0dZ.js +++ b/ui/dist/assets/index-DiLDboS9.js @@ -3,8 +3,8 @@ var e=Object.create,n=Object.defineProperty,r=Object.getOwnPropertyDescriptor,i= `),i=new Blob([r],{type:`text/csv;charset=utf-8;`}),a=window.URL.createObjectURL(i);f.download(a,n)},getApiExampleURL(){let e;if(app.pb.baseURL.startsWith(`http://`)||app.pb.baseURL.startsWith(`https://`))e=app.pb.baseURL;else{e=window.location.href;let n=e.indexOf(`/_/`);e=n>=0?e.substring(0,n):window.location.origin}return e.replace(`//localhost`,`//127.0.0.1`)},isActivePath(e,n=!0,r=``){r||=d.hash;let i;return i=RegExp(n?`^`+RegExp.escape(e)+`\\/?.*$`:`^`+RegExp.escape(e)+`\\/?(?:\\?.+)?$`),i.test(r)},getHashQueryParams(e=``){e||=d.hash;let n=``,r=e.indexOf(`?`);return r>-1&&(n=window.location.hash.substring(r+1)),Object.fromEntries(new URLSearchParams(n))},replaceHashQueryParams(e,n=null){e||={};let r=``,i=window.location.hash,a=i.indexOf(`?`);a>-1&&(r=i.substring(a+1),i=i.substring(0,a));let o=new URLSearchParams(r);for(let n in e){let r=e[n];f.isEmpty(r)?o.delete(n):o.set(n,r)}r=o.toString(),r!=``&&(i+=`?`+r);let s=window.location.href,c=s.indexOf(`#`);c>-1&&(s=s.substring(0,c));let l=s+i;return n===!1||(n===!0?window.history.pushState(null,``,l):window.history.replaceState(null,``,l)),l},getLocalHistory(e,n=null){try{let r=window.localStorage.getItem(e);if(r)return JSON.parse(r)||n}catch(n){console.log(`failed to load local history:`,e,n)}return n},saveLocalHistory(e,n){try{app.utils.isEmpty(n)?window.localStorage.removeItem(e):typeof n==`string`?window.localStorage.setItem(e,n):window.localStorage.setItem(e,JSON.stringify(n))}catch(n){console.log(`failed to save local history:`,e,n)}},generateThumb(e,n=100,r=100){return new Promise(i=>{let a=new FileReader;a.onload=function(a){let o=new Image;o.onload=function(){let a=document.createElement(`canvas`),s=a.getContext(`2d`),c=o.width,l=o.height;return a.width=n,a.height=r,s.drawImage(o,c>l?(c-l)/2:0,0,c>l?l:c,c>l?l:c,0,0,n,r),i(a.toDataURL(e.type))},o.src=a.target.result},a.readAsDataURL(e)})},normalizeSearchFilter(e,n=[]){if(e=(e||``).trim(),!e||!n.length)return e;for(let n of[`=`,`~`,`>`,`<`])if(e.includes(n))return e;return e=isNaN(e)&&e!=`true`&&e!=`false`?e.replace(/^[\"\'\`]|[\"\'\`]$/gm,``):e,n.map(n=>app.pb.filter(`${n}?~{:searchTerm}`,{searchTerm:e})).join(`||`)},logLevels:{[-4]:{label:`DEBUG`,class:``},0:{label:`INFO`,class:`success`},4:{label:`WARN`,class:`warning`},8:{label:`ERROR`,class:`danger`}},logDataFormatters:{execTime:function(e){return e?.data?.execTime===void 0?`N/A`:e.data.execTime+`ms`}},extendStore(e,n={},...r){let i=[];for(let a in n){let o=n[a];typeof e.__raw?.[a]==`function`||typeof o!=`function`||a.length>2&&a.startsWith(`on`)||r.includes(a)?e[a]=o:i.push(watch(o,n=>{e[a]=n}))}return i},cssTimeToMs(e){return e?(e=e.toLowerCase(),e.endsWith(`ms`)?Number(e.substring(0,e.length-2)):e.endsWith(`s`)?Number(e.substring(0,e.length-1)):Number(e)||0):0},isDarkEnoughForWhiteText(e){if(e=e?.startsWith(`#`)?e.substring(1):e,e?.length!=6)return!1;let n=parseInt(e.substring(0,2),16),r=parseInt(e.substring(2,4),16),i=parseInt(e.substring(4,6),16);return(n*299+r*587+i*114)/1e3<128},bulkSelectRange(e,n,r,i){let a=e.findIndex(e=>e.id==r.id);if(a==-1)return;let o=Object.keys(n),s=o[o.length-1],c=e.findIndex(e=>e.id==s);if(c==-1)return;let l=c,u=a;l>u&&([l,u]=[u,l]);for(let r=l;r<=u;r++)i?n[e[r].id]=e[r]:delete n[e[r].id]},imageExtensions:[`.jpg`,`.jpeg`,`.png`,`.svg`,`.gif`,`.jfif`,`.webp`,`.avif`],videoExtensions:[`.mp4`,`.avi`,`.mov`,`.3gp`,`.wmv`],audioExtensions:[`.aa`,`.aac`,`.m4v`,`.mp3`,`.ogg`,`.oga`,`.mogg`,`.amr`],documentExtensions:[`.pdf`,`.doc`,`.docx`,`.xls`,`.xlsx`,`.ppt`,`.pptx`,`.odp`,`.odt`,`.ods`,`.txt`],archiveExtensions:[`.zip`,`.gz`,`.tar`,`.rar`,`.7z`],hasExtension(e,n){return typeof n==`string`&&(n=n.toLowerCase(),!!e?.find(e=>n.endsWith(e)))},hasImageExtension(e){return app.utils.hasExtension(app.utils.imageExtensions,e)},hasVideoExtension(e){return app.utils.hasExtension(app.utils.videoExtensions,e)},hasAudioExtension(e){return app.utils.hasExtension(app.utils.audioExtensions,e)},hasDocumentExtension(e){return app.utils.hasExtension(app.utils.documentExtensions,e)},hasArchiveExtension(e){return app.utils.hasExtension(app.utils.archiveExtensions,e)},getFileType(e){return app.utils.hasImageExtension(e)?`image`:app.utils.hasVideoExtension(e)?`video`:app.utils.hasAudioExtension(e)?`audio`:app.utils.hasDocumentExtension(e)?`document`:app.utils.hasArchiveExtension(e)?`archive`:`file`},fileTypeIcons:{image:`ri-image-line`,video:`ri-movie-line`,audio:`ri-music-2-line`,document:`ri-file-line`,archive:`ri-file-zip-line`,file:`ri-file-line`},fallbackFieldIcon:`ri-puzzle-line`,fallbackCollectionIcon:`ri-puzzle-line`,fallbackProviderIcon:`ri-puzzle-line`,fallbackPresentableProps:[`title`,`name`,`slug`,`email`,`username`,`nickname`,`displayName`,`label`,`subject`,`topic`,`message`,`heading`,`headline`,`header`,`caption`,`key`,`identifier`,`id`],sortedCollections(e=[]){let n,r;function i(e,i){return n=e.name.startsWith(`_`),r=i.name.startsWith(`_`),n&&!r?1:!n&&r?-1:e.name>i.name?1:e.namee?.id&&!a.find(n=>n.id==e.id)),s=a.filter(e=>e?.id&&!i.find(n=>n.id==e.id)),c=a.filter(e=>{let n=app.utils.isObject(e)&&i.find(n=>n.id==e.id);if(!n)return!1;for(let r in n)if(JSON.stringify(e[r])!=JSON.stringify(n[r]))return!0;return!1});return!!(s.length||c.length||r&&o.length)},extractColumnsFromQuery(e){let n=`__PBGROUP__`;e=(e||``).replace(/\([\s\S]+?\)/gm,n).replace(/[\t\r\n]|(?:\s\s)+/g,` `);let r=e.match(/select\s+([\s\S]+)\s+from/)?.[1]?.split(`,`)||[],i=[];for(let e of r){let r=e.trim().split(` `).pop();r!=``&&r!=n&&i.push(r.replace(/[\'\"\`\[\]\s]/g,``))}return i},getAllCollectionIdentifiers(e,n=``){if(!e)return[];let r=[n+`id`],i=e.type==`auth`;if(e.type===`view`)for(let i of app.utils.extractColumnsFromQuery(e.viewQuery))app.utils.pushUnique(r,n+i);let a=e.fields||[];for(let e of a)if(!(e.type==`password`||i&&e.name==`tokenKey`)){if(app.fieldTypes[e.type]?.identifierExtractor){let i=app.utils.toArray(app.fieldTypes[e.type]?.identifierExtractor(e,n));for(let e of i)app.utils.pushUnique(r,e)}else app.utils.pushUnique(r,n+e.name)}return r},getDummyFieldsData(e,n=!1){let r=e?.fields||[],i={};for(let e of r)if(!e.hidden){if(app.fieldTypes[e.type]?.dummyData){let r=app.fieldTypes[e.type].dummyData(e,n);r!==void 0&&(i[e.name]=r)}else i[e.name]=`[[DATA]]`}return i},parseIndex(e){let n={unique:!1,optional:!1,schemaName:``,indexName:``,tableName:``,columns:[],where:``},r=/create\s+(unique\s+)?\s*index\s*(if\s+not\s+exists\s+)?(\S*)\s+on\s+(\S*)\s*\(([\s\S]*)\)(?:\s*where\s+([\s\S]*))?/gim.exec((e||``).trim());if(r?.length!=7)return n;let i=/^[\"\'\`\[\{}]|[\"\'\`\]\}]$/gm;n.unique=r[1]?.trim().toLowerCase()===`unique`,n.optional=!app.utils.isEmpty(r[2]?.trim());let a=(r[3]||``).split(`.`);a.length==2?(n.schemaName=a[0].replace(i,``),n.indexName=a[1].replace(i,``)):(n.schemaName=``,n.indexName=a[0].replace(i,``)),n.tableName=(r[4]||``).replace(i,``);let o=(r[5]||``).replace(/,(?=[^\(]*\))/gim,`{PB_TEMP}`).split(`,`);for(let e of o){e=e.trim().replaceAll(`{PB_TEMP}`,`,`);let r=/^([\s\S]+?)(?:\s+collate\s+([\w]+))?(?:\s+(asc|desc))?$/gim.exec(e);if(r?.length!=4)continue;let a=r[1]?.trim()?.replace(i,``);a&&n.columns.push({name:a,collate:r[2]||``,sort:r[3]?.toUpperCase()||``})}return n.where=r[6]||``,n},buildIndex(e){let n=`CREATE `;e.unique&&(n+=`UNIQUE `),n+=`INDEX `,e.optional&&(n+=`IF NOT EXISTS `),e.schemaName&&(n+=`\`${e.schemaName}\`.`),n+=`\`${e.indexName||`idx_`+app.utils.randomString(10)}\` `,n+=`ON \`${e.tableName}\` (`;let r=e.columns.filter(e=>!!e?.name);return r.length>1&&(n+=` `),n+=r.map(e=>{let n=``;return e.name.includes(`(`)||e.name.includes(` `)?n+=e.name:n+="`"+e.name+"`",e.collate&&(n+=` COLLATE `+e.collate),e.sort&&(n+=` `+e.sort.toUpperCase()),n}).join(`, `),r.length>1&&(n+=` -`),n+=`)`,e.where&&(n+=` WHERE ${e.where}`),n},replaceIndexFields(e,n){let r=app.utils.parseIndex(e);return typeof n==`function`?Object.assign(r,n(r)||{}):Object.assign(r,n||{}),app.utils.buildIndex(r)},replaceIndexColumn(e,n,r){if(n===r)return e;let i=app.utils.parseIndex(e),a=!1;for(let e of i.columns)e.name===n&&(e.name=r,a=!0);return a?app.utils.buildIndex(i):e}};window.app=window.app||{},window.app.utils=f,window.app=window.app||{},window.app.utils=window.app.utils||{},window.app.utils.mimeTypes=[{ext:``,mimeType:`application/octet-stream`},{ext:`.xpm`,mimeType:`image/x-xpixmap`},{ext:`.7z`,mimeType:`application/x-7z-compressed`},{ext:`.zip`,mimeType:`application/zip`},{ext:`.xlsx`,mimeType:`application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`},{ext:`.docx`,mimeType:`application/vnd.openxmlformats-officedocument.wordprocessingml.document`},{ext:`.pptx`,mimeType:`application/vnd.openxmlformats-officedocument.presentationml.presentation`},{ext:`.epub`,mimeType:`application/epub+zip`},{ext:`.apk`,mimeType:`application/vnd.android.package-archive`},{ext:`.jar`,mimeType:`application/jar`},{ext:`.odt`,mimeType:`application/vnd.oasis.opendocument.text`},{ext:`.ott`,mimeType:`application/vnd.oasis.opendocument.text-template`},{ext:`.ods`,mimeType:`application/vnd.oasis.opendocument.spreadsheet`},{ext:`.ots`,mimeType:`application/vnd.oasis.opendocument.spreadsheet-template`},{ext:`.odp`,mimeType:`application/vnd.oasis.opendocument.presentation`},{ext:`.otp`,mimeType:`application/vnd.oasis.opendocument.presentation-template`},{ext:`.odg`,mimeType:`application/vnd.oasis.opendocument.graphics`},{ext:`.otg`,mimeType:`application/vnd.oasis.opendocument.graphics-template`},{ext:`.odf`,mimeType:`application/vnd.oasis.opendocument.formula`},{ext:`.odc`,mimeType:`application/vnd.oasis.opendocument.chart`},{ext:`.sxc`,mimeType:`application/vnd.sun.xml.calc`},{ext:`.pdf`,mimeType:`application/pdf`},{ext:`.fdf`,mimeType:`application/vnd.fdf`},{ext:``,mimeType:`application/x-ole-storage`},{ext:`.msi`,mimeType:`application/x-ms-installer`},{ext:`.aaf`,mimeType:`application/octet-stream`},{ext:`.msg`,mimeType:`application/vnd.ms-outlook`},{ext:`.xls`,mimeType:`application/vnd.ms-excel`},{ext:`.pub`,mimeType:`application/vnd.ms-publisher`},{ext:`.ppt`,mimeType:`application/vnd.ms-powerpoint`},{ext:`.doc`,mimeType:`application/msword`},{ext:`.ps`,mimeType:`application/postscript`},{ext:`.psd`,mimeType:`image/vnd.adobe.photoshop`},{ext:`.p7s`,mimeType:`application/pkcs7-signature`},{ext:`.ogg`,mimeType:`application/ogg`},{ext:`.oga`,mimeType:`audio/ogg`},{ext:`.ogv`,mimeType:`video/ogg`},{ext:`.png`,mimeType:`image/png`},{ext:`.png`,mimeType:`image/vnd.mozilla.apng`},{ext:`.jpg`,mimeType:`image/jpeg`},{ext:`.jxl`,mimeType:`image/jxl`},{ext:`.jp2`,mimeType:`image/jp2`},{ext:`.jpf`,mimeType:`image/jpx`},{ext:`.jpm`,mimeType:`image/jpm`},{ext:`.jxs`,mimeType:`image/jxs`},{ext:`.gif`,mimeType:`image/gif`},{ext:`.webp`,mimeType:`image/webp`},{ext:`.exe`,mimeType:`application/vnd.microsoft.portable-executable`},{ext:``,mimeType:`application/x-elf`},{ext:``,mimeType:`application/x-object`},{ext:``,mimeType:`application/x-executable`},{ext:`.so`,mimeType:`application/x-sharedlib`},{ext:``,mimeType:`application/x-coredump`},{ext:`.a`,mimeType:`application/x-archive`},{ext:`.deb`,mimeType:`application/vnd.debian.binary-package`},{ext:`.tar`,mimeType:`application/x-tar`},{ext:`.xar`,mimeType:`application/x-xar`},{ext:`.bz2`,mimeType:`application/x-bzip2`},{ext:`.fits`,mimeType:`application/fits`},{ext:`.tiff`,mimeType:`image/tiff`},{ext:`.bmp`,mimeType:`image/bmp`},{ext:`.ico`,mimeType:`image/x-icon`},{ext:`.mp3`,mimeType:`audio/mpeg`},{ext:`.flac`,mimeType:`audio/flac`},{ext:`.midi`,mimeType:`audio/midi`},{ext:`.ape`,mimeType:`audio/ape`},{ext:`.mpc`,mimeType:`audio/musepack`},{ext:`.amr`,mimeType:`audio/amr`},{ext:`.wav`,mimeType:`audio/wav`},{ext:`.aiff`,mimeType:`audio/aiff`},{ext:`.au`,mimeType:`audio/basic`},{ext:`.mpeg`,mimeType:`video/mpeg`},{ext:`.mov`,mimeType:`video/quicktime`},{ext:`.mp4`,mimeType:`video/mp4`},{ext:`.avif`,mimeType:`image/avif`},{ext:`.3gp`,mimeType:`video/3gpp`},{ext:`.3g2`,mimeType:`video/3gpp2`},{ext:`.mp4`,mimeType:`audio/mp4`},{ext:`.mqv`,mimeType:`video/quicktime`},{ext:`.m4a`,mimeType:`audio/x-m4a`},{ext:`.m4v`,mimeType:`video/x-m4v`},{ext:`.heic`,mimeType:`image/heic`},{ext:`.heic`,mimeType:`image/heic-sequence`},{ext:`.heif`,mimeType:`image/heif`},{ext:`.heif`,mimeType:`image/heif-sequence`},{ext:`.mj2`,mimeType:`video/mj2`},{ext:`.dvb`,mimeType:`video/vnd.dvb.file`},{ext:`.webm`,mimeType:`video/webm`},{ext:`.avi`,mimeType:`video/x-msvideo`},{ext:`.flv`,mimeType:`video/x-flv`},{ext:`.mkv`,mimeType:`video/x-matroska`},{ext:`.asf`,mimeType:`video/x-ms-asf`},{ext:`.asf`,mimeType:`video/x-ms-wmv`},{ext:`.asf`,mimeType:`video/asf`},{ext:`.aac`,mimeType:`audio/aac`},{ext:`.voc`,mimeType:`audio/x-unknown`},{ext:`.m3u`,mimeType:`application/vnd.apple.mpegurl`},{ext:`.rmvb`,mimeType:`application/vnd.rn-realmedia-vbr`},{ext:`.gz`,mimeType:`application/gzip`},{ext:`.class`,mimeType:`application/x-java-applet`},{ext:`.swf`,mimeType:`application/x-shockwave-flash`},{ext:`.crx`,mimeType:`application/x-chrome-extension`},{ext:`.ttf`,mimeType:`font/ttf`},{ext:`.woff`,mimeType:`font/woff`},{ext:`.woff2`,mimeType:`font/woff2`},{ext:`.otf`,mimeType:`font/otf`},{ext:`.ttc`,mimeType:`font/collection`},{ext:`.eot`,mimeType:`application/vnd.ms-fontobject`},{ext:`.wasm`,mimeType:`application/wasm`},{ext:`.shx`,mimeType:`application/vnd.shx`},{ext:`.shp`,mimeType:`application/vnd.shp`},{ext:`.dbf`,mimeType:`application/x-dbf`},{ext:`.dcm`,mimeType:`application/dicom`},{ext:`.rar`,mimeType:`application/x-rar-compressed`},{ext:`.djvu`,mimeType:`image/vnd.djvu`},{ext:`.mobi`,mimeType:`application/x-mobipocket-ebook`},{ext:`.lit`,mimeType:`application/x-ms-reader`},{ext:`.bpg`,mimeType:`image/bpg`},{ext:`.cbor`,mimeType:`application/cbor`},{ext:`.sqlite`,mimeType:`application/vnd.sqlite3`},{ext:`.dwg`,mimeType:`image/vnd.dwg`},{ext:`.nes`,mimeType:`application/vnd.nintendo.snes.rom`},{ext:`.lnk`,mimeType:`application/x-ms-shortcut`},{ext:`.macho`,mimeType:`application/x-mach-binary`},{ext:`.qcp`,mimeType:`audio/qcelp`},{ext:`.icns`,mimeType:`image/x-icns`},{ext:`.hdr`,mimeType:`image/vnd.radiance`},{ext:`.mrc`,mimeType:`application/marc`},{ext:`.mdb`,mimeType:`application/x-msaccess`},{ext:`.accdb`,mimeType:`application/x-msaccess`},{ext:`.zst`,mimeType:`application/zstd`},{ext:`.cab`,mimeType:`application/vnd.ms-cab-compressed`},{ext:`.rpm`,mimeType:`application/x-rpm`},{ext:`.xz`,mimeType:`application/x-xz`},{ext:`.lz`,mimeType:`application/lzip`},{ext:`.torrent`,mimeType:`application/x-bittorrent`},{ext:`.cpio`,mimeType:`application/x-cpio`},{ext:``,mimeType:`application/tzif`},{ext:`.xcf`,mimeType:`image/x-xcf`},{ext:`.pat`,mimeType:`image/x-gimp-pat`},{ext:`.gbr`,mimeType:`image/x-gimp-gbr`},{ext:`.glb`,mimeType:`model/gltf-binary`},{ext:`.cab`,mimeType:`application/x-installshield`},{ext:`.jxr`,mimeType:`image/jxr`},{ext:`.parquet`,mimeType:`application/vnd.apache.parquet`},{ext:`.txt`,mimeType:`text/plain`},{ext:`.html`,mimeType:`text/html`},{ext:`.svg`,mimeType:`image/svg+xml`},{ext:`.xml`,mimeType:`text/xml`},{ext:`.rss`,mimeType:`application/rss+xml`},{ext:`.atom`,mimeType:`application/atom+xml`},{ext:`.x3d`,mimeType:`model/x3d+xml`},{ext:`.kml`,mimeType:`application/vnd.google-earth.kml+xml`},{ext:`.xlf`,mimeType:`application/x-xliff+xml`},{ext:`.dae`,mimeType:`model/vnd.collada+xml`},{ext:`.gml`,mimeType:`application/gml+xml`},{ext:`.gpx`,mimeType:`application/gpx+xml`},{ext:`.tcx`,mimeType:`application/vnd.garmin.tcx+xml`},{ext:`.amf`,mimeType:`application/x-amf`},{ext:`.3mf`,mimeType:`application/vnd.ms-package.3dmanufacturing-3dmodel+xml`},{ext:`.xfdf`,mimeType:`application/vnd.adobe.xfdf`},{ext:`.owl`,mimeType:`application/owl+xml`},{ext:`.php`,mimeType:`text/x-php`},{ext:`.js`,mimeType:`text/javascript`},{ext:`.lua`,mimeType:`text/x-lua`},{ext:`.pl`,mimeType:`text/x-perl`},{ext:`.py`,mimeType:`text/x-python`},{ext:`.json`,mimeType:`application/json`},{ext:`.geojson`,mimeType:`application/geo+json`},{ext:`.har`,mimeType:`application/json`},{ext:`.ndjson`,mimeType:`application/x-ndjson`},{ext:`.rtf`,mimeType:`text/rtf`},{ext:`.srt`,mimeType:`application/x-subrip`},{ext:`.tcl`,mimeType:`text/x-tcl`},{ext:`.csv`,mimeType:`text/csv`},{ext:`.tsv`,mimeType:`text/tab-separated-values`},{ext:`.vcf`,mimeType:`text/vcard`},{ext:`.ics`,mimeType:`text/calendar`},{ext:`.warc`,mimeType:`application/warc`},{ext:`.vtt`,mimeType:`text/vtt`},{ext:`.pbm`,mimeType:`image/x-portable-bitmap`},{ext:`.pgm`,mimeType:`image/x-portable-graymap`},{ext:`.ppm`,mimeType:`image/x-portable-pixmap`},{ext:`.eml`,mimeType:`message/rfc822`}];var p=new BroadcastChannel(`tabsSync`),m=`pbSettings`,h=`pbColorScheme`;window.app=window.app||{},window.app.store=store({_ready:!1,superuser:null,showHeader:!0,page:t.div({className:`page`},()=>{if(!app.store._ready)return t.span({className:`loader lg m-auto`,title:`Loading plugins...`})}),mainLogo:`./images/logo.svg`,headerLogo:`./images/logo_white.svg`,favicon:``,title:``,_mediaColorScheme:``,userColorScheme:window.localStorage.getItem(h)||``,get activeColorScheme(){return app.store.userColorScheme?app.store.userColorScheme:app.store._mediaColorScheme||`light`},errors:null,creditLinks:[{href:`https://pocketbase.io/docs`,icon:`ri-book-open-line`,label:`Docs`},{href:`https://github.com/pocketbase/pocketbase/releases`,icon:`ri-github-line`,label:`PocketBase v0.39.11`}],headerLinks:[{href:`#/collections`,icon:`ri-database-2-line`,label:`Collections`},{href:`#/logs`,icon:`ri-bar-chart-box-line`,label:`Logs`},{href:`#/settings`,icon:`ri-settings-3-line`,label:`Settings`}],settingsNavGroups:{System:[{href:`#/settings`,icon:`ri-home-gear-line`,label:`Application`},{href:`#/settings/mail`,icon:`ri-send-plane-2-line`,label:`Mail settings`},{href:`#/settings/storage`,icon:`ri-archive-drawer-line`,label:`Files storage`},{href:`#/settings/backups`,icon:`ri-archive-line`,label:`Backups`},{href:`#/settings/crons`,icon:`ri-time-line`,label:`Crons`}],Sync:[{href:`#/settings/export-collections`,icon:`ri-uninstall-line`,label:`Export collections`},{href:`#/settings/import-collections`,icon:`ri-install-line`,label:`Import collections`}],Debug:[{href:`#/settings/sql`,icon:`ri-terminal-box-line`,label:`SQL console`}]},predefinedAccentColors:[`#1055c9`,`#a3142a`,`#096d5c`,`#e6620a`,`#007d9c`,`#3f3da9`],settings:app.utils.getLocalHistory(m,{}),isLoadingSettings:!1,async loadSettings(){app.store.isLoadingSettings=!0;try{let e=await app.pb.settings.getAll({requestKey:`appStore.loadSettings`});app.store.settings=e,app.store.isLoadingSettings=!1}catch(e){e.isAbort||(app.store.isLoadingSettings=!1,app.checkApiError(e))}},collections:[],collectionScaffolds:{},isLoadingCollections:!1,_activeCollectionIdOrName:``,get activeCollection(){let e=app.store._activeCollectionIdOrName;return app.store.collections.find(n=>n.id==e||n.name==e)||app.store.collections[0]},set activeCollection(e){typeof e==`string`?app.store._activeCollectionIdOrName=e:app.store._activeCollectionIdOrName=e?.id},async silentlyReloadCollections(){try{let e=await app.pb.collections.getFullList({requestKey:`appStore.silentlyReloadCollections`});e=app.utils.sortedCollectionsByType(e),JSON.stringify(e)!=JSON.stringify(app.store.collections)&&(app.store.collections=e)}catch(e){e.isAbort||console.warn(`failed to reload app store collections:`,e)}},async loadCollections(e=null){app.store.isLoadingCollections=!0;try{let[n,r]=await Promise.all([app.pb.collections.getScaffolds({requestKey:`appStore.loadCollections.getScaffolds`}),app.pb.collections.getFullList({requestKey:`appStore.loadCollections.getFullList`})]);r=app.utils.sortedCollectionsByType(r),JSON.stringify(app.store.collections)!=JSON.stringify(r)&&(app.store.collections=r),app.store.collectionScaffolds=n,app.store._activeCollectionIdOrName=e||app.store._activeCollectionIdOrName||app.store.collections[0]?.id||``,app.store.isLoadingCollections=!1}catch(e){e.isAbort||(app.store.isLoadingCollections=!1,app.checkApiError(e))}},addOrUpdateCollection(e){let n=app.store.collections.findIndex(n=>n.id==e.id);n>=0?(app.store.activeCollection?.id==e.id&&(app.store._activeCollectionIdOrName=e.id),app.store.collections[n]=e):app.store.collections.push(e),app.store.collections=app.utils.sortedCollectionsByType(app.store.collections)},oauth2Providers:[],isLoadingOAuth2Providers:!1,async loadOAuth2Providers(){app.store.isLoadingOAuth2Providers=!0;try{app.store.oauth2Providers=await app.pb.collections.getAllOAuth2Providers(),app.store.isLoadingOAuth2Providers=!1}catch(e){e.isAbort||(app.checkApiError(e),app.store.isLoadingOAuth2Providers=!1)}}}),window.addEventListener(`hashchange`,()=>{app.store.title=``,app.store.errors=null}),watch(()=>{let e=app.utils.toArray(app.store.title),n=app.store.settings?.meta?.appName||``;n&&e.push(n),document.title=e.join(` - `)});var g;watch(()=>app.store.settings?.meta?.accentColor,e=>{g||(g=t.meta({name:`theme-color`}),document.head.appendChild(g)),e?(g?.setAttribute(`content`,e),document.documentElement.style.setProperty(`--accentColor`,e)):(g?.removeAttribute(`content`),document.documentElement.style.removeProperty(`--accentColor`))});var _;watch(()=>app.store.favicon,e=>{_||(_=t.link({rel:`icon`}),document.head.appendChild(_)),e?_.href=e:_.href=window.location.href.startsWith(`https://`)?`./images/favicon_prod.png`:`./images/favicon.png`});var v=window.matchMedia(`(prefers-color-scheme: dark)`);app.store._mediaColorScheme=v.matches?`dark`:`light`,v.addEventListener(`change`,({matches:e})=>{app.store._mediaColorScheme=e?`dark`:`light`}),watch(()=>app.store.userColorScheme,e=>{e?window.localStorage.setItem(h,e):window.localStorage.removeItem(h),p?.postMessage({colorScheme:e})});var ee;watch(()=>app.store.activeColorScheme,e=>{clearTimeout(ee),document.documentElement.style.setProperty(`--animationSpeed`,`0`),document.documentElement.setAttribute(`data-color-scheme`,e),ee=setTimeout(()=>{document.documentElement.style.removeProperty(`--animationSpeed`)},100)});function te(e,n){e.__errListener&&=(e.removeEventListener(`input`,e.__errListener),e.removeEventListener(`change`,e.__errListener),null),e.setCustomValidity&&(e.setCustomValidity(``),e._oldTitle?e.setAttribute(`title`,e._oldTitle):e.removeAttribute(`title`)),e.removeAttribute(`data-error`);let r=n.nextSibling;r&&r.classList?.contains(`generated-error`)&&r.getAttribute(`data-input-name`)==e.getAttribute(`name`)&&r.remove(),n.querySelector(`[data-error]`)||n.classList.remove(`error`)}watch(()=>JSON.stringify(app.store.errors)&&app.store.errors,e=>{let n=document.querySelectorAll(`[name]`);for(let r of n){if(r.classList.contains(`no-error`))continue;let n=r.closest(`.field-list`)||r.closest(`.fields`)||r.closest(`.field`);if(!n)continue;let i=r.getAttribute(`name`);te(r,n);let a=app.utils.getByPath(e,i),o=a?.message||``;if(!o&&!app.utils.isEmpty(a)){let e=[];for(let n in a)a[n]?.message&&e.push(`${n}: ${a[n]?.message}`);o=e.join(` -`)}o&&(n.classList.add(`error`),r.__errListener=function(){te(r,n),app.utils.deleteByPath(app.store.errors,i)},r.addEventListener(`input`,r.__errListener),r.addEventListener(`change`,r.__errListener),r.setAttribute(`data-error`,!0),r.setCustomValidity&&r.reportValidity&&r.classList.contains(`inline-error`)?(r.setCustomValidity(o),r.reportValidity(),r._oldTitle=r.title,r.title=o):n.after(t.div({"html-data-input-name":i,className:`field-help error generated-error`,textContent:o})))}}),p.onmessage=e=>{e.data?.collections&&JSON.stringify(app.store.collections)!=JSON.stringify(e.data.collections)&&(app.store.collections=e.data.collections),e.data?.settings&&JSON.stringify(app.store.settings)!=JSON.stringify(e.data.settings)&&(app.store.settings=e.data.settings),e.data?.colorScheme&&(app.store.userColorScheme=e.data.colorScheme)},watch(()=>JSON.stringify(app.store.collections),(e,n)=>{e&&e!=`[]`&&n&&n!=`[]`&&e!=n&&p?.postMessage({collections:JSON.parse(e)})}),watch(()=>JSON.stringify(app.store.settings),(e,n)=>{e&&e!=`{}`&&n&&n!=`{}`&&e!=n&&p?.postMessage({settings:JSON.parse(e)}),window.localStorage.setItem(m,e)});var y=class e extends Error{constructor(n){super(`ClientResponseError`),this.url=``,this.status=0,this.response={},this.isAbort=!1,this.originalError=null,Object.setPrototypeOf(this,e.prototype),typeof n==`object`&&n&&(this.originalError=n.originalError,this.url=typeof n.url==`string`?n.url:``,this.status=typeof n.status==`number`?n.status:0,this.isAbort=!!n.isAbort||n.name===`AbortError`||n.message===`Aborted`,this.response=n.response!==null&&typeof n.response==`object`?n.response:n.data!==null&&typeof n.data==`object`?n.data:{}),this.originalError||n instanceof e||(this.originalError=n),this.name=`ClientResponseError `+this.status,this.message=this.response?.message,this.message||=this.isAbort?`The request was aborted (most likely autocancelled; you can find more info in https://github.com/pocketbase/js-sdk#auto-cancellation).`:this.originalError?.cause?.message?.includes(`ECONNREFUSED ::1`)?`Failed to connect to the PocketBase server. Try changing the SDK URL from localhost to 127.0.0.1 (https://github.com/pocketbase/js-sdk/issues/21).`:`Something went wrong.`,this.cause=this.originalError}get data(){return this.response}toJSON(){return{...this}}},ne=/^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;function re(e,n){let r={};if(typeof e!=`string`)return r;let i=Object.assign({},n||{}).decode||ae,a=0;for(;a0&&(!r.exp||r.exp-n>Date.now()/1e3))}oe=typeof atob!=`function`||x?e=>{let n=String(e).replace(/=+$/,``);if(n.length%4==1)throw Error(`'atob' failed: The string to be decoded is not correctly encoded.`);for(var r,i,a=0,o=0,s=``;i=n.charAt(o++);~i&&(r=a%4?64*r+i:i,a++%4)&&(s+=String.fromCharCode(255&r>>(-2*a&6))))i=`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=`.indexOf(i);return s}:atob;var C=`pb_auth`,ce=class{constructor(){this.baseToken=``,this.baseModel=null,this._onChangeCallbacks=[]}get token(){return this.baseToken}get record(){return this.baseModel}get model(){return this.baseModel}get isValid(){return!se(this.token)}get isSuperuser(){let e=S(this.token);return e.type==`auth`&&(this.record?.collectionName==`_superusers`||!this.record?.collectionName&&e.collectionId==`pbc_3142635823`)}get isAdmin(){return console.warn(`Please replace pb.authStore.isAdmin with pb.authStore.isSuperuser OR simply check the value of pb.authStore.record?.collectionName`),this.isSuperuser}get isAuthRecord(){return console.warn(`Please replace pb.authStore.isAuthRecord with !pb.authStore.isSuperuser OR simply check the value of pb.authStore.record?.collectionName`),S(this.token).type==`auth`&&!this.isSuperuser}save(e,n){this.baseToken=e||``,this.baseModel=n||null,this.triggerChange()}clear(){this.baseToken=``,this.baseModel=null,this.triggerChange()}loadFromCookie(e,n=C){let r=re(e||``)[n]||``,i={};try{i=JSON.parse(r),(typeof i!=`object`||Array.isArray(i))&&(i={})}catch{}this.save(i.token||``,i.record||i.model||null)}exportToCookie(e,n=C){let r={secure:!0,sameSite:!0,httpOnly:!0,path:`/`},i=S(this.token);r.expires=i?.exp?new Date(1e3*i.exp):new Date(`1970-01-01`),e=Object.assign({},r,e);let a={token:this.token,record:this.record?JSON.parse(JSON.stringify(this.record)):null},o=ie(n,JSON.stringify(a),e),s=typeof Blob<`u`?new Blob([o]).size:o.length;if(a.record&&s>4096){a.record={id:a.record?.id,email:a.record?.email};let r=[`collectionId`,`collectionName`,`verified`];for(let e in this.record)r.includes(e)&&(a.record[e]=this.record[e]);o=ie(n,JSON.stringify(a),e)}return o}onChange(e,n=!1){return this._onChangeCallbacks.push(e),n&&e(this.token,this.record),()=>{for(let n=this._onChangeCallbacks.length-1;n>=0;n--)if(this._onChangeCallbacks[n]==e)return delete this._onChangeCallbacks[n],void this._onChangeCallbacks.splice(n,1)}}triggerChange(){for(let e of this._onChangeCallbacks)e&&e(this.token,this.record)}},w=class extends ce{constructor(e=`pocketbase_auth`){super(),this.storageFallback={},this.storageKey=e,this._bindStorageEvent()}get token(){return(this._storageGet(this.storageKey)||{}).token||``}get record(){let e=this._storageGet(this.storageKey)||{};return e.record||e.model||null}get model(){return this.record}save(e,n){this._storageSet(this.storageKey,{token:e,record:n}),super.save(e,n)}clear(){this._storageRemove(this.storageKey),super.clear()}_storageGet(e){if(typeof window<`u`&&window?.localStorage){let n=window.localStorage.getItem(e)||``;try{return JSON.parse(n)}catch{return n}}return this.storageFallback[e]}_storageSet(e,n){if(typeof window<`u`&&window?.localStorage){let r=n;typeof n!=`string`&&(r=JSON.stringify(n)),window.localStorage.setItem(e,r)}else this.storageFallback[e]=n}_storageRemove(e){typeof window<`u`&&window?.localStorage&&window.localStorage?.removeItem(e),delete this.storageFallback[e]}_bindStorageEvent(){typeof window<`u`&&window?.localStorage&&window.addEventListener&&window.addEventListener(`storage`,(e=>{if(e.key!=this.storageKey)return;let n=this._storageGet(this.storageKey)||{};super.save(n.token||``,n.record||n.model||null)}))}},T=class{constructor(e){this.client=e}},E=class extends T{async getAll(e){return e=Object.assign({method:`GET`},e),this.client.send(`/api/settings`,e)}async update(e,n){return n=Object.assign({method:`PATCH`,body:e},n),this.client.send(`/api/settings`,n)}async testS3(e=`storage`,n){return n=Object.assign({method:`POST`,body:{filesystem:e}},n),this.client.send(`/api/settings/test/s3`,n).then((()=>!0))}async testEmail(e,n,r,i){return i=Object.assign({method:`POST`,body:{email:n,template:r,collection:e}},i),this.client.send(`/api/settings/test/email`,i).then((()=>!0))}async generateAppleClientSecret(e,n,r,i,a,o){return o=Object.assign({method:`POST`,body:{clientId:e,teamId:n,keyId:r,privateKey:i,duration:a}},o),this.client.send(`/api/settings/apple/generate-client-secret`,o)}},D=[`requestKey`,`$cancelKey`,`$autoCancel`,`fetch`,`headers`,`body`,`query`,`params`,`cache`,`credentials`,`headers`,`integrity`,`keepalive`,`method`,`mode`,`redirect`,`referrer`,`referrerPolicy`,`signal`,`window`];function O(e){if(e){e.query=e.query||{};for(let n in e)D.includes(n)||(e.query[n]=e[n],delete e[n])}}function k(e){let n=[];for(let r in e){let i=encodeURIComponent(r),a=Array.isArray(e[r])?e[r]:[e[r]];for(let e of a)e=A(e),e!==null&&n.push(i+`=`+e)}return n.join(`&`)}function A(e){return e==null?null:e instanceof Date?encodeURIComponent(e.toISOString().replace(`T`,` `)):encodeURIComponent(typeof e==`object`?JSON.stringify(e):e)}var j=class extends T{constructor(){super(...arguments),this.clientId=``,this.eventSource=null,this.subscriptions={},this.lastSentSubscriptions=[],this.maxConnectTimeout=15e3,this.reconnectAttempts=0,this.maxReconnectAttempts=1/0,this.predefinedReconnectIntervals=[200,300,500,1e3,1200,1500,2e3],this.pendingConnects=[],this.pendingSubmits=[],this.isProcessingPendingSubmits=!1}get isConnected(){return!!this.eventSource&&!!this.clientId&&!this.pendingConnects.length}async subscribe(e,n,r){if(!e)throw Error(`topic must be set.`);let i=e;if(r){O(r=Object.assign({},r));let e=`options=`+encodeURIComponent(JSON.stringify({query:r.query,headers:r.headers}));i+=(i.includes(`?`)?`&`:`?`)+e}let a=function(e){let r=e,i;try{i=JSON.parse(r?.data)}catch{}n(i||{})};return this.subscriptions[i]||(this.subscriptions[i]=[]),this.subscriptions[i].push(a),this.isConnected?this.subscriptions[i].length===1?await this.submitSubscriptions():this.eventSource?.addEventListener(i,a):await this.connect(),async()=>this.unsubscribeByTopicAndListener(e,a)}async unsubscribe(e){if(e){let n=this.getSubscriptionsByTopic(e);for(let e in n)if(this.hasSubscriptionListeners(e)){for(let n of this.subscriptions[e])this.eventSource?.removeEventListener(e,n);delete this.subscriptions[e]}}else this.subscriptions={};await this.submitSubscriptions()}async unsubscribeByPrefix(e){let n=!1;for(let r in this.subscriptions)if((r+`?`).startsWith(e)){n=!0;for(let e of this.subscriptions[r])this.eventSource?.removeEventListener(r,e);delete this.subscriptions[r]}n&&await this.submitSubscriptions()}async unsubscribeByTopicAndListener(e,n){let r=this.getSubscriptionsByTopic(e);for(let e in r){if(!Array.isArray(this.subscriptions[e])||!this.subscriptions[e].length)continue;let r=!1;for(let i=this.subscriptions[e].length-1;i>=0;i--)this.subscriptions[e][i]===n&&(r=!0,delete this.subscriptions[e][i],this.subscriptions[e].splice(i,1),this.eventSource?.removeEventListener(e,n));r&&(this.subscriptions[e].length||delete this.subscriptions[e])}await this.submitSubscriptions()}hasSubscriptionListeners(e){if(this.subscriptions=this.subscriptions||{},e)return!!this.subscriptions[e]?.length;for(let e in this.subscriptions)if(this.subscriptions[e]?.length)return!0;return!1}async submitSubscriptions(){return new Promise(((e,n)=>{this.pendingSubmits.push({resolve:e,reject:n}),this.pendingSubmits.length==1&&queueMicrotask((()=>this.finalizePendingSubscriptions()))}))}async finalizePendingSubscriptions(){if(this.isProcessingPendingSubmits||!this.pendingSubmits.length)return;let e=this.pendingSubmits.slice();this.pendingSubmits=[],this.isProcessingPendingSubmits=!0;try{await this.sendSubscriptions();for(let n of e)n.resolve()}catch(n){for(let r of e)n?r.reject(n):r.resolve()}finally{this.isProcessingPendingSubmits=!1,this.pendingSubmits.length>0&&await this.finalizePendingSubscriptions()}}getSubscriptionsCancelKey(){return`realtime_`+this.clientId}getSubscriptionsByTopic(e){let n={};e=e.includes(`?`)?e:e+`?`;for(let r in this.subscriptions)(r+`?`).startsWith(e)&&(n[r]=this.subscriptions[r]);return n}getNonEmptySubscriptionKeys(){let e=[];for(let n in this.subscriptions)this.subscriptions[n].length&&e.push(n);return e}hasUnsentSubscriptions(){let e=this.getNonEmptySubscriptionKeys();if(e.length!=this.lastSentSubscriptions.length)return!0;for(let n of e)if(!this.lastSentSubscriptions.includes(n))return!0;return!1}async sendSubscriptions(){if(this.clientId){if(!this.hasSubscriptionListeners())return this.disconnect();if(this.hasUnsentSubscriptions())return this.addAllSubscriptionListeners(),this.lastSentSubscriptions=this.getNonEmptySubscriptionKeys(),this.client.send(`/api/realtime`,{method:`POST`,body:{clientId:this.clientId,subscriptions:this.lastSentSubscriptions},requestKey:this.getSubscriptionsCancelKey()}).catch((e=>{if(!e?.isAbort)throw e}))}}addAllSubscriptionListeners(){if(this.eventSource){this.removeAllSubscriptionListeners();for(let e in this.subscriptions)for(let n of this.subscriptions[e])this.eventSource.addEventListener(e,n)}}removeAllSubscriptionListeners(){if(this.eventSource)for(let e in this.subscriptions)for(let n of this.subscriptions[e])this.eventSource.removeEventListener(e,n)}async connect(){if(!(this.reconnectAttempts>0))return new Promise(((e,n)=>{this.pendingConnects.push({resolve:e,reject:n}),this.pendingConnects.length==1&&queueMicrotask((()=>this.initConnect()))}))}initConnect(){this.disconnect(!0),clearTimeout(this.connectTimeoutId),this.connectTimeoutId=setTimeout((()=>{this.connectErrorHandler(Error(`EventSource connect took too long.`))}),this.maxConnectTimeout),this.eventSource=new EventSource(this.client.buildURL(`/api/realtime`)),this.eventSource.onerror=e=>{this.connectErrorHandler(Error(`Failed to establish realtime connection.`))},this.eventSource.addEventListener(`PB_CONNECT`,(e=>{let n=e;this.clientId=n?.lastEventId,this.lastSentSubscriptions=[],this.submitSubscriptions().then((async()=>{let n=3;for(;this.hasUnsentSubscriptions()&&n>0;)n--,await this.submitSubscriptions();for(let e of this.pendingConnects)e.resolve();this.pendingConnects=[],this.reconnectAttempts=0,clearTimeout(this.reconnectTimeoutId),clearTimeout(this.connectTimeoutId);let r=this.getSubscriptionsByTopic(`PB_CONNECT`);for(let n in r)for(let i of r[n])i(e);this.hasUnsentSubscriptions()&&await this.submitSubscriptions()})).catch((e=>{this.clientId=``,this.lastSentSubscriptions=[],this.connectErrorHandler(e)}))}))}connectErrorHandler(e){if(clearTimeout(this.connectTimeoutId),clearTimeout(this.reconnectTimeoutId),!this.clientId&&!this.reconnectAttempts||this.reconnectAttempts>this.maxReconnectAttempts){for(let n of this.pendingConnects)n.reject(new y(e));this.pendingConnects=[],this.disconnect();return}this.disconnect(!0);let n=this.predefinedReconnectIntervals[this.reconnectAttempts]||this.predefinedReconnectIntervals[this.predefinedReconnectIntervals.length-1];this.reconnectAttempts++,this.reconnectTimeoutId=setTimeout((()=>{this.initConnect()}),n)}disconnect(e=!1){if(this.clientId&&this.onDisconnect&&this.onDisconnect(Object.keys(this.subscriptions)),clearTimeout(this.connectTimeoutId),clearTimeout(this.reconnectTimeoutId),this.removeAllSubscriptionListeners(),this.client.cancelRequest(this.getSubscriptionsCancelKey()),this.eventSource?.close(),this.eventSource=null,this.clientId=``,this.lastSentSubscriptions=[],!e){this.reconnectAttempts=0;for(let e of this.pendingConnects)e.resolve();this.pendingConnects=[]}}},M=class extends T{decode(e){return e}async getFullList(e,n){if(typeof e==`number`)return this._getFullList(e,n);let r=1e3;return(n=Object.assign({},e,n)).batch&&(r=n.batch,delete n.batch),this._getFullList(r,n)}async getList(e=1,n=30,r){return(r=Object.assign({method:`GET`},r)).query=Object.assign({page:e,perPage:n},r.query),this.client.send(this.baseCrudPath,r).then((e=>(e.items=e.items?.map((e=>this.decode(e)))||[],e)))}async getFirstListItem(e,n){return(n=Object.assign({requestKey:`one_by_filter_`+this.baseCrudPath+`_`+e},n)).query=Object.assign({filter:e,skipTotal:1},n.query),this.getList(1,1,n).then((e=>{if(!e?.items?.length)throw new y({status:404,response:{code:404,message:`The requested resource wasn't found.`,data:{}}});return e.items[0]}))}async getOne(e,n){if(!e)throw new y({url:this.client.buildURL(this.baseCrudPath+`/`),status:404,response:{code:404,message:`Missing required record id.`,data:{}}});return n=Object.assign({method:`GET`},n),this.client.send(this.baseCrudPath+`/`+encodeURIComponent(e),n).then((e=>this.decode(e)))}async create(e,n){return n=Object.assign({method:`POST`,body:e},n),this.client.send(this.baseCrudPath,n).then((e=>this.decode(e)))}async update(e,n,r){return r=Object.assign({method:`PATCH`,body:n},r),this.client.send(this.baseCrudPath+`/`+encodeURIComponent(e),r).then((e=>this.decode(e)))}async delete(e,n){return n=Object.assign({method:`DELETE`},n),this.client.send(this.baseCrudPath+`/`+encodeURIComponent(e),n).then((()=>!0))}_getFullList(e=1e3,n){(n||={}).query=Object.assign({skipTotal:1},n.query);let r=[],i=async a=>this.getList(a,e||1e3,n).then((e=>{let n=e.items;return r=r.concat(n),n.length==e.perPage?i(a+1):r}));return i(1)}};function N(e,n,r,i){let a=i!==void 0;return a||r!==void 0?a?(console.warn(e),n.body=Object.assign({},n.body,r),n.query=Object.assign({},n.query,i),n):Object.assign(n,r):n}function le(e){e._resetAutoRefresh?.()}var ue=class extends M{constructor(e,n){super(e),this.collectionIdOrName=n}get baseCrudPath(){return this.baseCollectionPath+`/records`}get baseCollectionPath(){return`/api/collections/`+encodeURIComponent(this.collectionIdOrName)}get isSuperusers(){return this.collectionIdOrName==`_superusers`||this.collectionIdOrName==`_pbc_2773867675`}async subscribe(e,n,r){if(!e)throw Error(`Missing topic.`);if(!n)throw Error(`Missing subscription callback.`);return this.client.realtime.subscribe(this.collectionIdOrName+`/`+e,n,r)}async unsubscribe(e){return e?this.client.realtime.unsubscribe(this.collectionIdOrName+`/`+e):this.client.realtime.unsubscribeByPrefix(this.collectionIdOrName)}async getFullList(e,n){if(typeof e==`number`)return super.getFullList(e,n);let r=Object.assign({},e,n);return super.getFullList(r)}async getList(e=1,n=30,r){return super.getList(e,n,r)}async getFirstListItem(e,n){return super.getFirstListItem(e,n)}async getOne(e,n){return super.getOne(e,n)}async create(e,n){return super.create(e,n)}async update(e,n,r){return super.update(e,n,r).then((e=>{if(this.client.authStore.record?.id===e?.id&&(this.client.authStore.record?.collectionId===this.collectionIdOrName||this.client.authStore.record?.collectionName===this.collectionIdOrName)){let n=Object.assign({},this.client.authStore.record.expand),r=Object.assign({},this.client.authStore.record,e);n&&(r.expand=Object.assign(n,e.expand)),this.client.authStore.save(this.client.authStore.token,r)}return e}))}async delete(e,n){return super.delete(e,n).then((n=>(!n||this.client.authStore.record?.id!==e||this.client.authStore.record?.collectionId!==this.collectionIdOrName&&this.client.authStore.record?.collectionName!==this.collectionIdOrName||this.client.authStore.clear(),n)))}authResponse(e){let n=this.decode(e?.record||{});return this.client.authStore.save(e?.token,n),Object.assign({},e,{token:e?.token||``,record:n})}async listAuthMethods(e){return e=Object.assign({method:`GET`,fields:`mfa,otp,password,oauth2`},e),this.client.send(this.baseCollectionPath+`/auth-methods`,e)}async authWithPassword(e,n,r){let i;r=Object.assign({method:`POST`,body:{identity:e,password:n}},r),this.isSuperusers&&(i=r.autoRefreshThreshold,delete r.autoRefreshThreshold,r.autoRefresh||le(this.client));let a=await this.client.send(this.baseCollectionPath+`/auth-with-password`,r);return a=this.authResponse(a),i&&this.isSuperusers&&function(e,n,r,i){le(e);let a=e.beforeSend,o=e.authStore.record,s=e.authStore.onChange(((n,r)=>{(!n||r?.id!=o?.id||(r?.collectionId||o?.collectionId)&&r?.collectionId!=o?.collectionId)&&le(e)}));e._resetAutoRefresh=function(){s(),e.beforeSend=a,delete e._resetAutoRefresh},e.beforeSend=async(o,s)=>{let c=e.authStore.token;if(s.query?.autoRefresh)return a?a(o,s):{url:o,sendOptions:s};let l=e.authStore.isValid;if(l&&se(e.authStore.token,n))try{await r()}catch{l=!1}l||await i();let u=s.headers||{};for(let n in u)if(n.toLowerCase()==`authorization`&&c==u[n]&&e.authStore.token){u[n]=e.authStore.token;break}return s.headers=u,a?a(o,s):{url:o,sendOptions:s}}}(this.client,i,(()=>this.authRefresh({autoRefresh:!0})),(()=>this.authWithPassword(e,n,Object.assign({autoRefresh:!0},r)))),a}async authWithOAuth2Code(e,n,r,i,a,o,s){let c={method:`POST`,body:{provider:e,code:n,codeVerifier:r,redirectURL:i,createData:a}};return c=N(`This form of authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createData?, body?, query?) is deprecated. Consider replacing it with authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createData?, options?).`,c,o,s),this.client.send(this.baseCollectionPath+`/auth-with-oauth2`,c).then((e=>this.authResponse(e)))}authWithOAuth2(...e){if(e.length>1||typeof e?.[0]==`string`)return console.warn(`PocketBase: This form of authWithOAuth2() is deprecated and may get removed in the future. Please replace with authWithOAuth2Code() OR use the authWithOAuth2() realtime form as shown in https://pocketbase.io/docs/authentication/#oauth2-integration.`),this.authWithOAuth2Code(e?.[0]||``,e?.[1]||``,e?.[2]||``,e?.[3]||``,e?.[4]||{},e?.[5]||{},e?.[6]||{});let n=e?.[0]||{},r=null;n.urlCallback||(r=de(void 0));let i=new j(this.client);function a(){r?.close(),i.unsubscribe()}let o={},s=n.requestKey;return s&&(o.requestKey=s),this.listAuthMethods(o).then((e=>{let o=e.oauth2.providers.find((e=>e.name===n.provider));if(!o)throw new y(Error(`Missing or invalid provider "${n.provider}".`));let c=this.client.buildURL(`/api/oauth2-redirect`);return new Promise((async(e,l)=>{let u=s?this.client.cancelControllers?.[s]:void 0;u&&(u.signal.onabort=()=>{a(),l(new y({isAbort:!0,message:`manually cancelled`}))}),i.onDisconnect=e=>{e.length&&l&&(a(),l(new y(Error(`realtime connection interrupted`))))};try{await i.subscribe(`@oauth2`,(async r=>{let s=i.clientId;try{if(!r.state||s!==r.state)throw Error(`State parameters don't match.`);if(r.error||!r.code)throw Error(`OAuth2 redirect error or missing code: `+r.error);let i=Object.assign({},n);delete i.provider,delete i.scopes,delete i.createData,delete i.urlCallback,u?.signal?.onabort&&(u.signal.onabort=null),e(await this.authWithOAuth2Code(o.name,r.code,o.codeVerifier,c,n.createData,i))}catch(e){l(new y(e))}a()}));let s={state:i.clientId};n.scopes?.length&&(s.scope=n.scopes.join(` `));let d=this._replaceQueryParams(o.authURL+c,s);await(n.urlCallback||function(e){r?r.location.href=e:r=de(e)})(d)}catch(e){u?.signal?.onabort&&(u.signal.onabort=null),a(),l(new y(e))}}))})).catch((e=>{throw a(),e}))}async authRefresh(e,n){let r={method:`POST`};return r=N(`This form of authRefresh(body?, query?) is deprecated. Consider replacing it with authRefresh(options?).`,r,e,n),this.client.send(this.baseCollectionPath+`/auth-refresh`,r).then((e=>this.authResponse(e)))}async requestPasswordReset(e,n,r){let i={method:`POST`,body:{email:e}};return i=N(`This form of requestPasswordReset(email, body?, query?) is deprecated. Consider replacing it with requestPasswordReset(email, options?).`,i,n,r),this.client.send(this.baseCollectionPath+`/request-password-reset`,i).then((()=>!0))}async confirmPasswordReset(e,n,r,i,a){let o={method:`POST`,body:{token:e,password:n,passwordConfirm:r}};return o=N(`This form of confirmPasswordReset(token, password, passwordConfirm, body?, query?) is deprecated. Consider replacing it with confirmPasswordReset(token, password, passwordConfirm, options?).`,o,i,a),this.client.send(this.baseCollectionPath+`/confirm-password-reset`,o).then((()=>!0))}async requestVerification(e,n,r){let i={method:`POST`,body:{email:e}};return i=N(`This form of requestVerification(email, body?, query?) is deprecated. Consider replacing it with requestVerification(email, options?).`,i,n,r),this.client.send(this.baseCollectionPath+`/request-verification`,i).then((()=>!0))}async confirmVerification(e,n,r){let i={method:`POST`,body:{token:e}};return i=N(`This form of confirmVerification(token, body?, query?) is deprecated. Consider replacing it with confirmVerification(token, options?).`,i,n,r),this.client.send(this.baseCollectionPath+`/confirm-verification`,i).then((()=>{let n=S(e),r=this.client.authStore.record;return r&&!r.verified&&r.id===n.id&&r.collectionId===n.collectionId&&(r.verified=!0,this.client.authStore.save(this.client.authStore.token,r)),!0}))}async requestEmailChange(e,n,r){let i={method:`POST`,body:{newEmail:e}};return i=N(`This form of requestEmailChange(newEmail, body?, query?) is deprecated. Consider replacing it with requestEmailChange(newEmail, options?).`,i,n,r),this.client.send(this.baseCollectionPath+`/request-email-change`,i).then((()=>!0))}async confirmEmailChange(e,n,r,i){let a={method:`POST`,body:{token:e,password:n}};return a=N(`This form of confirmEmailChange(token, password, body?, query?) is deprecated. Consider replacing it with confirmEmailChange(token, password, options?).`,a,r,i),this.client.send(this.baseCollectionPath+`/confirm-email-change`,a).then((()=>{let n=S(e),r=this.client.authStore.record;return r&&r.id===n.id&&r.collectionId===n.collectionId&&this.client.authStore.clear(),!0}))}async listExternalAuths(e,n){return this.client.collection(`_externalAuths`).getFullList(Object.assign({},n,{filter:this.client.filter(`recordRef = {:id}`,{id:e})}))}async unlinkExternalAuth(e,n,r){let i=await this.client.collection(`_externalAuths`).getFirstListItem(this.client.filter(`recordRef = {:recordId} && provider = {:provider}`,{recordId:e,provider:n}));return this.client.collection(`_externalAuths`).delete(i.id,r).then((()=>!0))}async requestOTP(e,n){return n=Object.assign({method:`POST`,body:{email:e}},n),this.client.send(this.baseCollectionPath+`/request-otp`,n)}async authWithOTP(e,n,r){return r=Object.assign({method:`POST`,body:{otpId:e,password:n}},r),this.client.send(this.baseCollectionPath+`/auth-with-otp`,r).then((e=>this.authResponse(e)))}async impersonate(e,n,r){(r=Object.assign({method:`POST`,body:{duration:n}},r)).headers=r.headers||{},r.headers.Authorization||(r.headers.Authorization=this.client.authStore.token);let i=new Ee(this.client.baseURL,new ce,this.client.lang),a=await i.send(this.baseCollectionPath+`/impersonate/`+encodeURIComponent(e),r);return i.authStore.save(a?.token,this.decode(a?.record||{})),i}_replaceQueryParams(e,n={}){let r=e,i=``;e.indexOf(`?`)>=0&&(r=e.substring(0,e.indexOf(`?`)),i=e.substring(e.indexOf(`?`)+1));let a={},o=i.split(`&`);for(let e of o){if(e==``)continue;let n=e.split(`=`);a[decodeURIComponent(n[0].replace(/\+/g,` `))]=decodeURIComponent((n[1]||``).replace(/\+/g,` `))}for(let e in n)n.hasOwnProperty(e)&&(n[e]==null?delete a[e]:a[e]=n[e]);i=``;for(let e in a)a.hasOwnProperty(e)&&(i!=``&&(i+=`&`),i+=encodeURIComponent(e.replace(/%20/g,`+`))+`=`+encodeURIComponent(a[e].replace(/%20/g,`+`)));return i==``?r:r+`?`+i}};function de(e){if(typeof window>`u`||!window?.open)throw new y(Error(`Not in a browser context - please pass a custom urlCallback function.`));let n=1024,r=768,i=window.innerWidth,a=window.innerHeight;n=n>i?i:n,r=r>a?a:r;let o=i/2-n/2,s=a/2-r/2;return window.open(e,`popup_window`,`width=`+n+`,height=`+r+`,top=`+s+`,left=`+o+`,resizable,menubar=no`)}var fe=class extends M{get baseCrudPath(){return`/api/collections`}async import(e,n=!1,r){return r=Object.assign({method:`PUT`,body:{collections:e,deleteMissing:n}},r),this.client.send(this.baseCrudPath+`/import`,r).then((()=>!0))}async truncate(e,n){return n=Object.assign({method:`DELETE`},n),this.client.send(this.baseCrudPath+`/`+encodeURIComponent(e)+`/truncate`,n).then((()=>!0))}async getScaffolds(e){return e=Object.assign({method:`GET`},e),this.client.send(this.baseCrudPath+`/meta/scaffolds`,e)}async getAllOAuth2Providers(e){return e=Object.assign({method:`GET`},e),this.client.send(this.baseCrudPath+`/meta/oauth2-providers`,e)}async dryRunViewQuery(e,n){return n=Object.assign({method:`POST`,body:{query:e}},n),this.client.send(this.baseCrudPath+`/meta/dry-run-view`,n)}},pe=class extends T{async getList(e=1,n=30,r){return(r=Object.assign({method:`GET`},r)).query=Object.assign({page:e,perPage:n},r.query),this.client.send(`/api/logs`,r)}async getOne(e,n){if(!e)throw new y({url:this.client.buildURL(`/api/logs/`),status:404,response:{code:404,message:`Missing required log id.`,data:{}}});return n=Object.assign({method:`GET`},n),this.client.send(`/api/logs/`+encodeURIComponent(e),n)}async getStats(e){return e=Object.assign({method:`GET`},e),this.client.send(`/api/logs/stats`,e)}},me=class extends T{async check(e){return e=Object.assign({method:`GET`},e),this.client.send(`/api/health`,e)}},he=class extends T{getUrl(e,n,r={}){return console.warn(`Please replace pb.files.getUrl() with pb.files.getURL()`),this.getURL(e,n,r)}getURL(e,n,r={}){if(!n||!e?.id||!e?.collectionId&&!e?.collectionName)return``;let i=[];i.push(`api`),i.push(`files`),i.push(encodeURIComponent(e.collectionId||e.collectionName)),i.push(encodeURIComponent(e.id)),i.push(encodeURIComponent(n));let a=this.client.buildURL(i.join(`/`));!1===r.download&&delete r.download;let o=k(r);return o&&(a+=(a.includes(`?`)?`&`:`?`)+o),a}async getToken(e){return e=Object.assign({method:`POST`},e),this.client.send(`/api/files/token`,e).then((e=>e?.token||``))}},ge=class extends T{async getFullList(e){return e=Object.assign({method:`GET`},e),this.client.send(`/api/backups`,e)}async create(e,n){return n=Object.assign({method:`POST`,body:{name:e}},n),this.client.send(`/api/backups`,n).then((()=>!0))}async upload(e,n){return n=Object.assign({method:`POST`,body:e},n),this.client.send(`/api/backups/upload`,n).then((()=>!0))}async delete(e,n){return n=Object.assign({method:`DELETE`},n),this.client.send(`/api/backups/${encodeURIComponent(e)}`,n).then((()=>!0))}async restore(e,n){return n=Object.assign({method:`POST`},n),this.client.send(`/api/backups/${encodeURIComponent(e)}/restore`,n).then((()=>!0))}getDownloadUrl(e,n){return console.warn(`Please replace pb.backups.getDownloadUrl() with pb.backups.getDownloadURL()`),this.getDownloadURL(e,n)}getDownloadURL(e,n){return this.client.buildURL(`/api/backups/${encodeURIComponent(n)}?token=${encodeURIComponent(e)}`)}},_e=class extends T{async getFullList(e){return e=Object.assign({method:`GET`},e),this.client.send(`/api/crons`,e)}async run(e,n){return n=Object.assign({method:`POST`},n),this.client.send(`/api/crons/${encodeURIComponent(e)}`,n).then((()=>!0))}},ve=class extends T{async run(e,n){return n=Object.assign({method:`POST`,body:{query:e}},n),this.client.send(`/api/sql`,n)}};function ye(e){return typeof Blob<`u`&&e instanceof Blob||typeof File<`u`&&e instanceof File||typeof e==`object`&&!!e&&e.uri&&(typeof navigator<`u`&&navigator.product===`ReactNative`||typeof global<`u`&&global.HermesInternal)}function be(e){return e&&(e.constructor?.name===`FormData`||typeof FormData<`u`&&e instanceof FormData)}function xe(e){for(let n in e){let r=Array.isArray(e[n])?e[n]:[e[n]];for(let e of r)if(ye(e))return!0}return!1}var Se=/^[\-\.\d]+$/;function Ce(e){if(typeof e!=`string`)return e;if(e==`true`)return!0;if(e==`false`)return!1;if((e[0]===`-`||e[0]>=`0`&&e[0]<=`9`)&&Se.test(e)){let n=+e;if(``+n===e)return n}return e}var we=class extends T{constructor(){super(...arguments),this.requests=[],this.subs={}}collection(e){return this.subs[e]||(this.subs[e]=new Te(this.requests,e)),this.subs[e]}async send(e){let n=new FormData,r=[];for(let e=0;e{if(r===`@jsonPayload`&&typeof e==`string`)try{let r=JSON.parse(e);Object.assign(n,r)}catch(e){console.warn(`@jsonPayload error:`,e)}else n[r]===void 0?n[r]=Ce(e):(Array.isArray(n[r])||(n[r]=[n[r]]),n[r].push(Ce(e)))})),n}(r));for(let n in r){let i=r[n];if(ye(i))e.files[n]=e.files[n]||[],e.files[n].push(i);else if(Array.isArray(i)){let r=[],a=[];for(let e of i)ye(e)?r.push(e):a.push(e);if(r.length>0&&r.length==i.length){e.files[n]=e.files[n]||[];for(let i of r)e.files[n].push(i)}else if(e.json[n]=a,r.length>0){let i=n;n.startsWith(`+`)||n.endsWith(`+`)||(i+=`+`),e.files[i]=e.files[i]||[];for(let n of r)e.files[i].push(n)}}else e.json[n]=i}}},Ee=class{get baseUrl(){return this.baseURL}set baseUrl(e){this.baseURL=e}constructor(e=`/`,n,r=`en-US`){this.cancelControllers={},this.recordServices={},this.enableAutoCancellation=!0,this.baseURL=e,this.lang=r,this.authStore=n||(typeof window<`u`&&window.Deno?new ce:new w),this.collections=new fe(this),this.files=new he(this),this.logs=new pe(this),this.settings=new E(this),this.realtime=new j(this),this.health=new me(this),this.backups=new ge(this),this.crons=new _e(this),this.sql=new ve(this)}get admins(){return this.collection(`_superusers`)}createBatch(){return new we(this)}collection(e){return this.recordServices[e]||(this.recordServices[e]=new ue(this,e)),this.recordServices[e]}autoCancellation(e){return this.enableAutoCancellation=!!e,this}cancelRequest(e){return this.cancelControllers[e]&&(this.cancelControllers[e].abort(),delete this.cancelControllers[e]),this}cancelAllRequests(){for(let e in this.cancelControllers)this.cancelControllers[e].abort();return this.cancelControllers={},this}filter(e,n){if(!n)return e;function r(e){switch(typeof e){case`boolean`:case`number`:return``+e;case`string`:return JSON.stringify(e);default:if(e==null)return`null`;if(e instanceof Date)return JSON.stringify(e.toISOString().replace(`T`,` `));{let n=JSON.stringify(e);return n.startsWith(`[`)||n.startsWith(`{`)?JSON.stringify(n):n}}}for(let i in n)e=e.replaceAll(`{:`+i+`}`,r(n[i]));return e}getFileUrl(e,n,r={}){return console.warn(`Please replace pb.getFileUrl() with pb.files.getURL()`),this.files.getURL(e,n,r)}buildUrl(e){return console.warn(`Please replace pb.buildUrl() with pb.buildURL()`),this.buildURL(e)}buildURL(e){let n=this.baseURL;return typeof window>`u`||!window.location||n.startsWith(`https://`)||n.startsWith(`http://`)||(n=window.location.origin?.endsWith(`/`)?window.location.origin.substring(0,window.location.origin.length-1):window.location.origin||``,this.baseURL.startsWith(`/`)||(n+=window.location.pathname||`/`,n+=n.endsWith(`/`)?``:`/`),n+=this.baseURL),e&&(n+=n.endsWith(`/`)?``:`/`,n+=e.startsWith(`/`)?e.substring(1):e),n}async send(e,n){n=this.initSendOptions(e,n);let r=this.buildURL(e);if(this.beforeSend){let e=Object.assign({},await this.beforeSend(r,n));e.url!==void 0||e.options!==void 0?(r=e.url||r,n=e.options||n):Object.keys(e).length&&(n=e,console?.warn&&console.warn("Deprecated format of beforeSend return: please use `return { url, options }`, instead of `return options`."))}if(n.query!==void 0){let e=k(n.query);e&&(r+=(r.includes(`?`)?`&`:`?`)+e),delete n.query}return this.getHeader(n.headers,`Content-Type`)==`application/json`&&n.body&&typeof n.body!=`string`&&(n.body=JSON.stringify(n.body)),(n.fetch||fetch)(r,n).then((async e=>{let r={};try{r=await e.json()}catch(e){if(n.signal?.aborted||e?.name==`AbortError`||e?.message==`Aborted`)throw e}if(this.afterSend&&(r=await this.afterSend(e,r,n)),e.status>=400)throw new y({url:e.url,status:e.status,data:r});return r})).catch((e=>{throw new y(e)}))}initSendOptions(e,n){if((n=Object.assign({method:`GET`},n)).body=function(e){if(typeof FormData>`u`||e===void 0||typeof e!=`object`||!e||be(e)||!xe(e))return e;let n=new FormData;for(let r in e){let i=e[r];if(i!==void 0){if(typeof i!=`object`||xe({data:i})){let e=Array.isArray(i)?i:[i];for(let i of e)n.append(r,i)}else{let e={};e[r]=i,n.append(`@jsonPayload`,JSON.stringify(e))}}}return n}(n.body),O(n),n.query=Object.assign({},n.params,n.query),n.requestKey===void 0&&(!1===n.$autoCancel||!1===n.query.$autoCancel?n.requestKey=null:(n.$cancelKey||n.query.$cancelKey)&&(n.requestKey=n.$cancelKey||n.query.$cancelKey)),delete n.$autoCancel,delete n.query.$autoCancel,delete n.$cancelKey,delete n.query.$cancelKey,this.getHeader(n.headers,`Content-Type`)!==null||be(n.body)||(n.headers=Object.assign({},n.headers,{"Content-Type":`application/json`})),this.getHeader(n.headers,`Accept-Language`)===null&&(n.headers=Object.assign({},n.headers,{"Accept-Language":this.lang})),this.authStore.token&&this.getHeader(n.headers,`Authorization`)===null&&(n.headers=Object.assign({},n.headers,{Authorization:this.authStore.token})),this.enableAutoCancellation&&n.requestKey!==null){let r=n.requestKey||(n.method||`GET`)+e;delete n.requestKey,this.cancelRequest(r);let i=new AbortController;this.cancelControllers[r]=i,n.signal=i.signal}return n}getHeader(e,n){e||={},n=n.toLowerCase();for(let r in e)if(r.toLowerCase()==n)return e[r];return null}},De=`#/login`,Oe=window.location.pathname.endsWith(`/`)?window.location.pathname.substring(0,window.location.pathname.length-1):window.location.pathname;window.app=window.app||{},window.app.pb=new Ee(`../`,new w(`__pb_superusers__`+Oe)),app.pb.beforeSend=function(e,n){return n.headers[`x-request-source`]=`pbui`,{url:e,options:n}},app.store.superuser=app.pb.authStore.record,app.pb.authStore.onChange((e,n)=>{!n&&window.location.hash!=De&&(app.modals.close(),window.location.hash=De),app.store.superuser=n}),app.pb.authStore.isValid&&app.pb.collection(app.pb.authStore.record?.collectionName||`_superusers`).authRefresh().catch(e=>{console.warn(`Failed to refresh the existing auth token:`,e);let n=e?.status<<0;(n==401||n==403)&&(app.pb.cancelAllRequests(),app.pb.authStore.clear())}),app.pb.authStore.onChange((e,n)=>{n?.id&&(app.store.loadCollections(),app.store.loadSettings(),app.store.loadOAuth2Providers())});var ke=app.pb.collection;app.pb.collection=function(e){let n=ke.call(this,e);return Ae(n),n};function Ae(e){if(e.__customUIEvents)return;e.__customUIEvents=!0;let n=e.create;e.create=function(){return n.apply(e,arguments).then(e=>(setTimeout(()=>{document.dispatchEvent(new CustomEvent(`record:create`,{detail:e})),document.dispatchEvent(new CustomEvent(`record:save`,{detail:e}))},0),e))};let r=e.update;e.update=function(){return r.apply(e,arguments).then(e=>(setTimeout(()=>{document.dispatchEvent(new CustomEvent(`record:update`,{detail:e})),document.dispatchEvent(new CustomEvent(`record:save`,{detail:e}))},0),e))};let i=e.delete;e.delete=function(){return i.apply(e,arguments).then(n=>{let r={id:arguments[0],collectionId:e.collectionIdOrName,collectionName:e.collectionIdOrName};return setTimeout(()=>{document.dispatchEvent(new CustomEvent(`record:delete`,{detail:r}))},0),n})}}var je=`pbLastFileToken`,Me=!1,Ne=[];app.pb.authStore.onChange((e,n)=>{n?.id||window.localStorage.removeItem(je)}),window.app.getFileToken=async function(e=``){let n=e&&app.store.collections?.find(n=>n.id==e||n.name==e);if(n&&!n.fields?.find(e=>e.type==`file`&&e.protected))return;let r=window.localStorage.getItem(je);return(!r||se(r,60))&&(r=await Pe()),r};async function Pe(){return new Promise(async(e,n)=>{if(Ne.push({resolve:e,reject:n}),!Me){Me=!0;try{let e=await app.pb.files.getToken();window.localStorage.setItem(je,e),Ne.forEach(n=>n.resolve(e))}catch(e){Ne.forEach(n=>n.reject(e))}Me=!1,Ne=[]}})}window.app.checkApiError=function(e,n=!0){if(!e||!(e instanceof Error)||e.isAbort){console.warn(`checkApiError - unexpected error type:`,e);return}let r=e?.status<<0,i=e?.response||{},a=n&&(i.message||e.message||`Something went wrong!`);if(a&&app.toasts.error(a),r==0&&console.log(e),app.utils.isEmpty(i.data)||(app.store.errors=i.data),r===401&&window.location.hash!=De)return app.pb.cancelAllRequests(),app.pb.authStore.clear()};function Fe(){return()=>{if(!(!app.store._ready||!app.store.showHeader||!app.store.superuser?.id))return t.header({pbEvent:`appHeader`,rid:`appHeader`,className:`app-header accent-surface`,onmount:async e=>{await new Promise(e=>setTimeout(e,0)),e._scrollToActiveMenuItem=function(){e?.querySelector(`.app-main-nav .header-link.active`)?.scrollIntoView()},e._scrollToActiveMenuItem(),window.addEventListener(`hashchange`,e._scrollToActiveMenuItem)},onunmount:e=>{window.removeEventListener(`hashchange`,e?._scrollToActiveMenuItem)}},t.a({href:`#/`,className:`logo`},t.img({src:()=>app.store.headerLogo,alt:`App logo`})),t.nav({pbEvent:`mainNav`,className:`app-main-nav`},()=>app.store.headerLinks.map(e=>{let n=e.href.startsWith(`#/`);return t.a({href:()=>e.href,target:()=>n?void 0:`_blank`,rel:()=>n?void 0:`noopener noreferrer`,className:n=>`header-link ${e.isActive?.(n)||app.utils.isActivePath(e.href)?`active`:``}`},()=>{if(e.icon)return t.i({className:e.icon,ariaHidden:!0})},t.span({className:`txt`},()=>e.label))})),t.div({className:`flex-fill app-header-separator`}),Ie(),t.button({type:`button`,className:`header-link logged-user txt-normal`,"html-popovertarget":`logged-user-dropdown`},t.span({className:`superuser-name txt-ellipsis`},()=>app.store.superuser?.email),t.i({className:`ri-arrow-drop-down-line`,ariaHidden:!0})),t.div({pbEvent:`loggedUserDropdown`,id:`logged-user-dropdown`,className:`dropdown sm nowrap logged-user-dropdown`,popover:`auto`},t.a({className:`dropdown-item dropdown-item-manage`,href:`#/collections?collection=_superusers`,onclick:e=>{e.target.closest(`.dropdown`).hidePopover()}},t.i({className:`ri-group-line`,ariaHidden:!0}),t.span({className:`txt`},`Manage superusers`)),t.hr(),t.button({type:`button`,className:`dropdown-item txt-danger dropdown-item-logout`,onclick:()=>app.pb.authStore.clear()},t.i({className:`ri-logout-circle-line`,ariaHidden:!0}),t.span({className:`txt`},`Logout`))))}}function Ie(){let e=[{value:`light`,icon:`ri-sun-line`,label:`Light`},{value:`dark`,icon:`ri-moon-line`,label:`Dark`},{value:``,icon:`ri-subtract-line`,label:`Auto`}];return[t.button({type:`button`,className:`header-link color-scheme-picker`,"html-popovertarget":`color-scheme-dropdown`,title:`Color scheme`},t.i({className:()=>app.store.activeColorScheme==`dark`?`ri-moon-line`:`ri-sun-line`,ariaHidden:!0})),t.div({pbEvent:`colorSchemeDropdown`,id:`color-scheme-dropdown`,className:`dropdown sm nowrap color-scheme-dropdown`,popover:`auto`},()=>e.map(e=>t.button({type:`button`,className:()=>`dropdown-item dropdown-item-light ${app.store.userColorScheme==e.value?`active`:``}`,onclick:n=>{n.target.closest(`.dropdown`).hidePopover(),app.store.userColorScheme=e.value}},t.i({className:e.icon,ariaHidden:!0}),t.span({className:`txt`},e.label))))]}document.addEventListener(`invalid`,e=>{let n=e.target.closest(`details`);n&&!n.open&&!e.target.closest(`summary`)&&(n.open=!0,e.target.reportValidity&&e.target.reportValidity())},!0);var Le=`dropdown-item`;document.addEventListener(`toggle`,e=>{if(e.newState!=`open`||!e.target||e.target.__keyboardNavRegistered||!e.target.matches(`.dropdown`))return;e.target.__keyboardNavRegistered=!0;let n=e.target;function r(e){if(!n.isConnected){document.removeEventListener(`keydown`,r);return}let i;if(i=document.activeElement&&document.activeElement.classList.contains(Le)?document.activeElement:n.querySelector(`.dropdown-item:not([hidden]):not(.disabled)`),i){if(e.key==`ArrowUp`){e.preventDefault();let n=ze(i,-1);i==document.activeElement&&n?.classList?.contains(Le)?n?.focus():i.focus()}else if(e.key==`ArrowDown`){e.preventDefault();let n=ze(i,1);i==document.activeElement&&n?.classList?.contains(Le)?n?.focus():i.focus()}else if(e.keyCode>=65&&e.keyCode<=90||e.keyCode>=48&&e.keyCode<=57){let e=n.querySelectorAll(`input,textare,select`);e.length==1&&e[0].focus()}}}n.addEventListener(`toggle`,e=>{e.newState==`open`?(n.__dropdownSource=e.source,n.__dropdownSource?.setAttribute(`data-popover-state`,!0),Re(e.target.id,!0),document.addEventListener(`keydown`,r)):(n.__dropdownSource?.setAttribute(`data-popover-state`,!1),Re(e.target.id,!1),document.removeEventListener(`keydown`,r))})},!0);function Re(e,n=!1){e&&document.querySelectorAll(`[popovertarget='`+e+`']`)?.forEach(e=>e.setAttribute(`data-popover-state`,n))}function ze(e,n=-1){let r=n<0?e.previousElementSibling:e.nextElementSibling;return r&&(r.hidden||r.classList.contains(`disabled`)||!r.classList.contains(Le))?ze(r,n):r}var Be=5,P=t.div({ariaHidden:!0,popover:`manual`,className:`pb-tooltip`});document.body.appendChild(P);function Ve(e,n,r){P._relatedNode=e;let i=e.getBoundingClientRect();P.setAttribute(`data-style`,r||``),P.setAttribute(`data-position`,n),P.style.top=`0px`,P.style.left=`0px`;let a=P.offsetHeight,o=P.offsetWidth,s=0,c=0;n==`left`?(s=i.top+i.height/2-a/2,c=i.left-o-Be):n==`right`?(s=i.top+i.height/2-a/2,c=i.right+Be):n==`top`?(s=i.top-a-Be,c=i.left+i.width/2-o/2):n==`top-left`?(s=i.top-a-Be,c=i.left):n==`top-right`?(s=i.top-a-Be,c=i.right-o):n==`bottom-left`?(s=i.top+i.height+Be,c=i.left):n==`bottom-right`?(s=i.top+i.height+Be,c=i.right-o):(s=i.top+i.height+Be,c=i.left+i.width/2-o/2),c+o>document.documentElement.clientWidth&&(c=document.documentElement.clientWidth-o),c=c>=0?c:0,s+a>document.documentElement.clientHeight&&(s=document.documentElement.clientHeight-a),s=s>=0?s:0,P.style.top=s+`px`,P.style.left=c+`px`}function He(){P._relatedNode=null,P.hidePopover()}function Ue(e,n,r,i){if(!e||!n){He();return}P.showPopover(),P.textContent=n,Ve(e,r,i)}document.body.addEventListener(`mouseleave`,()=>{He()});function We(e,n=`top`,r=``){return i=>{if(!i._tooltipText){i._tooltipText=store({value:``});let e;function a(){e?.unwatch(),e=watch(()=>i._tooltipText.value,async e=>{Ue(i,e,n,r)})}async function o(){e?.unwatch(),e=null,He()}i.addEventListener(`mouseenter`,a),i.addEventListener(`focusin`,a),i.addEventListener(`mouseleave`,o),i.addEventListener(`focusout`,o),i.addEventListener(`blur`,o);let s=i.onunmount;i.onunmount=n=>{P._relatedNode==n&&He(),e?.unwatch(),n._tooltipText=null,n?.removeEventListener(`mouseenter`,a),n?.removeEventListener(`focusin`,a),n?.removeEventListener(`mouseleave`,o),n?.removeEventListener(`focusout`,o),n?.removeEventListener(`blur`,o),s(n)}}return typeof e==`function`?i._tooltipText.value=e():i._tooltipText.value=e,i._tooltipText.value}}window.app=window.app||{},window.app.attrs=window.app.attrs||{},window.app.attrs.tooltip=We,window.app=window.app||{},window.app.modals=window.app.modals||{},window.app.modals.confirm=function(e,n,r,i={className:void 0,yesButton:``,noButton:``}){F.textOrElem=e,F.yesCallback=n,F.yesCallbackWaiting=!1,F.noCallback=r,F.noCallbackWaiting=!1,F.className=typeof i.className==`string`?i.className:`sm`,F.yesButton=i.yesButton||`Yes`,F.noButton=i.noButton||`No`,Ge.isConnected||document.body.appendChild(Ge),window.app.modals.open(Ge)};var F=store({className:``,textOrElem:null,yesButton:``,yesCallback:null,yesCallbackWaiting:!1,noButton:``,noCallback:null,noCallbackWaiting:!1,get isBusy(){return F.yesCallbackWaiting||F.noCallbackWaiting}}),Ge=t.div({className:()=>`modal popup manual ${F.className||``}`},t.div({className:`modal-content`},e=>typeof F.textOrElem==`string`?t.h6({className:`block txt-center`},F.textOrElem):typeof F.textOrElem==`function`?F.textOrElem(e):F.textOrElem),t.footer({className:`modal-footer p-sm`},t.div({className:`grid sm`},t.div({className:`col-sm-6`},t.button({type:`button`,className:()=>`btn lg block secondary ${F.noCallbackWaiting?`loading`:``}`,disabled:()=>F.isBusy,onclick:async()=>{if(F.noCallback){F.noCallbackWaiting=!0;try{if(await F.noCallback()===!1)return}catch(e){console.log(`confirm noCallback error:`,e)}finally{F.noCallbackWaiting=!1}}window.app.modals.close(Ge)}},()=>typeof F.noButton==`string`?t.span({className:`txt`},F.noButton):typeof F.noButton==`function`?F.noButton(el):F.noButton)),t.div({className:`col-sm-6`},t.button({type:`button`,className:()=>`btn lg block warning ${F.yesCallbackWaiting?`loading`:``}`,disabled:()=>F.isBusy,onclick:async()=>{if(F.yesCallback){F.yesCallbackWaiting=!0;try{if(await F.yesCallback()===!1)return}catch(e){console.log(`confirm yesCallback error:`,e)}finally{F.yesCallbackWaiting=!1}}window.app.modals.close(Ge)}},()=>typeof F.yesButton==`string`?t.span({className:`txt`},F.yesButton):typeof F.yesButton==`function`?F.yesButton(el):F.yesButton)))));window.app=window.app||{},window.app.components=window.app.components||{},window.app.components.dragline=function(e={}){let n,r=store({rid:void 0,id:void 0,hidden:void 0,inert:void 0,className:``,tolerance:0,ondragstart:function(e){},ondragstop:function(e){},ondragging:function(e,n,r){}}),i=app.utils.extendStore(r,e),a=store({dragStarted:!1}),o,s,c,l;function u(){document.addEventListener(`touchmove`,m),document.addEventListener(`mousemove`,m),document.addEventListener(`touchend`,p),document.addEventListener(`mouseup`,p)}function d(){document.removeEventListener(`touchmove`,m),document.removeEventListener(`mousemove`,m),document.removeEventListener(`touchend`,p),document.removeEventListener(`mouseup`,p)}function f(e){e.stopPropagation(),o=e.clientX,s=e.clientY,c=e.clientX-n.offsetLeft,l=e.clientY-n.offsetTop,u()}function p(e){a.dragStarted&&(e.preventDefault(),a.dragStarted=!1,r.ondragstop?.(e)),d()}function m(e){let i=e.clientX-o,u=e.clientY-s,d=e.clientX-c,f=e.clientY-l;!a.dragStarted&&Math.abs(d-n.offsetLeft)r.id,hidden:()=>r.hidden,inert:()=>r.inert,className:()=>`dragline ${a.dragStarted?`dragging`:``} ${r.className}`,onmousedown:e=>{e.button==0&&f(e)},ontouchstart:f,onunmount:()=>{d(),i.forEach(e=>e?.unwatch())}}),n},window.app=window.app||{},window.app.components=window.app.components||{},window.app.components.slide=function(e,...n){let r;return t.div({className:n=>`block slide-block ${e?.(n)?``:`hidden`}`,onmount:e=>{r=setTimeout(()=>{e?.setAttribute(`data-slide`,`1`)},200)},onunmount:()=>{clearTimeout(r)}},...n)},window.app=window.app||{},window.app.modals=window.app.modals||{};var Ke=`data-modal-state`,qe=`manual`,Je;window.app.modals.open=async function(e){if(!e?.isConnected){console.error(`modals.open requies an active DOM element`,e);return}let n;if(e.onbeforeopen&&(n=await e.onbeforeopen(e)),n===!1)return;if(e.onafteropen){let n=new ResizeObserver(r=>{for(let i of r)i.contentRect.height>0&&e?.onafteropen&&(e.onafteropen(e),n.disconnect(),n=null)});n.observe(e)}Je=document.activeElement,Ye(e),e._forceClose=()=>app.modals.close(e,!0),window.addEventListener(`popstate`,e._forceClose);let r=Math.max(Xe(e)?.style.zIndex<<0,1e3);e.style.zIndex=r+1,e.setAttribute(Ke,`open`)},window.app.modals.close=async function(e=null,n=!1){if(e||=Xe(),e){if(window.removeEventListener(`popstate`,e._forceClose),n)e.onbeforeclose?.(e,!0),e.setAttribute(Ke,`close`),e.onafterclose?.(e,!0);else{if(e.onbeforeclose&&await e.onbeforeclose(e,!1)===!1)return;if(e.onafterclose){let n=new ResizeObserver(r=>{for(let i of r)i.contentRect.height<=0&&e?.onafterclose&&(e.onafterclose(e,!1),n.disconnect(),n=null)});n.observe(e)}e.setAttribute(Ke,`close`)}Je&&(Je.focus?.(),setTimeout(()=>{Je?.blur?.(),Je=null},0))}};function Ye(e){if(e.getAttribute(`tabindex`)||e.setAttribute(`tabindex`,`-1`),setTimeout(()=>{let n=e?.querySelector(`[autofocus]`);n?n.focus():e?.focus()},0),e.getAttribute(Ke))return;e.setAttribute(Ke,``),e.addEventListener(`keydown`,n=>{n.key!=`Escape`||e.classList.contains(qe)||n.target!==e&&e.contains(n.target)||window.app.modals.close(e)});let n=!1,r=r=>{n=r.target!==e&&e.contains(r.target)};e.addEventListener(`mousedown`,r),e.addEventListener(`touchstart`,r);let i=!1,a=n=>{i=n.target!==e&&e.contains(n.target)};e.addEventListener(`mouseup`,a),e.addEventListener(`touchend`,a),e.addEventListener(`click`,r=>{n||i||e.classList.contains(qe)||r.target!==e&&e.contains(r.target)||window.app.modals.close(e)})}function Xe(e){let n=document.querySelectorAll(`[${Ke}="open"]`),r=0,i=0,a;for(let o of n)e&&o==e||(r=o.style.zIndex<<0,r>i&&(i=r,a=o));return a}window.app.modals.getTop=function(){return Xe()};var Ze=new Map,Qe=t.div({className:`toasts-container`});function I(e,n=!0){let r=Ze.get(e);!r||!r.isConnected||(Ze.delete(e),clearTimeout(r._removeTimeout),n?(r.classList.add(`removing`),setTimeout(()=>{r.remove()},300)):r.remove())}function R(e=!0){Ze.forEach((n,r)=>{window.app.toasts.remove(r,e)})}function $e(e,n={}){n.type=`info`,n.duration=n.duration||3e3,nt(e,n)}function et(e,n={}){n.type=`success`,n.duration=n.duration||3e3,nt(e,n)}function tt(e,n={}){n.type=`error`,n.duration=n.duration||3500,nt(e,n)}function nt(e,n={}){n=Object.assign({duration:3e3,key:void 0,type:`info`},n),Qe.isConnected||document.body.appendChild(Qe);let r=n.key||e;Ze.has(r)&&I(r,!1);function i(e){e?._removeTimeout&&clearTimeout(e?._removeTimeout),e._removeTimeout=setTimeout(()=>{I(r)},n.duration)}let a=t.div({className:`toast ${n.type||``}`,onmount:e=>{i(e)},onunmount:e=>{e?._removeTimeout&&(clearTimeout(e?._removeTimeout),a=null)},onmouseover:()=>{clearTimeout(a?._removeTimeout)},onmouseout:()=>{i(a)}},t.div({className:`toast-container`},t.div({className:`toast-icon`}),t.div({className:`toast-content`},e,t.button({type:`button`,className:`m-l-auto btn circle sm transparent secondary toast-remove`,title:`Clear`,onclick:()=>I(r)},t.i({className:`ri-close-line`,ariaHidden:!0})))));Ze.set(r,a),Qe.prepend(a)}window.app=window.app||{},window.app.toasts=window.app.toasts||{},window.app.toasts.info=$e,window.app.toasts.error=tt,window.app.toasts.success=et,window.app.toasts.remove=I,window.app.toasts.removeAll=R,window.app=window.app||{},window.app.components=window.app.components||{},window.app.components.sortable=function(e={}){let n=store({rid:void 0,id:void 0,hidden:void 0,inert:void 0,className:``,data:[],dataItem:function(e,n,r){return t.span(null,`Item `+n)},onchange:function(e,n,r){},handle:``,before:void 0,after:void 0}),r=app.utils.extendStore(n,e);function i(e){function r(){e.querySelectorAll(`:scope > [data-dragstart="true"]`)?.forEach(e=>{e.dataset.dragstart=!1}),e.querySelectorAll(`:scope > [data-dragover="true"]`)?.forEach(e=>{e.dataset.dragover=!1})}e.addEventListener(`dragstart`,r=>{if(n.handle&&!r.target.closest(n.handle)){r.preventDefault();return}let i=o(e,r.target);i&&(i.dataset.dragstart=!0)}),e.addEventListener(`dragenter`,n=>{for(let n of e.children)n.dataset.dragover&&(n.dataset.dragover=!1);let r=o(e,n.target);r&&(r.dataset.dragover=!0)}),e.addEventListener(`dragover`,e=>{e.preventDefault()}),e.addEventListener(`drop`,i=>{if(!n.onchange){r();return}let s=e.querySelector(`:scope > [data-dragstart="true"]`),c=o(e,i.target);if(r(),!s||!c||c==s)return;let l=e.querySelectorAll(`:scope > [data-sortable-child]`),u=a(l,s),d=a(l,c);if(u==-1||d==-1)return;let f=n.data.slice(),p=f.splice(u,1);f.splice(d,0,p[0]),n.onchange(f,u,d)}),e._documentDragend=function(){r()},document.addEventListener(`dragend`,e._documentDragend)}function a(e,n){for(let r=0;rn.id,hidden:()=>n.hidden,inert:()=>n.inert,className:()=>n.className,onmount:e=>{i(e)},onunmount:e=>{r.forEach(e=>e?.unwatch()),e._documentDragend&&document.removeEventListener(`dragend`,e._documentDragend)}},e=>typeof n.before==`function`?n.before(e):n.before,e=>{let r=[];for(let i=0;itypeof n.after==`function`?n.after(e):n.after)},window.app=window.app||{},window.app.components=window.app.components||{},window.app.components.copyButton=function(e,...n){let r=store({active:!1}),i;function a(){let n=e;typeof n==`function`&&(n=e()),app.utils.copyToClipboard(n),r.active=!0,clearTimeout(i),i=setTimeout(()=>{r.active=!1},500)}return t.button({tabIndex:-1,type:`button`,className:()=>`copy-to-clipboard ${r.active?`active`:``}`,title:`Copy`,ariaDescription:app.attrs.tooltip(()=>r.active?`Copied`:null),onclick:e=>{e.preventDefault(),e.stopPropagation(),a()}},t.i({hidden:n?.length,ariaHidden:!0,className:()=>`copy-icon ${r.active?`ri-check-double-line`:`ri-file-copy-line`}`}),...n)},window.app=window.app||{},window.app.components=window.app.components||{},window.app.components.codeBlock=function(e={}){let n=store({rid:void 0,id:void 0,hidden:void 0,inert:void 0,className:``,language:`js`,value:void 0,footnote:void 0}),r=app.utils.extendStore(n,e);return t.div({rid:n.rid,id:()=>n.id,hidden:()=>n.hidden,inert:()=>n.inert,className:()=>`code-wrapper ${n.className}`,tabIndex:-1,onmount:e=>{e.addEventListener(`keydown`,n=>{(n.ctrlKey||n.metaKey)&&(n.key==`a`||n.key==`A`)&&(n.preventDefault(),window.getSelection().selectAllChildren(e))})},onunmount:()=>{r.forEach(e=>e?.unwatch())}},t.code({className:`block`,innerHTML:()=>rt(n.value,n.language)}),t.div({className:`footnote`},e=>typeof n.footnote==`function`?n.footnote(e):n.footnote))};function rt(e,n){return e=typeof e==`string`?e:``,e=Prism.plugins.NormalizeWhitespace.normalize(e,{"remove-trailing":!0,"remove-indent":!0,"left-trim":!0,"right-trim":!0}),Prism.highlight(e,Prism.languages[n]||Prism.languages.js,n)}window.app=window.app||{},window.app.components=window.app.components||{},window.app.components.codeEditor=function(e={}){let n=store({rid:void 0,id:void 0,hidden:void 0,inert:void 0,name:void 0,className:``,value:``,language:`js`,placeholder:``,disabled:!1,required:!1,singleLine:!1,autocomplete:void 0,oninput:function(e){},onfocus:function(e){},onblur:function(e){}}),r=app.utils.extendStore(n,e,`autocomplete`),i,a,o=!0;function s(e){c(),i=t.div({className:`dropdown autocomplete code-editor-dropdown`,onmount:e=>{e._updatePosition=()=>{o?dt(i):c()},e._closeOnEsc=e=>{e.key==`Escape`&&(e.preventDefault(),c())},window.addEventListener(`scroll`,e._updatePosition,!0),window.addEventListener(`resize`,e._updatePosition),window.addEventListener(`keydown`,e._closeOnEsc),e._updatePosition()},onunmount:e=>{e&&(window.removeEventListener(`scroll`,e._updatePosition,!0),window.removeEventListener(`resize`,e._updatePosition),window.removeEventListener(`keydown`,e._closeOnEsc))}},e),document.body.appendChild(i),f&&(a?.disconnect(),a=new IntersectionObserver(([e])=>{o=e.isIntersecting},{root:null,threshold:.1}),a.observe(f))}function c(){i&&=(i.remove(),null),a&&=(a.disconnect(),null),o=!0}function l(e){n.value=e,n.oninput?.(e),f.dispatchEvent(new CustomEvent(`change`,{detail:e}))}let u=!1,d,f=t.div({contentEditable:()=>!n.disabled&&`plaintext-only`,tabIndex:0,spellcheck:!1,autocorrect:!1,autocomplete:`off`,autocapitalize:`off`,role:`textbox`,className:`editor-content`,"html-data-placeholder":()=>n.placeholder,onmount:e=>{d?.unwatch(),d=watch(()=>n.value,e=>{e!=f.textContent&&(f.textContent=e,c())})},onunmount:e=>{d?.unwatch(),c()},onfocus:()=>{n.onfocus?.(n.value)},onblur:e=>{i&&!i.contains(e.relatedTarget)&&c(),n.onblur?.(n.value)},oninput:e=>{if(c(),l(f.textContent),!n.value?.length){f.textContent=``;return}if(!f?.isConnected)return;let r=ut(f),i=st(n.value,r);if(!i.word.length||r==i.start)return;let a=[];if(typeof n.autocomplete==`function`)a=n.autocomplete(i.word)||[];else if(!app.utils.isEmpty(n.autocomplete)){let e=i.word.toLowerCase();a=n.autocomplete.filter(n=>(typeof n==`object`&&(n=n?.value),n=n?.toLowerCase(),n&&n!=e&&n.includes(e)))}a?.length&&s(()=>a.map((e,n)=>t.button({type:`button`,className:`dropdown-item ${n==0?`active`:``}`,textContent:e.label||e.value||e,onclick:n=>{n.preventDefault(),f.focus();let r=e.value||e;f.textContent=f.textContent.substring(0,i.start)+r+f.textContent.substring(i.end+1),l(f.textContent);try{window.getSelection().setPosition(f.childNodes[0],i.start+r.length)}catch(e){console.warn(`failed to set caret position`,e)}c()}})))},onkeydown:e=>{if(u=e.ctrlKey||e.metaKey,(e.key==`Enter`||e.key==`Tab`)&&i?.isConnected){e.preventDefault(),i.querySelector(`.dropdown-item.active`)?.click();return}if(e.key==`ArrowUp`&&i?.isConnected){e.preventDefault();let n=i.querySelector(`.dropdown-item.active`);n?.previousElementSibling&&(n.classList.remove(`active`),n.previousElementSibling.classList.add(`active`),n.previousElementSibling.scrollIntoView(!1));return}if(e.key==`ArrowDown`&&i?.isConnected){e.preventDefault();let n=i.querySelector(`.dropdown-item.active`);n?.nextElementSibling&&(n.classList.remove(`active`),n.nextElementSibling.classList.add(`active`),n.nextElementSibling.scrollIntoView(!1));return}if(u&&e.key.toLowerCase()==`l`){e.preventDefault(),ct(f);return}if(u&&e.key.toLowerCase()==`d`){e.preventDefault(),lt(f);return}if(!n.singleLine&&e.key==`Escape`&&!i?.isConnected){f?.blur();return}if(!n.singleLine&&e.key==`Tab`){e.preventDefault();let n=window.getSelection();if(!n)return;if(e.shiftKey){n.modify(`extend`,`backward`,`character`),n.toString()[0]==` `?(n.deleteFromDocument(),l(f.textContent)):(n.modify(`extend`,`forward`,`character`),n.toString()[0]==` `&&(n.deleteFromDocument(),l(f.textContent)));return}let r=n.getRangeAt(0);r&&(r.deleteContents(),r.insertNode(document.createTextNode(` `)),r.collapse(),l(f.textContent));return}if(n.singleLine&&e.key==`Enter`){e.preventDefault(),m.click();return}},onscroll:()=>{c(),p&&(p.scrollLeft=f.scrollLeft,p.scrollTop=f.scrollTop)}}),p=t.div({className:`highlight-overlay`,innerHTML:()=>at(n.value,n.language),onscroll:()=>{f&&(f.scrollLeft=p.scrollLeft,f.scrollTop=p.scrollTop)}}),m=t.button({type:`submit`,className:`hidden`});return t.div({rid:n.rid,id:()=>n.id,inert:()=>n.inert,hidden:()=>n.hidden,"html-name":()=>n.name,"html-required":()=>n.required||void 0,className:()=>`input code-editor ${n.className} ${n.disabled?`disabled`:``} ${n.singleLine?`single-line`:``}`,onclick:()=>{f?.focus()},onunmount:()=>{r?.forEach(e=>e?.unwatch())}},t.div({className:`code-editor-container`},f,p,m))};var it=500;function at(e,n){return e=typeof e==`string`?e:``,e?((!Prism.languages[n]||e.length>it)&&(n=`plain`),Prism.highlight(e,Prism.languages[n],n)):``}var ot=new RegExp(/[\p{Alphabetic}\p{Number}_@:\."'{}]/,`u`);function st(e,n){let r=n;for(let i=n-1;i>=0&&ot.test(e[i]);i--)r=i;let i=r;for(let r=n-1;rdocument.documentElement.clientHeight&&(o=Math.max(n.top-r,0)),a+i>document.documentElement.clientWidth&&(a=Math.max(document.documentElement.clientWidth-i,0)),e.style.left=a+`px`,e.style.top=o+`px`}window.app=window.app||{},window.app.components=window.app.components||{},window.app.components.codeBlockTabs=function(e={}){let n=store({rid:void 0,id:void 0,hidden:void 0,inert:void 0,className:``,activeTabIndex:0,historyKey:``,tabs:[],get activeTab(){return n.tabs[n.activeTabIndex]||n.tabs[0]}}),r=app.utils.extendStore(n,e);return r.push(watch(()=>n.activeTabIndex,(e,r)=>{r!=null&&n.historyKey&&localStorage.setItem(n.historyKey,e)})),t.div({rid:n.rid,id:()=>n.id,hidden:()=>n.hidden||!n.tabs.length,inert:()=>n.inert,className:()=>`code-block-tabs ${n.className}`,onmount:()=>{n.historyKey&&(n.activeTabIndex=localStorage.getItem(n.historyKey)<<0)},onunmount:()=>{r.forEach(e=>e?.unwatch())}},t.header({className:`tabs-header`},()=>n.tabs.map((e,r)=>t.button({type:`button`,className:()=>`tab-item ${n.activeTabIndex==r?`active`:``}`,onclick:()=>n.activeTabIndex=r},n=>typeof e.title==`function`?e.title(n):e.title))),t.div({className:`code-block-tabs-content`},()=>{if(n.activeTab)return app.components.codeBlock(n.activeTab)}))},window.app=window.app||{},window.app.components=window.app.components||{},window.app.components.select=function(e={}){let n=store({rid:void 0,id:void 0,name:void 0,hidden:void 0,inert:void 0,className:``,value:void 0,options:[],before:null,after:null,max:1,searchThreshold:6,required:!1,disabled:!1,placeholder:`- Select -`,noItemsFoundText:`No items found`,onchange:function(e){},ondropdowntoggle:function(e){}}),r=app.utils.extendStore(n,e);n.max<=0&&(n.max=1);let i=store({selected:[],search:``,get hasSearch(){return i.search?.length>0},get allowRemove(){return!n.disabled&&(!n.required||n.max>1)}});function a(){if(n.value===void 0)return;let e=app.utils.toArray(n.value,!0),r=e.slice(0,n.max||1);e.length!=r.length&&(console.warn(`[select] the provided select values (${e.length}) are more than the allowed max selected options (${r.length}):`,e),n.value=n.max>1?r:r[0]),i.selected=e.map(e=>n.options.find(n=>n.value===e)).filter(Boolean)}r.push(watch(()=>n.value,()=>a()));async function o(e){let r=i.selected.findIndex(n=>n.value===e.value);if(r>=0){if(!i.allowRemove){f?.hidePopover();return}i.selected.splice(r,1)}else{let r=i.selected.length-n.max;for(;r>=0;)i.selected.pop(),r--;i.selected.push(e)}n.max<=1&&f?.hidePopover(),n.onchange&&(await n.onchange(i.selected),a()),p?.isConnected&&p.dispatchEvent(new CustomEvent(`change`,{detail:i.selected,bubbles:!0}))}function s(e){return i.selected.findIndex(n=>n.value===e.value)>=0}let c=t.input({type:`text`,placeholder:`Search...`,value:()=>i.search,oninput:e=>i.search=e.target.value});function l(e=!1){i.search=``,e&&c?.focus()}let u=t.div({className:`txt-hint txt-center m-0 p-5`,hidden:!0},e=>typeof n.noItemsFoundText==`function`?n.noItemsFoundText(e):n.noItemsFoundText);async function d(){f&&(await new Promise(e=>setTimeout(e,0)),f.querySelector(`.select-option:not([hidden])`)?u.hidden=!0:u.hidden=!1)}let f=t.div({tabIndex:-1,popover:`auto`,className:`dropdown`,onbeforetoggle:e=>(e.newState==`closed`&&l(),n.ondropdowntoggle?.(e))},t.div({className:`fields dropdown-search`,hidden:()=>n.options.length!i.hasSearch},t.button({type:`button`,title:`Clear`,className:`btn sm secondary transparent circle`,onclick:()=>l(!0)},t.i({className:`ri-close-line`,ariaHidden:!0})))),()=>n.before?.__raw||n.before,()=>n.options.map(e=>t.button({type:`button`,className:()=>`dropdown-item select-option ${s(e)?`active`:``}`,onclick:()=>(o(e),!1)},e.label||e.value)),u,()=>n.after?.__raw||n.after),p=t.button({type:`button`,id:()=>n.id,name:()=>n.name,disabled:()=>n.disabled,className:()=>`selected-container ${n.className}`,popoverTargetElement:f,onclick:e=>{e.stopPropagation()}},()=>i.selected.length?i.selected.map(e=>t.div({className:`selected-item`},e.selected||e.label||e.value,()=>{if(i.allowRemove)return t.i({tabIndex:-1,role:`button`,className:`ri-close-line link-hint btn-option-unset`,ariaLabel:app.attrs.tooltip(`Unset`),onclick:()=>(o(e),!1)})})):t.span({rid:`selected-placeholder`,className:`placeholder`},e=>typeof n.placeholder==`function`?n.placeholder(e):n.placeholder));r.push(watch(()=>n.options,()=>{d()}));let m;return r.push(watch(()=>i.search,()=>{let e=i.search.toLowerCase().replaceAll(` `,``);clearTimeout(m),m=setTimeout(()=>{let n=f.querySelectorAll(`.select-option`);e.length?n.forEach(n=>{n.hidden=!n.textContent.toLowerCase().replaceAll(` `,``).includes(e)}):n.forEach(e=>e.hidden=!1),d()},100)})),t.div({rid:n.rid,hidden:()=>n.hidden,inert:()=>n.inert,onmount:e=>{e.addEventListener(`focusout`,function(n){(!n.relatedTarget||!e.contains(n.relatedTarget))&&f?.hidePopover()})},onunmount:()=>{clearTimeout(m),r.forEach(e=>e.unwatch())},className:()=>[`input`,`select`,n.max>1?`multiple`:`single`,n.disabled?`disabled`:``,n.required?`required`:``].join(` `)},p,f)},window.app=window.app||{},window.app.components=window.app.components||{};var ft=Intl.DateTimeFormat().resolvedOptions().timeZone;window.app.components.formattedDate=function(e={}){let n=store({rid:void 0,id:void 0,hidden:void 0,value:``,short:!1}),r=app.utils.extendStore(n,e);return t.div({rid:n.rid,id:()=>n.id,hidden:()=>n.hidden,ariaDescription:app.attrs.tooltip(()=>n.short&&n.value?app.utils.toLocalDatetime(n.value)+` +`),n+=`)`,e.where&&(n+=` WHERE ${e.where}`),n},replaceIndexFields(e,n){let r=app.utils.parseIndex(e);return typeof n==`function`?Object.assign(r,n(r)||{}):Object.assign(r,n||{}),app.utils.buildIndex(r)},replaceIndexColumn(e,n,r){if(n===r)return e;let i=app.utils.parseIndex(e),a=!1;for(let e of i.columns)e.name===n&&(e.name=r,a=!0);return a?app.utils.buildIndex(i):e}};window.app=window.app||{},window.app.utils=f,window.app=window.app||{},window.app.utils=window.app.utils||{},window.app.utils.mimeTypes=[{ext:``,mimeType:`application/octet-stream`},{ext:`.xpm`,mimeType:`image/x-xpixmap`},{ext:`.7z`,mimeType:`application/x-7z-compressed`},{ext:`.zip`,mimeType:`application/zip`},{ext:`.xlsx`,mimeType:`application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`},{ext:`.docx`,mimeType:`application/vnd.openxmlformats-officedocument.wordprocessingml.document`},{ext:`.pptx`,mimeType:`application/vnd.openxmlformats-officedocument.presentationml.presentation`},{ext:`.epub`,mimeType:`application/epub+zip`},{ext:`.apk`,mimeType:`application/vnd.android.package-archive`},{ext:`.jar`,mimeType:`application/jar`},{ext:`.odt`,mimeType:`application/vnd.oasis.opendocument.text`},{ext:`.ott`,mimeType:`application/vnd.oasis.opendocument.text-template`},{ext:`.ods`,mimeType:`application/vnd.oasis.opendocument.spreadsheet`},{ext:`.ots`,mimeType:`application/vnd.oasis.opendocument.spreadsheet-template`},{ext:`.odp`,mimeType:`application/vnd.oasis.opendocument.presentation`},{ext:`.otp`,mimeType:`application/vnd.oasis.opendocument.presentation-template`},{ext:`.odg`,mimeType:`application/vnd.oasis.opendocument.graphics`},{ext:`.otg`,mimeType:`application/vnd.oasis.opendocument.graphics-template`},{ext:`.odf`,mimeType:`application/vnd.oasis.opendocument.formula`},{ext:`.odc`,mimeType:`application/vnd.oasis.opendocument.chart`},{ext:`.sxc`,mimeType:`application/vnd.sun.xml.calc`},{ext:`.pdf`,mimeType:`application/pdf`},{ext:`.fdf`,mimeType:`application/vnd.fdf`},{ext:``,mimeType:`application/x-ole-storage`},{ext:`.msi`,mimeType:`application/x-ms-installer`},{ext:`.aaf`,mimeType:`application/octet-stream`},{ext:`.msg`,mimeType:`application/vnd.ms-outlook`},{ext:`.xls`,mimeType:`application/vnd.ms-excel`},{ext:`.pub`,mimeType:`application/vnd.ms-publisher`},{ext:`.ppt`,mimeType:`application/vnd.ms-powerpoint`},{ext:`.doc`,mimeType:`application/msword`},{ext:`.ps`,mimeType:`application/postscript`},{ext:`.psd`,mimeType:`image/vnd.adobe.photoshop`},{ext:`.p7s`,mimeType:`application/pkcs7-signature`},{ext:`.ogg`,mimeType:`application/ogg`},{ext:`.oga`,mimeType:`audio/ogg`},{ext:`.ogv`,mimeType:`video/ogg`},{ext:`.png`,mimeType:`image/png`},{ext:`.png`,mimeType:`image/vnd.mozilla.apng`},{ext:`.jpg`,mimeType:`image/jpeg`},{ext:`.jxl`,mimeType:`image/jxl`},{ext:`.jp2`,mimeType:`image/jp2`},{ext:`.jpf`,mimeType:`image/jpx`},{ext:`.jpm`,mimeType:`image/jpm`},{ext:`.jxs`,mimeType:`image/jxs`},{ext:`.gif`,mimeType:`image/gif`},{ext:`.webp`,mimeType:`image/webp`},{ext:`.exe`,mimeType:`application/vnd.microsoft.portable-executable`},{ext:``,mimeType:`application/x-elf`},{ext:``,mimeType:`application/x-object`},{ext:``,mimeType:`application/x-executable`},{ext:`.so`,mimeType:`application/x-sharedlib`},{ext:``,mimeType:`application/x-coredump`},{ext:`.a`,mimeType:`application/x-archive`},{ext:`.deb`,mimeType:`application/vnd.debian.binary-package`},{ext:`.tar`,mimeType:`application/x-tar`},{ext:`.xar`,mimeType:`application/x-xar`},{ext:`.bz2`,mimeType:`application/x-bzip2`},{ext:`.fits`,mimeType:`application/fits`},{ext:`.tiff`,mimeType:`image/tiff`},{ext:`.bmp`,mimeType:`image/bmp`},{ext:`.ico`,mimeType:`image/x-icon`},{ext:`.mp3`,mimeType:`audio/mpeg`},{ext:`.flac`,mimeType:`audio/flac`},{ext:`.midi`,mimeType:`audio/midi`},{ext:`.ape`,mimeType:`audio/ape`},{ext:`.mpc`,mimeType:`audio/musepack`},{ext:`.amr`,mimeType:`audio/amr`},{ext:`.wav`,mimeType:`audio/wav`},{ext:`.aiff`,mimeType:`audio/aiff`},{ext:`.au`,mimeType:`audio/basic`},{ext:`.mpeg`,mimeType:`video/mpeg`},{ext:`.mov`,mimeType:`video/quicktime`},{ext:`.mp4`,mimeType:`video/mp4`},{ext:`.avif`,mimeType:`image/avif`},{ext:`.3gp`,mimeType:`video/3gpp`},{ext:`.3g2`,mimeType:`video/3gpp2`},{ext:`.mp4`,mimeType:`audio/mp4`},{ext:`.mqv`,mimeType:`video/quicktime`},{ext:`.m4a`,mimeType:`audio/x-m4a`},{ext:`.m4v`,mimeType:`video/x-m4v`},{ext:`.heic`,mimeType:`image/heic`},{ext:`.heic`,mimeType:`image/heic-sequence`},{ext:`.heif`,mimeType:`image/heif`},{ext:`.heif`,mimeType:`image/heif-sequence`},{ext:`.mj2`,mimeType:`video/mj2`},{ext:`.dvb`,mimeType:`video/vnd.dvb.file`},{ext:`.webm`,mimeType:`video/webm`},{ext:`.avi`,mimeType:`video/x-msvideo`},{ext:`.flv`,mimeType:`video/x-flv`},{ext:`.mkv`,mimeType:`video/x-matroska`},{ext:`.asf`,mimeType:`video/x-ms-asf`},{ext:`.asf`,mimeType:`video/x-ms-wmv`},{ext:`.asf`,mimeType:`video/asf`},{ext:`.aac`,mimeType:`audio/aac`},{ext:`.voc`,mimeType:`audio/x-unknown`},{ext:`.m3u`,mimeType:`application/vnd.apple.mpegurl`},{ext:`.rmvb`,mimeType:`application/vnd.rn-realmedia-vbr`},{ext:`.gz`,mimeType:`application/gzip`},{ext:`.class`,mimeType:`application/x-java-applet`},{ext:`.swf`,mimeType:`application/x-shockwave-flash`},{ext:`.crx`,mimeType:`application/x-chrome-extension`},{ext:`.ttf`,mimeType:`font/ttf`},{ext:`.woff`,mimeType:`font/woff`},{ext:`.woff2`,mimeType:`font/woff2`},{ext:`.otf`,mimeType:`font/otf`},{ext:`.ttc`,mimeType:`font/collection`},{ext:`.eot`,mimeType:`application/vnd.ms-fontobject`},{ext:`.wasm`,mimeType:`application/wasm`},{ext:`.shx`,mimeType:`application/vnd.shx`},{ext:`.shp`,mimeType:`application/vnd.shp`},{ext:`.dbf`,mimeType:`application/x-dbf`},{ext:`.dcm`,mimeType:`application/dicom`},{ext:`.rar`,mimeType:`application/x-rar-compressed`},{ext:`.djvu`,mimeType:`image/vnd.djvu`},{ext:`.mobi`,mimeType:`application/x-mobipocket-ebook`},{ext:`.lit`,mimeType:`application/x-ms-reader`},{ext:`.bpg`,mimeType:`image/bpg`},{ext:`.cbor`,mimeType:`application/cbor`},{ext:`.sqlite`,mimeType:`application/vnd.sqlite3`},{ext:`.dwg`,mimeType:`image/vnd.dwg`},{ext:`.nes`,mimeType:`application/vnd.nintendo.snes.rom`},{ext:`.lnk`,mimeType:`application/x-ms-shortcut`},{ext:`.macho`,mimeType:`application/x-mach-binary`},{ext:`.qcp`,mimeType:`audio/qcelp`},{ext:`.icns`,mimeType:`image/x-icns`},{ext:`.hdr`,mimeType:`image/vnd.radiance`},{ext:`.mrc`,mimeType:`application/marc`},{ext:`.mdb`,mimeType:`application/x-msaccess`},{ext:`.accdb`,mimeType:`application/x-msaccess`},{ext:`.zst`,mimeType:`application/zstd`},{ext:`.cab`,mimeType:`application/vnd.ms-cab-compressed`},{ext:`.rpm`,mimeType:`application/x-rpm`},{ext:`.xz`,mimeType:`application/x-xz`},{ext:`.lz`,mimeType:`application/lzip`},{ext:`.torrent`,mimeType:`application/x-bittorrent`},{ext:`.cpio`,mimeType:`application/x-cpio`},{ext:``,mimeType:`application/tzif`},{ext:`.xcf`,mimeType:`image/x-xcf`},{ext:`.pat`,mimeType:`image/x-gimp-pat`},{ext:`.gbr`,mimeType:`image/x-gimp-gbr`},{ext:`.glb`,mimeType:`model/gltf-binary`},{ext:`.cab`,mimeType:`application/x-installshield`},{ext:`.jxr`,mimeType:`image/jxr`},{ext:`.parquet`,mimeType:`application/vnd.apache.parquet`},{ext:`.txt`,mimeType:`text/plain`},{ext:`.html`,mimeType:`text/html`},{ext:`.svg`,mimeType:`image/svg+xml`},{ext:`.xml`,mimeType:`text/xml`},{ext:`.rss`,mimeType:`application/rss+xml`},{ext:`.atom`,mimeType:`application/atom+xml`},{ext:`.x3d`,mimeType:`model/x3d+xml`},{ext:`.kml`,mimeType:`application/vnd.google-earth.kml+xml`},{ext:`.xlf`,mimeType:`application/x-xliff+xml`},{ext:`.dae`,mimeType:`model/vnd.collada+xml`},{ext:`.gml`,mimeType:`application/gml+xml`},{ext:`.gpx`,mimeType:`application/gpx+xml`},{ext:`.tcx`,mimeType:`application/vnd.garmin.tcx+xml`},{ext:`.amf`,mimeType:`application/x-amf`},{ext:`.3mf`,mimeType:`application/vnd.ms-package.3dmanufacturing-3dmodel+xml`},{ext:`.xfdf`,mimeType:`application/vnd.adobe.xfdf`},{ext:`.owl`,mimeType:`application/owl+xml`},{ext:`.php`,mimeType:`text/x-php`},{ext:`.js`,mimeType:`text/javascript`},{ext:`.lua`,mimeType:`text/x-lua`},{ext:`.pl`,mimeType:`text/x-perl`},{ext:`.py`,mimeType:`text/x-python`},{ext:`.json`,mimeType:`application/json`},{ext:`.geojson`,mimeType:`application/geo+json`},{ext:`.har`,mimeType:`application/json`},{ext:`.ndjson`,mimeType:`application/x-ndjson`},{ext:`.rtf`,mimeType:`text/rtf`},{ext:`.srt`,mimeType:`application/x-subrip`},{ext:`.tcl`,mimeType:`text/x-tcl`},{ext:`.csv`,mimeType:`text/csv`},{ext:`.tsv`,mimeType:`text/tab-separated-values`},{ext:`.vcf`,mimeType:`text/vcard`},{ext:`.ics`,mimeType:`text/calendar`},{ext:`.warc`,mimeType:`application/warc`},{ext:`.vtt`,mimeType:`text/vtt`},{ext:`.pbm`,mimeType:`image/x-portable-bitmap`},{ext:`.pgm`,mimeType:`image/x-portable-graymap`},{ext:`.ppm`,mimeType:`image/x-portable-pixmap`},{ext:`.eml`,mimeType:`message/rfc822`}];var p=new BroadcastChannel(`tabsSync`),m=`pbSettings`,h=`pbColorScheme`;window.app=window.app||{},window.app.store=store({_ready:!1,superuser:null,showHeader:!0,page:t.div({className:`page`},()=>{if(!app.store._ready)return t.span({className:`loader lg m-auto`,title:`Loading plugins...`})}),mainLogo:`./images/logo.svg`,headerLogo:`./images/logo_white.svg`,favicon:``,title:``,_mediaColorScheme:``,userColorScheme:window.localStorage.getItem(h)||``,get activeColorScheme(){return app.store.userColorScheme?app.store.userColorScheme:app.store._mediaColorScheme||`light`},errors:null,creditLinks:[{href:`https://pocketbase.io/docs`,icon:`ri-book-open-line`,label:`Docs`},{href:`https://github.com/pocketbase/pocketbase/releases`,icon:`ri-github-line`,label:`PocketBase v0.40.0`}],headerLinks:[{href:`#/collections`,icon:`ri-database-2-line`,label:`Collections`},{href:`#/logs`,icon:`ri-bar-chart-box-line`,label:`Logs`},{href:`#/settings`,icon:`ri-settings-3-line`,label:`Settings`}],settingsNavGroups:{System:[{href:`#/settings`,icon:`ri-home-gear-line`,label:`Application`},{href:`#/settings/mail`,icon:`ri-send-plane-2-line`,label:`Mail settings`},{href:`#/settings/storage`,icon:`ri-archive-drawer-line`,label:`Files storage`},{href:`#/settings/backups`,icon:`ri-archive-line`,label:`Backups`},{href:`#/settings/crons`,icon:`ri-time-line`,label:`Crons`}],Sync:[{href:`#/settings/export-collections`,icon:`ri-uninstall-line`,label:`Export collections`},{href:`#/settings/import-collections`,icon:`ri-install-line`,label:`Import collections`}],Debug:[{href:`#/settings/sql`,icon:`ri-terminal-box-line`,label:`SQL console`}]},predefinedAccentColors:[`#1055c9`,`#a3142a`,`#096d5c`,`#e6620a`,`#007d9c`,`#3f3da9`],settings:app.utils.getLocalHistory(m,{}),isLoadingSettings:!1,async loadSettings(){app.store.isLoadingSettings=!0;try{let e=await app.pb.settings.getAll({requestKey:`appStore.loadSettings`});app.store.settings=e,app.store.isLoadingSettings=!1}catch(e){e.isAbort||(app.store.isLoadingSettings=!1,app.checkApiError(e))}},collections:[],collectionScaffolds:{},isLoadingCollections:!1,_activeCollectionIdOrName:``,get activeCollection(){let e=app.store._activeCollectionIdOrName;return app.store.collections.find(n=>n.id==e||n.name==e)||app.store.collections[0]},set activeCollection(e){typeof e==`string`?app.store._activeCollectionIdOrName=e:app.store._activeCollectionIdOrName=e?.id},async silentlyReloadCollections(){try{let e=await app.pb.collections.getFullList({requestKey:`appStore.silentlyReloadCollections`});e=app.utils.sortedCollectionsByType(e),JSON.stringify(e)!=JSON.stringify(app.store.collections)&&(app.store.collections=e)}catch(e){e.isAbort||console.warn(`failed to reload app store collections:`,e)}},async loadCollections(e=null){app.store.isLoadingCollections=!0;try{let[n,r]=await Promise.all([app.pb.collections.getScaffolds({requestKey:`appStore.loadCollections.getScaffolds`}),app.pb.collections.getFullList({requestKey:`appStore.loadCollections.getFullList`})]);r=app.utils.sortedCollectionsByType(r),JSON.stringify(app.store.collections)!=JSON.stringify(r)&&(app.store.collections=r),app.store.collectionScaffolds=n,app.store._activeCollectionIdOrName=e||app.store._activeCollectionIdOrName||app.store.collections[0]?.id||``,app.store.isLoadingCollections=!1}catch(e){e.isAbort||(app.store.isLoadingCollections=!1,app.checkApiError(e))}},addOrUpdateCollection(e){let n=app.store.collections.findIndex(n=>n.id==e.id);n>=0?(app.store.activeCollection?.id==e.id&&(app.store._activeCollectionIdOrName=e.id),app.store.collections[n]=e):app.store.collections.push(e),app.store.collections=app.utils.sortedCollectionsByType(app.store.collections)},oauth2Providers:[],isLoadingOAuth2Providers:!1,async loadOAuth2Providers(){app.store.isLoadingOAuth2Providers=!0;try{app.store.oauth2Providers=await app.pb.collections.getAllOAuth2Providers(),app.store.isLoadingOAuth2Providers=!1}catch(e){e.isAbort||(app.checkApiError(e),app.store.isLoadingOAuth2Providers=!1)}}}),window.addEventListener(`hashchange`,()=>{app.store.title=``,app.store.errors=null}),watch(()=>{let e=app.utils.toArray(app.store.title),n=app.store.settings?.meta?.appName||``;n&&e.push(n),document.title=e.join(` - `)});var g;watch(()=>app.store.settings?.meta?.accentColor,e=>{g||(g=t.meta({name:`theme-color`}),document.head.appendChild(g)),e?(g?.setAttribute(`content`,e),document.documentElement.style.setProperty(`--accentColor`,e)):(g?.removeAttribute(`content`),document.documentElement.style.removeProperty(`--accentColor`))});var _;watch(()=>app.store.favicon,e=>{_||(_=t.link({rel:`icon`}),document.head.appendChild(_)),e?_.href=e:_.href=window.location.href.startsWith(`https://`)?`./images/favicon_prod.png`:`./images/favicon.png`});var v=window.matchMedia(`(prefers-color-scheme: dark)`);app.store._mediaColorScheme=v.matches?`dark`:`light`,v.addEventListener(`change`,({matches:e})=>{app.store._mediaColorScheme=e?`dark`:`light`}),watch(()=>app.store.userColorScheme,e=>{e?window.localStorage.setItem(h,e):window.localStorage.removeItem(h),p?.postMessage({colorScheme:e})});var ee;watch(()=>app.store.activeColorScheme,e=>{clearTimeout(ee),document.documentElement.style.setProperty(`--animationSpeed`,`0`),document.documentElement.setAttribute(`data-color-scheme`,e),ee=setTimeout(()=>{document.documentElement.style.removeProperty(`--animationSpeed`)},100)});function te(e,n){e.__errListener&&=(e.removeEventListener(`input`,e.__errListener),e.removeEventListener(`change`,e.__errListener),null),e.setCustomValidity&&(e.setCustomValidity(``),e._oldTitle?e.setAttribute(`title`,e._oldTitle):e.removeAttribute(`title`)),e.removeAttribute(`data-error`);let r=n.nextSibling;r&&r.classList?.contains(`generated-error`)&&r.getAttribute(`data-input-name`)==e.getAttribute(`name`)&&r.remove(),n.querySelector(`[data-error]`)||n.classList.remove(`error`)}watch(()=>JSON.stringify(app.store.errors)&&app.store.errors,e=>{let n=document.querySelectorAll(`[name]`);for(let r of n){if(r.classList.contains(`no-error`))continue;let n=r.closest(`.field-list`)||r.closest(`.fields`)||r.closest(`.field`);if(!n)continue;let i=r.getAttribute(`name`);te(r,n);let a=app.utils.getByPath(e,i),o=a?.message||``;if(!o&&!app.utils.isEmpty(a)){let e=[];for(let n in a)a[n]?.message&&e.push(`${n}: ${a[n]?.message}`);o=e.join(` +`)}o&&(n.classList.add(`error`),r.__errListener=function(){te(r,n),app.utils.deleteByPath(app.store.errors,i)},r.addEventListener(`input`,r.__errListener),r.addEventListener(`change`,r.__errListener),r.setAttribute(`data-error`,!0),r.setCustomValidity&&r.reportValidity&&r.classList.contains(`inline-error`)?(r.setCustomValidity(o),r.reportValidity(),r._oldTitle=r.title,r.title=o):n.after(t.div({"html-data-input-name":i,className:`field-help error generated-error`,textContent:o})))}}),p.onmessage=e=>{e.data?.collections&&JSON.stringify(app.store.collections)!=JSON.stringify(e.data.collections)&&(app.store.collections=e.data.collections),e.data?.settings&&JSON.stringify(app.store.settings)!=JSON.stringify(e.data.settings)&&(app.store.settings=e.data.settings),e.data?.colorScheme&&(app.store.userColorScheme=e.data.colorScheme)},watch(()=>JSON.stringify(app.store.collections),(e,n)=>{e&&e!=`[]`&&n&&n!=`[]`&&e!=n&&p?.postMessage({collections:JSON.parse(e)})}),watch(()=>JSON.stringify(app.store.settings),(e,n)=>{e&&e!=`{}`&&n&&n!=`{}`&&e!=n&&p?.postMessage({settings:JSON.parse(e)}),window.localStorage.setItem(m,e)});var y=class e extends Error{constructor(n){super(`ClientResponseError`),this.url=``,this.status=0,this.response={},this.isAbort=!1,this.originalError=null,Object.setPrototypeOf(this,e.prototype),typeof n==`object`&&n&&(this.originalError=n.originalError,this.url=typeof n.url==`string`?n.url:``,this.status=typeof n.status==`number`?n.status:0,this.isAbort=!!n.isAbort||n.name===`AbortError`||n.message===`Aborted`,this.response=n.response!==null&&typeof n.response==`object`?n.response:n.data!==null&&typeof n.data==`object`?n.data:{}),this.originalError||n instanceof e||(this.originalError=n),this.name=`ClientResponseError `+this.status,this.message=this.response?.message,this.message||=this.isAbort?`The request was aborted (most likely autocancelled; you can find more info in https://github.com/pocketbase/js-sdk#auto-cancellation).`:this.originalError?.cause?.message?.includes(`ECONNREFUSED ::1`)?`Failed to connect to the PocketBase server. Try changing the SDK URL from localhost to 127.0.0.1 (https://github.com/pocketbase/js-sdk/issues/21).`:`Something went wrong.`,this.cause=this.originalError}get data(){return this.response}toJSON(){return{...this}}},ne=/^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;function re(e,n){let r={};if(typeof e!=`string`)return r;let i=Object.assign({},n||{}).decode||ae,a=0;for(;a0&&(!r.exp||r.exp-n>Date.now()/1e3))}oe=typeof atob!=`function`||x?e=>{let n=String(e).replace(/=+$/,``);if(n.length%4==1)throw Error(`'atob' failed: The string to be decoded is not correctly encoded.`);for(var r,i,a=0,o=0,s=``;i=n.charAt(o++);~i&&(r=a%4?64*r+i:i,a++%4)&&(s+=String.fromCharCode(255&r>>(-2*a&6))))i=`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=`.indexOf(i);return s}:atob;var C=`pb_auth`,ce=class{constructor(){this.baseToken=``,this.baseModel=null,this._onChangeCallbacks=[]}get token(){return this.baseToken}get record(){return this.baseModel}get model(){return this.baseModel}get isValid(){return!se(this.token)}get isSuperuser(){let e=S(this.token);return e.type==`auth`&&(this.record?.collectionName==`_superusers`||!this.record?.collectionName&&e.collectionId==`pbc_3142635823`)}get isAdmin(){return console.warn(`Please replace pb.authStore.isAdmin with pb.authStore.isSuperuser OR simply check the value of pb.authStore.record?.collectionName`),this.isSuperuser}get isAuthRecord(){return console.warn(`Please replace pb.authStore.isAuthRecord with !pb.authStore.isSuperuser OR simply check the value of pb.authStore.record?.collectionName`),S(this.token).type==`auth`&&!this.isSuperuser}save(e,n){this.baseToken=e||``,this.baseModel=n||null,this.triggerChange()}clear(){this.baseToken=``,this.baseModel=null,this.triggerChange()}loadFromCookie(e,n=C){let r=re(e||``)[n]||``,i={};try{i=JSON.parse(r),(typeof i!=`object`||Array.isArray(i))&&(i={})}catch{}this.save(i.token||``,i.record||i.model||null)}exportToCookie(e,n=C){let r={secure:!0,sameSite:!0,httpOnly:!0,path:`/`},i=S(this.token);r.expires=i?.exp?new Date(1e3*i.exp):new Date(`1970-01-01`),e=Object.assign({},r,e);let a={token:this.token,record:this.record?JSON.parse(JSON.stringify(this.record)):null},o=ie(n,JSON.stringify(a),e),s=typeof Blob<`u`?new Blob([o]).size:o.length;if(a.record&&s>4096){a.record={id:a.record?.id,email:a.record?.email};let r=[`collectionId`,`collectionName`,`verified`];for(let e in this.record)r.includes(e)&&(a.record[e]=this.record[e]);o=ie(n,JSON.stringify(a),e)}return o}onChange(e,n=!1){return this._onChangeCallbacks.push(e),n&&e(this.token,this.record),()=>{for(let n=this._onChangeCallbacks.length-1;n>=0;n--)if(this._onChangeCallbacks[n]==e)return delete this._onChangeCallbacks[n],void this._onChangeCallbacks.splice(n,1)}}triggerChange(){for(let e of this._onChangeCallbacks)e&&e(this.token,this.record)}},w=class extends ce{constructor(e=`pocketbase_auth`){super(),this.storageFallback={},this.storageKey=e,this._bindStorageEvent()}get token(){return(this._storageGet(this.storageKey)||{}).token||``}get record(){let e=this._storageGet(this.storageKey)||{};return e.record||e.model||null}get model(){return this.record}save(e,n){this._storageSet(this.storageKey,{token:e,record:n}),super.save(e,n)}clear(){this._storageRemove(this.storageKey),super.clear()}_storageGet(e){if(typeof window<`u`&&window?.localStorage){let n=window.localStorage.getItem(e)||``;try{return JSON.parse(n)}catch{return n}}return this.storageFallback[e]}_storageSet(e,n){if(typeof window<`u`&&window?.localStorage){let r=n;typeof n!=`string`&&(r=JSON.stringify(n)),window.localStorage.setItem(e,r)}else this.storageFallback[e]=n}_storageRemove(e){typeof window<`u`&&window?.localStorage&&window.localStorage?.removeItem(e),delete this.storageFallback[e]}_bindStorageEvent(){typeof window<`u`&&window?.localStorage&&window.addEventListener&&window.addEventListener(`storage`,(e=>{if(e.key!=this.storageKey)return;let n=this._storageGet(this.storageKey)||{};super.save(n.token||``,n.record||n.model||null)}))}},T=class{constructor(e){this.client=e}},E=class extends T{async getAll(e){return e=Object.assign({method:`GET`},e),this.client.send(`/api/settings`,e)}async update(e,n){return n=Object.assign({method:`PATCH`,body:e},n),this.client.send(`/api/settings`,n)}async testS3(e=`storage`,n){return n=Object.assign({method:`POST`,body:{filesystem:e}},n),this.client.send(`/api/settings/test/s3`,n).then((()=>!0))}async testEmail(e,n,r,i){return i=Object.assign({method:`POST`,body:{email:n,template:r,collection:e}},i),this.client.send(`/api/settings/test/email`,i).then((()=>!0))}async generateAppleClientSecret(e,n,r,i,a,o){return o=Object.assign({method:`POST`,body:{clientId:e,teamId:n,keyId:r,privateKey:i,duration:a}},o),this.client.send(`/api/settings/apple/generate-client-secret`,o)}},D=[`requestKey`,`$cancelKey`,`$autoCancel`,`fetch`,`headers`,`body`,`query`,`params`,`cache`,`credentials`,`headers`,`integrity`,`keepalive`,`method`,`mode`,`redirect`,`referrer`,`referrerPolicy`,`signal`,`window`];function O(e){if(e){e.query=e.query||{};for(let n in e)D.includes(n)||(e.query[n]=e[n],delete e[n])}}function k(e){let n=[];for(let r in e){let i=encodeURIComponent(r),a=Array.isArray(e[r])?e[r]:[e[r]];for(let e of a)e=A(e),e!==null&&n.push(i+`=`+e)}return n.join(`&`)}function A(e){return e==null?null:e instanceof Date?encodeURIComponent(e.toISOString().replace(`T`,` `)):encodeURIComponent(typeof e==`object`?JSON.stringify(e):e)}var j=class extends T{constructor(){super(...arguments),this.clientId=``,this.eventSource=null,this.subscriptions={},this.lastSentSubscriptions=[],this.maxConnectTimeout=15e3,this.reconnectAttempts=0,this.maxReconnectAttempts=1/0,this.predefinedReconnectIntervals=[200,300,500,1e3,1200,1500,2e3],this.pendingConnects=[],this.pendingSubmits=[],this.isProcessingPendingSubmits=!1}get isConnected(){return!!this.eventSource&&!!this.clientId&&!this.pendingConnects.length}async subscribe(e,n,r){if(!e)throw Error(`topic must be set.`);let i=e;if(r){O(r=Object.assign({},r));let e=`options=`+encodeURIComponent(JSON.stringify({query:r.query,headers:r.headers}));i+=(i.includes(`?`)?`&`:`?`)+e}let a=function(e){let r=e,i;try{i=JSON.parse(r?.data)}catch{}n(i||{})};return this.subscriptions[i]||(this.subscriptions[i]=[]),this.subscriptions[i].push(a),this.isConnected?this.subscriptions[i].length===1?await this.submitSubscriptions():this.eventSource?.addEventListener(i,a):await this.connect(),async()=>this.unsubscribeByTopicAndListener(e,a)}async unsubscribe(e){if(e){let n=this.getSubscriptionsByTopic(e);for(let e in n)if(this.hasSubscriptionListeners(e)){for(let n of this.subscriptions[e])this.eventSource?.removeEventListener(e,n);delete this.subscriptions[e]}}else this.subscriptions={};await this.submitSubscriptions()}async unsubscribeByPrefix(e){let n=!1;for(let r in this.subscriptions)if((r+`?`).startsWith(e)){n=!0;for(let e of this.subscriptions[r])this.eventSource?.removeEventListener(r,e);delete this.subscriptions[r]}n&&await this.submitSubscriptions()}async unsubscribeByTopicAndListener(e,n){let r=this.getSubscriptionsByTopic(e);for(let e in r){if(!Array.isArray(this.subscriptions[e])||!this.subscriptions[e].length)continue;let r=!1;for(let i=this.subscriptions[e].length-1;i>=0;i--)this.subscriptions[e][i]===n&&(r=!0,delete this.subscriptions[e][i],this.subscriptions[e].splice(i,1),this.eventSource?.removeEventListener(e,n));r&&(this.subscriptions[e].length||delete this.subscriptions[e])}await this.submitSubscriptions()}hasSubscriptionListeners(e){if(this.subscriptions=this.subscriptions||{},e)return!!this.subscriptions[e]?.length;for(let e in this.subscriptions)if(this.subscriptions[e]?.length)return!0;return!1}async submitSubscriptions(){return new Promise(((e,n)=>{this.pendingSubmits.push({resolve:e,reject:n}),this.pendingSubmits.length==1&&queueMicrotask((()=>this.finalizePendingSubscriptions()))}))}async finalizePendingSubscriptions(){if(this.isProcessingPendingSubmits||!this.pendingSubmits.length)return;let e=this.pendingSubmits.slice();this.pendingSubmits=[],this.isProcessingPendingSubmits=!0;try{await this.sendSubscriptions();for(let n of e)n.resolve()}catch(n){for(let r of e)n?r.reject(n):r.resolve()}finally{this.isProcessingPendingSubmits=!1,this.pendingSubmits.length>0&&await this.finalizePendingSubscriptions()}}getSubscriptionsCancelKey(){return`realtime_`+this.clientId}getSubscriptionsByTopic(e){let n={};e=e.includes(`?`)?e:e+`?`;for(let r in this.subscriptions)(r+`?`).startsWith(e)&&(n[r]=this.subscriptions[r]);return n}getNonEmptySubscriptionKeys(){let e=[];for(let n in this.subscriptions)this.subscriptions[n].length&&e.push(n);return e}hasUnsentSubscriptions(){let e=this.getNonEmptySubscriptionKeys();if(e.length!=this.lastSentSubscriptions.length)return!0;for(let n of e)if(!this.lastSentSubscriptions.includes(n))return!0;return!1}async sendSubscriptions(){if(this.clientId){if(!this.hasSubscriptionListeners())return this.disconnect();if(this.hasUnsentSubscriptions())return this.addAllSubscriptionListeners(),this.lastSentSubscriptions=this.getNonEmptySubscriptionKeys(),this.client.send(`/api/realtime`,{method:`POST`,body:{clientId:this.clientId,subscriptions:this.lastSentSubscriptions},requestKey:this.getSubscriptionsCancelKey()}).catch((e=>{if(!e?.isAbort)throw e}))}}addAllSubscriptionListeners(){if(this.eventSource){this.removeAllSubscriptionListeners();for(let e in this.subscriptions)for(let n of this.subscriptions[e])this.eventSource.addEventListener(e,n)}}removeAllSubscriptionListeners(){if(this.eventSource)for(let e in this.subscriptions)for(let n of this.subscriptions[e])this.eventSource.removeEventListener(e,n)}async connect(){if(!(this.reconnectAttempts>0))return new Promise(((e,n)=>{this.pendingConnects.push({resolve:e,reject:n}),this.pendingConnects.length==1&&queueMicrotask((()=>this.initConnect()))}))}initConnect(){this.disconnect(!0),clearTimeout(this.connectTimeoutId),this.connectTimeoutId=setTimeout((()=>{this.connectErrorHandler(Error(`EventSource connect took too long.`))}),this.maxConnectTimeout),this.eventSource=new EventSource(this.client.buildURL(`/api/realtime`)),this.eventSource.onerror=e=>{this.connectErrorHandler(Error(`Failed to establish realtime connection.`))},this.eventSource.addEventListener(`PB_CONNECT`,(e=>{let n=e;this.clientId=n?.lastEventId,this.lastSentSubscriptions=[],this.submitSubscriptions().then((async()=>{let n=3;for(;this.hasUnsentSubscriptions()&&n>0;)n--,await this.submitSubscriptions();for(let e of this.pendingConnects)e.resolve();this.pendingConnects=[],this.reconnectAttempts=0,clearTimeout(this.reconnectTimeoutId),clearTimeout(this.connectTimeoutId);let r=this.getSubscriptionsByTopic(`PB_CONNECT`);for(let n in r)for(let i of r[n])i(e);this.hasUnsentSubscriptions()&&await this.submitSubscriptions()})).catch((e=>{this.clientId=``,this.lastSentSubscriptions=[],this.connectErrorHandler(e)}))}))}connectErrorHandler(e){if(clearTimeout(this.connectTimeoutId),clearTimeout(this.reconnectTimeoutId),!this.clientId&&!this.reconnectAttempts||this.reconnectAttempts>this.maxReconnectAttempts){for(let n of this.pendingConnects)n.reject(new y(e));this.pendingConnects=[],this.disconnect();return}this.disconnect(!0);let n=this.predefinedReconnectIntervals[this.reconnectAttempts]||this.predefinedReconnectIntervals[this.predefinedReconnectIntervals.length-1];this.reconnectAttempts++,this.reconnectTimeoutId=setTimeout((()=>{this.initConnect()}),n)}disconnect(e=!1){if(this.clientId&&this.onDisconnect&&this.onDisconnect(Object.keys(this.subscriptions)),clearTimeout(this.connectTimeoutId),clearTimeout(this.reconnectTimeoutId),this.removeAllSubscriptionListeners(),this.client.cancelRequest(this.getSubscriptionsCancelKey()),this.eventSource?.close(),this.eventSource=null,this.clientId=``,this.lastSentSubscriptions=[],!e){this.reconnectAttempts=0;for(let e of this.pendingConnects)e.resolve();this.pendingConnects=[]}}},M=class extends T{decode(e){return e}async getFullList(e,n){if(typeof e==`number`)return this._getFullList(e,n);let r=1e3;return(n=Object.assign({},e,n)).batch&&(r=n.batch,delete n.batch),this._getFullList(r,n)}async getList(e=1,n=30,r){return(r=Object.assign({method:`GET`},r)).query=Object.assign({page:e,perPage:n},r.query),this.client.send(this.baseCrudPath,r).then((e=>(e.items=e.items?.map((e=>this.decode(e)))||[],e)))}async getFirstListItem(e,n){return(n=Object.assign({requestKey:`one_by_filter_`+this.baseCrudPath+`_`+e},n)).query=Object.assign({filter:e,skipTotal:1},n.query),this.getList(1,1,n).then((e=>{if(!e?.items?.length)throw new y({status:404,response:{code:404,message:`The requested resource wasn't found.`,data:{}}});return e.items[0]}))}async getOne(e,n){if(!e)throw new y({url:this.client.buildURL(this.baseCrudPath+`/`),status:404,response:{code:404,message:`Missing required record id.`,data:{}}});return n=Object.assign({method:`GET`},n),this.client.send(this.baseCrudPath+`/`+encodeURIComponent(e),n).then((e=>this.decode(e)))}async create(e,n){return n=Object.assign({method:`POST`,body:e},n),this.client.send(this.baseCrudPath,n).then((e=>this.decode(e)))}async update(e,n,r){return r=Object.assign({method:`PATCH`,body:n},r),this.client.send(this.baseCrudPath+`/`+encodeURIComponent(e),r).then((e=>this.decode(e)))}async delete(e,n){return n=Object.assign({method:`DELETE`},n),this.client.send(this.baseCrudPath+`/`+encodeURIComponent(e),n).then((()=>!0))}_getFullList(e=1e3,n){(n||={}).query=Object.assign({skipTotal:1},n.query);let r=[],i=async a=>this.getList(a,e||1e3,n).then((e=>{let n=e.items;return r=r.concat(n),n.length==e.perPage?i(a+1):r}));return i(1)}};function N(e,n,r,i){let a=i!==void 0;return a||r!==void 0?a?(console.warn(e),n.body=Object.assign({},n.body,r),n.query=Object.assign({},n.query,i),n):Object.assign(n,r):n}function le(e){e._resetAutoRefresh?.()}var ue=class extends M{constructor(e,n){super(e),this.collectionIdOrName=n}get baseCrudPath(){return this.baseCollectionPath+`/records`}get baseCollectionPath(){return`/api/collections/`+encodeURIComponent(this.collectionIdOrName)}get isSuperusers(){return this.collectionIdOrName==`_superusers`||this.collectionIdOrName==`_pbc_2773867675`}async subscribe(e,n,r){if(!e)throw Error(`Missing topic.`);if(!n)throw Error(`Missing subscription callback.`);return this.client.realtime.subscribe(this.collectionIdOrName+`/`+e,n,r)}async unsubscribe(e){return e?this.client.realtime.unsubscribe(this.collectionIdOrName+`/`+e):this.client.realtime.unsubscribeByPrefix(this.collectionIdOrName)}async getFullList(e,n){if(typeof e==`number`)return super.getFullList(e,n);let r=Object.assign({},e,n);return super.getFullList(r)}async getList(e=1,n=30,r){return super.getList(e,n,r)}async getFirstListItem(e,n){return super.getFirstListItem(e,n)}async getOne(e,n){return super.getOne(e,n)}async create(e,n){return super.create(e,n)}async update(e,n,r){return super.update(e,n,r).then((e=>{if(this.client.authStore.record?.id===e?.id&&(this.client.authStore.record?.collectionId===this.collectionIdOrName||this.client.authStore.record?.collectionName===this.collectionIdOrName)){let n=Object.assign({},this.client.authStore.record.expand),r=Object.assign({},this.client.authStore.record,e);n&&(r.expand=Object.assign(n,e.expand)),this.client.authStore.save(this.client.authStore.token,r)}return e}))}async delete(e,n){return super.delete(e,n).then((n=>(!n||this.client.authStore.record?.id!==e||this.client.authStore.record?.collectionId!==this.collectionIdOrName&&this.client.authStore.record?.collectionName!==this.collectionIdOrName||this.client.authStore.clear(),n)))}authResponse(e){let n=this.decode(e?.record||{});return this.client.authStore.save(e?.token,n),Object.assign({},e,{token:e?.token||``,record:n})}async listAuthMethods(e){return e=Object.assign({method:`GET`,fields:`mfa,otp,password,oauth2`},e),this.client.send(this.baseCollectionPath+`/auth-methods`,e)}async authWithPassword(e,n,r){let i;r=Object.assign({method:`POST`,body:{identity:e,password:n}},r),this.isSuperusers&&(i=r.autoRefreshThreshold,delete r.autoRefreshThreshold,r.autoRefresh||le(this.client));let a=await this.client.send(this.baseCollectionPath+`/auth-with-password`,r);return a=this.authResponse(a),i&&this.isSuperusers&&function(e,n,r,i){le(e);let a=e.beforeSend,o=e.authStore.record,s=e.authStore.onChange(((n,r)=>{(!n||r?.id!=o?.id||(r?.collectionId||o?.collectionId)&&r?.collectionId!=o?.collectionId)&&le(e)}));e._resetAutoRefresh=function(){s(),e.beforeSend=a,delete e._resetAutoRefresh},e.beforeSend=async(o,s)=>{let c=e.authStore.token;if(s.query?.autoRefresh)return a?a(o,s):{url:o,sendOptions:s};let l=e.authStore.isValid;if(l&&se(e.authStore.token,n))try{await r()}catch{l=!1}l||await i();let u=s.headers||{};for(let n in u)if(n.toLowerCase()==`authorization`&&c==u[n]&&e.authStore.token){u[n]=e.authStore.token;break}return s.headers=u,a?a(o,s):{url:o,sendOptions:s}}}(this.client,i,(()=>this.authRefresh({autoRefresh:!0})),(()=>this.authWithPassword(e,n,Object.assign({autoRefresh:!0},r)))),a}async authWithOAuth2Code(e,n,r,i,a,o,s){let c={method:`POST`,body:{provider:e,code:n,codeVerifier:r,redirectURL:i,createData:a}};return c=N(`This form of authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createData?, body?, query?) is deprecated. Consider replacing it with authWithOAuth2Code(provider, code, codeVerifier, redirectURL, createData?, options?).`,c,o,s),this.client.send(this.baseCollectionPath+`/auth-with-oauth2`,c).then((e=>this.authResponse(e)))}authWithOAuth2(...e){if(e.length>1||typeof e?.[0]==`string`)return console.warn(`PocketBase: This form of authWithOAuth2() is deprecated and may get removed in the future. Please replace with authWithOAuth2Code() OR use the authWithOAuth2() realtime form as shown in https://pocketbase.io/docs/authentication/#oauth2-integration.`),this.authWithOAuth2Code(e?.[0]||``,e?.[1]||``,e?.[2]||``,e?.[3]||``,e?.[4]||{},e?.[5]||{},e?.[6]||{});let n=e?.[0]||{},r=null;n.urlCallback||(r=de(void 0));let i=new j(this.client);function a(){r?.close(),i.unsubscribe()}let o={},s=n.requestKey;return s&&(o.requestKey=s),this.listAuthMethods(o).then((e=>{let o=e.oauth2.providers.find((e=>e.name===n.provider));if(!o)throw new y(Error(`Missing or invalid provider "${n.provider}".`));let c=this.client.buildURL(`/api/oauth2-redirect`);return new Promise((async(e,l)=>{let u=s?this.client.cancelControllers?.[s]:void 0;u&&(u.signal.onabort=()=>{a(),l(new y({isAbort:!0,message:`manually cancelled`}))}),i.onDisconnect=e=>{e.length&&l&&(a(),l(new y(Error(`realtime connection interrupted`))))};try{await i.subscribe(`@oauth2`,(async r=>{let s=i.clientId;try{if(!r.state||s!==r.state)throw Error(`State parameters don't match.`);if(r.error||!r.code)throw Error(`OAuth2 redirect error or missing code: `+r.error);let i=Object.assign({},n);delete i.provider,delete i.scopes,delete i.createData,delete i.urlCallback,u?.signal?.onabort&&(u.signal.onabort=null),e(await this.authWithOAuth2Code(o.name,r.code,o.codeVerifier,c,n.createData,i))}catch(e){l(new y(e))}a()}));let s={state:i.clientId};n.scopes?.length&&(s.scope=n.scopes.join(` `));let d=this._replaceQueryParams(o.authURL+c,s);await(n.urlCallback||function(e){r?r.location.href=e:r=de(e)})(d)}catch(e){u?.signal?.onabort&&(u.signal.onabort=null),a(),l(new y(e))}}))})).catch((e=>{throw a(),e}))}async authRefresh(e,n){let r={method:`POST`};return r=N(`This form of authRefresh(body?, query?) is deprecated. Consider replacing it with authRefresh(options?).`,r,e,n),this.client.send(this.baseCollectionPath+`/auth-refresh`,r).then((e=>this.authResponse(e)))}async requestPasswordReset(e,n,r){let i={method:`POST`,body:{email:e}};return i=N(`This form of requestPasswordReset(email, body?, query?) is deprecated. Consider replacing it with requestPasswordReset(email, options?).`,i,n,r),this.client.send(this.baseCollectionPath+`/request-password-reset`,i).then((()=>!0))}async confirmPasswordReset(e,n,r,i,a){let o={method:`POST`,body:{token:e,password:n,passwordConfirm:r}};return o=N(`This form of confirmPasswordReset(token, password, passwordConfirm, body?, query?) is deprecated. Consider replacing it with confirmPasswordReset(token, password, passwordConfirm, options?).`,o,i,a),this.client.send(this.baseCollectionPath+`/confirm-password-reset`,o).then((()=>!0))}async requestVerification(e,n,r){let i={method:`POST`,body:{email:e}};return i=N(`This form of requestVerification(email, body?, query?) is deprecated. Consider replacing it with requestVerification(email, options?).`,i,n,r),this.client.send(this.baseCollectionPath+`/request-verification`,i).then((()=>!0))}async confirmVerification(e,n,r){let i={method:`POST`,body:{token:e}};return i=N(`This form of confirmVerification(token, body?, query?) is deprecated. Consider replacing it with confirmVerification(token, options?).`,i,n,r),this.client.send(this.baseCollectionPath+`/confirm-verification`,i).then((()=>{let n=S(e),r=this.client.authStore.record;return r&&!r.verified&&r.id===n.id&&r.collectionId===n.collectionId&&(r.verified=!0,this.client.authStore.save(this.client.authStore.token,r)),!0}))}async requestEmailChange(e,n,r){let i={method:`POST`,body:{newEmail:e}};return i=N(`This form of requestEmailChange(newEmail, body?, query?) is deprecated. Consider replacing it with requestEmailChange(newEmail, options?).`,i,n,r),this.client.send(this.baseCollectionPath+`/request-email-change`,i).then((()=>!0))}async confirmEmailChange(e,n,r,i){let a={method:`POST`,body:{token:e,password:n}};return a=N(`This form of confirmEmailChange(token, password, body?, query?) is deprecated. Consider replacing it with confirmEmailChange(token, password, options?).`,a,r,i),this.client.send(this.baseCollectionPath+`/confirm-email-change`,a).then((()=>{let n=S(e),r=this.client.authStore.record;return r&&r.id===n.id&&r.collectionId===n.collectionId&&this.client.authStore.clear(),!0}))}async listExternalAuths(e,n){return this.client.collection(`_externalAuths`).getFullList(Object.assign({},n,{filter:this.client.filter(`recordRef = {:id}`,{id:e})}))}async unlinkExternalAuth(e,n,r){let i=await this.client.collection(`_externalAuths`).getFirstListItem(this.client.filter(`recordRef = {:recordId} && provider = {:provider}`,{recordId:e,provider:n}));return this.client.collection(`_externalAuths`).delete(i.id,r).then((()=>!0))}async requestOTP(e,n){return n=Object.assign({method:`POST`,body:{email:e}},n),this.client.send(this.baseCollectionPath+`/request-otp`,n)}async authWithOTP(e,n,r){return r=Object.assign({method:`POST`,body:{otpId:e,password:n}},r),this.client.send(this.baseCollectionPath+`/auth-with-otp`,r).then((e=>this.authResponse(e)))}async impersonate(e,n,r){(r=Object.assign({method:`POST`,body:{duration:n}},r)).headers=r.headers||{},r.headers.Authorization||(r.headers.Authorization=this.client.authStore.token);let i=new Ee(this.client.baseURL,new ce,this.client.lang),a=await i.send(this.baseCollectionPath+`/impersonate/`+encodeURIComponent(e),r);return i.authStore.save(a?.token,this.decode(a?.record||{})),i}_replaceQueryParams(e,n={}){let r=e,i=``;e.indexOf(`?`)>=0&&(r=e.substring(0,e.indexOf(`?`)),i=e.substring(e.indexOf(`?`)+1));let a={},o=i.split(`&`);for(let e of o){if(e==``)continue;let n=e.split(`=`);a[decodeURIComponent(n[0].replace(/\+/g,` `))]=decodeURIComponent((n[1]||``).replace(/\+/g,` `))}for(let e in n)n.hasOwnProperty(e)&&(n[e]==null?delete a[e]:a[e]=n[e]);i=``;for(let e in a)a.hasOwnProperty(e)&&(i!=``&&(i+=`&`),i+=encodeURIComponent(e.replace(/%20/g,`+`))+`=`+encodeURIComponent(a[e].replace(/%20/g,`+`)));return i==``?r:r+`?`+i}};function de(e){if(typeof window>`u`||!window?.open)throw new y(Error(`Not in a browser context - please pass a custom urlCallback function.`));let n=1024,r=768,i=window.innerWidth,a=window.innerHeight;n=n>i?i:n,r=r>a?a:r;let o=i/2-n/2,s=a/2-r/2;return window.open(e,`popup_window`,`width=`+n+`,height=`+r+`,top=`+s+`,left=`+o+`,resizable,menubar=no`)}var fe=class extends M{get baseCrudPath(){return`/api/collections`}async import(e,n=!1,r){return r=Object.assign({method:`PUT`,body:{collections:e,deleteMissing:n}},r),this.client.send(this.baseCrudPath+`/import`,r).then((()=>!0))}async truncate(e,n){return n=Object.assign({method:`DELETE`},n),this.client.send(this.baseCrudPath+`/`+encodeURIComponent(e)+`/truncate`,n).then((()=>!0))}async getScaffolds(e){return e=Object.assign({method:`GET`},e),this.client.send(this.baseCrudPath+`/meta/scaffolds`,e)}async getAllOAuth2Providers(e){return e=Object.assign({method:`GET`},e),this.client.send(this.baseCrudPath+`/meta/oauth2-providers`,e)}async dryRunViewQuery(e,n){return n=Object.assign({method:`POST`,body:{query:e}},n),this.client.send(this.baseCrudPath+`/meta/dry-run-view`,n)}},pe=class extends T{async getList(e=1,n=30,r){return(r=Object.assign({method:`GET`},r)).query=Object.assign({page:e,perPage:n},r.query),this.client.send(`/api/logs`,r)}async getOne(e,n){if(!e)throw new y({url:this.client.buildURL(`/api/logs/`),status:404,response:{code:404,message:`Missing required log id.`,data:{}}});return n=Object.assign({method:`GET`},n),this.client.send(`/api/logs/`+encodeURIComponent(e),n)}async getStats(e){return e=Object.assign({method:`GET`},e),this.client.send(`/api/logs/stats`,e)}async truncate(e){return e=Object.assign({method:`DELETE`},e),this.client.send(`/api/logs`,e).then((()=>!0))}},me=class extends T{async check(e){return e=Object.assign({method:`GET`},e),this.client.send(`/api/health`,e)}},he=class extends T{getUrl(e,n,r={}){return console.warn(`Please replace pb.files.getUrl() with pb.files.getURL()`),this.getURL(e,n,r)}getURL(e,n,r={}){if(!n||!e?.id||!e?.collectionId&&!e?.collectionName)return``;let i=[];i.push(`api`),i.push(`files`),i.push(encodeURIComponent(e.collectionId||e.collectionName)),i.push(encodeURIComponent(e.id)),i.push(encodeURIComponent(n));let a=this.client.buildURL(i.join(`/`));!1===r.download&&delete r.download;let o=k(r);return o&&(a+=(a.includes(`?`)?`&`:`?`)+o),a}async getToken(e){return e=Object.assign({method:`POST`},e),this.client.send(`/api/files/token`,e).then((e=>e?.token||``))}},ge=class extends T{async getFullList(e){return e=Object.assign({method:`GET`},e),this.client.send(`/api/backups`,e)}async create(e,n){return n=Object.assign({method:`POST`,body:{name:e}},n),this.client.send(`/api/backups`,n).then((()=>!0))}async upload(e,n){return n=Object.assign({method:`POST`,body:e},n),this.client.send(`/api/backups/upload`,n).then((()=>!0))}async delete(e,n){return n=Object.assign({method:`DELETE`},n),this.client.send(`/api/backups/${encodeURIComponent(e)}`,n).then((()=>!0))}async restore(e,n){return n=Object.assign({method:`POST`},n),this.client.send(`/api/backups/${encodeURIComponent(e)}/restore`,n).then((()=>!0))}getDownloadUrl(e,n){return console.warn(`Please replace pb.backups.getDownloadUrl() with pb.backups.getDownloadURL()`),this.getDownloadURL(e,n)}getDownloadURL(e,n){return this.client.buildURL(`/api/backups/${encodeURIComponent(n)}?token=${encodeURIComponent(e)}`)}},_e=class extends T{async getFullList(e){return e=Object.assign({method:`GET`},e),this.client.send(`/api/crons`,e)}async run(e,n){return n=Object.assign({method:`POST`},n),this.client.send(`/api/crons/${encodeURIComponent(e)}`,n).then((()=>!0))}},ve=class extends T{async run(e,n){return n=Object.assign({method:`POST`,body:{query:e}},n),this.client.send(`/api/sql`,n)}};function ye(e){return typeof Blob<`u`&&e instanceof Blob||typeof File<`u`&&e instanceof File||typeof e==`object`&&!!e&&e.uri&&(typeof navigator<`u`&&navigator.product===`ReactNative`||typeof global<`u`&&global.HermesInternal)}function be(e){return e&&(e.constructor?.name===`FormData`||typeof FormData<`u`&&e instanceof FormData)}function xe(e){for(let n in e){let r=Array.isArray(e[n])?e[n]:[e[n]];for(let e of r)if(ye(e))return!0}return!1}var Se=/^[\-\.\d]+$/;function Ce(e){if(typeof e!=`string`)return e;if(e==`true`)return!0;if(e==`false`)return!1;if((e[0]===`-`||e[0]>=`0`&&e[0]<=`9`)&&Se.test(e)){let n=+e;if(``+n===e)return n}return e}var we=class extends T{constructor(){super(...arguments),this.requests=[],this.subs={}}collection(e){return this.subs[e]||(this.subs[e]=new Te(this.requests,e)),this.subs[e]}async send(e){let n=new FormData,r=[];for(let e=0;e{if(r===`@jsonPayload`&&typeof e==`string`)try{let r=JSON.parse(e);Object.assign(n,r)}catch(e){console.warn(`@jsonPayload error:`,e)}else n[r]===void 0?n[r]=Ce(e):(Array.isArray(n[r])||(n[r]=[n[r]]),n[r].push(Ce(e)))})),n}(r));for(let n in r){let i=r[n];if(ye(i))e.files[n]=e.files[n]||[],e.files[n].push(i);else if(Array.isArray(i)){let r=[],a=[];for(let e of i)ye(e)?r.push(e):a.push(e);if(r.length>0&&r.length==i.length){e.files[n]=e.files[n]||[];for(let i of r)e.files[n].push(i)}else if(e.json[n]=a,r.length>0){let i=n;n.startsWith(`+`)||n.endsWith(`+`)||(i+=`+`),e.files[i]=e.files[i]||[];for(let n of r)e.files[i].push(n)}}else e.json[n]=i}}},Ee=class{get baseUrl(){return this.baseURL}set baseUrl(e){this.baseURL=e}constructor(e=`/`,n,r=`en-US`){this.cancelControllers={},this.recordServices={},this.enableAutoCancellation=!0,this.baseURL=e,this.lang=r,this.authStore=n||(typeof window<`u`&&window.Deno?new ce:new w),this.collections=new fe(this),this.files=new he(this),this.logs=new pe(this),this.settings=new E(this),this.realtime=new j(this),this.health=new me(this),this.backups=new ge(this),this.crons=new _e(this),this.sql=new ve(this)}get admins(){return this.collection(`_superusers`)}createBatch(){return new we(this)}collection(e){return this.recordServices[e]||(this.recordServices[e]=new ue(this,e)),this.recordServices[e]}autoCancellation(e){return this.enableAutoCancellation=!!e,this}cancelRequest(e){return this.cancelControllers[e]&&(this.cancelControllers[e].abort(),delete this.cancelControllers[e]),this}cancelAllRequests(){for(let e in this.cancelControllers)this.cancelControllers[e].abort();return this.cancelControllers={},this}filter(e,n){if(!n)return e;function r(e){switch(typeof e){case`boolean`:case`number`:return``+e;case`string`:return JSON.stringify(e);default:if(e==null)return`null`;if(e instanceof Date)return JSON.stringify(e.toISOString().replace(`T`,` `));{let n=JSON.stringify(e);return n.startsWith(`[`)||n.startsWith(`{`)?JSON.stringify(n):n}}}for(let i in n)e=e.replaceAll(`{:`+i+`}`,r(n[i]));return e}getFileUrl(e,n,r={}){return console.warn(`Please replace pb.getFileUrl() with pb.files.getURL()`),this.files.getURL(e,n,r)}buildUrl(e){return console.warn(`Please replace pb.buildUrl() with pb.buildURL()`),this.buildURL(e)}buildURL(e){let n=this.baseURL;return typeof window>`u`||!window.location||n.startsWith(`https://`)||n.startsWith(`http://`)||(n=window.location.origin?.endsWith(`/`)?window.location.origin.substring(0,window.location.origin.length-1):window.location.origin||``,this.baseURL.startsWith(`/`)||(n+=window.location.pathname||`/`,n+=n.endsWith(`/`)?``:`/`),n+=this.baseURL),e&&(n+=n.endsWith(`/`)?``:`/`,n+=e.startsWith(`/`)?e.substring(1):e),n}async send(e,n){n=this.initSendOptions(e,n);let r=this.buildURL(e);if(this.beforeSend){let e=Object.assign({},await this.beforeSend(r,n));e.url!==void 0||e.options!==void 0?(r=e.url||r,n=e.options||n):Object.keys(e).length&&(n=e,console?.warn&&console.warn("Deprecated format of beforeSend return: please use `return { url, options }`, instead of `return options`."))}if(n.query!==void 0){let e=k(n.query);e&&(r+=(r.includes(`?`)?`&`:`?`)+e),delete n.query}return this.getHeader(n.headers,`Content-Type`)==`application/json`&&n.body&&typeof n.body!=`string`&&(n.body=JSON.stringify(n.body)),(n.fetch||fetch)(r,n).then((async e=>{let r={};try{r=await e.json()}catch(e){if(n.signal?.aborted||e?.name==`AbortError`||e?.message==`Aborted`)throw e}if(this.afterSend&&(r=await this.afterSend(e,r,n)),e.status>=400)throw new y({url:e.url,status:e.status,data:r});return r})).catch((e=>{throw new y(e)}))}initSendOptions(e,n){if((n=Object.assign({method:`GET`},n)).body=function(e){if(typeof FormData>`u`||e===void 0||typeof e!=`object`||!e||be(e)||!xe(e))return e;let n=new FormData;for(let r in e){let i=e[r];if(i!==void 0){if(typeof i!=`object`||xe({data:i})){let e=Array.isArray(i)?i:[i];for(let i of e)n.append(r,i)}else{let e={};e[r]=i,n.append(`@jsonPayload`,JSON.stringify(e))}}}return n}(n.body),O(n),n.query=Object.assign({},n.params,n.query),n.requestKey===void 0&&(!1===n.$autoCancel||!1===n.query.$autoCancel?n.requestKey=null:(n.$cancelKey||n.query.$cancelKey)&&(n.requestKey=n.$cancelKey||n.query.$cancelKey)),delete n.$autoCancel,delete n.query.$autoCancel,delete n.$cancelKey,delete n.query.$cancelKey,this.getHeader(n.headers,`Content-Type`)!==null||be(n.body)||(n.headers=Object.assign({},n.headers,{"Content-Type":`application/json`})),this.getHeader(n.headers,`Accept-Language`)===null&&(n.headers=Object.assign({},n.headers,{"Accept-Language":this.lang})),this.authStore.token&&this.getHeader(n.headers,`Authorization`)===null&&(n.headers=Object.assign({},n.headers,{Authorization:this.authStore.token})),this.enableAutoCancellation&&n.requestKey!==null){let r=n.requestKey||(n.method||`GET`)+e;delete n.requestKey,this.cancelRequest(r);let i=new AbortController;this.cancelControllers[r]=i,n.signal=i.signal}return n}getHeader(e,n){e||={},n=n.toLowerCase();for(let r in e)if(r.toLowerCase()==n)return e[r];return null}},De=`#/login`,Oe=window.location.pathname.endsWith(`/`)?window.location.pathname.substring(0,window.location.pathname.length-1):window.location.pathname;window.app=window.app||{},window.app.pb=new Ee(`../`,new w(`__pb_superusers__`+Oe)),app.pb.beforeSend=function(e,n){return n.headers[`x-request-source`]=`pbui`,{url:e,options:n}},app.store.superuser=app.pb.authStore.record,app.pb.authStore.onChange((e,n)=>{!n&&window.location.hash!=De&&(app.modals.close(),window.location.hash=De),app.store.superuser=n}),app.pb.authStore.isValid&&app.pb.collection(app.pb.authStore.record?.collectionName||`_superusers`).authRefresh().catch(e=>{console.warn(`Failed to refresh the existing auth token:`,e);let n=e?.status<<0;(n==401||n==403)&&(app.pb.cancelAllRequests(),app.pb.authStore.clear())}),app.pb.authStore.onChange((e,n)=>{n?.id&&(app.store.loadCollections(),app.store.loadSettings(),app.store.loadOAuth2Providers())});var ke=app.pb.collection;app.pb.collection=function(e){let n=ke.call(this,e);return Ae(n),n};function Ae(e){if(e.__customUIEvents)return;e.__customUIEvents=!0;let n=e.create;e.create=function(){return n.apply(e,arguments).then(e=>(setTimeout(()=>{document.dispatchEvent(new CustomEvent(`record:create`,{detail:e})),document.dispatchEvent(new CustomEvent(`record:save`,{detail:e}))},0),e))};let r=e.update;e.update=function(){return r.apply(e,arguments).then(e=>(setTimeout(()=>{document.dispatchEvent(new CustomEvent(`record:update`,{detail:e})),document.dispatchEvent(new CustomEvent(`record:save`,{detail:e}))},0),e))};let i=e.delete;e.delete=function(){return i.apply(e,arguments).then(n=>{let r={id:arguments[0],collectionId:e.collectionIdOrName,collectionName:e.collectionIdOrName};return setTimeout(()=>{document.dispatchEvent(new CustomEvent(`record:delete`,{detail:r}))},0),n})}}var je=`pbLastFileToken`,Me=!1,Ne=[];app.pb.authStore.onChange((e,n)=>{n?.id||window.localStorage.removeItem(je)}),window.app.getFileToken=async function(e=``){let n=e&&app.store.collections?.find(n=>n.id==e||n.name==e);if(n&&!n.fields?.find(e=>e.type==`file`&&e.protected))return;let r=window.localStorage.getItem(je);return(!r||se(r,60))&&(r=await Pe()),r};async function Pe(){return new Promise(async(e,n)=>{if(Ne.push({resolve:e,reject:n}),!Me){Me=!0;try{let e=await app.pb.files.getToken();window.localStorage.setItem(je,e),Ne.forEach(n=>n.resolve(e))}catch(e){Ne.forEach(n=>n.reject(e))}Me=!1,Ne=[]}})}window.app.checkApiError=function(e,n=!0){if(!e||!(e instanceof Error)||e.isAbort){console.warn(`checkApiError - unexpected error type:`,e);return}let r=e?.status<<0,i=e?.response||{},a=n&&(i.message||e.message||`Something went wrong!`);if(a&&app.toasts.error(a),r==0&&console.log(e),app.utils.isEmpty(i.data)||(app.store.errors=i.data),r===401&&window.location.hash!=De)return app.pb.cancelAllRequests(),app.pb.authStore.clear()};function Fe(){return()=>{if(!(!app.store._ready||!app.store.showHeader||!app.store.superuser?.id))return t.header({pbEvent:`appHeader`,rid:`appHeader`,className:`app-header accent-surface`,onmount:async e=>{await new Promise(e=>setTimeout(e,0)),e._scrollToActiveMenuItem=function(){e?.querySelector(`.app-main-nav .header-link.active`)?.scrollIntoView()},e._scrollToActiveMenuItem(),window.addEventListener(`hashchange`,e._scrollToActiveMenuItem)},onunmount:e=>{window.removeEventListener(`hashchange`,e?._scrollToActiveMenuItem)}},t.a({href:`#/`,className:`logo`},t.img({src:()=>app.store.headerLogo,alt:`App logo`})),t.nav({pbEvent:`mainNav`,className:`app-main-nav`},()=>app.store.headerLinks.map(e=>{let n=e.href.startsWith(`#/`);return t.a({href:()=>e.href,target:()=>n?void 0:`_blank`,rel:()=>n?void 0:`noopener noreferrer`,className:n=>`header-link ${e.isActive?.(n)||app.utils.isActivePath(e.href)?`active`:``}`},()=>{if(e.icon)return t.i({className:e.icon,ariaHidden:!0})},t.span({className:`txt`},()=>e.label))})),t.div({className:`flex-fill app-header-separator`}),Ie(),t.button({type:`button`,className:`header-link logged-user txt-normal`,"html-popovertarget":`logged-user-dropdown`},t.span({className:`superuser-name txt-ellipsis`},()=>app.store.superuser?.email),t.i({className:`ri-arrow-drop-down-line`,ariaHidden:!0})),t.div({pbEvent:`loggedUserDropdown`,id:`logged-user-dropdown`,className:`dropdown sm nowrap logged-user-dropdown`,popover:`auto`},t.a({className:`dropdown-item dropdown-item-manage`,href:`#/collections?collection=_superusers`,onclick:e=>{e.target.closest(`.dropdown`).hidePopover()}},t.i({className:`ri-group-line`,ariaHidden:!0}),t.span({className:`txt`},`Manage superusers`)),t.hr(),t.button({type:`button`,className:`dropdown-item txt-danger dropdown-item-logout`,onclick:()=>app.pb.authStore.clear()},t.i({className:`ri-logout-circle-line`,ariaHidden:!0}),t.span({className:`txt`},`Logout`))))}}function Ie(){let e=[{value:`light`,icon:`ri-sun-line`,label:`Light`},{value:`dark`,icon:`ri-moon-line`,label:`Dark`},{value:``,icon:`ri-subtract-line`,label:`Auto`}];return[t.button({type:`button`,className:`header-link color-scheme-picker`,"html-popovertarget":`color-scheme-dropdown`,title:`Color scheme`},t.i({className:()=>app.store.activeColorScheme==`dark`?`ri-moon-line`:`ri-sun-line`,ariaHidden:!0})),t.div({pbEvent:`colorSchemeDropdown`,id:`color-scheme-dropdown`,className:`dropdown sm nowrap color-scheme-dropdown`,popover:`auto`},()=>e.map(e=>t.button({type:`button`,className:()=>`dropdown-item dropdown-item-light ${app.store.userColorScheme==e.value?`active`:``}`,onclick:n=>{n.target.closest(`.dropdown`).hidePopover(),app.store.userColorScheme=e.value}},t.i({className:e.icon,ariaHidden:!0}),t.span({className:`txt`},e.label))))]}document.addEventListener(`invalid`,e=>{let n=e.target.closest(`details`);n&&!n.open&&!e.target.closest(`summary`)&&(n.open=!0,e.target.reportValidity&&e.target.reportValidity())},!0);var Le=`dropdown-item`;document.addEventListener(`toggle`,e=>{if(e.newState!=`open`||!e.target||e.target.__keyboardNavRegistered||!e.target.matches(`.dropdown`))return;e.target.__keyboardNavRegistered=!0;let n=e.target;function r(e){if(!n.isConnected){document.removeEventListener(`keydown`,r);return}let i;if(i=document.activeElement&&document.activeElement.classList.contains(Le)?document.activeElement:n.querySelector(`.dropdown-item:not([hidden]):not(.disabled)`),i){if(e.key==`ArrowUp`){e.preventDefault();let n=ze(i,-1);i==document.activeElement&&n?.classList?.contains(Le)?n?.focus():i.focus()}else if(e.key==`ArrowDown`){e.preventDefault();let n=ze(i,1);i==document.activeElement&&n?.classList?.contains(Le)?n?.focus():i.focus()}else if(e.keyCode>=65&&e.keyCode<=90||e.keyCode>=48&&e.keyCode<=57){let e=n.querySelectorAll(`input,textare,select`);e.length==1&&e[0].focus()}}}n.addEventListener(`toggle`,e=>{e.newState==`open`?(n.__dropdownSource=e.source,n.__dropdownSource?.setAttribute(`data-popover-state`,!0),Re(e.target.id,!0),document.addEventListener(`keydown`,r)):(n.__dropdownSource?.setAttribute(`data-popover-state`,!1),Re(e.target.id,!1),document.removeEventListener(`keydown`,r))})},!0);function Re(e,n=!1){e&&document.querySelectorAll(`[popovertarget='`+e+`']`)?.forEach(e=>e.setAttribute(`data-popover-state`,n))}function ze(e,n=-1){let r=n<0?e.previousElementSibling:e.nextElementSibling;return r&&(r.hidden||r.classList.contains(`disabled`)||!r.classList.contains(Le))?ze(r,n):r}var Be=5,P=t.div({ariaHidden:!0,popover:`manual`,className:`pb-tooltip`});document.body.appendChild(P);function Ve(e,n,r){P._relatedNode=e;let i=e.getBoundingClientRect();P.setAttribute(`data-style`,r||``),P.setAttribute(`data-position`,n),P.style.top=`0px`,P.style.left=`0px`;let a=P.offsetHeight,o=P.offsetWidth,s=0,c=0;n==`left`?(s=i.top+i.height/2-a/2,c=i.left-o-Be):n==`right`?(s=i.top+i.height/2-a/2,c=i.right+Be):n==`top`?(s=i.top-a-Be,c=i.left+i.width/2-o/2):n==`top-left`?(s=i.top-a-Be,c=i.left):n==`top-right`?(s=i.top-a-Be,c=i.right-o):n==`bottom-left`?(s=i.top+i.height+Be,c=i.left):n==`bottom-right`?(s=i.top+i.height+Be,c=i.right-o):(s=i.top+i.height+Be,c=i.left+i.width/2-o/2),c+o>document.documentElement.clientWidth&&(c=document.documentElement.clientWidth-o),c=c>=0?c:0,s+a>document.documentElement.clientHeight&&(s=document.documentElement.clientHeight-a),s=s>=0?s:0,P.style.top=s+`px`,P.style.left=c+`px`}function He(){P._relatedNode=null,P.hidePopover()}function Ue(e,n,r,i){if(!e||!n){He();return}P.showPopover(),P.textContent=n,Ve(e,r,i)}document.body.addEventListener(`mouseleave`,()=>{He()});function We(e,n=`top`,r=``){return i=>{if(!i._tooltipText){i._tooltipText=store({value:``});let e;function a(){e?.unwatch(),e=watch(()=>i._tooltipText.value,async e=>{Ue(i,e,n,r)})}async function o(){e?.unwatch(),e=null,He()}i.addEventListener(`mouseenter`,a),i.addEventListener(`focusin`,a),i.addEventListener(`mouseleave`,o),i.addEventListener(`focusout`,o),i.addEventListener(`blur`,o);let s=i.onunmount;i.onunmount=n=>{P._relatedNode==n&&He(),e?.unwatch(),n._tooltipText=null,n?.removeEventListener(`mouseenter`,a),n?.removeEventListener(`focusin`,a),n?.removeEventListener(`mouseleave`,o),n?.removeEventListener(`focusout`,o),n?.removeEventListener(`blur`,o),s(n)}}return typeof e==`function`?i._tooltipText.value=e():i._tooltipText.value=e,i._tooltipText.value}}window.app=window.app||{},window.app.attrs=window.app.attrs||{},window.app.attrs.tooltip=We,window.app=window.app||{},window.app.modals=window.app.modals||{},window.app.modals.confirm=function(e,n,r,i={className:void 0,yesButton:``,noButton:``}){F.textOrElem=e,F.yesCallback=n,F.yesCallbackWaiting=!1,F.noCallback=r,F.noCallbackWaiting=!1,F.className=typeof i.className==`string`?i.className:`sm`,F.yesButton=i.yesButton||`Yes`,F.noButton=i.noButton||`No`,Ge.isConnected||document.body.appendChild(Ge),window.app.modals.open(Ge)};var F=store({className:``,textOrElem:null,yesButton:``,yesCallback:null,yesCallbackWaiting:!1,noButton:``,noCallback:null,noCallbackWaiting:!1,get isBusy(){return F.yesCallbackWaiting||F.noCallbackWaiting}}),Ge=t.div({className:()=>`modal popup manual ${F.className||``}`},t.div({className:`modal-content`},e=>typeof F.textOrElem==`string`?t.h6({className:`block txt-center`},F.textOrElem):typeof F.textOrElem==`function`?F.textOrElem(e):F.textOrElem),t.footer({className:`modal-footer p-sm`},t.div({className:`grid sm`},t.div({className:`col-sm-6`},t.button({type:`button`,className:()=>`btn lg block secondary ${F.noCallbackWaiting?`loading`:``}`,disabled:()=>F.isBusy,onclick:async()=>{if(F.noCallback){F.noCallbackWaiting=!0;try{if(await F.noCallback()===!1)return}catch(e){console.log(`confirm noCallback error:`,e)}finally{F.noCallbackWaiting=!1}}window.app.modals.close(Ge)}},()=>typeof F.noButton==`string`?t.span({className:`txt`},F.noButton):typeof F.noButton==`function`?F.noButton(el):F.noButton)),t.div({className:`col-sm-6`},t.button({type:`button`,className:()=>`btn lg block warning ${F.yesCallbackWaiting?`loading`:``}`,disabled:()=>F.isBusy,onclick:async()=>{if(F.yesCallback){F.yesCallbackWaiting=!0;try{if(await F.yesCallback()===!1)return}catch(e){console.log(`confirm yesCallback error:`,e)}finally{F.yesCallbackWaiting=!1}}window.app.modals.close(Ge)}},()=>typeof F.yesButton==`string`?t.span({className:`txt`},F.yesButton):typeof F.yesButton==`function`?F.yesButton(el):F.yesButton)))));window.app=window.app||{},window.app.components=window.app.components||{},window.app.components.dragline=function(e={}){let n,r=store({rid:void 0,id:void 0,hidden:void 0,inert:void 0,className:``,tolerance:0,ondragstart:function(e){},ondragstop:function(e){},ondragging:function(e,n,r){}}),i=app.utils.extendStore(r,e),a=store({dragStarted:!1}),o,s,c,l;function u(){document.addEventListener(`touchmove`,m),document.addEventListener(`mousemove`,m),document.addEventListener(`touchend`,p),document.addEventListener(`mouseup`,p)}function d(){document.removeEventListener(`touchmove`,m),document.removeEventListener(`mousemove`,m),document.removeEventListener(`touchend`,p),document.removeEventListener(`mouseup`,p)}function f(e){e.stopPropagation(),o=e.clientX,s=e.clientY,c=e.clientX-n.offsetLeft,l=e.clientY-n.offsetTop,u()}function p(e){a.dragStarted&&(e.preventDefault(),a.dragStarted=!1,r.ondragstop?.(e)),d()}function m(e){let i=e.clientX-o,u=e.clientY-s,d=e.clientX-c,f=e.clientY-l;!a.dragStarted&&Math.abs(d-n.offsetLeft)r.id,hidden:()=>r.hidden,inert:()=>r.inert,className:()=>`dragline ${a.dragStarted?`dragging`:``} ${r.className}`,onmousedown:e=>{e.button==0&&f(e)},ontouchstart:f,onunmount:()=>{d(),i.forEach(e=>e?.unwatch())}}),n},window.app=window.app||{},window.app.components=window.app.components||{},window.app.components.slide=function(e,...n){let r;return t.div({className:n=>`block slide-block ${e?.(n)?``:`hidden`}`,onmount:e=>{r=setTimeout(()=>{e?.setAttribute(`data-slide`,`1`)},200)},onunmount:()=>{clearTimeout(r)}},...n)},window.app=window.app||{},window.app.modals=window.app.modals||{};var Ke=`data-modal-state`,qe=`manual`,Je;window.app.modals.open=async function(e){if(!e?.isConnected){console.error(`modals.open requies an active DOM element`,e);return}let n;if(e.onbeforeopen&&(n=await e.onbeforeopen(e)),n===!1)return;if(e.onafteropen){let n=new ResizeObserver(r=>{for(let i of r)i.contentRect.height>0&&e?.onafteropen&&(e.onafteropen(e),n.disconnect(),n=null)});n.observe(e)}Je=document.activeElement,Ye(e),e._forceClose=()=>app.modals.close(e,!0),window.addEventListener(`popstate`,e._forceClose);let r=Math.max(Xe(e)?.style.zIndex<<0,1e3);e.style.zIndex=r+1,e.setAttribute(Ke,`open`)},window.app.modals.close=async function(e=null,n=!1){if(e||=Xe(),e){if(window.removeEventListener(`popstate`,e._forceClose),n)e.onbeforeclose?.(e,!0),e.setAttribute(Ke,`close`),e.onafterclose?.(e,!0);else{if(e.onbeforeclose&&await e.onbeforeclose(e,!1)===!1)return;if(e.onafterclose){let n=new ResizeObserver(r=>{for(let i of r)i.contentRect.height<=0&&e?.onafterclose&&(e.onafterclose(e,!1),n.disconnect(),n=null)});n.observe(e)}e.setAttribute(Ke,`close`)}Je&&(Je.focus?.(),setTimeout(()=>{Je?.blur?.(),Je=null},0))}};function Ye(e){if(e.getAttribute(`tabindex`)||e.setAttribute(`tabindex`,`-1`),setTimeout(()=>{let n=e?.querySelector(`[autofocus]`);n?n.focus():e?.focus()},0),e.getAttribute(Ke))return;e.setAttribute(Ke,``),e.addEventListener(`keydown`,n=>{n.key!=`Escape`||e.classList.contains(qe)||n.target!==e&&e.contains(n.target)||window.app.modals.close(e)});let n=!1,r=r=>{n=r.target!==e&&e.contains(r.target)};e.addEventListener(`mousedown`,r),e.addEventListener(`touchstart`,r);let i=!1,a=n=>{i=n.target!==e&&e.contains(n.target)};e.addEventListener(`mouseup`,a),e.addEventListener(`touchend`,a),e.addEventListener(`click`,r=>{n||i||e.classList.contains(qe)||r.target!==e&&e.contains(r.target)||window.app.modals.close(e)})}function Xe(e){let n=document.querySelectorAll(`[${Ke}="open"]`),r=0,i=0,a;for(let o of n)e&&o==e||(r=o.style.zIndex<<0,r>i&&(i=r,a=o));return a}window.app.modals.getTop=function(){return Xe()};var Ze=new Map,Qe=t.div({className:`toasts-container`});function I(e,n=!0){let r=Ze.get(e);!r||!r.isConnected||(Ze.delete(e),clearTimeout(r._removeTimeout),n?(r.classList.add(`removing`),setTimeout(()=>{r.remove()},300)):r.remove())}function R(e=!0){Ze.forEach((n,r)=>{window.app.toasts.remove(r,e)})}function $e(e,n={}){n.type=`info`,n.duration=n.duration||3e3,nt(e,n)}function et(e,n={}){n.type=`success`,n.duration=n.duration||3e3,nt(e,n)}function tt(e,n={}){n.type=`error`,n.duration=n.duration||3500,nt(e,n)}function nt(e,n={}){n=Object.assign({duration:3e3,key:void 0,type:`info`},n),Qe.isConnected||document.body.appendChild(Qe);let r=n.key||e;Ze.has(r)&&I(r,!1);function i(e){e?._removeTimeout&&clearTimeout(e?._removeTimeout),e._removeTimeout=setTimeout(()=>{I(r)},n.duration)}let a=t.div({className:`toast ${n.type||``}`,onmount:e=>{i(e)},onunmount:e=>{e?._removeTimeout&&(clearTimeout(e?._removeTimeout),a=null)},onmouseover:()=>{clearTimeout(a?._removeTimeout)},onmouseout:()=>{i(a)}},t.div({className:`toast-container`},t.div({className:`toast-icon`}),t.div({className:`toast-content`},e,t.button({type:`button`,className:`m-l-auto btn circle sm transparent secondary toast-remove`,title:`Clear`,onclick:()=>I(r)},t.i({className:`ri-close-line`,ariaHidden:!0})))));Ze.set(r,a),Qe.prepend(a)}window.app=window.app||{},window.app.toasts=window.app.toasts||{},window.app.toasts.info=$e,window.app.toasts.error=tt,window.app.toasts.success=et,window.app.toasts.remove=I,window.app.toasts.removeAll=R,window.app=window.app||{},window.app.components=window.app.components||{},window.app.components.sortable=function(e={}){let n=store({rid:void 0,id:void 0,hidden:void 0,inert:void 0,className:``,data:[],dataItem:function(e,n,r){return t.span(null,`Item `+n)},onchange:function(e,n,r){},handle:``,before:void 0,after:void 0}),r=app.utils.extendStore(n,e);function i(e){function r(){e.querySelectorAll(`:scope > [data-dragstart="true"]`)?.forEach(e=>{e.dataset.dragstart=!1}),e.querySelectorAll(`:scope > [data-dragover="true"]`)?.forEach(e=>{e.dataset.dragover=!1})}e.addEventListener(`dragstart`,r=>{if(n.handle&&!r.target.closest(n.handle)){r.preventDefault();return}let i=o(e,r.target);i&&(i.dataset.dragstart=!0)}),e.addEventListener(`dragenter`,n=>{for(let n of e.children)n.dataset.dragover&&(n.dataset.dragover=!1);let r=o(e,n.target);r&&(r.dataset.dragover=!0)}),e.addEventListener(`dragover`,e=>{e.preventDefault()}),e.addEventListener(`drop`,i=>{if(!n.onchange){r();return}let s=e.querySelector(`:scope > [data-dragstart="true"]`),c=o(e,i.target);if(r(),!s||!c||c==s)return;let l=e.querySelectorAll(`:scope > [data-sortable-child]`),u=a(l,s),d=a(l,c);if(u==-1||d==-1)return;let f=n.data.slice(),p=f.splice(u,1);f.splice(d,0,p[0]),n.onchange(f,u,d)}),e._documentDragend=function(){r()},document.addEventListener(`dragend`,e._documentDragend)}function a(e,n){for(let r=0;rn.id,hidden:()=>n.hidden,inert:()=>n.inert,className:()=>n.className,onmount:e=>{i(e)},onunmount:e=>{r.forEach(e=>e?.unwatch()),e._documentDragend&&document.removeEventListener(`dragend`,e._documentDragend)}},e=>typeof n.before==`function`?n.before(e):n.before,e=>{let r=[];for(let i=0;itypeof n.after==`function`?n.after(e):n.after)},window.app=window.app||{},window.app.components=window.app.components||{},window.app.components.copyButton=function(e,...n){let r=store({active:!1}),i;function a(){let n=e;typeof n==`function`&&(n=e()),app.utils.copyToClipboard(n),r.active=!0,clearTimeout(i),i=setTimeout(()=>{r.active=!1},500)}return t.button({tabIndex:-1,type:`button`,className:()=>`copy-to-clipboard ${r.active?`active`:``}`,title:`Copy`,ariaDescription:app.attrs.tooltip(()=>r.active?`Copied`:null),onclick:e=>{e.preventDefault(),e.stopPropagation(),a()}},t.i({hidden:n?.length,ariaHidden:!0,className:()=>`copy-icon ${r.active?`ri-check-double-line`:`ri-file-copy-line`}`}),...n)},window.app=window.app||{},window.app.components=window.app.components||{},window.app.components.codeBlock=function(e={}){let n=store({rid:void 0,id:void 0,hidden:void 0,inert:void 0,className:``,language:`js`,value:void 0,footnote:void 0}),r=app.utils.extendStore(n,e);return t.div({rid:n.rid,id:()=>n.id,hidden:()=>n.hidden,inert:()=>n.inert,className:()=>`code-wrapper ${n.className}`,tabIndex:-1,onmount:e=>{e.addEventListener(`keydown`,n=>{(n.ctrlKey||n.metaKey)&&(n.key==`a`||n.key==`A`)&&(n.preventDefault(),window.getSelection().selectAllChildren(e))})},onunmount:()=>{r.forEach(e=>e?.unwatch())}},t.code({className:`block`,innerHTML:()=>rt(n.value,n.language)}),t.div({className:`footnote`},e=>typeof n.footnote==`function`?n.footnote(e):n.footnote))};function rt(e,n){return e=typeof e==`string`?e:``,e=Prism.plugins.NormalizeWhitespace.normalize(e,{"remove-trailing":!0,"remove-indent":!0,"left-trim":!0,"right-trim":!0}),Prism.highlight(e,Prism.languages[n]||Prism.languages.js,n)}window.app=window.app||{},window.app.components=window.app.components||{},window.app.components.codeEditor=function(e={}){let n=store({rid:void 0,id:void 0,hidden:void 0,inert:void 0,name:void 0,className:``,value:``,language:`js`,placeholder:``,disabled:!1,required:!1,singleLine:!1,autocomplete:void 0,oninput:function(e){},onfocus:function(e){},onblur:function(e){}}),r=app.utils.extendStore(n,e,`autocomplete`),i,a,o=!0;function s(e){c(),i=t.div({className:`dropdown autocomplete code-editor-dropdown`,onmount:e=>{e._updatePosition=()=>{o?dt(i):c()},e._closeOnEsc=e=>{e.key==`Escape`&&(e.preventDefault(),c())},window.addEventListener(`scroll`,e._updatePosition,!0),window.addEventListener(`resize`,e._updatePosition),window.addEventListener(`keydown`,e._closeOnEsc),e._updatePosition()},onunmount:e=>{e&&(window.removeEventListener(`scroll`,e._updatePosition,!0),window.removeEventListener(`resize`,e._updatePosition),window.removeEventListener(`keydown`,e._closeOnEsc))}},e),document.body.appendChild(i),f&&(a?.disconnect(),a=new IntersectionObserver(([e])=>{o=e.isIntersecting},{root:null,threshold:.1}),a.observe(f))}function c(){i&&=(i.remove(),null),a&&=(a.disconnect(),null),o=!0}function l(e){n.value=e,n.oninput?.(e),f.dispatchEvent(new CustomEvent(`change`,{detail:e}))}let u=!1,d,f=t.div({contentEditable:()=>!n.disabled&&`plaintext-only`,tabIndex:0,spellcheck:!1,autocorrect:!1,autocomplete:`off`,autocapitalize:`off`,role:`textbox`,className:`editor-content`,"html-data-placeholder":()=>n.placeholder,onmount:e=>{d?.unwatch(),d=watch(()=>n.value,e=>{e!=f.textContent&&(f.textContent=e,c())})},onunmount:e=>{d?.unwatch(),c()},onfocus:()=>{n.onfocus?.(n.value)},onblur:e=>{i&&!i.contains(e.relatedTarget)&&c(),n.onblur?.(n.value)},oninput:e=>{if(c(),l(f.textContent),!n.value?.length){f.textContent=``;return}if(!f?.isConnected)return;let r=ut(f),i=st(n.value,r);if(!i.word.length||r==i.start)return;let a=[];if(typeof n.autocomplete==`function`)a=n.autocomplete(i.word)||[];else if(!app.utils.isEmpty(n.autocomplete)){let e=i.word.toLowerCase();a=n.autocomplete.filter(n=>(typeof n==`object`&&(n=n?.value),n=n?.toLowerCase(),n&&n!=e&&n.includes(e)))}a?.length&&s(()=>a.map((e,n)=>t.button({type:`button`,className:`dropdown-item ${n==0?`active`:``}`,textContent:e.label||e.value||e,onclick:n=>{n.preventDefault(),f.focus();let r=e.value||e;f.textContent=f.textContent.substring(0,i.start)+r+f.textContent.substring(i.end+1),l(f.textContent);try{window.getSelection().setPosition(f.childNodes[0],i.start+r.length)}catch(e){console.warn(`failed to set caret position`,e)}c()}})))},onkeydown:e=>{if(u=e.ctrlKey||e.metaKey,(e.key==`Enter`||e.key==`Tab`)&&i?.isConnected){e.preventDefault(),i.querySelector(`.dropdown-item.active`)?.click();return}if(e.key==`ArrowUp`&&i?.isConnected){e.preventDefault();let n=i.querySelector(`.dropdown-item.active`);n?.previousElementSibling&&(n.classList.remove(`active`),n.previousElementSibling.classList.add(`active`),n.previousElementSibling.scrollIntoView(!1));return}if(e.key==`ArrowDown`&&i?.isConnected){e.preventDefault();let n=i.querySelector(`.dropdown-item.active`);n?.nextElementSibling&&(n.classList.remove(`active`),n.nextElementSibling.classList.add(`active`),n.nextElementSibling.scrollIntoView(!1));return}if(u&&e.key.toLowerCase()==`l`){e.preventDefault(),ct(f);return}if(u&&e.key.toLowerCase()==`d`){e.preventDefault(),lt(f);return}if(!n.singleLine&&e.key==`Escape`&&!i?.isConnected){f?.blur();return}if(!n.singleLine&&e.key==`Tab`){e.preventDefault();let n=window.getSelection();if(!n)return;if(e.shiftKey){n.modify(`extend`,`backward`,`character`),n.toString()[0]==` `?(n.deleteFromDocument(),l(f.textContent)):(n.modify(`extend`,`forward`,`character`),n.toString()[0]==` `&&(n.deleteFromDocument(),l(f.textContent)));return}let r=n.getRangeAt(0);r&&(r.deleteContents(),r.insertNode(document.createTextNode(` `)),r.collapse(),l(f.textContent));return}if(n.singleLine&&e.key==`Enter`){e.preventDefault(),m.click();return}},onscroll:()=>{c(),p&&(p.scrollLeft=f.scrollLeft,p.scrollTop=f.scrollTop)}}),p=t.div({className:`highlight-overlay`,innerHTML:()=>at(n.value,n.language),onscroll:()=>{f&&(f.scrollLeft=p.scrollLeft,f.scrollTop=p.scrollTop)}}),m=t.button({type:`submit`,className:`hidden`});return t.div({rid:n.rid,id:()=>n.id,inert:()=>n.inert,hidden:()=>n.hidden,"html-name":()=>n.name,"html-required":()=>n.required||void 0,className:()=>`input code-editor ${n.className} ${n.disabled?`disabled`:``} ${n.singleLine?`single-line`:``}`,onclick:()=>{f?.focus()},onunmount:()=>{r?.forEach(e=>e?.unwatch())}},t.div({className:`code-editor-container`},f,p,m))};var it=500;function at(e,n){return e=typeof e==`string`?e:``,e?((!Prism.languages[n]||e.length>it)&&(n=`plain`),Prism.highlight(e,Prism.languages[n],n)):``}var ot=new RegExp(/[\p{Alphabetic}\p{Number}_@:\."'{}]/,`u`);function st(e,n){let r=n;for(let i=n-1;i>=0&&ot.test(e[i]);i--)r=i;let i=r;for(let r=n-1;rdocument.documentElement.clientHeight&&(o=Math.max(n.top-r,0)),a+i>document.documentElement.clientWidth&&(a=Math.max(document.documentElement.clientWidth-i,0)),e.style.left=a+`px`,e.style.top=o+`px`}window.app=window.app||{},window.app.components=window.app.components||{},window.app.components.codeBlockTabs=function(e={}){let n=store({rid:void 0,id:void 0,hidden:void 0,inert:void 0,className:``,activeTabIndex:0,historyKey:``,tabs:[],get activeTab(){return n.tabs[n.activeTabIndex]||n.tabs[0]}}),r=app.utils.extendStore(n,e);return r.push(watch(()=>n.activeTabIndex,(e,r)=>{r!=null&&n.historyKey&&localStorage.setItem(n.historyKey,e)})),t.div({rid:n.rid,id:()=>n.id,hidden:()=>n.hidden||!n.tabs.length,inert:()=>n.inert,className:()=>`code-block-tabs ${n.className}`,onmount:()=>{n.historyKey&&(n.activeTabIndex=localStorage.getItem(n.historyKey)<<0)},onunmount:()=>{r.forEach(e=>e?.unwatch())}},t.header({className:`tabs-header`},()=>n.tabs.map((e,r)=>t.button({type:`button`,className:()=>`tab-item ${n.activeTabIndex==r?`active`:``}`,onclick:()=>n.activeTabIndex=r},n=>typeof e.title==`function`?e.title(n):e.title))),t.div({className:`code-block-tabs-content`},()=>{if(n.activeTab)return app.components.codeBlock(n.activeTab)}))},window.app=window.app||{},window.app.components=window.app.components||{},window.app.components.select=function(e={}){let n=store({rid:void 0,id:void 0,name:void 0,hidden:void 0,inert:void 0,className:``,value:void 0,options:[],before:null,after:null,max:1,searchThreshold:6,required:!1,disabled:!1,placeholder:`- Select -`,noItemsFoundText:`No items found`,onchange:function(e){},ondropdowntoggle:function(e){}}),r=app.utils.extendStore(n,e);n.max<=0&&(n.max=1);let i=store({selected:[],search:``,get hasSearch(){return i.search?.length>0},get allowRemove(){return!n.disabled&&(!n.required||n.max>1)}});function a(){if(n.value===void 0)return;let e=app.utils.toArray(n.value,!0),r=e.slice(0,n.max||1);e.length!=r.length&&(console.warn(`[select] the provided select values (${e.length}) are more than the allowed max selected options (${r.length}):`,e),n.value=n.max>1?r:r[0]),i.selected=e.map(e=>n.options.find(n=>n.value===e)).filter(Boolean)}r.push(watch(()=>n.value,()=>a()));async function o(e){let r=i.selected.findIndex(n=>n.value===e.value);if(r>=0){if(!i.allowRemove){f?.hidePopover();return}i.selected.splice(r,1)}else{let r=i.selected.length-n.max;for(;r>=0;)i.selected.pop(),r--;i.selected.push(e)}n.max<=1&&f?.hidePopover(),n.onchange&&(await n.onchange(i.selected),a()),p?.isConnected&&p.dispatchEvent(new CustomEvent(`change`,{detail:i.selected,bubbles:!0}))}function s(e){return i.selected.findIndex(n=>n.value===e.value)>=0}let c=t.input({type:`text`,placeholder:`Search...`,value:()=>i.search,oninput:e=>i.search=e.target.value});function l(e=!1){i.search=``,e&&c?.focus()}let u=t.div({className:`txt-hint txt-center m-0 p-5`,hidden:!0},e=>typeof n.noItemsFoundText==`function`?n.noItemsFoundText(e):n.noItemsFoundText);async function d(){f&&(await new Promise(e=>setTimeout(e,0)),f.querySelector(`.select-option:not([hidden])`)?u.hidden=!0:u.hidden=!1)}let f=t.div({tabIndex:-1,popover:`auto`,className:`dropdown`,onbeforetoggle:e=>(e.newState==`closed`&&l(),n.ondropdowntoggle?.(e))},t.div({className:`fields dropdown-search`,hidden:()=>n.options.length!i.hasSearch},t.button({type:`button`,title:`Clear`,className:`btn sm secondary transparent circle`,onclick:()=>l(!0)},t.i({className:`ri-close-line`,ariaHidden:!0})))),()=>n.before?.__raw||n.before,()=>n.options.map(e=>t.button({type:`button`,className:()=>`dropdown-item select-option ${s(e)?`active`:``}`,onclick:()=>(o(e),!1)},e.label||e.value)),u,()=>n.after?.__raw||n.after),p=t.button({type:`button`,id:()=>n.id,disabled:()=>n.disabled,className:`selected-container`,popoverTargetElement:f,onclick:e=>{e.stopPropagation()}},()=>i.selected.length?i.selected.map(e=>t.div({className:`selected-item`},e.selected||e.label||e.value,()=>{if(i.allowRemove)return t.i({tabIndex:-1,role:`button`,className:`ri-close-line link-hint btn-option-unset`,ariaLabel:app.attrs.tooltip(`Unset`),onclick:()=>(o(e),!1)})})):t.span({rid:`selected-placeholder`,className:`placeholder`},e=>typeof n.placeholder==`function`?n.placeholder(e):n.placeholder));r.push(watch(()=>n.options,()=>{d()}));let m;return r.push(watch(()=>i.search,()=>{let e=i.search.toLowerCase().replaceAll(` `,``);clearTimeout(m),m=setTimeout(()=>{let n=f.querySelectorAll(`.select-option`);e.length?n.forEach(n=>{n.hidden=!n.textContent.toLowerCase().replaceAll(` `,``).includes(e)}):n.forEach(e=>e.hidden=!1),d()},100)})),t.output({rid:n.rid,name:()=>n.name,hidden:()=>n.hidden,inert:()=>n.inert,onmount:e=>{e.addEventListener(`focusout`,function(n){(!n.relatedTarget||!e.contains(n.relatedTarget))&&f?.hidePopover()})},onunmount:()=>{clearTimeout(m),r.forEach(e=>e.unwatch())},className:()=>[`input`,`select`,n.max>1?`multiple`:`single`,n.disabled?`disabled`:null,n.required?`required`:null,n.className||null].filter(Boolean).join(` `)},p,f)},window.app=window.app||{},window.app.components=window.app.components||{};var ft=Intl.DateTimeFormat().resolvedOptions().timeZone;window.app.components.formattedDate=function(e={}){let n=store({rid:void 0,id:void 0,hidden:void 0,value:``,short:!1}),r=app.utils.extendStore(n,e);return t.div({rid:n.rid,id:()=>n.id,hidden:()=>n.hidden,ariaDescription:app.attrs.tooltip(()=>n.short&&n.value?app.utils.toLocalDatetime(n.value)+` `+ft:null),"html-class":`formatted-date`,className:()=>`formatted-date ${n.short?`short`:`full`}`,onunmount:()=>{r.forEach(e=>e?.unwatch())}},()=>{if(!n.value)return t.span({className:`missing-value`});if(n.short){let e=n.value.split(` `);return[t.span({className:`primary-date`},e[0]),t.span({className:`secondary-date`},e[1])]}return[t.span({className:`primary-date`},app.utils.toLocalDatetime(n.value)),t.span({className:`secondary-date`},n.value)]})},window.app=window.app||{},window.app.components=window.app.components||{},window.app.components.refreshButton=function(e={}){let n=store({rid:void 0,id:void 0,hidden:void 0,inert:void 0,tooltip:`Refresh`,className:`btn transparent secondary circle rotate-btn`,disabled:!1,onclick:function(e){}}),r=app.utils.extendStore(n,e),i,a=t.button({rid:n.rid,id:()=>n.id,hidden:()=>n.hidden,inert:()=>n.inert,type:`button`,ariaLabel:app.attrs.tooltip(()=>n.tooltip),disabled:()=>n.disabled,className:()=>n.className,onunmount:()=>{clearTimeout(i),r.forEach(e=>e?.unwatch())},onclick:e=>{e.preventDefault(),n.onclick&&n.onclick(e),a.dataset.rotate=!0,a.addEventListener(`animationend`,()=>{a.dataset.rotate=!1}),clearTimeout(i),i=setTimeout(()=>{clearTimeout(i),a.dataset.rotate=!1},500)}},t.i({className:`ri-refresh-line`,ariaHidden:!0}));return a},window.app=window.app||{},window.app.components=window.app.components||{},window.app.components.searchHistoryButton=function(e={}){let n=store({rid:void 0,id:void 0,hidden:void 0,inert:void 0,value:void 0,historyKey:`default`,max:15,openInNewTabParam:`filter`,btnClassName:`btn sm pill secondary transparent p-r-5`,onselect:function(e){}}),r=app.utils.extendStore(n,e),i=store({items:app.utils.getLocalHistory(n.historyKey,[])});function a(e){o(e),i.items.unshift(e)}function o(e){app.utils.removeByValue(i.items,e)}let s=`history_dropdown_`+app.utils.randomString();r.push(watch(()=>n.value,e=>{e&&a(e)})),r.push(watch(()=>{i.items.length>n.max&&(i.items=i.items.slice(0,n.max)),app.utils.saveLocalHistory(n.historyKey,i.items)}));let c=t.div({id:s,className:`dropdown sm left nowrap history-searchbar-dropdown`,popover:`hint`,onclick:e=>(e.stopPropagation(),!1)},t.div({className:`block p-5`},t.small({className:`txt-hint`},`Search history`)),()=>i.items?.length?i.items.slice(0,n.max).map(e=>t.button({type:`button`,className:`dropdown-item txt-code`,onclick:()=>{c.hidePopover(),n.onselect?.(e),a(e)},onauxclick:()=>{if(n.openInNewTabParam){a(e),c.hidePopover();let r=app.utils.replaceHashQueryParams({[n.openInNewTabParam]:e},!1);window.open(r,`_blank`)}}},t.span({className:`txt-ellipsis`,title:e,textContent:e}),t.small({role:`button`,className:`remove-btn link-hint m-l-auto p-l-5 p-r-5`,title:`Clear`,onauxclick:e=>(e.stopPropagation(),!1),onclick:n=>(n.stopPropagation(),o(e),!1)},t.i({className:`ri-close-line`,ariaHidden:!0})))):t.div({rid:`no-history`,className:`block p-5`},t.span(null,`Your recent searches will show up here.`)));return t.button({rid:n.rid,id:()=>n.id,hidden:()=>n.hidden,inert:()=>n.inert,type:`button`,title:`Search history`,className:()=>n.btnClassName,"html-popovertarget":s,onunmount:()=>{r?.forEach(e=>e?.unwatch())}},t.i({className:`ri-search-line`,ariaHidden:!0}),t.i({className:`ri-arrow-drop-down-line`,ariaHidden:!0}),c)},window.app=window.app||{},window.app.components=window.app.components||{},window.app.components.s3Test=function(e={}){let n=`s3_test_request`,r=store({rid:void 0,config:null,label:`Use S3 storage`,testFilesystem:`storage`}),i=app.utils.extendStore(r,e),a=store({isTesting:!1,testError:null,get hasError(){return!app.utils.isEmpty(a.testError)}}),o,s;function c(e=150){if(!r.config.enabled){clearTimeout(o);return}a.isTesting=!0,clearTimeout(o),o=setTimeout(()=>{l()},e)}async function l(){if(a.isTesting=!0,!r.config.enabled||!r.testFilesystem){a.testError=null,a.isTesting=!1;return}app.pb.cancelRequest(n),clearTimeout(s),s=setTimeout(()=>{app.pb.cancelRequest(n),a.testError=Error(`S3 test connection timeout.`),a.isTesting=!1},3e4);try{await app.pb.props.testS3(r.testFilesystem,{requestKey:n}),a.testError=null,a.isTesting=!1}catch(e){e?.isAbort||(a.testError=e,a.isTesting=!1,clearTimeout(s))}}return i.push(watch(()=>r.testFilesystem&&r.config,()=>c())),t.div({pbEvent:`s3Test`,rid:r.rid,hidden:()=>!r.testFilesystem,className:()=>`label s3-test-label txt-nowrap ${a.hasError?`warning`:`success`}`,ariaDescription:app.attrs.tooltip(()=>a.testError?.data?.message),onunmount:()=>{clearTimeout(s),clearTimeout(o),i.forEach(e=>e?.unwatch())}},()=>a.isTesting?t.span({className:`loader sm`}):a.hasError?[t.i({className:`ri-error-warning-line txt-warning`,ariaHidden:!0}),t.span({className:`txt`},`Failed to establish S3 connection`)]:[t.i({className:`ri-checkbox-circle-line txt-success`,ariaHidden:!0}),t.span({className:`txt`},`S3 connected successfully`)])},window.app=window.app||{},window.app.components=window.app.components||{},window.app.components.s3ConfigFields=function(e={}){let n=store({rid:void 0,id:void 0,hidden:void 0,inert:void 0,className:``,config:{},configKey:`s3`,toggleLabel:`Use S3 storage`,testFilesystem:`storage`,before:null,after:null}),r=app.utils.extendStore(n,e);n.configKey.endsWith(`.`)&&(n.configKey=n.configKey.substring(0,n.configKey.length-1));let i=store({originalHash:``,originalConfig:null});return r.push(watch(()=>n.config,e=>{i.originalHash=JSON.stringify(e),i.originalConfig=JSON.parse(i.originalHash)})),t.div({pbEvent:`s3ConfigFields`,rid:n.rid,id:()=>n.id,hidden:()=>n.hidden,inert:()=>n.inert,className:()=>`block s3-fields s3-config-${n.configKey} ${n.className}`,onunmount:()=>{r.forEach(e=>e?.unwatch())}},t.div({className:`field`},t.input({id:()=>`${n.configKey}.enabled`,name:()=>`${n.configKey}.enabled`,type:`checkbox`,className:`switch`,checked:()=>n.config.enabled,onchange:e=>n.config.enabled=e.target.checked}),t.label({htmlFor:()=>`${n.configKey}.enabled`},()=>n.toggleLabel)),e=>typeof n.before==`function`?n.before(e):n.before,app.components.slide(()=>n.config.enabled,t.div({className:`grid m-t-base`},t.div({className:`col-lg-6`},t.div({className:`field`},t.label({htmlFor:()=>`${n.configKey}.endpoint`},`Endpoint`),t.input({id:()=>`${n.configKey}.endpoint`,name:()=>`${n.configKey}.endpoint`,type:`text`,required:()=>n.config.enabled,value:()=>n.config.endpoint||``,oninput:e=>n.config.endpoint=e.target.value}))),t.div({className:`col-lg-3`},t.div({className:`field`},t.label({htmlFor:()=>`${n.configKey}.bucket`},`Bucket`),t.input({id:()=>`${n.configKey}.bucket`,name:()=>`${n.configKey}.bucket`,type:`text`,required:()=>n.config.enabled,value:()=>n.config.bucket||``,oninput:e=>n.config.bucket=e.target.value}))),t.div({className:`col-lg-3`},t.div({className:`field`},t.label({htmlFor:()=>`${n.configKey}.region`},`Region`),t.input({id:()=>`${n.configKey}.region`,name:()=>`${n.configKey}.region`,type:`text`,required:()=>n.config.enabled,value:()=>n.config.region||``,oninput:e=>n.config.region=e.target.value}))),t.div({className:`col-lg-6`},t.div({className:`field`},t.label({htmlFor:()=>`${n.configKey}.accessKey`},`Access key`),t.input({id:()=>`${n.configKey}.accessKey`,name:()=>`${n.configKey}.accessKey`,type:`text`,autocomplete:`off`,required:()=>n.config.enabled,value:()=>n.config.accessKey||``,oninput:e=>n.config.accessKey=e.target.value}))),t.div({className:`col-lg-6`},t.div({className:()=>`field ${n.config.enabled?``:`required`}`},t.label({htmlFor:()=>`${n.configKey}.secret`},`Secret`),t.input({id:()=>`${n.configKey}.secret`,name:()=>`${n.configKey}.secret`,type:`password`,autocomplete:`new-password`,value:()=>n.config.secret||``,oninput:e=>n.config.secret=e.target.value,onkeyup:e=>{e.key==`Backspace`&&n.config.secret===void 0&&(n.config.secret=``)},placeholder:()=>n.config.secret===void 0?`* * * * * *`:``}))),t.div({className:`col-lg-6`,style:`min-height: 25px`},t.div({className:`field`},t.input({id:()=>`${n.configKey}.forcePathStyle`,name:()=>`${n.configKey}.forcePathStyle`,type:`checkbox`,checked:()=>n.config.forcePathStyle||!1,onchange:e=>n.config.forcePathStyle=e.target.checked}),t.label({htmlFor:()=>`${n.configKey}.forcePathStyle`},t.span({className:`txt`},`Force path-style addressing`),t.i({className:`ri-information-line link-hint`,ariaDescription:app.attrs.tooltip(`Forces the request to use path-style addressing, eg. "https://s3.amazonaws.com/BUCKET/KEY" instead of the default "https://BUCKET.s3.amazonaws.com/KEY".`)})))),t.div({className:`col-lg-6 txt-right`},()=>{if(!(!n.config?.enabled||i.originalHash!=JSON.stringify(n.config)))return app.components.s3Test({config:()=>n.config,testFilesystem:()=>n.testFilesystem})}))),e=>typeof n.after==`function`?n.after(e):n.after)};var pt=l(s(((e,n)=>{(function(r,i){typeof e==`object`&&n!==void 0?i(e):typeof define==`function`&&define.amd?define([`exports`],i):(r=typeof globalThis<`u`?globalThis:r||self,i(r.leaflet={}))})(e,(function(e){var n=`1.9.4`;function r(e){var n,r,i,a;for(r=1,i=arguments.length;r`u`||!L||!L.Mixin)){e=v(e)?e:[e];for(var n=0;n0?Math.floor(e):Math.ceil(e)};w.prototype={clone:function(){return new w(this.x,this.y)},add:function(e){return this.clone()._add(E(e))},_add:function(e){return this.x+=e.x,this.y+=e.y,this},subtract:function(e){return this.clone()._subtract(E(e))},_subtract:function(e){return this.x-=e.x,this.y-=e.y,this},divideBy:function(e){return this.clone()._divideBy(e)},_divideBy:function(e){return this.x/=e,this.y/=e,this},multiplyBy:function(e){return this.clone()._multiplyBy(e)},_multiplyBy:function(e){return this.x*=e,this.y*=e,this},scaleBy:function(e){return new w(this.x*e.x,this.y*e.y)},unscaleBy:function(e){return new w(this.x/e.x,this.y/e.y)},round:function(){return this.clone()._round()},_round:function(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this},floor:function(){return this.clone()._floor()},_floor:function(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this},ceil:function(){return this.clone()._ceil()},_ceil:function(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this},trunc:function(){return this.clone()._trunc()},_trunc:function(){return this.x=T(this.x),this.y=T(this.y),this},distanceTo:function(e){e=E(e);var n=e.x-this.x,r=e.y-this.y;return Math.sqrt(n*n+r*r)},equals:function(e){return e=E(e),e.x===this.x&&e.y===this.y},contains:function(e){return e=E(e),Math.abs(e.x)<=Math.abs(this.x)&&Math.abs(e.y)<=Math.abs(this.y)},toString:function(){return`Point(`+d(this.x)+`, `+d(this.y)+`)`}};function E(e,n,r){return e instanceof w?e:v(e)?new w(e[0],e[1]):e==null?e:typeof e==`object`&&`x`in e&&`y`in e?new w(e.x,e.y):new w(e,n,r)}function D(e,n){if(e)for(var r=n?[e,n]:e,i=0,a=r.length;i=this.min.x&&r.x<=this.max.x&&n.y>=this.min.y&&r.y<=this.max.y},intersects:function(e){e=O(e);var n=this.min,r=this.max,i=e.min,a=e.max,o=a.x>=n.x&&i.x<=r.x,s=a.y>=n.y&&i.y<=r.y;return o&&s},overlaps:function(e){e=O(e);var n=this.min,r=this.max,i=e.min,a=e.max,o=a.x>n.x&&i.xn.y&&i.y=n.lat&&a.lat<=r.lat&&i.lng>=n.lng&&a.lng<=r.lng},intersects:function(e){e=A(e);var n=this._southWest,r=this._northEast,i=e.getSouthWest(),a=e.getNorthEast(),o=a.lat>=n.lat&&i.lat<=r.lat,s=a.lng>=n.lng&&i.lng<=r.lng;return o&&s},overlaps:function(e){e=A(e);var n=this._southWest,r=this._northEast,i=e.getSouthWest(),a=e.getNorthEast(),o=a.lat>n.lat&&i.latn.lng&&i.lng1,Ke=function(){var e=!1;try{var n=Object.defineProperty({},"passive",{get:function(){e=!0}});window.addEventListener(`testPassiveEventSupport`,u,n),window.removeEventListener(`testPassiveEventSupport`,u,n)}catch{}return e}(),qe=function(){return!!document.createElement(`canvas`).getContext}(),Je=!!(document.createElementNS&&ge(`svg`).createSVGRect),Ye=!!Je&&(function(){var e=document.createElement(`div`);return e.innerHTML=``,(e.firstChild&&e.firstChild.namespaceURI)===`http://www.w3.org/2000/svg`})(),Xe=!Je&&function(){try{var e=document.createElement(`div`);e.innerHTML=``;var n=e.firstChild;return n.style.behavior=`url(#default#VML)`,n&&typeof n.adj==`object`}catch{return!1}}(),Ze=navigator.platform.indexOf(`Mac`)===0,Qe=navigator.platform.indexOf(`Linux`)===0;function I(e){return navigator.userAgent.toLowerCase().indexOf(e)>=0}var R={ie:ye,ielt9:be,edge:xe,webkit:Se,android:Ce,android23:we,androidStock:Ee,opera:De,chrome:Oe,gecko:ke,safari:Ae,phantom:je,opera12:Me,win:Ne,ie3d:Pe,webkit3d:Fe,gecko3d:Ie,any3d:Le,mobile:Re,mobileWebkit:ze,mobileWebkit3d:Be,msPointer:P,pointer:Ve,touch:Ue,touchNative:He,mobileOpera:We,mobileGecko:F,retina:Ge,passiveEvents:Ke,canvas:qe,svg:Je,vml:Xe,inlineSvg:Ye,mac:Ze,linux:Qe},$e=R.msPointer?`MSPointerDown`:`pointerdown`,et=R.msPointer?`MSPointerMove`:`pointermove`,tt=R.msPointer?`MSPointerUp`:`pointerup`,nt=R.msPointer?`MSPointerCancel`:`pointercancel`,rt={touchstart:$e,touchmove:et,touchend:tt,touchcancel:nt},it={touchstart:mt,touchmove:pt,touchend:pt,touchcancel:pt},at={},ot=!1;function st(e,n,r){return n===`touchstart`&&ft(),it[n]?(r=it[n].bind(this,r),e.addEventListener(rt[n],r,!1),r):(console.warn(`wrong event specified:`,n),u)}function ct(e,n,r){if(!rt[n]){console.warn(`wrong event specified:`,n);return}e.removeEventListener(rt[n],r,!1)}function lt(e){at[e.pointerId]=e}function ut(e){at[e.pointerId]&&(at[e.pointerId]=e)}function dt(e){delete at[e.pointerId]}function ft(){ot||=(document.addEventListener($e,lt,!0),document.addEventListener(et,ut,!0),document.addEventListener(tt,dt,!0),document.addEventListener(nt,dt,!0),!0)}function pt(e,n){if(n.pointerType!==(n.MSPOINTER_TYPE_MOUSE||`mouse`)){for(var r in n.touches=[],at)n.touches.push(at[r]);n.changedTouches=[n],e(n)}}function mt(e,n){n.MSPOINTER_TYPE_TOUCH&&n.pointerType===n.MSPOINTER_TYPE_TOUCH&&X(n),pt(e,n)}function ht(e){var n={},r,i;for(i in e)r=e[i],n[i]=r&&r.bind?r.bind(e):r;return e=n,n.type=`dblclick`,n.detail=2,n.isTrusted=!1,n._simulated=!0,n}var gt=200;function _t(e,n){e.addEventListener(`dblclick`,n);var r=0,i;function a(e){if(e.detail!==1){i=e.detail;return}if(!(e.pointerType===`mouse`||e.sourceCapabilities&&!e.sourceCapabilities.firesTouchEvents)){var a=$t(e);if(!(a.some(function(e){return e instanceof HTMLLabelElement&&e.attributes.for})&&!a.some(function(e){return e instanceof HTMLInputElement||e instanceof HTMLSelectElement}))){var o=Date.now();o-r<=gt?(i++,i===2&&n(ht(e))):i=1,r=o}}}return e.addEventListener(`click`,a),{dblclick:n,simDblclick:a}}function vt(e,n){e.removeEventListener(`dblclick`,n.dblclick),e.removeEventListener(`click`,n.simDblclick)}var yt=jt([`transform`,`webkitTransform`,`OTransform`,`MozTransform`,`msTransform`]),bt=jt([`webkitTransition`,`transition`,`OTransition`,`MozTransition`,`msTransition`]),xt=bt===`webkitTransition`||bt===`OTransition`?bt+`End`:`transitionend`;function St(e){return typeof e==`string`?document.getElementById(e):e}function Ct(e,n){var r=e.style[n]||e.currentStyle&&e.currentStyle[n];if((!r||r===`auto`)&&document.defaultView){var i=document.defaultView.getComputedStyle(e,null);r=i?i[n]:null}return r===`auto`?null:r}function z(e,n,r){var i=document.createElement(e);return i.className=n||``,r&&r.appendChild(i),i}function B(e){var n=e.parentNode;n&&n.removeChild(e)}function wt(e){for(;e.firstChild;)e.removeChild(e.firstChild)}function Tt(e){var n=e.parentNode;n&&n.lastChild!==e&&n.appendChild(e)}function Et(e){var n=e.parentNode;n&&n.firstChild!==e&&n.insertBefore(e,n.firstChild)}function Dt(e,n){if(e.classList!==void 0)return e.classList.contains(n);var r=kt(e);return r.length>0&&RegExp(`(^|\\s)`+n+`(\\s|$)`).test(r)}function V(e,n){if(e.classList!==void 0)for(var r=p(n),i=0,a=r.length;i0?2*window.devicePixelRatio:1;function nn(e){return R.edge?e.wheelDeltaY/2:e.deltaY&&e.deltaMode===0?-e.deltaY/tn:e.deltaY&&e.deltaMode===1?-e.deltaY*20:e.deltaY&&e.deltaMode===2?-e.deltaY*60:e.deltaX||e.deltaZ?0:e.wheelDelta?(e.wheelDeltaY||e.wheelDelta)/2:e.detail&&Math.abs(e.detail)<32765?-e.detail*20:e.detail?e.detail/-32765*60:0}function rn(e,n){var r=n.relatedTarget;if(!r)return!0;try{for(;r&&r!==e;)r=r.parentNode}catch{return!1}return r!==e}var an={__proto__:null,on:q,off:Y,stopPropagation:Yt,disableScrollPropagation:Xt,disableClickPropagation:Zt,preventDefault:X,stop:Qt,getPropagationPath:$t,getMousePosition:en,getWheelDelta:nn,isExternalTarget:rn,addListener:q,removeListener:Y},on=ce.extend({run:function(e,n,r,i){this.stop(),this._el=e,this._inProgress=!0,this._duration=r||.25,this._easeOutPower=1/Math.max(i||.5,.2),this._startPos=Nt(e),this._offset=n.subtract(this._startPos),this._startTime=+new Date,this.fire(`start`),this._animate()},stop:function(){this._inProgress&&(this._step(!0),this._complete())},_animate:function(){this._animId=b(this._animate,this),this._step()},_step:function(e){var n=+new Date-this._startTime,r=this._duration*1e3;nthis.options.maxZoom)?this.setZoom(e):this},panInsideBounds:function(e,n){this._enforcingBounds=!0;var r=this.getCenter(),i=this._limitCenter(r,this._zoom,A(e));return r.equals(i)||this.panTo(i,n),this._enforcingBounds=!1,this},panInside:function(e,n){n||={};var r=E(n.paddingTopLeft||n.padding||[0,0]),i=E(n.paddingBottomRight||n.padding||[0,0]),a=this.project(this.getCenter()),o=this.project(e),s=this.getPixelBounds(),c=O([s.min.add(r),s.max.subtract(i)]),l=c.getSize();if(!c.contains(o)){this._enforcingBounds=!0;var u=o.subtract(c.getCenter()),d=c.extend(o).getSize().subtract(l);a.x+=u.x<0?-d.x:d.x,a.y+=u.y<0?-d.y:d.y,this.panTo(this.unproject(a),n),this._enforcingBounds=!1}return this},invalidateSize:function(e){if(!this._loaded)return this;e=r({animate:!1,pan:!0},e===!0?{animate:!0}:e);var n=this.getSize();this._sizeChanged=!0,this._lastCenter=null;var i=this.getSize(),o=n.divideBy(2).round(),s=i.divideBy(2).round(),c=o.subtract(s);return!c.x&&!c.y?this:(e.animate&&e.pan?this.panBy(c):(e.pan&&this._rawPanBy(c),this.fire(`move`),e.debounceMoveend?(clearTimeout(this._sizeTimer),this._sizeTimer=setTimeout(a(this.fire,this,`moveend`),200)):this.fire(`moveend`)),this.fire(`resize`,{oldSize:n,newSize:i}))},stop:function(){return this.setZoom(this._limitZoom(this._zoom)),this.options.zoomSnap||this.fire(`viewreset`),this._stop()},locate:function(e){if(e=this._locateOptions=r({timeout:1e4,watch:!1},e),!(`geolocation`in navigator))return this._handleGeolocationError({code:0,message:`Geolocation not supported.`}),this;var n=a(this._handleGeolocationResponse,this),i=a(this._handleGeolocationError,this);return e.watch?this._locationWatchId=navigator.geolocation.watchPosition(n,i,e):navigator.geolocation.getCurrentPosition(n,i,e),this},stopLocate:function(){return navigator.geolocation&&navigator.geolocation.clearWatch&&navigator.geolocation.clearWatch(this._locationWatchId),this._locateOptions&&(this._locateOptions.setView=!1),this},_handleGeolocationError:function(e){if(this._container._leaflet_id){var n=e.code,r=e.message||(n===1?`permission denied`:n===2?`position unavailable`:`timeout`);this._locateOptions.setView&&!this._loaded&&this.fitWorld(),this.fire(`locationerror`,{code:n,message:`Geolocation error: `+r+`.`})}},_handleGeolocationResponse:function(e){if(this._container._leaflet_id){var n=e.coords.latitude,r=e.coords.longitude,i=new j(n,r),a=i.toBounds(e.coords.accuracy*2),o=this._locateOptions;if(o.setView){var s=this.getBoundsZoom(a);this.setView(i,o.maxZoom?Math.min(s,o.maxZoom):s)}var c={latlng:i,bounds:a,timestamp:e.timestamp};for(var l in e.coords)typeof e.coords[l]==`number`&&(c[l]=e.coords[l]);this.fire(`locationfound`,c)}},addHandler:function(e,n){if(!n)return this;var r=this[e]=new n(this);return this._handlers.push(r),this.options[e]&&r.enable(),this},remove:function(){if(this._initEvents(!0),this.options.maxBounds&&this.off(`moveend`,this._panInsideMaxBounds),this._containerId!==this._container._leaflet_id)throw Error(`Map container is being reused by another instance`);try{delete this._container._leaflet_id,delete this._containerId}catch{this._container._leaflet_id=void 0,this._containerId=void 0}for(var e in this._locationWatchId!==void 0&&this.stopLocate(),this._stop(),B(this._mapPane),this._clearControlPos&&this._clearControlPos(),this._resizeRequest&&=(x(this._resizeRequest),null),this._clearHandlers(),this._loaded&&this.fire(`unload`),this._layers)this._layers[e].remove();for(e in this._panes)B(this._panes[e]);return this._layers=[],this._panes=[],delete this._mapPane,delete this._renderer,this},createPane:function(e,n){var r=z(`div`,`leaflet-pane`+(e?` leaflet-`+e.replace(`Pane`,``)+`-pane`:``),n||this._mapPane);return e&&(this._panes[e]=r),r},getCenter:function(){return this._checkIfLoaded(),this._lastCenter&&!this._moved()?this._lastCenter.clone():this.layerPointToLatLng(this._getCenterLayerPoint())},getZoom:function(){return this._zoom},getBounds:function(){var e=this.getPixelBounds();return new k(this.unproject(e.getBottomLeft()),this.unproject(e.getTopRight()))},getMinZoom:function(){return this.options.minZoom===void 0?this._layersMinZoom||0:this.options.minZoom},getMaxZoom:function(){return this.options.maxZoom===void 0?this._layersMaxZoom===void 0?1/0:this._layersMaxZoom:this.options.maxZoom},getBoundsZoom:function(e,n,r){e=A(e),r=E(r||[0,0]);var i=this.getZoom()||0,a=this.getMinZoom(),o=this.getMaxZoom(),s=e.getNorthWest(),c=e.getSouthEast(),l=this.getSize().subtract(r),u=O(this.project(c,i),this.project(s,i)).getSize(),d=R.any3d?this.options.zoomSnap:1,f=l.x/u.x,p=l.y/u.y,m=n?Math.max(f,p):Math.min(f,p);return i=this.getScaleZoom(m,i),d&&(i=Math.round(i/(d/100))*(d/100),i=n?Math.ceil(i/d)*d:Math.floor(i/d)*d),Math.max(a,Math.min(o,i))},getSize:function(){return(!this._size||this._sizeChanged)&&(this._size=new w(this._container.clientWidth||0,this._container.clientHeight||0),this._sizeChanged=!1),this._size.clone()},getPixelBounds:function(e,n){var r=this._getTopLeftPoint(e,n);return new D(r,r.add(this.getSize()))},getPixelOrigin:function(){return this._checkIfLoaded(),this._pixelOrigin},getPixelWorldBounds:function(e){return this.options.crs.getProjectedBounds(e===void 0?this.getZoom():e)},getPane:function(e){return typeof e==`string`?this._panes[e]:e},getPanes:function(){return this._panes},getContainer:function(){return this._container},getZoomScale:function(e,n){var r=this.options.crs;return n=n===void 0?this._zoom:n,r.scale(e)/r.scale(n)},getScaleZoom:function(e,n){var r=this.options.crs;n=n===void 0?this._zoom:n;var i=r.zoom(e*r.scale(n));return isNaN(i)?1/0:i},project:function(e,n){return n=n===void 0?this._zoom:n,this.options.crs.latLngToPoint(M(e),n)},unproject:function(e,n){return n=n===void 0?this._zoom:n,this.options.crs.pointToLatLng(E(e),n)},layerPointToLatLng:function(e){var n=E(e).add(this.getPixelOrigin());return this.unproject(n)},latLngToLayerPoint:function(e){return this.project(M(e))._round()._subtract(this.getPixelOrigin())},wrapLatLng:function(e){return this.options.crs.wrapLatLng(M(e))},wrapLatLngBounds:function(e){return this.options.crs.wrapLatLngBounds(A(e))},distance:function(e,n){return this.options.crs.distance(M(e),M(n))},containerPointToLayerPoint:function(e){return E(e).subtract(this._getMapPanePos())},layerPointToContainerPoint:function(e){return E(e).add(this._getMapPanePos())},containerPointToLatLng:function(e){var n=this.containerPointToLayerPoint(E(e));return this.layerPointToLatLng(n)},latLngToContainerPoint:function(e){return this.layerPointToContainerPoint(this.latLngToLayerPoint(M(e)))},mouseEventToContainerPoint:function(e){return en(e,this._container)},mouseEventToLayerPoint:function(e){return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e))},mouseEventToLatLng:function(e){return this.layerPointToLatLng(this.mouseEventToLayerPoint(e))},_initContainer:function(e){var n=this._container=St(e);if(!n)throw Error(`Map container not found.`);if(n._leaflet_id)throw Error(`Map container is already initialized.`);q(n,`scroll`,this._onScroll,this),this._containerId=s(n)},_initLayout:function(){var e=this._container;this._fadeAnimated=this.options.fadeAnimation&&R.any3d,V(e,`leaflet-container`+(R.touch?` leaflet-touch`:``)+(R.retina?` leaflet-retina`:``)+(R.ielt9?` leaflet-oldie`:``)+(R.safari?` leaflet-safari`:``)+(this._fadeAnimated?` leaflet-fade-anim`:``));var n=Ct(e,`position`);n!==`absolute`&&n!==`relative`&&n!==`fixed`&&n!==`sticky`&&(e.style.position=`relative`),this._initPanes(),this._initControlPos&&this._initControlPos()},_initPanes:function(){var e=this._panes={};this._paneRenderers={},this._mapPane=this.createPane(`mapPane`,this._container),W(this._mapPane,new w(0,0)),this.createPane(`tilePane`),this.createPane(`overlayPane`),this.createPane(`shadowPane`),this.createPane(`markerPane`),this.createPane(`tooltipPane`),this.createPane(`popupPane`),this.options.markerZoomAnimation||(V(e.markerPane,`leaflet-zoom-hide`),V(e.shadowPane,`leaflet-zoom-hide`))},_resetView:function(e,n,r){W(this._mapPane,new w(0,0));var i=!this._loaded;this._loaded=!0,n=this._limitZoom(n),this.fire(`viewprereset`);var a=this._zoom!==n;this._moveStart(a,r)._move(e,n)._moveEnd(a),this.fire(`viewreset`),i&&this.fire(`load`)},_moveStart:function(e,n){return e&&this.fire(`zoomstart`),n||this.fire(`movestart`),this},_move:function(e,n,r,i){n===void 0&&(n=this._zoom);var a=this._zoom!==n;return this._zoom=n,this._lastCenter=e,this._pixelOrigin=this._getNewPixelOrigin(e),i?r&&r.pinch&&this.fire(`zoom`,r):((a||r&&r.pinch)&&this.fire(`zoom`,r),this.fire(`move`,r)),this},_moveEnd:function(e){return e&&this.fire(`zoomend`),this.fire(`moveend`)},_stop:function(){return x(this._flyToFrame),this._panAnim&&this._panAnim.stop(),this},_rawPanBy:function(e){W(this._mapPane,this._getMapPanePos().subtract(e))},_getZoomSpan:function(){return this.getMaxZoom()-this.getMinZoom()},_panInsideMaxBounds:function(){this._enforcingBounds||this.panInsideBounds(this.options.maxBounds)},_checkIfLoaded:function(){if(!this._loaded)throw Error(`Set map center and zoom first.`)},_initEvents:function(e){this._targets={},this._targets[s(this._container)]=this;var n=e?Y:q;n(this._container,`click dblclick mousedown mouseup mouseover mouseout mousemove contextmenu keypress keydown keyup`,this._handleDOMEvent,this),this.options.trackResize&&n(window,`resize`,this._onResize,this),R.any3d&&this.options.transform3DLimit&&(e?this.off:this.on).call(this,`moveend`,this._onMoveEnd)},_onResize:function(){x(this._resizeRequest),this._resizeRequest=b(function(){this.invalidateSize({debounceMoveend:!0})},this)},_onScroll:function(){this._container.scrollTop=0,this._container.scrollLeft=0},_onMoveEnd:function(){var e=this._getMapPanePos();Math.max(Math.abs(e.x),Math.abs(e.y))>=this.options.transform3DLimit&&this._resetView(this.getCenter(),this.getZoom())},_findEventTargets:function(e,n){for(var r=[],i,a=n===`mouseout`||n===`mouseover`,o=e.target||e.srcElement,c=!1;o;){if(i=this._targets[s(o)],i&&(n===`click`||n===`preclick`)&&this._draggableMoved(i)){c=!0;break}if(i&&i.listens(n,!0)&&(a&&!rn(o,e)||(r.push(i),a))||o===this._container)break;o=o.parentNode}return!r.length&&!c&&!a&&this.listens(n,!0)&&(r=[this]),r},_isClickDisabled:function(e){for(;e&&e!==this._container;){if(e._leaflet_disable_click)return!0;e=e.parentNode}},_handleDOMEvent:function(e){var n=e.target||e.srcElement;if(!(!this._loaded||n._leaflet_disable_events||e.type===`click`&&this._isClickDisabled(n))){var r=e.type;r===`mousedown`&&K(n),this._fireDOMEvent(e,r)}},_mouseEvents:[`click`,`dblclick`,`mouseover`,`mouseout`,`contextmenu`],_fireDOMEvent:function(e,n,i){if(e.type===`click`){var a=r({},e);a.type=`preclick`,this._fireDOMEvent(a,a.type,i)}var o=this._findEventTargets(e,n);if(i){for(var s=[],c=0;c0?Math.round(e-n)/2:Math.max(0,Math.ceil(e))-Math.max(0,Math.floor(n))},_limitZoom:function(e){var n=this.getMinZoom(),r=this.getMaxZoom(),i=R.any3d?this.options.zoomSnap:1;return i&&(e=Math.round(e/i)*i),Math.max(n,Math.min(r,e))},_onPanTransitionStep:function(){this.fire(`move`)},_onPanTransitionEnd:function(){H(this._mapPane,`leaflet-pan-anim`),this.fire(`moveend`)},_tryAnimatedPan:function(e,n){var r=this._getCenterOffset(e)._trunc();return(n&&n.animate)!==!0&&!this.getSize().contains(r)?!1:(this.panBy(r,n),!0)},_createAnimProxy:function(){var e=this._proxy=z(`div`,`leaflet-proxy leaflet-zoom-animated`);this._panes.mapPane.appendChild(e),this.on(`zoomanim`,function(e){var n=yt,r=this._proxy.style[n];Mt(this._proxy,this.project(e.center,e.zoom),this.getZoomScale(e.zoom,1)),r===this._proxy.style[n]&&this._animatingZoom&&this._onZoomTransitionEnd()},this),this.on(`load moveend`,this._animMoveEnd,this),this._on(`unload`,this._destroyAnimProxy,this)},_destroyAnimProxy:function(){B(this._proxy),this.off(`load moveend`,this._animMoveEnd,this),delete this._proxy},_animMoveEnd:function(){var e=this.getCenter(),n=this.getZoom();Mt(this._proxy,this.project(e,n),this.getZoomScale(n,1))},_catchTransitionEnd:function(e){this._animatingZoom&&e.propertyName.indexOf(`transform`)>=0&&this._onZoomTransitionEnd()},_nothingToAnimate:function(){return!this._container.getElementsByClassName(`leaflet-zoom-animated`).length},_tryAnimatedZoom:function(e,n,r){if(this._animatingZoom)return!0;if(r||={},!this._zoomAnimated||r.animate===!1||this._nothingToAnimate()||Math.abs(n-this._zoom)>this.options.zoomAnimationThreshold)return!1;var i=this.getZoomScale(n),a=this._getCenterOffset(e)._divideBy(1-1/i);return r.animate!==!0&&!this.getSize().contains(a)?!1:(b(function(){this._moveStart(!0,r.noMoveStart||!1)._animateZoom(e,n,!0)},this),!0)},_animateZoom:function(e,n,r,i){this._mapPane&&(r&&(this._animatingZoom=!0,this._animateToCenter=e,this._animateToZoom=n,V(this._mapPane,`leaflet-zoom-anim`)),this.fire(`zoomanim`,{center:e,zoom:n,noUpdate:i}),this._tempFireZoomEvent||=this._zoom!==this._animateToZoom,this._move(this._animateToCenter,this._animateToZoom,void 0,!0),setTimeout(a(this._onZoomTransitionEnd,this),250))},_onZoomTransitionEnd:function(){this._animatingZoom&&(this._mapPane&&H(this._mapPane,`leaflet-zoom-anim`),this._animatingZoom=!1,this._move(this._animateToCenter,this._animateToZoom,void 0,!0),this._tempFireZoomEvent&&this.fire(`zoom`),delete this._tempFireZoomEvent,this.fire(`move`),this._moveEnd(!0))}});function sn(e,n){return new Z(e,n)}var Q=S.extend({options:{position:`topright`},initialize:function(e){m(this,e)},getPosition:function(){return this.options.position},setPosition:function(e){var n=this._map;return n&&n.removeControl(this),this.options.position=e,n&&n.addControl(this),this},getContainer:function(){return this._container},addTo:function(e){this.remove(),this._map=e;var n=this._container=this.onAdd(e),r=this.getPosition(),i=e._controlCorners[r];return V(n,`leaflet-control`),r.indexOf(`bottom`)===-1?i.appendChild(n):i.insertBefore(n,i.firstChild),this._map.on(`unload`,this.remove,this),this},remove:function(){return this._map?(B(this._container),this.onRemove&&this.onRemove(this._map),this._map.off(`unload`,this.remove,this),this._map=null,this):this},_refocusOnMap:function(e){this._map&&e&&e.screenX>0&&e.screenY>0&&this._map.getContainer().focus()}}),cn=function(e){return new Q(e)};Z.include({addControl:function(e){return e.addTo(this),this},removeControl:function(e){return e.remove(),this},_initControlPos:function(){var e=this._controlCorners={},n=`leaflet-`,r=this._controlContainer=z(`div`,n+`control-container`,this._container);function i(i,a){var o=n+i+` `+n+a;e[i+a]=z(`div`,o,r)}i(`top`,`left`),i(`top`,`right`),i(`bottom`,`left`),i(`bottom`,`right`)},_clearControlPos:function(){for(var e in this._controlCorners)B(this._controlCorners[e]);B(this._controlContainer),delete this._controlCorners,delete this._controlContainer}});var ln=Q.extend({options:{collapsed:!0,position:`topright`,autoZIndex:!0,hideSingleBase:!1,sortLayers:!1,sortFunction:function(e,n,r,i){return r1,this._baseLayersList.style.display=e?``:`none`),this._separator.style.display=n&&e?``:`none`,this},_onLayerChange:function(e){this._handlingClick||this._update();var n=this._getLayer(s(e.target)),r=n.overlay?e.type===`add`?`overlayadd`:`overlayremove`:e.type===`add`?`baselayerchange`:null;r&&this._map.fire(r,n)},_createRadioElement:function(e,n){var r=``,i=document.createElement(`div`);return i.innerHTML=r,i.firstChild},_addItem:function(e){var n=document.createElement(`label`),r=this._map.hasLayer(e.layer),i;e.overlay?(i=document.createElement(`input`),i.type=`checkbox`,i.className=`leaflet-control-layers-selector`,i.defaultChecked=r):i=this._createRadioElement(`leaflet-base-layers_`+s(this),r),this._layerControlInputs.push(i),i.layerId=s(e.layer),q(i,`click`,this._onInputClick,this);var a=document.createElement(`span`);a.innerHTML=` `+e.name;var o=document.createElement(`span`);return n.appendChild(o),o.appendChild(i),o.appendChild(a),(e.overlay?this._overlaysList:this._baseLayersList).appendChild(n),this._checkDisabledLayers(),n},_onInputClick:function(){if(!this._preventClick){var e=this._layerControlInputs,n,r,i=[],a=[];this._handlingClick=!0;for(var o=e.length-1;o>=0;o--)n=e[o],r=this._getLayer(n.layerId).layer,n.checked?i.push(r):n.checked||a.push(r);for(o=0;o=0;a--)n=e[a],r=this._getLayer(n.layerId).layer,n.disabled=r.options.minZoom!==void 0&&ir.options.maxZoom},_expandIfNotCollapsed:function(){return this._map&&!this.options.collapsed&&this.expand(),this},_expandSafely:function(){var e=this._section;this._preventClick=!0,q(e,`click`,X),this.expand();var n=this;setTimeout(function(){Y(e,`click`,X),n._preventClick=!1})}}),un=function(e,n,r){return new ln(e,n,r)},dn=Q.extend({options:{position:`topleft`,zoomInText:``,zoomInTitle:`Zoom in`,zoomOutText:``,zoomOutTitle:`Zoom out`},onAdd:function(e){var n=`leaflet-control-zoom`,r=z(`div`,n+` leaflet-bar`),i=this.options;return this._zoomInButton=this._createButton(i.zoomInText,i.zoomInTitle,n+`-in`,r,this._zoomIn),this._zoomOutButton=this._createButton(i.zoomOutText,i.zoomOutTitle,n+`-out`,r,this._zoomOut),this._updateDisabled(),e.on(`zoomend zoomlevelschange`,this._updateDisabled,this),r},onRemove:function(e){e.off(`zoomend zoomlevelschange`,this._updateDisabled,this)},disable:function(){return this._disabled=!0,this._updateDisabled(),this},enable:function(){return this._disabled=!1,this._updateDisabled(),this},_zoomIn:function(e){!this._disabled&&this._map._zoomthis._map.getMinZoom()&&this._map.zoomOut(this._map.options.zoomDelta*(e.shiftKey?3:1))},_createButton:function(e,n,r,i,a){var o=z(`a`,r,i);return o.innerHTML=e,o.href=`#`,o.title=n,o.setAttribute(`role`,`button`),o.setAttribute(`aria-label`,n),Zt(o),q(o,`click`,Qt),q(o,`click`,a,this),q(o,`click`,this._refocusOnMap,this),o},_updateDisabled:function(){var e=this._map,n=`leaflet-disabled`;H(this._zoomInButton,n),H(this._zoomOutButton,n),this._zoomInButton.setAttribute(`aria-disabled`,`false`),this._zoomOutButton.setAttribute(`aria-disabled`,`false`),(this._disabled||e._zoom===e.getMinZoom())&&(V(this._zoomOutButton,n),this._zoomOutButton.setAttribute(`aria-disabled`,`true`)),(this._disabled||e._zoom===e.getMaxZoom())&&(V(this._zoomInButton,n),this._zoomInButton.setAttribute(`aria-disabled`,`true`))}});Z.mergeOptions({zoomControl:!0}),Z.addInitHook(function(){this.options.zoomControl&&(this.zoomControl=new dn,this.addControl(this.zoomControl))});var fn=function(e){return new dn(e)},pn=Q.extend({options:{position:`bottomleft`,maxWidth:100,metric:!0,imperial:!0},onAdd:function(e){var n=`leaflet-control-scale`,r=z(`div`,n),i=this.options;return this._addScales(i,n+`-line`,r),e.on(i.updateWhenIdle?`moveend`:`move`,this._update,this),e.whenReady(this._update,this),r},onRemove:function(e){e.off(this.options.updateWhenIdle?`moveend`:`move`,this._update,this)},_addScales:function(e,n,r){e.metric&&(this._mScale=z(`div`,n,r)),e.imperial&&(this._iScale=z(`div`,n,r))},_update:function(){var e=this._map,n=e.getSize().y/2,r=e.distance(e.containerPointToLatLng([0,n]),e.containerPointToLatLng([this.options.maxWidth,n]));this._updateScales(r)},_updateScales:function(e){this.options.metric&&e&&this._updateMetric(e),this.options.imperial&&e&&this._updateImperial(e)},_updateMetric:function(e){var n=this._getRoundNum(e),r=n<1e3?n+` m`:n/1e3+` km`;this._updateScale(this._mScale,r,n/e)},_updateImperial:function(e){var n=e*3.2808399,r,i,a;n>5280?(r=n/5280,i=this._getRoundNum(r),this._updateScale(this._iScale,i+` mi`,i/r)):(a=this._getRoundNum(n),this._updateScale(this._iScale,a+` ft`,a/n))},_updateScale:function(e,n,r){e.style.width=Math.round(this.options.maxWidth*r)+`px`,e.innerHTML=n},_getRoundNum:function(e){var n=10**((Math.floor(e)+``).length-1),r=e/n;return r=r>=10?10:r>=5?5:r>=3?3:r>=2?2:1,n*r}}),mn=function(e){return new pn(e)},hn=Q.extend({options:{position:`bottomright`,prefix:``+(R.inlineSvg?` `:``)+`Leaflet`},initialize:function(e){m(this,e),this._attributions={}},onAdd:function(e){for(var n in e.attributionControl=this,this._container=z(`div`,`leaflet-control-attribution`),Zt(this._container),e._layers)e._layers[n].getAttribution&&this.addAttribution(e._layers[n].getAttribution());return this._update(),e.on(`layeradd`,this._addAttribution,this),this._container},onRemove:function(e){e.off(`layeradd`,this._addAttribution,this)},_addAttribution:function(e){e.layer.getAttribution&&(this.addAttribution(e.layer.getAttribution()),e.layer.once(`remove`,function(){this.removeAttribution(e.layer.getAttribution())},this))},setPrefix:function(e){return this.options.prefix=e,this._update(),this},addAttribution:function(e){return e?(this._attributions[e]||(this._attributions[e]=0),this._attributions[e]++,this._update(),this):this},removeAttribution:function(e){return e&&this._attributions[e]&&(this._attributions[e]--,this._update()),this},_update:function(){if(this._map){var e=[];for(var n in this._attributions)this._attributions[n]&&e.push(n);var r=[];this.options.prefix&&r.push(this.options.prefix),e.length&&r.push(e.join(`, `)),this._container.innerHTML=r.join(` `)}}});Z.mergeOptions({attributionControl:!0}),Z.addInitHook(function(){this.options.attributionControl&&new hn().addTo(this)}),Q.Layers=ln,Q.Zoom=dn,Q.Scale=pn,Q.Attribution=hn,cn.layers=un,cn.zoom=fn,cn.scale=mn,cn.attribution=function(e){return new hn(e)};var gn=S.extend({initialize:function(e){this._map=e},enable:function(){return this._enabled?this:(this._enabled=!0,this.addHooks(),this)},disable:function(){return this._enabled?(this._enabled=!1,this.removeHooks(),this):this},enabled:function(){return!!this._enabled}});gn.addTo=function(e,n){return e.addHandler(n,this),this};var _n={Events:C},vn=R.touch?`touchstart mousedown`:`mousedown`,yn=ce.extend({options:{clickTolerance:3},initialize:function(e,n,r,i){m(this,i),this._element=e,this._dragStartTarget=n||e,this._preventOutline=r},enable:function(){this._enabled||=(q(this._dragStartTarget,vn,this._onDown,this),!0)},disable:function(){this._enabled&&(yn._dragging===this&&this.finishDrag(!0),Y(this._dragStartTarget,vn,this._onDown,this),this._enabled=!1,this._moved=!1)},_onDown:function(e){if(this._enabled&&(this._moved=!1,!Dt(this._element,`leaflet-zoom-anim`))){if(e.touches&&e.touches.length!==1){yn._dragging===this&&this.finishDrag();return}if(!(yn._dragging||e.shiftKey||e.which!==1&&e.button!==1&&!e.touches)&&(yn._dragging=this,this._preventOutline&&K(this._element),Lt(),G(),!this._moving)){this.fire(`down`);var n=e.touches?e.touches[0]:e,r=Ht(this._element);this._startPoint=new w(n.clientX,n.clientY),this._startPos=Nt(this._element),this._parentScale=Ut(r);var i=e.type===`mousedown`;q(document,i?`mousemove`:`touchmove`,this._onMove,this),q(document,i?`mouseup`:`touchend touchcancel`,this._onUp,this)}}},_onMove:function(e){if(this._enabled){if(e.touches&&e.touches.length>1){this._moved=!0;return}var n=e.touches&&e.touches.length===1?e.touches[0]:e,r=new w(n.clientX,n.clientY)._subtract(this._startPoint);!r.x&&!r.y||Math.abs(r.x)+Math.abs(r.y)o&&(s=c,o=l);o>r&&(n[s]=1,On(e,n,r,i,s),On(e,n,r,s,a))}function kn(e,n){for(var r=[e[0]],i=1,a=0,o=e.length;in&&(r.push(e[i]),a=i);return an.max.x&&(r|=2),e.yn.max.y&&(r|=8),r}function Pn(e,n){var r=n.x-e.x,i=n.y-e.y;return r*r+i*i}function Fn(e,n,r,i){var a=n.x,o=n.y,s=r.x-a,c=r.y-o,l=s*s+c*c,u;return l>0&&(u=((e.x-a)*s+(e.y-o)*c)/l,u>1?(a=r.x,o=r.y):u>0&&(a+=s*u,o+=c*u)),s=e.x-a,c=e.y-o,i?s*s+c*c:new w(a,o)}function $(e){return!v(e[0])||typeof e[0][0]!=`object`&&e[0][0]!==void 0}function In(e){return console.warn(`Deprecated use of _flat, please use L.LineUtil.isFlat instead.`),$(e)}function Ln(e,n){var r,i,a,o,s,c,l,u;if(!e||e.length===0)throw Error(`latlngs not passed`);$(e)||(console.warn(`latlngs are not flat! Only the first ring will be used`),e=e[0]);var d=M([0,0]),f=A(e);f.getNorthWest().distanceTo(f.getSouthWest())*f.getNorthEast().distanceTo(f.getNorthWest())<1700&&(d=Sn(e));var p=e.length,m=[];for(r=0;ri){l=(o-i)/a,u=[c.x-l*(c.x-s.x),c.y-l*(c.y-s.y)];break}var g=n.unproject(E(u));return M([g.lat+d.lat,g.lng+d.lng])}var Rn={__proto__:null,simplify:wn,pointToSegmentDistance:Tn,closestPointOnSegment:En,clipSegment:jn,_getEdgeIntersection:Mn,_getBitCode:Nn,_sqClosestPointOnSegment:Fn,isFlat:$,_flat:In,polylineCenter:Ln},zn={project:function(e){return new w(e.lng,e.lat)},unproject:function(e){return new j(e.y,e.x)},bounds:new D([-180,-90],[180,90])},Bn={R:6378137,R_MINOR:6356752.314245179,bounds:new D([-20037508.34279,-15496570.73972],[20037508.34279,18764656.23138]),project:function(e){var n=Math.PI/180,r=this.R,i=e.lat*n,a=this.R_MINOR/r,o=Math.sqrt(1-a*a),s=o*Math.sin(i),c=Math.tan(Math.PI/4-i/2)/((1-s)/(1+s))**(o/2);return i=-r*Math.log(Math.max(c,1e-10)),new w(e.lng*n*r,i)},unproject:function(e){for(var n=180/Math.PI,r=this.R,i=this.R_MINOR/r,a=Math.sqrt(1-i*i),o=Math.exp(-e.y/r),s=Math.PI/2-2*Math.atan(o),c=0,l=.1,u;c<15&&Math.abs(l)>1e-7;c++)u=a*Math.sin(s),u=((1-u)/(1+u))**(a/2),l=Math.PI/2-2*Math.atan(o*u)-s,s+=l;return new j(s*n,e.x*n/r)}},Vn={__proto__:null,LonLat:zn,Mercator:Bn,SphericalMercator:de},Hn=r({},le,{code:`EPSG:3395`,projection:Bn,transformation:function(){var e=.5/(Math.PI*Bn.R);return pe(e,.5,-e,.5)}()}),Un=r({},le,{code:`EPSG:4326`,projection:zn,transformation:pe(1/180,1,-1/180,.5)}),Wn=r({},N,{projection:zn,transformation:pe(1,0,-1,0),scale:function(e){return 2**e},zoom:function(e){return Math.log(e)/Math.LN2},distance:function(e,n){var r=n.lng-e.lng,i=n.lat-e.lat;return Math.sqrt(r*r+i*i)},infinite:!0});N.Earth=le,N.EPSG3395=Hn,N.EPSG3857=me,N.EPSG900913=he,N.EPSG4326=Un,N.Simple=Wn;var Gn=ce.extend({options:{pane:`overlayPane`,attribution:null,bubblingMouseEvents:!0},addTo:function(e){return e.addLayer(this),this},remove:function(){return this.removeFrom(this._map||this._mapToAdd)},removeFrom:function(e){return e&&e.removeLayer(this),this},getPane:function(e){return this._map.getPane(e?this.options[e]||e:this.options.pane)},addInteractiveTarget:function(e){return this._map._targets[s(e)]=this,this},removeInteractiveTarget:function(e){return delete this._map._targets[s(e)],this},getAttribution:function(){return this.options.attribution},_layerAdd:function(e){var n=e.target;if(n.hasLayer(this)){if(this._map=n,this._zoomAnimated=n._zoomAnimated,this.getEvents){var r=this.getEvents();n.on(r,this),this.once(`remove`,function(){n.off(r,this)},this)}this.onAdd(n),this.fire(`add`),n.fire(`layeradd`,{layer:this})}}});Z.include({addLayer:function(e){if(!e._layerAdd)throw Error(`The provided object is not a Layer.`);var n=s(e);return this._layers[n]?this:(this._layers[n]=e,e._mapToAdd=this,e.beforeAdd&&e.beforeAdd(this),this.whenReady(e._layerAdd,e),this)},removeLayer:function(e){var n=s(e);return this._layers[n]?(this._loaded&&e.onRemove(this),delete this._layers[n],this._loaded&&(this.fire(`layerremove`,{layer:e}),e.fire(`remove`)),e._map=e._mapToAdd=null,this):this},hasLayer:function(e){return s(e)in this._layers},eachLayer:function(e,n){for(var r in this._layers)e.call(n,this._layers[r]);return this},_addLayers:function(e){e=e?v(e)?e:[e]:[];for(var n=0,r=e.length;nthis._layersMaxZoom&&this.setZoom(this._layersMaxZoom),this.options.minZoom===void 0&&this._layersMinZoom&&this.getZoom()=2&&n[0]instanceof j&&n[0].equals(n[r-1])&&n.pop(),n},_setLatLngs:function(e){sr.prototype._setLatLngs.call(this,e),$(this._latlngs)&&(this._latlngs=[this._latlngs])},_defaultShape:function(){return $(this._latlngs[0])?this._latlngs[0]:this._latlngs[0][0]},_clipPoints:function(){var e=this._renderer._bounds,n=this.options.weight,r=new w(n,n);if(e=new D(e.min.subtract(r),e.max.add(r)),this._parts=[],!(!this._pxBounds||!this._pxBounds.intersects(e))){if(this.options.noClip){this._parts=this._rings;return}for(var i=0,a=this._rings.length,o;ie.y!=a.y>e.y&&e.x<(a.x-i.x)*(e.y-i.y)/(a.y-i.y)+i.x&&(n=!n);return n||sr.prototype._containsPoint.call(this,e,!0)}});function ur(e,n){return new lr(e,n)}var dr=Jn.extend({initialize:function(e,n){m(this,n),this._layers={},e&&this.addData(e)},addData:function(e){var n=v(e)?e:e.features,r,i,a;if(n){for(r=0,i=n.length;r0&&a.push(a[0].slice()),a}function vr(e,n){return e.feature?r({},e.feature,{geometry:n}):yr(n)}function yr(e){return e.type===`Feature`||e.type===`FeatureCollection`?e:{type:`Feature`,properties:{},geometry:e}}var br={toGeoJSON:function(e){return vr(this,{type:`Point`,coordinates:gr(this.getLatLng(),e)})}};er.include(br),ar.include(br),rr.include(br),sr.include({toGeoJSON:function(e){var n=!$(this._latlngs),r=_r(this._latlngs,+!!n,!1,e);return vr(this,{type:(n?`Multi`:``)+`LineString`,coordinates:r})}}),lr.include({toGeoJSON:function(e){var n=!$(this._latlngs),r=n&&!$(this._latlngs[0]),i=_r(this._latlngs,r?2:+!!n,!0,e);return n||(i=[i]),vr(this,{type:(r?`Multi`:``)+`Polygon`,coordinates:i})}}),Kn.include({toMultiPoint:function(e){var n=[];return this.eachLayer(function(r){n.push(r.toGeoJSON(e).geometry.coordinates)}),vr(this,{type:`MultiPoint`,coordinates:n})},toGeoJSON:function(e){var n=this.feature&&this.feature.geometry&&this.feature.geometry.type;if(n===`MultiPoint`)return this.toMultiPoint(e);var r=n===`GeometryCollection`,i=[];return this.eachLayer(function(n){if(n.toGeoJSON){var a=n.toGeoJSON(e);if(r)i.push(a.geometry);else{var o=yr(a);o.type===`FeatureCollection`?i.push.apply(i,o.features):i.push(o)}}}),r?vr(this,{geometries:i,type:`GeometryCollection`}):{type:`FeatureCollection`,features:i}}});function xr(e,n){return new dr(e,n)}var Sr=xr,Cr=Gn.extend({options:{opacity:1,alt:``,interactive:!1,crossOrigin:!1,errorOverlayUrl:``,zIndex:1,className:``},initialize:function(e,n,r){this._url=e,this._bounds=A(n),m(this,r)},onAdd:function(){this._image||(this._initImage(),this.options.opacity<1&&this._updateOpacity()),this.options.interactive&&(V(this._image,`leaflet-interactive`),this.addInteractiveTarget(this._image)),this.getPane().appendChild(this._image),this._reset()},onRemove:function(){B(this._image),this.options.interactive&&this.removeInteractiveTarget(this._image)},setOpacity:function(e){return this.options.opacity=e,this._image&&this._updateOpacity(),this},setStyle:function(e){return e.opacity&&this.setOpacity(e.opacity),this},bringToFront:function(){return this._map&&Tt(this._image),this},bringToBack:function(){return this._map&&Et(this._image),this},setUrl:function(e){return this._url=e,this._image&&(this._image.src=e),this},setBounds:function(e){return this._bounds=A(e),this._map&&this._reset(),this},getEvents:function(){var e={zoom:this._reset,viewreset:this._reset};return this._zoomAnimated&&(e.zoomanim=this._animateZoom),e},setZIndex:function(e){return this.options.zIndex=e,this._updateZIndex(),this},getBounds:function(){return this._bounds},getElement:function(){return this._image},_initImage:function(){var e=this._url.tagName===`IMG`,n=this._image=e?this._url:z(`img`);if(V(n,`leaflet-image-layer`),this._zoomAnimated&&V(n,`leaflet-zoom-animated`),this.options.className&&V(n,this.options.className),n.onselectstart=u,n.onmousemove=u,n.onload=a(this.fire,this,`load`),n.onerror=a(this._overlayOnError,this,`error`),(this.options.crossOrigin||this.options.crossOrigin===``)&&(n.crossOrigin=this.options.crossOrigin===!0?``:this.options.crossOrigin),this.options.zIndex&&this._updateZIndex(),e){this._url=n.src;return}n.src=this._url,n.alt=this.options.alt},_animateZoom:function(e){var n=this._map.getZoomScale(e.zoom),r=this._map._latLngBoundsToNewLayerBounds(this._bounds,e.zoom,e.center).min;Mt(this._image,r,n)},_reset:function(){var e=this._image,n=new D(this._map.latLngToLayerPoint(this._bounds.getNorthWest()),this._map.latLngToLayerPoint(this._bounds.getSouthEast())),r=n.getSize();W(e,n.min),e.style.width=r.x+`px`,e.style.height=r.y+`px`},_updateOpacity:function(){U(this._image,this.options.opacity)},_updateZIndex:function(){this._image&&this.options.zIndex!==void 0&&this.options.zIndex!==null&&(this._image.style.zIndex=this.options.zIndex)},_overlayOnError:function(){this.fire(`error`);var e=this.options.errorOverlayUrl;e&&this._url!==e&&(this._url=e,this._image.src=e)},getCenter:function(){return this._bounds.getCenter()}}),wr=function(e,n,r){return new Cr(e,n,r)},Tr=Cr.extend({options:{autoplay:!0,loop:!0,keepAspectRatio:!0,muted:!1,playsInline:!0},_initImage:function(){var e=this._url.tagName===`VIDEO`,n=this._image=e?this._url:z(`video`);if(V(n,`leaflet-image-layer`),this._zoomAnimated&&V(n,`leaflet-zoom-animated`),this.options.className&&V(n,this.options.className),n.onselectstart=u,n.onmousemove=u,n.onloadeddata=a(this.fire,this,`load`),e){for(var r=n.getElementsByTagName(`source`),i=[],o=0;o0?i:[n.src];return}v(this._url)||(this._url=[this._url]),!this.options.keepAspectRatio&&Object.prototype.hasOwnProperty.call(n.style,`objectFit`)&&(n.style.objectFit=`fill`),n.autoplay=!!this.options.autoplay,n.loop=!!this.options.loop,n.muted=!!this.options.muted,n.playsInline=!!this.options.playsInline;for(var s=0;s