Skip to content

Releases: pthm/melange

melange v0.8.2

30 Apr 19:30

Choose a tag to compare

melange v0.8.1

12 Apr 23:31
f80be45

Choose a tag to compare

Changelog

melange v0.8.0

05 Apr 03:57
0abba25

Choose a tag to compare

Adds modular model support (fga.mod / Schema 1.2) for splitting schemas across multiple files, configurable database schema for installing melange objects into a custom PostgreSQL schema, and safe identifier truncation to handle PostgreSQL's 63-byte name limit.

Warning

Generated SQL function names have been renamed with shorter suffixes (e.g., _no_wildcard to _nw). Running melange migrate will drop the old functions and install the new ones automatically. If you call specialized check or list functions directly (e.g., check_document_viewer_no_wildcard), you will need to update those references. If you only use the dispatcher functions (check_permission, list_accessible_objects, list_accessible_subjects), no changes are needed.

Highlights

Modular Models (fga.mod / Schema 1.2)

Schemas can now be split across multiple .fga files using OpenFGA's modular model format. Each module can define new types or extend existing ones. Melange merges them into a single schema at compile time.

# fga.mod
schema: '1.2'
contents:
  - core.fga
  - tracker/projects.fga
  - wiki/spaces.fga

All CLI commands accept fga.mod paths. Pass --schema fga.mod where you previously passed --schema schema.fga.

Configurable Database Schema

Melange objects can now be installed into a custom PostgreSQL schema instead of public, keeping authorization functions and tables separate from your application.

# melange.yaml
database:
  url: postgres://localhost/mydb
  schema: authz

Also configurable via MELANGE_DATABASE_SCHEMA env var or --db-schema CLI flag.

Safe Identifier Truncation

PostgreSQL silently truncates identifiers longer than 63 bytes, which can cause collisions or runtime failures with long type or relation names. Melange now handles this with shorter function suffixes and automatic truncation with an 8-character SHA-256 hash when names would exceed the limit.

Old suffix New suffix Savings
_no_wildcard _nw 8 chars
_subjects _sub 5 chars
_objects _obj 4 chars

Improvements

  • melange init now asks whether to use the built-in migrator or versioned SQL file generation
  • Documentation site restructured with refreshed design and new homepage sections
  • Schema qualification bug fixes for DROP FUNCTION statements, identifier quoting, and per-schema validation
  • All Go dependencies updated to latest versions

Contributors

Thanks to @Desuuuu for implementing configurable database schema support.

Install / Upgrade

# CLI
brew install pthm/melange/melange

# Go runtime
go get github.com/pthm/melange/melange@v0.8.0

# TypeScript runtime
npm install @pthm/melange

# Apply migrations
melange migrate

Full blog post: https://melange.pthm.dev/blog/v0.8.0/

melange v0.7.4

23 Mar 02:13
d781a4c

Choose a tag to compare

Adds the melange generate migration command for external migration framework integration, introduces two-phase skip detection to the built-in migrator, and includes new unit tests and documentation improvements.

Note

No breaking changes from v0.7.3. The built-in migrator automatically adds a function_checksums column to melange_migrations on the next melange migrate run.

Highlights

melange generate migration — External Migration Framework Support

Users with existing migration systems (golang-migrate, sqlx, Flyway, etc.) can now generate versioned UP/DOWN SQL files instead of running melange migrate directly. Inspired by River Queue's migrate-get command pattern.

# Full migration (first time or periodic snapshot)
melange generate migration \
  --schema schemas/schema.fga \
  --output db/migrations

# Diff migration against main branch (ideal for PRs)
melange generate migration \
  --schema schemas/schema.fga \
  --output db/migrations \
  --git-ref main

Three comparison modes control which functions appear in the migration:

Mode Flag Use Case
Full (default) (none) First migration or periodic full snapshot
Database --db <url> Most precise — reads melange_migrations from a live database
Git --git-ref <ref> No database needed — compares against a branch, tag, or commit
File --previous-schema <path> Compares against a local backup of the last-deployed schema

Function-level change detection uses SHA-256 checksums to include only changed functions in diff migrations. Dispatcher functions are always regenerated. Orphaned functions from removed relations receive DROP FUNCTION IF EXISTS statements.

Two-Phase Skip Detection in the Built-in Migrator

melange migrate now uses two-phase skip detection to avoid unnecessary database writes:

  • Phase 1 (fast path): If both the schema checksum and melange version match the last migration, skip entirely — no SQL generation needed.
  • Phase 2 (smart apply): If the schema or version changed, generate SQL and compare function checksums. If every function is identical and no orphans exist, skip re-applying and only record the new migration state.

This makes melange version upgrades efficient when the generated SQL hasn't actually changed.

Improvements

  • Simplified config schema — redundant per-subcommand schema fields removed in favour of a single top-level schema field
  • Added Migrations concept guide covering both the built-in migrator and external framework integration
  • Documented the melange generate migration command in the CLI reference
  • Fixed all broken internal links across the documentation site

Testing & Maintenance

  • Added unit tests for pkg/migrator and pkg/clientgen
  • Added migration system integration tests covering two-phase skip detection
  • Upgraded google.golang.org/grpc from v1.78.0 to v1.79.3
  • Enhanced README with Matrix chat link and contribution details

Install / Upgrade

# CLI
brew install pthm/melange/melange

# Go runtime
go get github.com/pthm/melange/melange@v0.7.4

# TypeScript runtime
npm install @pthm/melange

# Apply migrations
melange migrate

Full blog post: https://melange.pthm.dev/blog/v0.7.4/

Melange v0.7.3

16 Mar 19:00
fcdcde6

Choose a tag to compare

Adds melange init for interactive project scaffolding, fixes four list_objects/list_users bugs, and improves melange doctor with table index recommendations and orphaned tuple detection.

No breaking changes from v0.7.2. Upgrade and run melange migrate to pick up the list function fixes.

Highlights

New melange init command bootstraps a melange project with config, starter schema (four templates), and runtime dependency installation. Detects Go modules and Node.js packages automatically. Use -y to accept all defaults.

Bug Fixes

  • list_objects returned results through non-recursive parent chains (#34) — Relations without from parent incorrectly propagated access to child objects. Thanks to @jtbeach for the fix.
  • list_objects over-propagated when recursive and non-recursive relations share a permission — Non-recursive relations could still propagate through parent chains when combined with recursive ones in the same permission.
  • list_objects and list_users ignored exclusions on userset lookups — Exclusions with userset patterns (e.g., member but not blocked via group#member) were not filtering out excluded subjects.
  • list_users returned incomplete results for multi-path TTU relations — Only the first parent path was walked when a permission could be reached through multiple parent relations.
  • melange doctor multi-table view parsing (#35) — Views joining multiple tables were incorrectly parsed, producing invalid CREATE INDEX suggestions. Thanks to @sgsfak for reporting.

Improvements

  • melange doctor table index recommendations — Checks for recommended indexes when melange_tuples is a table, with severity scaling by row count.
  • melange doctor orphaned tuple detection — Comprehensive validation of all tuples against the schema, replacing the previous sampling-based check. Detects unknown object types, relations, subject types, and invalid subject type assignments.

Testing & Maintenance

  • Removed 1,000+ lines of dead code (#33)
  • Added Codecov integration (#32)
  • Added 22 new test cases covering real-world authorization patterns

Contributors

Melange v0.7.2

15 Mar 18:05
17f7057

Choose a tag to compare

Adds performance linting to melange doctor and fixes two bugs reported by community contributors.

No breaking changes from v0.7.1. Upgrade and run melange migrate to pick up fixes.

Performance Linting in melange doctor

The doctor command now detects missing expression indexes for ::text casts in the melange_tuples view — the #1 production performance
issue, causing 26x slower queries at 1M tuples.

  • Parses view definition via pg_get_viewdef() to detect UNION vs UNION ALL and enumerate source tables
  • Detects missing ::text expression indexes per source table with severity based on row count and exact CREATE INDEX fix hints
  • Disable via --skip-performance flag or doctor.skip_performance config

Bug Fixes

  • Fix list_objects missing results for closure userset patterns (#30) — list_*_objects
    only expanded userset lookups for the first relation in a closure. When multiple relations shared the same userset pattern, only one got SQL
    expansion. Thanks to @jtbeach for the fix.

  • Fix uppercase relation names breaking re-migration (#26) — Schemas with uppercase relation
    names caused functions to be dropped on re-migration due to a casing mismatch between CollectFunctionNames and PostgreSQL's identifier
    folding. Thanks to @Desuuuu for reporting and submitting an initial fix
    (#27).

Contributors

Melange v0.7.1

12 Mar 05:50
07915ea

Choose a tag to compare

Patch release with two bug fixes from community reports, security dependency upgrades, and testing improvements.

Bug Fixes

  • Fix list_accessible_subjects returning empty results for TTU with implied parent relations (#22) — The parent closure optimization filtered tuples
    by exact relation name, missing implied relations like can_read: member. Now expands the filter to include all satisfying relations.
  • Fix TypeScript client listObjects/listSubjects pagination (#21, #23) — 0 was sent as p_limit instead of NULL when no limit was specified, and
    cursors read from a non-existent column name.

Security

  • Upgraded TypeScript dev dependencies (vitest v2→v4, rollup override) to resolve 5 Dependabot-flagged vulnerabilities (no runtime impact).

Testing

  • Derived list assertions automatically from check assertions for broader coverage
  • Standardized hand-rolled SQL tests on OpenFGA YAML format
  • Colocated parser unit tests with pkg/parser

Migration

No breaking changes. Run melange migrate and upgrade the TypeScript client if applicable.

Contributors

Thanks to @Desuuuu for reporting and fixing the TypeScript pagination issues and reporting the list_accessible_subjects bug.

Melange v0.7.0

08 Feb 03:20
8a2c8df

Choose a tag to compare

Melange v0.7.0 introduces the Bulk Check API for batching multiple permission checks into a single SQL call, alongside multi-driver testing, module layout improvements, and a bug fix for intersection parsing.

Note

No breaking changes from v0.6.4. Upgrade and run melange migrate to install the new check_permission_bulk function.

Highlights

Bulk Permission Check API

The headline feature of v0.7.0 is check_permission_bulk (#11) — a new SQL function that evaluates multiple permission checks in a single database call. This is designed for the common pattern of checking many permissions at once, such as filtering a list of resources for display or rendering UI with permission-aware controls.

SELECT * FROM check_permission_bulk(
    ARRAY['user',   'user'],        -- subject types
    ARRAY['alice',  'alice'],       -- subject IDs
    ARRAY['viewer', 'editor'],      -- relations
    ARRAY['document', 'document'],  -- object types
    ARRAY['doc-1',  'doc-2']        -- object IDs
);
-- Returns:
--  idx | allowed
-- -----+---------
--    0 |       1
--    1 |       0

The generated SQL groups branches by object type with IF guards so that a batch checking only document permissions never touches folder branches, and simple direct-assignment relations are inlined as EXISTS subqueries to avoid function call overhead.

Go API

results, err := checker.NewBulkCheck(ctx).
    Add(user, "viewer", doc1).
    Add(user, "editor", doc2).
    Execute()

if results.All() { /* all granted */ }
if results.Any() { /* at least one granted */ }

TypeScript API

const results = await checker
  .newBulkCheck()
  .add(user, "viewer", doc1)
  .add(user, "editor", doc2)
  .execute();

results.all();        // true if all granted
results.allOrError(); // throws BulkCheckDeniedError if any denied

Both clients include automatic deduplication, cache integration, decision overrides for testing, and a batch size guard (10,000 max).

Thanks to @sonalys for requesting this feature.

Bug Fix: Intersection with Tuple-to-Userset Unions

Fixed a bug (#13) where intersection rules containing unions of tuple-to-userset patterns were silently dropped during parsing — a permission bypass. The parser now correctly extracts both simple relations and tuple-to-userset patterns from unions and distributes them into intersection groups.

Thanks to @jake-wickstrom for reporting this.

Multi-Driver Integration Tests

Table-driven tests now run the same assertion suite against each supported driver:

  • Go: pgx/stdlib and lib/pq, plus transaction isolation
  • TypeScript: pg (node-postgres) and postgres.js

Module Layout and go install Fix

go install github.com/pthm/melange/cmd/melange@latest now works reliably. Fixed by removing the stale replace directive, renaming internal/ to lib/, and restoring cmd/melange as a proper sub-module.

What's Changed Since v0.6.4

  • Bulk permission checkcheck_permission_bulk SQL function, Go BulkCheckBuilder, TypeScript BulkCheckBuilder
  • Bulk dispatcher optimization — branches grouped by object type with IF guards, simple relations inlined as EXISTS
  • Intersection + TTU union bug fix — silently dropped tuple-to-userset unions in intersections (#13)
  • Multi-driver tests — Go (pgx, lib/pq) and TypeScript (pg, postgres.js) integration tests
  • Module restructuringinternal/lib/, cmd/melange sub-module restored, replace directive removed
  • go install @latest fixed — works reliably with the Go module proxy
  • CI improvements — race detection, TypeScript unit tests in CI, all-module linting, parallelism fix
  • Documentation — TypeScript client examples, bulk check guide, SQL API reference
  • Dependency update — OpenFGA v1.11.2 → v1.11.3

Install / Upgrade

# CLI
brew install pthm/tap/melange

# Go runtime
go get github.com/pthm/melange/melange@v0.7.0

# TypeScript runtime
npm install @pthm/melange@0.7.0

# Apply migrations
melange migrate

Full blog post: https://melange.pthm.dev/blog/v0.7.0/

melange v0.6.7

03 Feb 23:38
6506428

Choose a tag to compare

Changelog

melange v0.6.5

31 Jan 19:15
758d331

Choose a tag to compare

What's Changed

Security Fix

  • Fix intersection parsing for unions containing tuple-to-userset patterns (#14) - Fixes #13 reported by @jake-wickstrom

    When parsing intersection rules like viewer and (member from group or owner from group), the union containing TupleToUserset (TTU) patterns was silently dropped. This caused the generated SQL to only check the viewer relation, ignoring the group membership requirement entirely.

    If you use intersection rules with from patterns in unions, upgrade immediately.

Other

  • Fix release script to skip pushing root tag if goreleaser already pushed it

Full Changelog: v0.6.4...v0.6.5