| Name | Modified | Size | Downloads / Week |
|---|---|---|---|
| Parent folder | |||
| lapis-1.20.0-1.rockspec | 2026-09-29 | 5.4 kB | |
| lapis-1.20.0-1.src.rock | 2026-09-29 | 514.3 kB | |
| README.md | 2026-09-29 | 5.5 kB | |
| v1.20.0 source code.tar.gz | 2026-09-29 | 385.1 kB | |
| v1.20.0 source code.zip | 2026-09-29 | 511.6 kB | |
| Totals: 5 Items | 1.4 MB | 0 | |
Security
- Fixed request parsing that took quadratic time on crafted input. A single request could tie up a worker for seconds, or much longer if you've raised your body size limits. Both are now linear:
- Nested parameter names like
a[b][c], when the name has many[without a closing]. This runs on every routed request before before filters. - The
Cookieheader, when it contains long runs without=. - Nested parameters are expanded at most 64 levels deep. A name with more levels is kept as a single flat key instead of allocating a table for every level.
- The default route no longer redirects off domain. A request for
//evil.com/used to redirect to the protocol relative URL//evil.com. Leading slashes are now collapsed, so it redirects to/evil.com. - The HTML builder throws an error for attribute names that contain whitespace, control characters, or any of
"'>/=. Attribute names can't be escaped, so previously an untrusted name could inject markup. util.underscoreandutil.singularizeare now linear time. Previously they were quadratic on long input.
Requests
util.parse_query_stringnow decodes+as a space, the same asngx.decode_args. Values containing=are kept whole, and a segment without a key is skipped instead of failing the whole parse. Previouslya=b=c&d=eparsed as just{a = "b"}. This is also used to parse query strings and form bodies on the cqueues server.- Cookie parsing no longer requires a space after
;. Previouslya=1;b=2produced a cookie named;b. - The default route collapses multiple trailing slashes to one before redirecting, so
/hello///redirects to/hello/and can match a route that ends in/. A single trailing slash is still removed. json_paramsignores aContent-Typeheader that was sent more than once, instead of throwing an error.- The cqueues server parses form bodies whose content type has parameters, like
application/x-www-form-urlencoded; charset=UTF-8.
Models and Database
- Added
no_transactiontolapis.db.migrationsfor migrations that can't run inside a transaction, likeCREATE INDEX CONCURRENTLY. Inglobaltransaction mode the open transaction is committed before the migration and a new one is started after it. A dry run stops before anyno_transactionmigration, since it can't be rolled back.
```moon import no_transaction from require "lapis.db.migrations"
{ [1790452452]: no_transaction => db.query "create index concurrently games_title_idx on games (title)" } ```
- Postgres column types accept
references,on_delete, andon_updateoptions for foreign key constraints.
moon
types.foreign_key references: "users", on_delete: "cascade"
-- integer NOT NULL REFERENCES "users" ON DELETE CASCADE
- SQLite schema
typesthrows an error for an unknown column type instead of returningnil.
MySQL
Model:updatesupports thewhereoption.returningandon_conflictnow throw an error instead of being silently ignored. This applies todb.insert,db.update,db.delete,Model:create, andModel:update.Model:updatenow handles the model instance the same way as the Postgres and SQLite models: values are only stored on the instance after a row is updated, updating a column todb.NULLsets the field tonil, andupdated_atis stored on the instance. Previously values were assigned before the query ran, even if the update failed.
Validation
- Added
types.params_jointo combine params types into a single checker, useful for sharing a common set of fields across endpoints. Every member is tested against the same input and their outputs are merged. It replaces the undocumentedtypes.multi_params, which has been removed.
```moon paginated = types.params_shape { {"page", types.empty / 1 + types.db_id} }
search_params = types.params_join { paginated types.params_shape { {"q", types.limited_text 100} } } ```
- Fixed the legacy
is_colorvalidator accepting letters outside ofa-f, like#ZZZ.
Configuration
- Arrays in config are replaced instead of merged by index. An empty table clears an existing array. Merging a table that mixes array and hash keys, or merging a hash table into an existing array, throws an error.
```moon config {"alpha", "beta"}, -> emails {"one@example.com", "two@example.com"}
config "beta", -> emails {"three@example.com"}
-- beta's emails is now {"three@example.com"} -- previously it was {"three@example.com", "two@example.com"} ```
Utilities
util.escapematchesngx.escape_uri:-._~!*'()are no longer escaped, hex digits are uppercase, andnilreturns an empty string instead of throwing an error.util.to_jsondrops references back to a parent table instead of overflowing the stack. A table used in more than one place is still encoded each time.lapis.logging.flatten_paramsincludes nested tables in its output, which were previously dropped, and stops at 5 levels deep. Values that aren't strings are written withtostring.
Testing and CLI
lapis generate specworks with--type helpers, and generated Lua model specs no longer have a typo inrequire.simulate_actionthrows an error if the URL isn't a string.
Documentation
Most of the guides got a full pass: typos, broken examples, and a number of functions and options that were never documented.
Full Changelog: https://github.com/leafo/lapis/compare/v1.19.0...v1.20.0