Melange v0.7.0
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.
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.
The problem: An application rendering a dashboard with 50 documents previously needed 50 individual check_permission calls. Even with connection pooling, this means 50 round-trips to the database.
The solution: check_permission_bulk accepts parallel arrays of subjects, relations, and objects, evaluates them all in one call, and returns a row per check indicating allowed or denied.
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 | 0The generated SQL is smart about this — it 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 entirely.
Go API
The Go runtime provides a fluent BulkCheckBuilder:
results, err := checker.NewBulkCheck(ctx).
Add(user, "viewer", doc1).
Add(user, "editor", doc2).
AddWithID("custom-id", user, "owner", doc3).
Execute()
// Aggregate checks
if results.All() { /* all granted */ }
if results.Any() { /* at least one granted */ }
// Iterate results
for _, r := range results.Allowed() {
fmt.Printf("%s can %s %s\n", r.Subject(), r.Relation(), r.Object())
}
// Fail-fast: returns error if any check is denied
err = results.AllOrError() // returns BulkCheckDeniedErrorTypeScript API
Feature parity with Go:
const results = await checker
.newBulkCheck()
.add(user, "viewer", doc1)
.add(user, "editor", doc2)
.addWithId("custom-id", user, "owner", doc3)
.execute();
if (results.all()) {
/* all granted */
}
if (results.any()) {
/* at least one granted */
}
results.allOrError(); // throws BulkCheckDeniedError if any denied
Both clients include:
- Automatic deduplication — identical checks are collapsed into a single SQL row, results fanned out to all original indices
- Cache integration — reads from the Checker’s cache before SQL, populates it after
- Decision overrides — works with
DecisionAllow/DecisionDenyfor testing without a database - Batch size guard —
MaxBulkCheckSize(10,000) prevents accidental resource exhaustion
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. For example:
define viewer: writer and (member from group or owner from group)The union (member from group or owner from group) was ignored, causing the generated SQL to only check writer — a permission bypass. The parser now correctly extracts both simple relations and tuple-to-userset patterns from unions and distributes them into intersection groups using the distributive law.
Thanks to @jake-wickstrom for reporting this.
Multi-Driver Integration Tests
The Checker interfaces (Querier in Go, Queryable in TypeScript) are designed to work with any PostgreSQL driver, but previously only one driver per language was tested end-to-end. v0.7.0 adds table-driven tests that run the same assertion suite against each supported driver:
- Go:
pgx/stdlibandlib/pq, plus transaction isolation - TypeScript:
pg(node-postgres) andpostgres.jsviapostgresAdapter()
This ensures that driver-specific edge cases (array encoding, type coercion, connection handling) are caught before release.
Module Layout and go install Fix
go install github.com/pthm/melange/cmd/melange@latest was broken due to a replace directive in go.mod and stale cmd/melange/ tags cached in the Go module proxy. v0.7.0 fixes this by:
- Removing the
replacedirective (redundant sincego.workhandles local development) - Renaming
internal/tolib/socmd/melangecan be a proper sub-module that imports root packages across module boundaries - Restoring
cmd/melangeas a sub-module with retracted stale versions - Restructuring the release process into two phases to ensure the Go proxy indexes correctly
go install @latest now works reliably.
CI and Testing Improvements
- Race detection added to the OpenFGA test workflow
- TypeScript unit tests now run independently of the database in CI
- Linting and formatting extended to cover all four Go modules (
root,cmd/melange,melange,docs) - CI parallelism fix — test packages now run sequentially on the shared CI database to prevent deadlocks
Documentation Updates
- Checking Permissions guide rewritten to use the
@pthm/melangeTypeScript client throughout (previously showed raw SQL) - SQL API reference updated with full
check_permission_bulkdocumentation - Bulk check guide with Go, TypeScript, and SQL examples
What’s Changed Since v0.6.4
- Bulk permission check —
check_permission_bulkSQL function, GoBulkCheckBuilder, TypeScriptBulkCheckBuilder - Bulk dispatcher optimization — branches grouped by object type with
IFguards, simple relations inlined asEXISTS - 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 restructuring —
internal/→lib/,cmd/melangesub-module restored,replacedirective removed go install @latestfixed — 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
Migration Notes
From v0.6.4
No breaking changes. Upgrade and run migrations to install the new bulk dispatcher:
melange migrateThe new check_permission_bulk function is installed alongside existing functions. Your current check_permission, list_accessible_objects, and list_accessible_subjects calls continue to work unchanged.
Go module path: If you were using go install, the module path is unchanged but now resolves correctly via the Go proxy.
go install github.com/pthm/melange/cmd/melange@latestTry It Out
# Install / upgrade CLI
brew install pthm/melange/melange
# Apply migrations (installs check_permission_bulk)
melange migrate
# Go runtime
go get github.com/pthm/melange/melange@v0.7.0
# TypeScript runtime
npm install @pthm/melangeFeedback
We welcome feedback on the bulk check API and general experience. Please open an issue with questions, bug reports, or feature requests.
