Releases: digitallyinduced/ihp
v1.3.0
IHP is a modern batteries-included haskell web framework, built on top of Haskell and Nix. Blazing fast, secure, easy to refactor and the best developer experience with everything you need - from prototype to production.
This release brings many small quality of life improvements to IHP, fixes lots of bugs and comes with several helpful documentation improvements. There's been over 190 commits since v1.2.0
Major Changes
-
New CLI command:
new-session-secret
:
Until now it's always been a bit tricky to get a new secret for theIHP_SESSION_SECRET
env var. To fix that we've added a newnew-section-secret
command. Run it from the command line inside your IHP project, and it will generate a new random key to use for theIHP_SESSION_SECRET
env var:$ new-session-secret 1J8jtRW331a0IbHBCHmsFNoesQUNFnuHqY8cB5927KsoV5sYmiq3DMmvsYk5S7EDma9YhqZLZWeTFu2pGOxMT2F/5PnifW/5ffwJjZvZcJh9MKPh3Ez9fmPEyxZBDxVp
-
Proper support for non
id
primary keys:
Previously IHP assumed that your tables primary keys are calledid
, and that you don't use composite primary keys.With this release the following SQL schema will compile without any errors:
CREATE TABLE bits ( bit_arbitrary_ident UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL ); CREATE TABLE parts ( part_arbitrary_ident UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL ); CREATE TABLE bit_part_refs ( bit_ref UUID NOT NULL, part_ref UUID NOT NULL, PRIMARY KEY(bit_ref, part_ref) );
-
GHC Upgrade: 9.4 -> 9.8.2
-
New Function:
sqlQuerySingleRow
:
Runs a raw sql query, that is expected to return a single result row
Like 'sqlQuery', but useful when you expect only a single row as the resultdo user <- sqlQuerySingleRow "SELECT id, firstname, lastname FROM users WHERE id = ?" (Only userId)
-
New Function:
sqlExecDiscardResult
:
Runs a sql statement (like aCREATE
statement), but doesn't return any resultsqlExecDiscardResult "CREATE TABLE users ()" ()
There's now also
updateRecordDiscardResult
, which is likeupdateRecord
, but doesn't return the updated record. -
Worker support exponential backoff:
instance Job DraftEmailJob where backoffStrategy = ExponentialBackoff { delayInSeconds = 30 } perform job = do -- ...
Minor Changes
- DataSync: Configurable message timeout
- HSX: Allow attributes sse-connect and sse-swap
- Allow a submit button to be disabled
- Use a more robust check for
isUrl
validator validateMaybe
: Helper to allowvalidateField
to work on Maybe valuesfileOrNothing
should check fileContent isn't empty- Add debounce to fsnotify
- Form: Add
urlField
- ihp-new: Remove cachix install step
- Remove HLS config initialization
- nix: Extracted out duplicate binary cache config
- Fixed base64 error when deploying with deploy-to-nixos
- Fixed migrate service failing to build when the IHP app has no migrations yet
- nix: Fixed some postgres settings in appWithPostgres nixos config
- Disable nix sandbox for deploy-to-nixos: This makes it work better out of the box with projects that use npm
- ihp-new: Create git repo to avoid issues when IHP is part of a larger git repo
- Allow optimized builds with deploy-to-nixos
- deploy-to-nixos: Fixed db init script
- Use built-in ping pong mechanism of the latest version of websockets
- Fixed npm install when running an IHP app on latest nixpkgs
- deploy-to-nixos: Fixed database being used before it has been created
- deploy-to-nixos: Switch to the right user before creating tables
- Remove unused datePickers code
- Allow easier SMTP config envs
- Add loadSchema service
- Worker: Write to log instead of using putStrLn
- Worker: Fixed poller not scheduling concurrent jobs
- Allow sql dumping via the Makefile
- Fixed Underscore in ID column leads to compile error in code generation
- Fix SchemaCompiler to support non-id-pks
- Made request logger less verbose by not logging requests to static files: This was the existing behaviour until we switched out the static middleware a while ago
- make it easier to spot schema + fixture errors
- Only track exception (e.g. with sentry) in production: This was the expected behaviour, but turns out I missed one place
- ihp-openai: Don't use deprecated prompt anymore, as we mostly use the chat API
- ihp-openai: Added missing assistantMessage
- Simplify session vault key handling: Moved the session vault key from the ApplicationContext and RequestContext data structure to a global variable. This is the suggested way by the WAI developers.
- Allow overriding the logger in the controller context
- fixed long constraint names cause verbose patches in migrations
- Pin devenv to a stable release
- Document creation of Docker with Nix and Podman
Docs
- Show example how to send a link from Mail
- Update tailwindcss instructions
- Improvements to the emacs documentation (you're better off with vim)
- Add docs about Unique Constraints
- Docs about Passing JSON to the View
Full Changelog: v1.2.0...v1.3.0
Updating
→ See the UPGRADE.md for upgrade instructions.
If you have any problems with updating, [let us know on the IHP Discourse](https://discuss.ihp....
v1.2.0
IHP is a modern batteries-included haskell web framework, built on top of Haskell and Nix. Blazing fast, secure, easy to refactor and the best developer experience with everything you need - from prototype to production.
This release brings a new out of the box deployment process based on nixos-rebuild, a new way to get docker images for an IHP app and much more.
Major Changes
-
Deployment with
deploy-to-nixos
:
You can now easily deploy IHP apps to any NixOS server that is reachable via SSH. E.g. if you start a new AWS EC2 instance with an NixOS image and your SSH key, you can now deploy the IHP app, including database and migrations within minutes.The
flake.nix
can export a full NixOS configuration like this:{ inputs = { ihp.url = "github:digitallyinduced/ihp/v1.2"; nixpkgs.follows = "ihp/nixpkgs"; flake-parts.follows = "ihp/flake-parts"; devenv.follows = "ihp/devenv"; systems.follows = "ihp/systems"; }; outputs = inputs@{ ihp, flake-parts, systems, ... }: flake-parts.lib.mkFlake { inherit inputs; } { systems = import systems; imports = [ ihp.flakeModules.default ]; perSystem = { pkgs, ... }: { ihp = { enable = true; projectPath = ./.; packages = with pkgs; [ # Native dependencies, e.g. imagemagick nodejs ]; haskellPackages = p: with p; [ # Haskell dependencies go here p.ihp cabal-install base wai text hlint http-streams ihp-stripe ihp-oauth-google retry ]; }; }; flake.nixosConfigurations."staging.example.com" = nixpkgs.lib.nixosSystem { system = "x86_64-linux"; specialArgs = inputs; modules = [ "${nixpkgs}/nixos/modules/virtualisation/amazon-image.nix" ihp.nixosModules.appWithPostgres ({ ... }: { security.acme.defaults.email = "[email protected]"; services.ihp = { domain = "myihpapp.com"; migrations = ./Application/Migration; schema = ./Application/Schema.sql; fixtures = ./Application/Fixtures.sql; sessionSecret = "xxx"; additionalEnvVars = { GHCRTS = "-A32M -N2"; }; }; # This should reflect the nixos version from the NixOS AMI initally installed # After the initial install, it should not be changed. Otherwise e.g. the postgres # server might need a manual data migration if NixOS changes the default postgres version system.stateVersion = "23.05"; # Optional Example: Email on App Crash systemd.services.app.onFailure = [ "notify-email@%n.service" ]; systemd.services.worker.onFailure = [ "notify-email@%n.service" ]; programs.msmtp = { enable = true; defaults = { tls = true; port = 587; }; accounts = { default = { auth = true; from = "[email protected]"; host = "email-smtp.eu-west-1.amazonaws.com"; user = "XXXXXXXX"; passwordeval = "echo 'XXXXXXXX'"; }; }; }; systemd.services."notify-email@" = { serviceConfig.Type = "oneshot"; path = with pkgs; [ systemd system-sendmail ]; scriptArgs = "%I"; script = '' UNIT=$(systemd-escape $1) TO="[email protected]" SUBJECT="$UNIT Failed" HEADERS="To:$TO\nSubject: $SUBJECT\n" BODY=$(systemctl status --no-pager $UNIT || true) echo -e "$HEADERS\n$BODY" | sendmail -t ''; }; # Optional Example: Run an IHP script every 30mins systemd.services.monitorCampaigns = { serviceConfig = { Type = "oneshot"; WorkingDirectory = "${ihpApp}/lib"; ExecStart = "${ihpApp}/bin/MonitorCampaigns"; }; environment = config.systemd.services.app.environment; onFailure = [ "notify-email@%n.service" ]; }; systemd.timers.monitorCampaignsEvery30Mins = { wantedBy = [ "timers.target" ]; partOf = [ "monitorCampaigns.service" ]; timerConfig = { OnCalendar = "*-*-* *:30:00"; Unit = "monitorCampaigns.service"; }; }; }) ]; }; }; }
Assuming your NixOS server can be conneted to via
ssh staging.example.com
, you can now run this:deploy-to-nixos staging.example.com
If you use an external postgres (this is likely the case for most serious production deployments), use
ihp.nixosModules.app
instead ofihp.nixosModules.appWithPostgres
.This will now apply the full above NixOS configuration to the server. Internally this tool is a wrapper around
nixos-rebuild
. E.g. the above call with result in:nixos-rebuild switch -j auto --use-substitutes --fast --flake .#staging.example.com --target-host staging.example.com --build-host staging.example.com --option substituters https://digitallyinduced.cachix.org --option trusted-public-keys digitallyinduced.cachix.org:digitallyinduced.cachix.org-1:y+wQvrnxQ+PdEsCt91rmvv39qRCYzEgGQaldK26hCKE= ssh staging.example.com systemctl start migrate
If you e.g. want to build the binaries on a different server than your runtime server, you can call
nixos-rebuild
directly instead of using thedeploy-to-nixos
wrapper.IHP now ships serveral NixOS modules that you can use to compose your IHP NixOS infrastructure.
-
Docker Images:
You can now build docker images from your IHP apps with ease:# Faster build times, but unoptimized GHC binaries nix build .#unoptimized-docker-image # Slow build times, but optimized GHC binaries nix build .#optimized-docker-image
-
Support HSX expressions like
<input value={project.id}/>
You can now use IHP UUIDs/ID values like user ID or project ID in HSX attributes:
-- Previous: <input value={inputValue project.id}/> -- New: <input value={project.id}/>
Minor Changes
- isActiveAction should take query string into account (https://github.com/digitallyinduced/ihp/pull/1725)
- Allow pure nix builds
- add make to devenv
- Guide on how to remove an uploaded image
- [SSC do not encode nullary constructors to strings with the constructor tag](5e3b561569ae28dc85106ed97...
v1.1.0
IHP is a modern batteries-included haskell web framework, built on top of Haskell and Nix. Blazing fast, secure, easy to refactor and the best developer experience with everything you need - from prototype to production.
This release brings some large improvements to the dev environment by integrating devenv.sh, adds native GPT4 support through ihp-openai and much more.
Major Changes
-
devenv.sh x IHP:
IHP projects now use devenv.sh. devenv is a wrapper around nix flakes that provides fast, declarative, reproducable and composable development environments. It supercedes the previous.envrc
approach. Especially projects with lots of dependencies are much faster to open with devenv. -
ihp-openai:
The newihp-openai
package adds an easy way to integrate GPT3 and GPT4 to your Haskell web apps. The library is extracted from a production app at digitally induced. Compared to existing haskell libs this library is a streaming API (so works great with IHP AutoRefresh and IHP DataSync), works with the latest Chat API, and has smart retry on error without throwing away tokens. Also it's battle tested in real world production use cases.The package can be found in the IHP repo and a demo project can be found on GitHub as well.
Example:
module Web.Controller.Questions where import Web.Controller.Prelude import Web.View.Questions.Index import Web.View.Questions.New import Web.View.Questions.Edit import Web.View.Questions.Show import qualified IHP.OpenAI as GPT instance Controller QuestionsController where action QuestionsAction = autoRefresh do questions <- query @Question |> orderByDesc #createdAt |> fetch render IndexView { .. } action NewQuestionAction = do let question = newRecord |> set #question "What makes haskell so great?" render NewView { .. } action CreateQuestionAction = do let question = newRecord @Question question |> fill @'["question"] |> validateField #question nonEmpty |> ifValid \case Left question -> render NewView { .. } Right question -> do question <- question |> createRecord setSuccessMessage "Question created" fillAnswer question redirectTo QuestionsAction action DeleteQuestionAction { questionId } = do question <- fetch questionId deleteRecord question setSuccessMessage "Question deleted" redirectTo QuestionsAction fillAnswer :: (?modelContext :: ModelContext) => Question -> IO (Async ()) fillAnswer question = do -- Put your OpenAI secret key below: let secretKey = "sk-XXXXXXXX" -- This should be done with an IHP job worker instead of async async do GPT.streamCompletion secretKey (buildCompletionRequest question) (clearAnswer question) (appendToken question) pure () buildCompletionRequest :: Question -> GPT.CompletionRequest buildCompletionRequest Question { question } = -- Here you can adjust the parameters of the request GPT.newCompletionRequest { GPT.maxTokens = 512 , GPT.prompt = [trimming| Question: ${question} Answer: |] } -- | Sets the answer field back to an empty string clearAnswer :: (?modelContext :: ModelContext) => Question -> IO () clearAnswer question = do sqlExec "UPDATE questions SET answer = '' WHERE id = ?" (Only question.id) pure () -- | Stores a couple of newly received characters to the database appendToken :: (?modelContext :: ModelContext) => Question -> Text -> IO () appendToken question token = do sqlExec "UPDATE questions SET answer = answer || ? WHERE id = ?" (token, question.id) pure ()
Bildschirmaufnahme.2023-04-17.um.23.48.41.mov
-
onlyWhere
,onlyWhereReferences
andonlyWhereReferencesMaybe
:
In IHP code bases you often write filter functions such as these:getUserPosts user posts = filter (\p -> p.userId == user.id) posts
This can be written in a shorter way using
onlyWhere
:getUserPosts user posts = posts |> onlyWhere #userId user.id
Because the
userId
field is an Id, we can useonlyWhereReferences
to make it even shorter:getUserPosts user posts = posts |> onlyWhereReferences #userId user
If the Id field is nullable, we need to use
onlyWhereReferencesMaybe
:getUserTasks user tasks = tasks |> onlyWhereReferences #optionalUserId user
-
GHC 9.2.4 -> 9.4.4
We've moved to a newer GHC version 👍 -
Initalizers
You can now run code on the start up of your IHP app using an initializer. For that you can calladdInitializer
from your project'sConfig.hs
.The following example will print a hello world message on startup:
config = do addInitializer (putStrLn "Hello World!")
This is especially useful when using IHP's Row level security helpers. If your app is calling
ensureAuthenticatedRoleExists
from theFrontController
, you can now move that to the app startup to reduce latency of your application:config :: ConfigBuilder config = do -- ... addInitializer Role.ensureAuthenticatedRoleExists
-
Multiple Record Forms
You can now use
nestedFormFor
to make nested forms with the IHP form helpers. This helps solve more complex form use cases.Here's a code example:
renderForm :: Include "tags" Task -> Html renderForm task = formFor task [hsx| {textField #description} <fieldset> <legend>Tags</legend> {nestedFormFor #tags renderTagForm} </fieldset> <button type="button" class="btn btn-light" data-prototype={prototypeFor #tags (newRecord @Tag)} onclick="this.insertAdjacentHTML('beforebegin', this.dataset.prototype)">Add Tag</button> {submitButton} |] renderTagForm :: (?formContext :: FormContext Tag) => Html renderTagForm = [hsx| {(textField #name) { disableLabel = True, placeholder = "Tag name" } } |]
-
Faster Initial Startup for large IHP Apps:
TheGenerated.Types
module is a module generated by IHP based on your project'sSchema.sql
. The module is now splitted into multiple sub modules, one for each table in yourSchema.sql
. This makes the initial startup of your app faster, as the individual sub modules can now be loaded in parallel by the compiler. -
Static Files Changes:
IHP is now using the more actively maintainedwai-app-static
instead ofwai-middleware-static
for serving files from thestatic/
directory.The old
wai-middleware-static
had some issues, particular related to leaking file handles. Alsowai-app-static
has better cache handling for our dev mode.You might see some changes related to caching of your app's static files:
- files in
static/vendor/
previously had more aggressive caching rules, this is not supported anymore. - files in dev mode are now cached with
maxage=0
instead ofCache-Control: nocache
- application assets are now cached forever. As long as you're using IHP's
asssetPath
helper, this will not cause any issues.
Additionally the routing priority has changed to save some syscall overhead for every request:
Previously:
GET /test.txt Does file exists static/test.txt? => If yes: return file => If no: run IHP router to check for an action matching /test.txt
Now:
GET /test.txt Run IHP router to check for an action matching /test.txt Is there an action matching this? => If yes: Run IHP action => If no: Try to serve file static/test.txt?
- files in
-
.env
Files:
Next to the.envrc
, you can now save secrets outside your project repository by putting them into the.env
file.
The.env
is not committed to the repo, so all secrets are safe against being accidentally leaked. -
HSX Comments:
You can now write Haskell comments inside HSX expressions:render MyView { .. } = [hsx| <div> {- This is a comment and will not render to the output -} </div> |]
-
HSX Doctype:
HTML Doctypes are now supported in HSX. Previously you had to write them by using the underlying blaze-html library:render MyView { .. } = [hsx| <!DOCTYPE html> <html lang="en"> <body> hello </body> </html> |]
Minor Changes
- Fixed Google OAuth docs use outdated google JS library
- [added interval type to parser an...
v1.0.1
IHP is a modern batteries-included haskell web framework, built on top of Haskell and Nix. Blazing fast, secure, easy to refactor and the best developer experience with everything you need - from prototype to production.
A new IHP release, containing mostly bug fixes and small improvements to existing features. There's no major new feature in this release 🛠️
Minor Changes
- Serverside Components: fix update class attribute of input or textarea elements
- Support Postgres Arrays in DataSync
- Normalized Null handling in DataSync: This fixes a bug where queries like 'col = NULL' is generated instead of the correct 'col IS NULL'
- DataSync: Fixed whereIn
- Fixed null handling in DataSync 'field IN (null)' queries
- Fixed table deletion causing bad DROP POLICY statement
- Fixed race condition in build of scripts
- Fixed DataSync's opimistic deleteRecord operation not correctly undone when the operation fails on the server
- fix, ssc createNode operatin bug, when target node (by path) is a text node by @leobm
- Adding string instance to EnvVarReader useful for FilePath type in logger. by @Montmorency
- fix zsh config in ihp-new
- Fixed 'ENABLE ROW LEVEL SECURITY' statement for a table with spaces in it's name breaks the SQL
- Fixed problem with calling crypto.randomUUID on older browsers
- hsx: Allow { ...values} spread, with a space in front
- fix comment css sourcemap
- Fixed updateRecord calls in DataSync failing when the patch size is more than 8000 bytes
- Fixed DataSync crashing when an UPDATE query is executed that doesn't change any values
- Log exceptions in main loop of PGListener
- Fixed onPing callback never called: This caused AutoRefresh sessions to be accidentally garbage collected, even when they're still alive
- Added support for 'CREATE UNLOGGED TABLE' syntax in sql files
Notable Documentation Changes
- Lot's of typos and small issues have been fixed by @agreif
- IHP Cloud Deployment documentation has been removed
- Shipnix Deployment documentation has been added by @kodeFant
- Improvments to Emacs docs by @unhammer
- Various fixes by @rvarun11
Full Changelog: v1.0.0...v1.0.1
Feature Voting
Help decide what's coming next to IHP by using the Feature Voting!
Updating
→ See the UPGRADE.md for upgrade instructions.
If you have any problems with updating, let us know on the IHP forum.
📧 To stay in the loop, subscribe to the IHP release emails (right at the bottom of the page). Or follow digitally induced on twitter.
v1.0.0
Two years after the first public release of IHP I’m very happy and proud to announce the release of version 1.0! 🎉
→ Read the full release announcement on the IHP Website.
IHP is a modern batteries-included haskell web framework, built on top of Haskell and Nix. Blazing fast, secure, easy to refactor and the best developer experience with everything you need - from prototype to production.
Major Changes
-
🎨 Bootstrap 5:
We've finally upgraded from Bootstrap 4 to Bootstrap 5 🎉
E.g. all forms rendered withformFor
now expect you're using bootstrap 5 by default. Bootstrap 4 is still supported and available if needed, e.g. if you don't want to update your application. -
💻 M1 Builds:
Thanks to a new mac mini sponsored by MacStadium, we now have prebuilt binaries for Apple M1 devices. Previously it could take up multiple hours to compile everything needed for IHP from scratch. Now this just takes a minute of downloading binaries.
-
🗂️ Schema Designer: Index Management
The IHP Schema Designer now supports creating, editing and deleting column indexes from the GUI. Previously this was only possible by editing theSchema.sql
manually: -
🐎 Smaller & Faster Production Builds
We've optimized the nix build process. Previously when building for production, the output of the nix build process contained many dev tools, like the Postgres server and Haskell Language Server. These are not needed in production. With the recent changes they're excluded from the production build. This saves tons of space. E.g. when packaging a simple IHP app, the file size of the docker image moved from 300MB -> 80MB.The build process now uses all available cores when calling
make
. This will speed up builds that rely on many IHP Scripts.
Other Changes
- Removed github discussions from help popover
- Fixed darkmode design of help popover
- Run :l Main automatically when opening ghci
- Added callstacks to exceptions thrown during config
- added deepseq constraint on Enum so that paramList works as expected.
- Add hs-brolti and wai-middleware-brotli nix config
- Add hs-brotli wai-middleware-brotli To build
- Updated ihp-hsx package definitions
- Enabled split sections for app
- Fixed dev server start fails when build/ihp-lib points to a non existing directory
- Use
record.field
syntax instead ofget #field record
in docs - Added docs on dot notation
- Removed outdated note on unsupported syntax in HSX
- currentAdmin, currentAdminOrNothing, currentAdminId, ensureIsAdmin now works without explicit type passed
- Added generic currentRoleOrNothing
- Fixed haddock build
- Tried adding failing test for https://github.com/digitallyinduced/ihp/issues/1451
- Improve error report when when parsing the Schema.sql fails
- Added note on contributing docs
- Use dot notation in ModelSupport
- Make editor docs more visible
- Updated links to mailing list
- Improved support for preEscapedTextValue
- Refactored ConvertibleStrings instance to use ApplyAttribute instance
- adding some docs on using scripts from ghci
- added testConfig example for script with different logging level
- switch appConfig to testConfig for consistency
- Made a specific example for importing Config and running it from ghci
- Added ParPat and working on ConPat so that generic lambda pattern matching works in HSX
- More of the expression type checking still need to find map to
[HsType]
forTH.Conp
- Trying to map constructors to types for template haskell
- Now working with simple constructor pattern matching (no type annotation) no infix constructors yet.
- Implementation of Exact rdrName allows for use of infix operators in HSX!
- Infix and pattern matching on constructors working. Added tests. No type annotations in Constructor patterns supported.
- Allow formFor with GET method
- Use Queue for ghci output
- Extracted and refactored FileWatcher
- Refactored handling of toolserver
- Refactored state handling of live reload notification server
- Fixed rename table operation doesn't correctly rename other DDL statements
- Fixed running a broken migration causes a bad error message
- Replace ConfigProvider and LoggingProvider with dot notation
- make Welcome page page more responsible
- When dropping a column, also drop related policies
- Fixed user_id quick col has a ihp_user_id() default value, even when RLS is not used
- Fixed race condition in dev server that causes reload to not work
- Exported currentRole functions
- Disabled split-sections
Full Changelog: v0.20.0...v1.0.0
Feature Voting
Help decide what's coming next to IHP by using the Feature Voting!
Updating
→ See the UPGRADE.md for upgrade instructions.
If you have any problems with updating, [let us know on the IH...
v1.0-RC2
This is the second release candidate for the soon-to-be-published IHP v1.0. See #1501
See v1.0.0-rc1 for a list of all changes. This release contains small bug fixes only.
Other Changes
v1.0-RC1
This is a release candidate for the soon-to-be-published IHP v1.0. See #1501
IHP is a modern batteries-included haskell web framework, built on top of Haskell and Nix. Blazing fast, secure, easy to refactor and the best developer experience with everything you need - from prototype to production.
Major Changes
-
🎨 Bootstrap 5:
We've finally upgraded from Bootstrap 4 to Bootstrap 5 🎉
E.g. all forms rendered withformFor
now expect you're using bootstrap 5 by default. Bootstrap 4 is still supported and available if needed, e.g. if you don't want to update your application. -
💻 M1 Builds:
Thanks to a new mac mini sponsored by MacStadium, we now have prebuilt binaries for Apple M1 devices. Previously it could take up multiple hours to compile everything needed for IHP from scratch. Now this just takes a minute of downloading binaries.
-
🗂️ Schema Designer: Index Management
The IHP Schema Designer now supports creating, editing and deleting column indexes from the GUI. Previously this was only possible by editing theSchema.sql
manually: -
🐎 Smaller & Faster Production Builds
We've optimized the nix build process. Previously when building for production, the output of the nix build process contained many dev tools, like the Postgres server and Haskell Language Server. These are not needed in production. With the recent changes they're excluded from the production build. This saves tons of space. E.g. when packaging a simple IHP app, the file size of the docker image moved from 300MB -> 80MB.The build process now uses all available cores when calling
make
. This will speed up builds that rely on many IHP Scripts.
Other Changes
- Removed github discussions from help popover
- Fixed darkmode design of help popover
- Run :l Main automatically when opening ghci
- Added callstacks to exceptions thrown during config
- added deepseq constraint on Enum so that paramList works as expected.
- Add hs-brolti and wai-middleware-brotli nix config
- Add hs-brotli wai-middleware-brotli To build
- Updated ihp-hsx package definitions
- Enabled split sections for app
- Fixed dev server start fails when build/ihp-lib points to a non existing directory
- Use
record.field
syntax instead ofget #field record
in docs - Added docs on dot notation
- Removed outdated note on unsupported syntax in HSX
- currentAdmin, currentAdminOrNothing, currentAdminId, ensureIsAdmin now works without explicit type passed
- Added generic currentRoleOrNothing
- Fixed haddock build
- Tried adding failing test for https://github.com/digitallyinduced/ihp/issues/1451
- Improve error report when when parsing the Schema.sql fails
- Added note on contributing docs
- Use dot notation in ModelSupport
- Make editor docs more visible
- Updated links to mailing list
- Improved support for preEscapedTextValue
- Refactored ConvertibleStrings instance to use ApplyAttribute instance
- adding some docs on using scripts from ghci
- added testConfig example for script with different logging level
- switch appConfig to testConfig for consistency
- Made a specific example for importing Config and running it from ghci
- Added ParPat and working on ConPat so that generic lambda pattern matching works in HSX
- More of the expression type checking still need to find map to
[HsType]
forTH.Conp
- Trying to map constructors to types for template haskell
- Now working with simple constructor pattern matching (no type annotation) no infix constructors yet.
- Implementation of Exact rdrName allows for use of infix operators in HSX!
- Infix and pattern matching on constructors working. Added tests. No type annotations in Constructor patterns supported.
- Allow formFor with GET method
- Use Queue for ghci output
- Extracted and refactored FileWatcher
- Refactored handling of toolserver
- Refactored state handling of live reload notification server
- Fixed rename table operation doesn't correctly rename other DDL statements
- Fixed running a broken migration causes a bad error message
- Replace ConfigProvider and LoggingProvider with dot notation
- make Welcome page page more responsible
- When dropping a column, also drop related policies
- Fixed user_id quick col has a ihp_user_id() default value, even when RLS is not used
Full Changelog: v0.20.0...v1.0.0-rc1
Feature Voting
Help decide what's coming next to IHP by using the Feature Voting!
Updating
→ See the UPGRADE.md for upgrade instructions.
If you have any problems with updating, let us know on the IHP forum.
📧 To stay in the loop, subscribe to the IHP release emails (right at the bottom of the page). Or follow digitally induced on twitter.
v0.20.0
A new IHP release, containing bug fixes and productivity improvements to existing features 🛠️
IHP is a modern batteries-included haskell web framework, built on top of Haskell and Nix. Blazing fast, secure, easy to refactor and the best developer experience with everything you need - from prototype to production.
Major Changes
-
⚪ Dot Notation:
IHP goes OOP-ish. Thanks to a compiler update in this release we can now make use of dot notation à lasomeThing.someAttribute
🎉-- Previously: get #title project -- Now: project.title
This also works in HSX:
[hsx| <!-- Previously: --> <div>{get #title project}</div> <!-- Now: --> <div>{project.title}</div> |]
This can especially be useful when you're dealing with nested records, e.g. when working with
fetchRelated
:[hsx| <!-- Previously: --> <div>{get #name (get #userId project)}</div> <!-- Now: --> <div>{project.userId.name}</div> |]
-
🛠️ HSX Improvements:
We've refactored HSX to fix some of the common issues, like HSX not support expressions likeallEnumValues @MyEnum
. This refactoring was needed to make dot notation work in HSX as well.[hsx| <!-- Previously failed because of the @ symbol: --> {allEnumValues @Color} <!-- Just works with IHP 0.20: --> {allEnumValues @Color} |]
-
➕ New Function: isActiveAction
ReturnsTrue
when the given action matches the path of the currently executed action[hsx| {isActiveAction PostsAction} |]
-
🌎 I18n Routing:
As we're preparing I18n support for IHP apps, we've already refactored the routing to support langauge prefixes, e.g./de/
for a german site and/en/
for english.Here's an advanced code example of this in action:
instance FrontController WebApplication where ... -- the previous code assigning controllers is still required router additionalControllers = do mlocale <- optional do string "/" string "en" let router = defaultRouter additionalControllers case mlocale of Just locale -> case locale of "en" -> putContextRouter EN_UK router _ -> IHP.RouterPrelude.error "This should be unreachable" Nothing -> router
Once IHP has full i18n, you can find more on this in the documentation.
-
🤖 GHC 9.2.4:
We've updated to a new major version of GHC. Previously IHP apps were using GHC 8.10.7. This improves support for M1/M2 macs and allows for dot notation.
Other Changes
- Add Guide On Using Compression Middleware.
- normalize newlines in sql functions before diffing for migration
- Fixed unique constraint with multiple columns showing up as unmigrated changes
- Use "$out/lib" as the default working directory for binaries produced with nix
- fixed design of reference checkbox in new column view
- Extracted column rename logic from Schema Designer action into Schema Operations
- Fixed table renames not updating references indexes
- use font-display swap for inter
- Splitted Lockable.isLocked into an IO and a pure function
- Run migration sql commands in a single database statement: The stateful way of doing the transaction could potentially use a different database connection from the pool. This change avoids that issue.
- HSX: Support underscores in data attribute names
- Removed outdated nix install instructions for macOS
- Fixed SchemaCompiler crashing on Set and Select statements
- diffAppDatabase should work with the configured database url instead of the hardcoded dev db
- Enabled split-sections for IHP builds for smaller binaries on linux
- Normalize spaces in sql functions
- Added failing test for parsing table names starting with public
- Fixed schema parser not correctly parsing table names starting with public
- Use 'CREATE OR REPLACE' when updating a function in a migration
- Fixed update operations in DataSync operation not removing database records from a subscription when the WHERE condition doesn't match anymore
- Fixed broken rendering of reference checkbox
- Adapted reference checkbox to new layout
- Reference Checkboxes no longer have the same id (fixes clickability)
- Add slot parameter for custom elements
- Fixes qualified identifiers in policies causing a diff in the migrations: A policy in the form
CREATE POLICY a ON t USING t.f = public.f
contains a qualified sub-expressiont.f
. When dumping this policy from pg_dump we get a unqualifiedf = public.f
condition instead. So we need to normalize the Policy to the normal formCREATE POLICY a ON t USING f = public.f
- Added missing sql keyword ALL to list of protected keywords
- fixed favicon missing
- Fixed race-condition in closing of DataSync subscription
- Improved error message when DeleteDataSubscription fails because the DataSubscription doesn't exists
- Allow mounting websocket apps that have a HTTP fallback
- Extracted DataSync message handler
- Fixed migration generator fails with a IN (..) expression in a policy
- Fixed job runners running with 2 concurrency even when it's limited to max concurrency 1
Full Changelog: v0.19.0...v0.20.0
Feature Voting
Help decide what's coming next to IHP by using the Feature Voting!
Updating
→ See the UPGRADE.md for upgrade instructions.
If you have any problems with updating, let us know on the IHP forum.
📧 To stay in the loop, subscribe to the IHP release emails (right at the bottom of the page). Or follow digitally induced on twitter.
v0.19.0
A new IHP release, containing bug fixes and productivity improvements to existing features 🛠️
IHP is a modern batteries-included haskell web framework, built on top of Haskell and Nix. Blazing fast, secure, easy to refactor and the best developer experience with everything you need - from prototype to production.
Major Changes
-
💻 Apple M1 Support:
This release finally adds support for Apple M1 devices. So we all can get new macbooks now 🎉 -
🔄 Major DataSync Improvements:
We've implemented a lot of improvements to IHP DataSync:- Limits and Offsets
- Transactons using
withTransaction
:import { withTransaction } from 'ihp-datasync'; await withTransaction(async transaction => { const team = await transaction.createRecord('teams', { title: 'New Team' }); const project = await transaction.createRecord('projects', { title: 'Project 1', teamId: team.id }); return [ team, project ]; })
- Batch Inserts, Batch Deletes, Batch Creates
const todoA = { title: 'Finish Guide', userId: '49946f4d-8a2e-4f18-a399-58e3296ecff5' }; const todoB = { title: 'Learn Haskell', userId: '49946f4d-8a2e-4f18-a399-58e3296ecff5' }; const todos = await createRecord('todos', [ todoA, todoB ]);
- Distinct on
- Optimistic Updates
- Full Text Search
- Performance Improvements
🚀 We've also launched Thin Backend, which is IHP DataSync as a Service If you're into Frontend Development, give it a try!
-
🕶️ Dark Mode:
We've redesigned the dev tools and finally added Dark Mode to IHP:
There's also a couple of nice improvements in the UI in general, e.g. you can now add columns such ascreated_at
orupdated_at
with a single click:In the Data Editor you can now hover over IDs to show the referenced database record in a card:
-
🎨 Redesigned Migration Workflow:
Migrations can now be managed much better from the dev tools. E.g. you can see all migrations, see what has been run already and also manually run migrations here that haven't been executed yet:
-
🗳️ Filebase Integration
You can now use Filebase as an alternative to S3 for storing files with IHP Storage:module Config where import IHP.Prelude import IHP.Environment import IHP.FrameworkConfig import IHP.FileStorage.Config config :: ConfigBuilder config = do option Development option (AppHostname "localhost") initFilebaseStorage "my-bucket-name"
-
🔨 Custom Middlewares:
IHP provides an "escape-hatch" from the framework with theCustomMiddleware
option.
This can be used to run any WAI middleware after IHP's middleware stack, allowing for possibilities
such as embedding a Servant or Yesod app into an IHP app, adding GZIP compression, or any other
number of possibilities. See wai-extra for examples
of WAI middleware that could be added.The following example sets up a custom middleware that infers the real IP using
X-Forwarded-For
and adds a custom header for every request.module Config where import Network.Wai.Middleware.AddHeaders (addHeaders) import Network.Wai.Middleware.RealIp (realIp) config :: ConfigBuilder config = do option $ CustomMiddleware $ addHeaders [("X-My-Header", "Custom WAI Middleware!")] . realIp
Other Changes
- Correct type signature of breadcrumbLink
- Allow user-provided WAI middlewares
- Support limit and offset in DataSync
- Allow using DataSync without a user session
- DataSync: Prepend new records if the query is sorted newest first
- Added a subscribe function to the DataSync QueryBuilder
- DataSyncController: Added flag to keep be aware if first response was received already
- Added
useQuerySingleResult
- Fixed null not encoded correctly in jsValueToDynamicValue
- Added AuthCompletedContext to DataSync
- Improved strictness in DataSync implementation to avoid memory leaks
- Only set up DataSync database triggers once for every table
- Use strict modifyIORef in AutoRefresh
- Reduced amount of queries per DataSync operation: Previously we've been using
withRLS
to wrap database operations inside a RLS context. This added an overhead of several additional database queries. Now we pack allSET ..
statements right into the query string itself, so it's send to postgres inside a single operation. - Use finally to call Websocket onClose handler to make sure the finalizer is always called
- Added basic support for Point types in DataSync
- Added a batch update operation to DataSync
- Added a batch delete operation to DataSync
- Added transactions to DataSync
- Cancel PGListener reader async when a subscription is stopped: This avoids asyncs building up over time
- Implemented expected timeout handling in websockets: The 'withPingThread' function of the websockets package is not dealing with missing pong messages at all. This means that websocket connection might never be cleaned up when the connection is not closed correctly. This change implements a manual ping and pong handling that closes a connection after not receiving a pong message within 10 seconds after sending a ping to the client.
- allow chaining filterWhere/where for AND conditions in DataSync JS API
- Fixed several event listener leaks in DataSync and reduced memory usage
- implement or, and, filterWhereNot for DataSync; add where alias
- Fixed data subscriptions not always closed in specific cases: We switched from manual resource handling to Exception.bracket with MVars to make sure that all data subscriptions are always closed when the underlying websocket connection is closed
- Allow custom configuration of Datasync limits
- DataSync: add jest, run it with ESModules, add example test, include in github workflow
- Make query builder use Table API to access the table name: This allows for easier building custom data records that use an existing table
- small change to default.nix to allow dev customization of postgres extensions
- [Fixed DataSync not generating the correct sql query for...
v0.18.0 (Beta 25.01.2022)
A new IHP release, mostly containing bug fixes and productivty improvements to existing features 🛠️
IHP is a modern batteries-included haskell web framework, built on top of Haskell and Nix. Blazing fast, secure, easy to refactor and the best developer experience with everything you need - from prototype to production.
Major Changes
-
🚪Stripe Cancelations are Supported now:
The IHP Stripe Integration now supports two further events that allow your app to automatically handle with unsubscribes:on CustomerSubscriptionUpdated { subscription = stripeSubscription } = do -- ... on CustomerSubscriptionDeleted { subscriptionId } = do -- ...
-
🧰 Haskell Language Server: Performance Improvements
The Haskell Language Server provided by IHP is now compiled with the--enable-executable-dynamic
flag. This flag significantly improved performance of using HLS in IHP code bases as reported by multiple people.If you still have performance issues, please submit issues directly here haskell/haskell-language-server#2340
-
🍪 Cookies:
While IHP had supported session cookies for a long time now, we never got to add a simple function to add your own non-session cookies. Now it's finally there, meetsetCookie
:import Web.Cookie action MyAction = do setCookie defaultSetCookie { setCookieName = "exampleCookie" , setCookieValue = "exampleValue" }
-
📃 Multi-line Strings:
IHP apps now have[trimming|some text|]
in scope by default. Thistrimming
quasi quoter provides a useful way to write multi line strings, like this:let myString = [trimming| My multi line string |]
It's called trimming because the function automatically trims/removes the indentation spaces at the left. So the above string is
My\nmulti line\nstring
instead of<4 spaces>My\n<4 spaces>multi line\n<4 spaces>\nstring
. -
📫 Mail Improvements:
You can now easily set reply-to, cc, bcc or custom headers when using the IHP Mailer system:instance BuildMail ConfirmationMail where subject = "Subject" to ConfirmationMail { .. } = Address { addressName = Just "F L", addressEmail = "[email protected]" } from = "[email protected]" html ConfirmationMail { .. } = [hsx| Hello World |] -- New: replyTo ConfirmationMail { .. } = Just Address { addessName = "Support", addressEmail = "[email protected]" } cc ConfirmationMail { .. } = [ Address { addessName = "Some one", addressEmail = "[email protected]" } ] bcc ConfirmationMail { .. } = [ Address { addessName = "Some one", addressEmail = "[email protected]" } ] -- Custom headers headers ConfirmationMail { .. } = [ ("X-Mailer", "mail4j 2.17.0") , ("In-Reply-To", "<[email protected]>") ]
-
🗑️
deleteRecordByIds
You can now easier delete records when you only know their ids:let postId :: [Id Post] = ["5280e9a5-3105-45b3-8aea-6a081c596a11", "6761216b-c508-4c88-80fc-66316a1dc88c"] deleteRecordByIds postId
-
🆕
fetchLatest
There's now a helper to fetch latest record of something:latestUser <- query @User |> fetchLatest -- Previously you had to write query: latestUser <- query @User |> orderByDesc #createdAt |> fetchOneOrNothing
Other Changes
- When an SQL error happens, the SQL query that triggered the error is now shown
- Handle default value changes in migrations
- Handle dropping default values in migrations
- Fixed auto generated migration for a new enum value fails because SQL compiler branch is not implemented
- Mail: add support for replyTo, cc, bcc, and custom heards
- add srcset attribute to HSX parser
- Declare usage of es6 modules in module definition of DataSync to improve support with other JS toolings
- Removed window export in ihp-querybuilder.js as this causes babel to fail
- Re-export DataSync libraries from the main modules
- Renamed ihp-datasync-react.js to just react.js
- Added trimming quasi quoter to IHP.Prelude
- Added support for react 18's suspense feature in DataSync
- Use standard renderPagination in list page for Jobs Dashboard
- Added withRowLevelSecurityDisabled: Executes the given block with the main database role and temporarly sidesteps the row level security policies
- Fixed AutoRefresh crashing when database triggers are set up from the ihp_authenticated role
- Support types besides strings in DataSync's filterWhere
- Fixed URL not updating after form submission when the form is submitted as a GET request
- Fixed data editor errors when trying to edit the schema_migrations table
- Normalize table constraints before making diff for auto generated migrations
- Fixed parsing issue when dealing with functional calls with multiple arguments
- Fixed unchecking "Run Migration after Generate" not working
- Updated session docs
- Fixed deleting a table in the schema designer not deleting the table's policies, rls statements, indices
- Fixed PGListener breaking live reload because it's not stopping on cancel
- refactored job runner system to run more reliable in dev mode
- Added support for sql triggers
- Tailwind 3 support
- Fixed Schema Parser not accepting 'CREATE SEQUENCE public.a'
- Parser supports more variants of CREATE SEQUENCE statements now
- Fixed migrations not normalising SERIAL and BIGSERIAL columns
- Fixed space in generated code
- add postgres polygon support
- Support signed integers and floats in the Schema SQL parser. Fixes #1309
- Fixed Migration generator not normalizing index expressions
- Fixed migration generator detecting a difference between custom sql functions when the difference is only CREATE / CREATE OR REPLACE
- Added support for Double/Float64 in DataSync. Fixes #1298
- Support lowercased SELECT expressions
- [Im...