{"id":25368256,"url":"https://github.com/xh/hoist-core","last_synced_at":"2026-04-30T20:01:11.384Z","repository":{"id":38066950,"uuid":"117719530","full_name":"xh/hoist-core","owner":"xh","description":"🏗️  The XH Hoist toolkit for Grails","archived":false,"fork":false,"pushed_at":"2026-04-27T02:31:39.000Z","size":2556,"stargazers_count":9,"open_issues_count":27,"forks_count":2,"subscribers_count":6,"default_branch":"develop","last_synced_at":"2026-04-27T03:30:30.905Z","etag":null,"topics":["fintech","grails","groovy"],"latest_commit_sha":null,"homepage":"https://xh.io/","language":"Groovy","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/xh.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2018-01-16T17:52:16.000Z","updated_at":"2026-04-27T02:31:42.000Z","dependencies_parsed_at":"2026-01-21T20:08:57.310Z","dependency_job_id":null,"html_url":"https://github.com/xh/hoist-core","commit_stats":null,"previous_names":[],"tags_count":188,"template":false,"template_full_name":null,"purl":"pkg:github/xh/hoist-core","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xh%2Fhoist-core","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xh%2Fhoist-core/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xh%2Fhoist-core/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xh%2Fhoist-core/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xh","download_url":"https://codeload.github.com/xh/hoist-core/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xh%2Fhoist-core/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32475192,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-30T13:12:12.517Z","status":"ssl_error","status_checked_at":"2026-04-30T13:12:06.837Z","response_time":57,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fintech","grails","groovy"],"created_at":"2025-02-15T00:36:50.338Z","updated_at":"2026-04-30T20:01:11.376Z","avatar_url":"https://github.com/xh.png","language":"Groovy","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Welcome to Hoist\n\nHoist is a web application development toolkit developed by [Extremely Heavy](https://xh.io/).\n\nHoist is designed as a \"full stack\" UI development framework, meaning that it has both server and\nclient components that work together to provide an integrated set of tools and utilities for quickly\nconstructing sophisticated front-end interfaces - or entire applications - with a strong focus on\nbuilding for the enterprise.\n\nThe core technologies used are Java and its more dynamic cousin [Groovy](http://groovy-lang.org/) -\nvia the mature [Grails framework](https://grails.org/) - and JavaScript - via\n[React](https://reactjs.org/) and associated libraries.\n\nThis repository is *hoist-core*, which is the server-side implementation of Hoist. It is designed to\nbe used with [hoist-react](https://github.com/xh/hoist-react), our latest front-end toolkit.\nHoist's original front-end implementation. See that repository for detailed information on Hoist's\nclient-side features and conventions.\n\n## About this Doc\n\nThis readme is intended to provide a general orientation to the features provided by Hoist, notes on\nspecific features and implementation details of particular interest, and direct links into the most\nrelevant and commonly used source code classes and routines. We have aimed to make the code itself\nas clear, readable, and well-commented as possible, and we are working to ensure a consistent level\nof in-code documentation - especially at the class level and for essential public methods.\n\nWhen calling out particular source code files in the tables below, the following icons are used as\nthe links to the containing repository: 🏗 for this repo and ⚛ for hoist-react.\n\nThis document does _not_ provide an overview or introduction to Grails, Java, or other core\ntechnologies in general, and assumes a general familiarity with many concepts related to enterprise\nweb application development.\n\n## Core Features - Overview\n\nHoist grew out of our ongoing practice developing applications for enterprise clients - primarily in\nfinance - that required multiple interrelated yet distinct applications that were:\n\n* Data-dense - able to load and visualize large amounts of data, with a focus on grids and charts\n* Consistent - with shared UI controls, coding patterns, and styles\n* User friendly - efficient and enjoyable to operate, even for demanding users\n* Highly maintainable - stable, with strong tooling for deployment and operational support\n\nWhile there are clearly many application development libraries and frameworks, we required a toolkit\nthat could pre-select a set of libraries and bring together higher-level services such as:\n\n* Application configuration and administration\n* Activity tracking and auditing\n* User management and pluggable authentication\n* User preferences\n* Status monitoring and health checks\n* Distributed tracing and combined observability\n* Error reporting and feedback\n* Customized / wrapped components, including grids, charts, and dashboards\n* Shared and consistent formatters (dates/numbers) and styles\n\nWith Hoist, these features work together and build on each other. A simple utility method to make an\nAjax request to the server can automatically decode a JSON response, save a tracking record of who\nmade the call and how long it took, and clearly alert the user and/or app administrators if there\nwas an error. A customized grid component can offer a full-featured UI for column selection, support\nfiltering column choices based on user roles, and persist the user's choice of visible columns and\nsorting options as a preference that is maintained across browsing sessions and workstations.\n\n## Role of the Server\n\nThe primary focus of Hoist is on building user interfaces that sit in front of and display data from\na variety of back-end sources and services already deployed within an enterprise. As such, much of\nthe developer's interaction with Hoist is in the form of client-side JavaScript development.\n\nGiven this emphasis on the front-end, the role of the Grails server provided by hoist-core can be\nlimited almost exclusively to providing the built-in infrastructure expected by the client-side\ntoolkits - serving up and storing configuration, preference, tracking, and other related data. These\ntasks require a server capable of persisting data to a database - Grails supports a wide variety -\nand of securely processing requests and serializing data.\n\nWhen it comes to fetching business data specific to an application, a Hoist JS app can talk to\nindependent back-end systems directly via CORS or proxy arrangements. Indeed once an initial project\nsetup is complete, a developer _might_ never touch Java / Groovy code.\n\nThat said, Grails does provide a feature rich server layer with the full power of the Java ecosystem\navailable for use. Roles played by the Hoist server commonly include:\n\n* Authenticating users, via a username/password lookup or single-sign-on/NTLM auth.\n* Serving as an intermediate layer between the JS client app and other back-end systems, parsing and\n  validating queries, relaying them, and then potentially transforming, caching, or combining\n  results.\n* Directly proxying requests to other HTTP-based services, avoiding the need for CORS.\n* Querying a SQL database or alternative data store such as Redis.\n* Listening on or fetching data from a message queue such as Kafka or RabbitMQ.\n* Sending email or instant message notifications.\n* Managing its business objects directly, providing all services required for a full-stack app.\n\n## Application Structure and Deployment\n\nA Hoist app is structured primarily as a Grails 6.x application, with a file and directory layout\nthat follows the Grails conventions. The Grails project offers extensive and well maintained\n[documentation](http://docs.grails.org/latest/) on the framework's features and configuration. This\nlibrary - hoist-core - is packaged as a Grails plugin, and should be specified as such within the\nGrails app's build.gradle file, e.g.:\n\n```\ndependencies {\n  ...  // standard Grails dependencies / plugins / app-specific libraries\n  compile \"io.xh:hoist-core:$hoistCoreVersion\"`\n}\n```\n\nThis will add server-side support for Hoist's core back-end services, including a set of endpoints\nexpected by the client-side toolkit implementations. Versioned and snapshot builds of Hoist are\npushed to XH's maven repository, which must also be configured within build.gradle.\n\nGrails applications are built via [Gradle](https://gradle.org/), a highly flexible and popular build\ntool. The result is a single WAR file which can be deployed via\n[Apache Tomcat](http://tomcat.apache.org/).\n\nAll client-side code is commonly maintained within the same repository, but within its own dedicated\nfolder tree. For hoist-react applications, JS apps are built independently by Webpack and deployed\nvia nginx. We recommend and provide standardized Docker containers to ship both sides of the\napplication and tie them together in an integrated whole, with very minimal infrastructure\nrequirements or dependencies. See the [Hoist React readme](https://github.com/xh/hoist-react) for\na detailed outline on the build and deploy process for React-based applications, including\ninformation on building the Grails server-side components outlined here.\n\n## Custom plugins for enterprise deployments\n\nWhile Hoist Core and its associated client-side libraries provide a good deal of functionality on\ntheir own, we realize that enterprise clients will have configurations, authentication requirements,\ndependencies, data source definitions, and other reusable code constructs that are unique to their\nenvironment.\n\nTo support these needs while still encouraging maximum consistency across multiple applications, we\ncan assist in creating and maintaining a custom plugin layer between Hoist and business\napplications.\n\n## Hoist usage, licensing, and support\n\nHoist is currently developed exclusively by Extremely Heavy and intended for use by XH and our\nclient partners to develop enterprise web applications with XH's guidance and direction. That said,\nwe have released the toolkit under the permissive and open Apache 2.0 license. This allows other\ndevelopers, regardless of whether they are current XH clients or not, to checkout, use, modify, and\notherwise explore Hoist and its source code. See [this project's license file](LICENSE.md) for the\nfull license.\n\nWe have selected an open source license as part of our ongoing commitment to openness, transparency,\nand ease-of-use, and to clarify and emphasize the suitability of Hoist for use within a wide variety\nof enterprise software projects. Note, however, that we cannot at this time commit to any particular\nsupport or contribution model outside of our consulting work. But if you are interested in Hoist\nand/or think it might be helpful for a project, please don't hesitate to\n[contact us](https://xh.io)!\n\n## Core Features - Additional Details\n\nWhile this document does not aim to provide a definitive or complete guide to the components of the\nHoist framework or its usage, several key features are called out with additional details below.\n\n\n### User Authentication / Authorization\n\n| Class/File                         | Note                                        |                                    Link                                     |\n|------------------------------------|---------------------------------------------|:---------------------------------------------------------------------------:|\n| `BaseAuthenticationService.groovy` | App must implement to define auth scheme    | [🏗](src/main/groovy/io/xh/hoist/security/BaseAuthenticationService.groovy) |\n| `BaseUserService.groovy`           | App must implement to generate users        |        [🏗](src/main/groovy/io/xh/hoist/user/BaseUserService.groovy)        |\n| `HoistUser.groovy`                 | Trait/interface for core user data          |           [🏗](src/main/groovy/io/xh/hoist/user/HoistUser.groovy)           |\n| `IdentityService.groovy`           | Server-side source of current user info     |      [🏗](grails-app/services/io/xh/hoist/user/IdentityService.groovy)      |\n| `IdentityService.ts`               | Hoist-React source of current user info     | [⚛️](https://github.com/xh/hoist-react/blob/master/svc/IdentityService.ts)  |\n\n👫 As organizations and applications will have a wide variety of requirements for\nauthenticating and authorizing users, Hoist has a deliberately minimal interface in this regard. A\nprimary requirement for applications is that they implement a Grails Service named\n`AuthenticationService` that extends Hoist's `BaseAuthenticationService` and implement its\n`completeAuthentication()` method.\n\nImplementations of this method must lookup and/or create a User class which implements the\n`HoistUser` trait/interface. This specifies the core information Hoist expects to access for any\nlogged in user. Applications can choose to enhance their own user class with any additional details,\nmanaged via the app or sourced from systems such as Active Directory / LDAP.\n\nWhile not included in Hoist directly, OAuth can be supported via integration with libraries such as\nMSAL.  This is commonly done via a [custom plugin](#custom-plugins-for-enterprise-deployments).\n\nOnce authentication is complete, `IdentityService` is the primary server-side Service for getting a\nreference to the current user. Hoist's client side code calls a dedicated endpoint to verify and\nfetch core user info, making it easily available to the JS app via a corresponding JS service.\n\n#### Roles and Access\n\n\n| Class/File                 | Note                                        |                                 Link                                 |\n|----------------------------|---------------------------------------------|:--------------------------------------------------------------------:|\n| `BaseRoleService.groovy`   | App must implement to assign roles to users |    [🏗](src/main/groovy/io/xh/hoist/role/BaseRoleService.groovy)     |\n| `AccessRequiresRole.groovy`| Annotation for endpoint security            | [🏗](src/main/groovy/io/xh/hoist/security/AccessRequiresRole.groovy) |\n\n🔒 Structure is provided for application \"roles\", for use in defining access to various parts of the\napplication.  At their core, roles are simply strings -- it is up to the application to determine what\nmeaning they will have in the context of the app.  Note that Hoist will expect (and auto-create) certain\nroles (e.g. `\"HOIST_ADMIN\"`, `\"HOIST_ADMIN_READER\"`, `\"HOIST_IMPERSONATOR\"`, and `\"HOIST_ROLE_MANAGER\"`)\nthat protect built-in hoist functionality and should be assigned as needed to administrators of\nthe app.\n\nRoles are associated with users via an application defined `RoleService`.  Hoist provides an\nout-of-the-box solution for this -- `DefaultRoleService` --  that will store Role definitions and\nassignments in the database, can be integrated with other systems such as Active Directory, and\nhas a built-in management UI in Hoist React. `DefaultRoleService` may be customized, but for\napplications that require a fully custom solution, `BaseRoleService` establishes the minimal required\nAPI contract and may be subclassed directly.\n\nServer-side endpoints (Controllers and their non-private methods) can be restricted to users with a\ngiven role or roles via the `@AccessRequiresRole`,  `@AccessRequiresAnyRole` and\n`@AccessRequiresAllRoles` annotations, e.g. a controller that\nshould be accessible to users with either the \"READER\" or \"EDITOR\" roles with specific endpoints\nrestricted only to users with their corresponding role:\n\n```\n@AccessRequiresAnyRole(['READER', 'EDITOR'])\nclass ReportController extends BaseController {\n    @AccessRequiresRole('READER)\n    def listReport() { ... }\n\n    @AccessRequiresRole('EDITOR')\n    def saveReport(params) { ... }\n}\n```\n\nThe `@AccessAll` annotation allows any user access. A controller endpoint must be decorated with one\nof these annotations or an exception will be thrown.\n\n#### Impersonation\n\n😎 Administrators have access to an impersonation mode where they can \"act as\" another\nuser in the context of the application. This process is managed by `IdentityService`, which exposes\nseveral public methods for entering and exiting impersonation mode. When active,\n`IdentityService.getUser()` will return the user being impersonated, while\n`IdentityService.getAuthUser()` will return the actual admin.\n\nThe client toolkits provide built-in UIs for administrators to enter and exit impersonation mode.\nServices such as activity tracking are aware of impersonation and will log activity done while\nimpersonation is active with both the impersonated and real user.\n\n\n### Configuration\n\n| Class/File             |               Note               |                                   Link                                   |\n|------------------------|----------------------------------|:------------------------------------------------------------------------:|\n| `AppConfig.groovy`     | Domain object for config entries |       [🏗](grails-app/domain/io/xh/hoist/config/AppConfig.groovy)        |\n| `ConfigService.groovy` | Server-side source for configs   |    [🏗](grails-app/services/io/xh/hoist/config/ConfigService.groovy)     |\n| `ConfigService.ts`     | Hoist-React source for configs   | [⚛️](https://github.com/xh/hoist-react/blob/master/svc/ConfigService.ts) |\n\n🔧 The ability to store simple typed configuration values\n(`string|int|long|double|bool|json|pwd`) and manage / adjust them in a running application has\nproven to be an extremely useful core feature. `AppConfig` entries are stored in the UI server's\ndatabase and referenced via a simple string name.\n\nConfigs can also be made available to client applications (or not) via a dedicated flag, where they\ncan be referenced by JS code. The built-in Admin console provides a full UI for reviewing, updating,\nand organizing these entries.\n\n🙈 A special `pwd` type allows passwords and other sensitive info to be stored in an\nencrypted form for use on the server, avoiding the need to save these common types of configuration\nto the database in plaintext. Note however that any developer can deliberately print the output of\nan encrypted config by logging the (unencrypted) output of `configService.getPwd()`.\n\n#### Required Configs\n\nHoist requires certain configuration entries to be defined and present for the application to\ninitialize. Apps themselves might also have hard dependencies on configs. To help ensure these\nentries are in place and to aid in the spinning up of a new app with an empty database, the\n`ConfigService.ensureRequiredConfigsCreated()` method is available to verify and auto-create\nrequired configs. See `Bootstrap.groovy` in hoist-core for configs required at the Hoist level.\n\n\n### Preferences\n\n| Class/File              |                  Note                   |                                  Link                                  |\n|-------------------------|-----------------------------------------|:----------------------------------------------------------------------:|\n| `Preference.groovy`     | Domain object for preference definition |       [🏗](grails-app/domain/io/xh/hoist/pref/Preference.groovy)       |\n| `UserPreference.groovy` | Domain object for user-specific value   |     [🏗](grails-app/domain/io/xh/hoist/pref/UserPreference.groovy)     |\n| `PrefService.groovy`    | Server-side pref management             |     [🏗](grails-app/services/io/xh/hoist/pref/PrefService.groovy)      |\n| `PrefService.ts`        | Hoist-React pref management             | [⚛️](https://github.com/xh/hoist-react/blob/master/svc/PrefService.ts) |\n\n⭐ Preferences provide a lightweight way to persist user-specific options and settings. Similar\nto AppConfigs, preferences offer several predefined data types\n(`string|int|long|double|boolean|json`) and are referenced by a string `name` property. Preferences\nare assigned a default value that is returned if a user does not yet have a specific value set. When\na user preference is assigned via `PrefService.setPreference()` (or one of the typed setters) a\n`UserPreference` object is created and saved. Both objects can be managed via the built-in Admin\nconsole.\n\nPreferences may generally be accessed and used on the server and client, although they are primarily\na client-side tool.\n\nApplications are encouraged to provide end-users with controls to reset their preferences should\nthey wish to restore their profile to a default state. Server and client APIs exist to do such a\nthing - see `PrefService.clearPreferences()`.\n\n#### Required Preferences\n\nAs with configs (above) the `PrefService.ensureRequiredPrefsCreated()` method is available to verify\nand auto-create required preferences. See `Bootstrap.groovy` in hoist-core for prefs required at the\nHoist level.\n\n\n### Activity Tracking and Client Error Reporting\n\n| Class/File            |               Note               |                                          Link                                          |\n|-----------------------|----------------------------------|:--------------------------------------------------------------------------------------:|\n| `TrackLog.groovy`     | Domain object for track entries  |               [🏗](grails-app/domain/io/xh/hoist/track/TrackLog.groovy)                |\n| `TrackService.groovy` | Server-side API to log activity  |            [🏗](grails-app/services/io/xh/hoist/track/TrackService.groovy)             |\n| `TrackService.ts`     | Hoist-React API to log activity  |        [⚛️](https://github.com/xh/hoist-react/blob/master/svc/TrackService.ts)         |\n| `ExceptionHandler.ts` | Hoist-React API to track errors  | [⚛️](https://github.com/xh/hoist-react/blob/master/core/exception/ExceptionHandler.ts) |\n\n👀 Knowing which users are visiting an app and tracking specific actions of interest is another\ncommon need for apps. Hoist includes an API for easily tracking activity for the current user, and\nthe Admin console provides a UI for searching and reviewing activity. Hoist services track some\nactivities automatically (e.g. impersonation), but it is primarily up to app developers to determine\nwhich activities are of interest for tracking.\n\nIn its simplest form, a tracking record is a string - e.g. \"Viewed chart\". Tracks can also be given\na category for easier organization in the UI and a JSON map of data for additional details (i.e. to\nnote query parameters. The `TrackLog` object stores this record along with a timestamp, the current\nuser, and browser/device info.\n\nOn the client-side, a `track()` method is added to the Promise prototype to provide convenient\ntracking for asynchronous requests - e.g. tracking a particular API call. This method provides\nbuilt-in timing of the call and saves as 'TrackLog.elapsed'.\n\n#### Client Errors and Feedback\n\n💥 Specialized support exists for tracking messages of category \"Client Error\" and \"Feedback\".\nThese messages are produced by the built-in exception handling and Feedback component in Hoist\nReact. When properly configured Hoist will send email and email digest notifications of these\nmessages to the configured support email.\n\n### Emailing\n\n|      Class/File       |               Note                |                               Link                               |\n|-----------------------|-----------------------------------|:----------------------------------------------------------------:|\n| `EmailService.groovy` | Managed service for sending email | [🏗](grails-app/services/io/xh/hoist/email/EmailService.groovy) |\n\n📫 Hoist core provides `EmailService` to send mail from the server. This relies on the\n[Grails mail plugin](http://plugins.grails.org/plugin/grails/mail) which must be configured with a\nsuitable SMTP server within an app's `application.groovy` configuration file.\n\nSeveral default AppConfigs are available to default the sender and provide filtering and override\noptions for email, especially useful in dev/test scenarios where careful control of what emails are\nsent to which users is required.\n\n\n### Status Monitoring\n\n|         Class/File         |                 Note                  |                                  Link                                   |\n|----------------------------|---------------------------------------|:-----------------------------------------------------------------------:|\n| `Monitor.groovy`           | Domain object for monitor definitions |       [🏗](grails-app/domain/io/xh/hoist/monitor/Monitor.groovy)       |\n| `MonitorResult.groovy`     | In-memory object for monitor outcomes |     [🏗](src/main/groovy/io/xh/hoist/monitor/MonitorResult.groovy)     |\n| `MonitoringService.groovy` | Service that coordinates monitor runs | [🏗](grails-app/services/io/xh/hoist/monitor/MonitoringService.groovy) |\n\n👍👎 Hoist provides an API and services for runtime monitoring of the application, with a\ndeliberate focus on running application-specific checks that relate to the business logic and data\nsources specific to the app (as opposed to e.g. system or OS level monitoring of metrics like CPU or\nmemory usage).\n\nTo use monitoring, applications must implement a `MonitorDefinitionService` (i.e. a standard Grails\nservice with that name) that implements one or more monitoring methods of the form:\n\n```\n// Method signature\ndef customCheckName(MonitorResult result) {\n    // Run any custom business logic here, setting properties on MonitorResult to record outcomes\n}\n\n// For example...\ndef activeTradeCount(MonitorResult result) {\n    def trades = tradeReportingService.getActiveTrades()\n    result.metric = trades.size()\n}\n\ndef inventorAgeMs(MonitorResult result) {\n    def rptDate = inventoryWatcher.getReport().asOfDate\n    result.metric = System.currentTimeMillis() - rptDate.time\n    result.message = \"Latest inventory update: ${asOfDate}\"\n}\n```\n\nThe names of these methods should match the `code` property of `Monitor` objects created and managed\nvia the Admin console. These definition objects hold data-driven parameters to determine how monitor\nresults are evaluated. The `MonitorResult.status` property is the outcome of a given monitor. While\nthe app's `MonitorDefinitionService` can set this status directly on results within its methods, a\nmore flexible and common pattern is to have the service set a `metric` instead - e.g. the number of\nrows returned by a query, or the age in seconds of a particular result set.\n\nThis metric can then be evaluated against data-driven parameters on the `Monitor` object to\ndetermine the status dynamically, allowing for runtime adjustments and tuning of the checks. The\nHoist monitor runner will time all checks (and enforce a timeout) and catch any exceptions that\nmight get thrown (marking the check as having failed and noting the exception on the result).\n\nMonitor results can be viewed via the Admin console. The `xhMonitorConfig` and\n`xhMonitorEmailRecipients` configs control option for email-based alerting on monitor failures,\nincluding support for debouncing alerts. `MonitoringService` fires a server-side\n`xhMonitorStatusReport` event that can be picked up by other custom services for additional\nnotifications.\n\n🔮 Note an XH project is underway to provide a more general and cross-application\nimplementation of this monitoring API for both Hoist and non-Hoist based applications.\n\n\n### Distributed Tracing\n\n|         Class/File         |                 Note                  |                                  Link                                   |\n|----------------------------|---------------------------------------|:-----------------------------------------------------------------------:|\n| `TraceService.groovy`      | Central tracing service — SDK lifecycle, exporter pipeline, span API | [🏗](grails-app/services/io/xh/hoist/telemetry/TraceService.groovy) |\n| `ObservedRun.groovy`       | Composable builder for combined tracing + logging + metrics | [🏗](src/main/groovy/io/xh/hoist/telemetry/ObservedRun.groovy) |\n| `SpanRef.groovy`           | Wrapper around an active Span + Scope with tag/status helpers | [🏗](src/main/groovy/io/xh/hoist/telemetry/SpanRef.groovy) |\n| `TraceInterceptor.groovy`  | Creates SERVER spans for controller actions | [🏗](grails-app/controllers/io/xh/hoist/telemetry/TraceInterceptor.groovy) |\n\n🔍 Hoist provides OpenTelemetry-based distributed tracing with OTLP export, configured dynamically\nvia the `xhTraceConfig` soft config. Tracing is disabled by default with negligible overhead — all\npublic methods delegate to no-ops when disabled.\n\n`TraceService` provides the core span API (`withSpan` and `createSpan`), while `ObservedRun`\noffers a composable builder accessed via `BaseService.observe()` that wraps a closure with any\ncombination of tracing, logging, and Micrometer metrics in a single fluent call chain:\n\n```groovy\nobserve()\n    .span(name: 'generatePortfolio')\n    .logInfo('Generating Portfolio')\n    .timer(generationTimer)\n    .run {\n        // business logic\n    }\n```\n\nAdditional capabilities include automatic SERVER spans for controller actions, W3C `traceparent`\npropagation on both inbound and outbound HTTP calls, trace context propagation across Hazelcast\ncluster tasks and Grails `task {}` threads, client span relay for end-to-end browser-to-server\ntraces, and log correlation via `traceId` on log markers.\n\n## Development setup\n\n### JDK requirements\n\nhoist-core compiles with a **JDK 25 toolchain** but targets **Java 17 bytecode**\n(`javac --release 17`, Groovy `targetCompatibility = '17'`). The published JAR therefore runs\non any JDK 17+ runtime so client apps still on Java 17 are not forced to upgrade.\n\nContributors must not use Java 18+ APIs in hoist-core source — the CI build enforces this by\nalso running the test suite on JDK 17. See [build-and-publish.md](docs/build-and-publish.md)\nfor details.\n\n### Hot reloading.\nHot reloading is supported for Java 17 and Java 21 using the java hotswap agent.  Please\nsee http://hotswapagent.org/ and https://github.com/HotswapProjects/HotswapAgent for details on\nhow to setup.\n\nPlease note that the following is needed for running in this mode.  **See the toolbox application\nfor an example of this setup.**\n- A specialized hotswap jvm (see links above for setup, available for java17)\n- The file `groovyReset.jar` in your project or dependencies\n- The following JVM arguments when starting your application:\n  `-XX:HotswapAgent=fatjar`, `-XX:+AllowEnhancedClassRedefinition`,\n    and `-javaagent:absolute-path-to-groovyReset.jar`\n\nNote that hot swapping is not currently enabled for grails plugins being developed in\nwrapper projects.\n\n### Readme TODOs\n\n🚧 The sections below are planned near-term additions to this readme, covering\nadditional features and services provided by the framework.\n\n- [ ] Instance configuration\n- [ ] Environments (Grails vs. Hoist)\n- [ ] Base/Super classes\n- [ ] Development setup\n- [ ] Additional deployment info\n- [ ] Proxy Service\n- [ ] Logging levels and management\n\n🙏 Thanks for your patience, and for reading this far!\n\n------------------------------------------\n\n☎️ info@xh.io | \u003chttps://xh.io\u003e\nCopyright © 2026 Extremely Heavy Industries Inc.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxh%2Fhoist-core","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxh%2Fhoist-core","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxh%2Fhoist-core/lists"}