Releases: oliverandrich/burrow
v0.13.1
Changed
- Den upgraded to v0.7.0 — picks up PostgreSQL JSONB comparison fixes, GroupBy SQL pushdown, revision TOCTOU fix, and link validation enforcement
Fixed
- Invalid den struct tags in test models —
den:"name,index"andden:"key,unique"incorrectly included field names in the den tag (field names come from the json tag). Den v0.7.0 now rejects unknown tag options, catching these at registration time
v0.13.0
Breaking Changes
- Auth admin rewritten with hand-written handlers —
contrib/authno longer uses ModelAdmin for its admin views. User list, edit, delete, and invite management are now direct handlers with custom templates. TheHasAdmininterface (AdminRoutes,AdminNavItems) is unchanged. Auth admin templates now use globally registered icon functions (iconSearch,iconPlus,iconPersonSlash,iconPersonCheck,iconTrash,iconXCircle). - Jobs admin rewritten with hand-written handlers —
contrib/jobsno longer uses ModelAdmin. Job list, detail, delete, retry, and cancel are now direct handlers with custom templates including status filter pills and inline action buttons. - ModelAdmin package removed —
contrib/admin/modeladminhas been deleted entirely. All admin views now use hand-written handlers. The admin coordinator (layout, sidebar, auth middleware, nav groups) is unchanged.
Added
- User search in admin — the user admin list now supports search across username, name, and email fields.
- Inline invite creation — the invite admin list now features an htmx-powered inline form that slides open on button click instead of navigating to a separate page.
burrow.DefineTask[P]()— type-safe generic task definitions for the jobs system. WrapsQueue.HandleandQueue.Enqueuewith automatic JSON marshalling, ensuring compile-time agreement between producer and consumer payload types. Auth email jobs migrated as first consumer.burrow.DefineResultTask[P, R]()— variant ofDefineTaskfor handlers that return both a result and an error. Results are persisted as JSON on the job and visible in the admin detail view.- Job result persistence — completed jobs now store their handler's return value in a
Resultfield (JSON). Failed jobs additionally record the Go error type inErrorClassand the timestamp of the last handler invocation inLastAttemptedAt. All three fields are displayed in the admin detail view and cleared on retry. - Job priority — jobs now have a
Priorityfield (default 0, higher = more urgent). Set per-type viaburrow.WithPriority(n)at handler registration. The claim query picks highest-priority jobs first, with FIFO ordering within the same priority level.
Changed
- Seed requires
--seedflag —Seedable.Seed()no longer runs unconditionally on every server start. Pass--seed(or setSEED=true) to run seed functions. This prevents non-idempotent seeders from creating duplicates on restart. - Auth handlers split into focused files — the monolithic
handlers.go(714 lines, 25 functions) has been split intohandlers_registration.go,handlers_login.go,handlers_credentials.go,handlers_recovery.go,handlers_email.go, andhandlers_helpers.go. No API or behavior changes — pure file reorganization for better navigability. - Admin decoupled from auth package —
contrib/adminno longer importscontrib/authdirectly. Instead, it discovers auth middleware via the newburrow.AdminAuthinterface from the registry.contrib/authimplementsAdminAuthautomatically. Custom auth systems can provide their own implementation. Config.IsHTTPS()helper — replaces duplicatedstrings.HasPrefix(baseURL, "https://")checks in csrf, session, and secure apps.- WebAuthn flag aliases removed — the legacy
--webauthn-rp-id,--webauthn-rp-display-name,--webauthn-rp-originaliases (withoutauth-prefix) have been removed. Use the canonical--auth-webauthn-*names. burrow.Queuesplit intoEnqueuer+Queue— newEnqueuerinterface holdsEnqueue,EnqueueAt, andDequeue.QueueembedsEnqueuerand addsHandle. Code that only submits jobs can now acceptEnqueuerinstead of the fullQueue.TaskDefinitionandResultTaskstoreEnqueuerinternally.- Form fields with nil pointers render as zero values —
forms.extractFieldsnow returns the element type's zero value (e.g.""for*string) instead ofnilwhen a pointer field is nil. Templates can use{{ .Value }}on optional fields without special-casing nil.
Fixed
- Job retry backoff capped at 1 hour — exponential backoff (
baseDelay * 2^(attempts-1)) now caps at 1 hour. Previously, high attempt counts could overflowtime.Durationand produce negative or astronomically large delays. - Session flush logs encoding errors —
state.flush()now logs viaslog.Errorwhen cookie encoding fails instead of silently swallowing the error. - Admin rejects duplicate AdminAuth providers —
admin.Configure()returns an error if multiple apps implementAdminAuth, instead of silently using the first one. - Session deferredWriter supports http.Flusher — the session middleware's response wrapper now implements
Flush(), fixing SSE and streaming handlers that use directw.(http.Flusher)type assertions. - Session cookies written once per request —
session.Set(),Delete(), andSave()no longer write theSet-Cookieheader immediately. Instead, the session middleware defers the write until the response is sent, producing exactly oneSet-Cookieheader regardless of how many session mutations occur. Previously, eachSet()call wrote a separate header, and only the last one survived to the browser. - Jobs recover from handler panics — worker goroutines now recover from panics in job handlers, converting them into failures with a stack trace. The worker stays alive and continues processing other jobs.
- RenderError falls back to plaintext — when both
error/{code}anderror/defaulttemplates are missing,RenderErrornow writes a plaintext HTTP error instead of a blank response. - NavItem.LabelKey now translated in navLinks —
buildNavLinksnow translatesLabelKeyviai18n.Tat render time, falling back toLabelwhen no translation is found. PreviouslyLabelKeywas silently dropped. - Uploads no longer buffer entire file in memory —
LocalStorage.Storenow streams uploads directly to a temp file while computing the SHA-256 hash, instead of reading the entire file into[]byte. Only the first 512 bytes are buffered for MIME detection. MIME validation happens before reading the body, so rejected files are discarded early. - Jobs admin UI re-enabled —
contrib/jobsAdminRoutesandAdminNavItemswere stubbed out during the Den migration. The ModelAdmin integration is now wired up again, restoring the list/detail/retry/cancel/delete views and the sidebar nav entry. - Auth redirects use SmartRedirect —
Logout,RecoveryCodesPage, andAcknowledgeRecoveryCodesnow usehtmx.SmartRedirectinstead ofhttp.Redirect, fixing redirect behavior when triggered via htmx. - Invite creation uses SmartRedirect —
handleCreateInvitenow useshtmx.SmartRedirectinstead ofhttp.Redirect, fixing redirect behavior when the form is submitted via htmx.
v0.12.0
Breaking Changes
-
Den struct-tag validation is now enabled by default —
burrow.OpenDB()now enables Den'svalidate.WithValidation()automatically, so any document field tagged withvalidate:"..."(e.g.,validate:"required",validate:"email",validate:"oneof=...") is enforced before every Insert and Update. Violations return an error wrappingden.ErrValidation.Projects that had
validate:tags on Den documents where the tags previously had no effect will now see those constraints enforced. If the data layer needs to stay lax temporarily during migration, use the newburrow.OpenDBWithoutValidation()escape hatch. Remove it once the data is clean.burrow.TestDB()andauthtest.NewDB()also enable validation so test code runs with the same constraints as production. -
Upgraded to Den v0.6.0 — Den now runs mutating hooks (
BeforeInsert,BeforeUpdate,BeforeSave) before both struct-tag validation and theValidator.Validate()interface. This lets aBeforeInserthook populate a default value for a field that validation then requires, matching the ActiveRecord/Django/SQLAlchemy pattern. See the Den changelog for details. If you had custom validation that depended on running before the hooks (unusual), move it intoBeforeInsertitself.
Added
burrow.OpenDBWithoutValidation(dsn)— opens a database with struct-tag validation disabled. Intended only as a migration escape hatch when moving a project from pre-v0.12.0 behavior.
v0.11.4
Changed
- Den updated to v0.5.0 — adds support for composite indexes via
index_togetherandunique_togetherstruct tags, and fixesSettings.Indexesapplication duringRegister() - Composite indexes for Job model — added
index_together:claim(RunAt, Status, WorkerID) andindex_together:stale(Status, LockedAt) to optimize worker claim and stale-job-rescue queries - Composite index for RecoveryCode model — added
index_together:recovery_status(UserID, Used) to optimize unused recovery code lookups
v0.11.3
Changed
- Den updated to v0.4.2 — adds PostgreSQL version check (requires PostgreSQL 13+) and LLM documentation
v0.11.2
Changed
- License page — include full MIT license text, link to third-party licenses
- Release workflow — release now waits for CI success before creating a GitHub release
v0.11.1
Fixed
- Social card URLs — updated
og:imageandtwitter:imagemeta tags to point to readthedocs.io
v0.11.0
Breaking Changes
- Database layer replaced: Bun → Den — the entire database layer has been replaced with Den, an object-document mapper (ODM) for Go. Same API for SQLite and PostgreSQL via URL-based DSN.
- All IDs changed from
int64tostring— documents now use ULID-based string IDs viadocument.Base Migratableinterface replaced byHasDocuments— apps declare document types instead of providing SQL migration files. Den creates tables and indexes automatically from struct tags.- DSN requires URL scheme —
--database-dsn sqlite:///app.db(default) or--database-dsn postgres://host/db. Plain file paths no longer accepted. --jobs-databaserenamed to--jobs-database-dsn— env varJOBS_DATABASE_DSN, TOML keyjobs.database_dsn- SQL migration files removed — schema is managed automatically from document struct definitions
- License changed from EUPL-1.2 to MIT
Added
- PostgreSQL support — switch between SQLite and PostgreSQL with a single flag, same code, same API
- New contrib app:
humanize— i18n-aware template functions for human-friendly display of times, numbers, and file sizes, inspired by Django'sdjango.contrib.humanize
Changed
- Handler pattern simplified — handlers are now methods on
*Appinstead of a separateHandlersstruct - Bun dependency removed — replaced by
github.com/oliverandrich/den v0.4.0 - Job queue: ownership guard — workers stamp
worker_idon claimed jobs;Complete/Failverify ownership via guardedFindOneAndUpdate, preventing double processing under concurrent workers - Job queue:
Complete/Failtake*Job— no longer reload from DB, eliminating redundant reads and stale attempt counts
Fixed
- Admin HTMX navigation — boosted requests with a custom
hx-target(e.g.#main) no longer wrap content in the layout, fixing the doubled sidebar itoatemplate function removed — was a no-op after int64→string migration; templates use.IDdirectly- Dead code removed —
URLParamInt64,MustURLParamInt64, stalebun:tags in tests
v0.10.0
Breaking Changes
- App interface simplified:
Appnow only requiresName() string—Register(cfg *AppConfig) errorhas been removed. All setup logic moves intoConfigure(cfg *AppConfig, cmd *cli.Command) errorvia theConfigurableinterface. - Configurable signature changed:
Configure(cmd *cli.Command) error→Configure(cfg *AppConfig, cmd *cli.Command) error— update all implementations to accept the newcfgparameter - PostConfigurable signature changed:
PostConfigure(cmd *cli.Command) error→PostConfigure(cfg *AppConfig, cmd *cli.Command) error - HasFlags extracted:
Flags()is no longer part ofConfigurable— it is now a standaloneHasFlagsinterface - Registry.RegisterAll removed: Use
Registry.ConfigureAll(cfg *AppConfig)instead (callsConfigure(cfg, nil)on eachConfigurableapp)
v0.9.0
Breaking Changes
- HasRequestFuncMap: Changed signature from
RequestFuncMap(*http.Request)toRequestFuncMap(context.Context)— update all implementations by replacingr *http.Requestwithctx context.Contextandr.Context()withctx(#5) - TemplateExecutor: Changed signature from
func(*http.Request, string, map[string]any)tofunc(context.Context, string, map[string]any)(#5)
Added
Startablelifecycle interface — counterpart toHasShutdown, called after the full boot sequence (templates built, middleware and routes registered); receives*Serverso apps can access server resources likeTemplateExecutor()(#11)- jobs: automatic
TemplateExecutorinjection — the jobs app now implementsStartableand injects theTemplateExecutorinto every job handler's context, enablingRenderFragmentin background jobs without manual setup (#11) - Added
RenderFragment()for rendering templates outside HTTP handlers (background jobs, SSE, CLI) (#5) - Added
Server.TemplateExecutor()accessor to obtain the template executor after boot (#5) - Added
WithRequestPath()/RequestPath()context helpers for request path propagation (#5) - htmx: Added smart response helpers
SmartRedirectandRenderOrRedirectthat handle htmx/non-htmx branching, plusReselectheader setter andStatusStopPollingconstant (#9) - Added
URLParamInt64()andMustURLParamInt64()helpers for parsing numeric URL parameters (#8) - auth: Added
MustCurrentUser()helper that returns the authenticated user or panics — for use behindRequireAuthmiddleware (#7) - sse: Added
BrokerFromRegistry()for package-level access to the SSE broker without type assertions (#6) csrfHxHeaderstemplate function — rendershx-headers='{"X-CSRF-Token":"..."}'as an HTML attribute when the csrf app is registered, or nothing when it is not. Use<body {{ csrfHxHeaders }}>for automatic CSRF protection on all htmx requests.csrfToken/csrfField/csrfHxHeadersfallbacks — these template functions are always available (return empty values when the csrf app is not registered), so layouts can reference them unconditionally.
Fixed
- Bootstrap/admin layouts: automatic CSRF token for htmx —
bootstrap/layout,bootstrap/nav_layout, andadmin/layoutnow use{{ csrfHxHeaders }}on the<body>tag, so all htmx requests include the CSRF token automatically.