Documentation Index
Fetch the complete documentation index at: https://www.osohq.com/docs/llms.txt
Use this file to discover all available pages before exploring further.
This guide helps you migrate between major versions of Oso Cloud SDKs. Each major version introduces breaking changes that improve the developer experience, add new features, and enhance type safety.
General Migration Strategy
For all SDKs, follow this general approach when migrating:
- Update imports and package references
- Convert fact representations to new format
- Replace bulk operations with batch API
- Migrate query calls to QueryBuilder API
- Update wildcard usage to explicit patterns
- Test thoroughly with new API patterns
Node.js Migration (v1 → v2)
The v2 release represents a significant rethinking of the developer experience. The biggest new feature is generating TypeScript types from your Polar policy.
Key Changes:
- Minimum Node.js version: 14 → 16
- Centralized authorization data API: 6 methods → 4 methods
- New QueryBuilder API replaces Query API
- TypeScript type generation from policies
Breaking Changes
Facts API
Batch Operations
Query API
TypeScript Types
// OLD: tell() method
await oso.tell("has_role", user, "member", repo);
// NEW: insert() with array format
await oso.insert(["has_role", user, "member", repo]);
// OLD: delete() method
await oso.delete("has_role", user, "member", repo);
// NEW: delete() with array format and wildcard support
await oso.delete(["has_role", user, "member", repo]);
await oso.delete(["has_role", user, null, null]); // Remove all roles for user
// OLD: get() method
const roles = await oso.get("has_role", user, null, null);
// NEW: get() with array format
const roles = await oso.get(["has_role", user, null, null]);
// OLD: bulk() method
await oso.bulk(
[["has_role", user, null, null]], // delete
[["has_role", user, "member", repo]] // insert
);
// NEW: batch() with callback API
await oso.batch((tx) => {
tx.delete(["has_role", user, null, null]);
tx.insert(["has_role", user, "member", repo]);
});
// OLD: bulkTell() method
await oso.bulkTell(facts);
// NEW: batch() with forEach
await oso.batch((tx) => facts.forEach((f) => tx.insert(f)));
// OLD: bulkDelete() method
await oso.bulkDelete(facts);
// NEW: batch() with forEach
await oso.batch((tx) => facts.forEach((f) => tx.delete(f)));
// OLD: query() for boolean checks
const results = await oso.query("has_role", user, "reader", repo);
const ok = results.length > 0;
// NEW: buildQuery() with evaluate()
const ok = await oso.buildQuery(["has_role", user, "reader", repo]).evaluate();
// OLD: query() with type-constrained wildcards
await oso.query("allow", user, "read", { type: "Repository" });
// NEW: buildQuery() with typedVar()
const repos = typedVar("Repository");
await oso.buildQuery(["allow", user, "read", repos]).evaluate(repos);
// OLD: authorizeResources()
await oso.authorizeResources(user, "read", repos);
// NEW: buildQuery() with .in() method
const repoVar = typedVar("Repo");
await oso.buildQuery(["allow", user, "read", repoVar])
.in(repoVar, repoIds)
.evaluate(repoVar);
// OLD: bulkActions()
await oso.bulkActions(user, repos); // Returns in-order list
// NEW: buildQuery() with Map evaluation
const actionVar = typedVar("String");
const repoVar = typedVar("Repo");
await oso.buildQuery(["allow", user, actionVar, repoVar])
.in(repoVar, repoIds)
.evaluate(new Map([[repoVar, actionVar]]));
// OLD: Instance type
// Instance type
// NEW: IntoValue type
// IntoValue type
// Generate TypeScript types from policy
oso-cloud generate-types typescript path/to/policy.polar > path/to/polarTypes.d.ts
// Use generated types
import type { PolarTypes } from "./polarTypes";
const oso = new Oso<PolarTypes>(...);
New Features
// Chain additional conditions
const query = oso.buildQuery(["allow", user, "read", repo])
.and(["has_relation", repo, "folder", folder]);
// Constrain variables to specific value sets
const filteredQuery = oso.buildQuery(["allow", user, action, repo])
.in(repo, repositories);
// Add context facts to queries
const contextQuery = oso.buildQuery(["allow", user, "read", repo])
.withContextFacts([["has_role", user, "owner", repo]]);
// Various evaluation formats
const exists = await query.evaluate(); // Boolean
const actions = await query.evaluate(action); // Single variable
const pairs = await query.evaluate([action, repository]); // Tuple variables
const mapped = await query.evaluate(new Map([[repository, action]])); // Map
Python Migration (v1 → v2)
The v2 release represents a significant rethinking of the developer experience with changes to fact representation and input types.
Key Changes:
- Facts now represented as tuples instead of dicts
- Arguments as Value objects instead of dicts
- Input types changed from Value/Fact to IntoValue/IntoFact
- Management API condensed from 6 methods to 4
Breaking Changes
Fact Representation
Input Types
Management API
Batch Operations
Query API
# OLD: Dict-based facts
context_fact = {"name": "has_role", "args": [user, "member", repo]}
# NEW: Tuple-based facts
context_fact = ("has_role", user, "member", repo)
# OLD: Dict-based arguments
alice = {"type": "User", "id": "alice"}
# NEW: Value objects
from oso_cloud import Value
alice = Value("User", "alice")
# OLD: Type declarations
# oso_cloud.Value and oso_cloud.Fact for type declarations
# NEW: Type declarations
# oso_cloud.IntoValue and oso_cloud.IntoFact for type declarations
# Use IntoValue and IntoFact for input parameters to accommodate primitive value conversion
# OLD: tell() method
oso.tell("has_role", user, "member", repo)
# NEW: insert() with tuple format
oso.insert(("has_role", user, "member", repo))
# OLD: delete() method
oso.delete("has_role", user, "member", repo)
# NEW: delete() with tuple format and wildcard support
oso.delete(("has_role", user, "member", repo))
oso.delete(("has_role", user, None, None)) # Remove all roles
# OLD: get() method
oso.get("has_role", user, None, None)
# NEW: get() with tuple format and explicit wildcards
oso.get(("has_role", user, None, None))
# Use ValueOfType("Repo") instead of {"type": "Repo"}
# Use None instead of {}
from oso_cloud import ValueOfType
oso.get(("has_role", user, None, ValueOfType("Repository")))
# OLD: bulk() method
oso.bulk(
[{"name": "has_role", "args": [user, None, None]}], # delete
[{"name": "has_role", "args": [user, "member", repo]}] # insert
)
# NEW: batch() context manager
with oso.batch() as tx:
tx.delete(("has_role", user, None, None))
tx.insert(("has_role", user, "member", repo))
# OLD: bulk_tell() method
oso.bulk_tell([{"name": "has_role", "args": [user, "member", repo]}])
# NEW: batch() context manager with insert
with oso.batch() as tx:
tx.insert(("has_role", user, "member", repo))
# OLD: bulk_delete() method
oso.bulk_delete([{"name": "has_role", "args": [user, "member", repo]}])
# NEW: batch() context manager with delete
with oso.batch() as tx:
tx.delete(("has_role", user, "member", repo))
# OLD: query() for boolean checks
results = oso.query({"name": "has_role", "args": [user, "reader", repo]})
ok = bool(results)
# NEW: build_query() with evaluate()
ok = oso.build_query(("has_role", user, "reader", repo)).evaluate()
# OLD: query() with type-constrained wildcards
oso.query({"name": "allow", "args": [user, "read", {"type": "Repository"}]})
# NEW: build_query() with typed_var()
from oso_cloud import typed_var
repos = typed_var("Repository")
oso.build_query(("allow", user, "read", repos)).evaluate(repos)
# OLD: authorize_resources()
oso.authorize_resources(user, "read", repos)
# NEW: build_query() with in_() method
repo_var = typed_var("Repo")
oso.build_query(("allow", user, "read", repo_var)).in_(repo_var, repo_ids).evaluate(repo_var)
# Wildcard results changed from None to "*" string
New Features
QueryBuilder Methods
Value Types
# Chain additional conditions
query = oso.build_query(("allow", user, "read", repo)) \
.and_(("has_relation", repo, "folder", folder))
# Constrain variables to specific value sets
filtered_query = oso.build_query(("allow", user, action, repo)) \
.in_(repo, repositories)
# Add context facts to queries
context_query = oso.build_query(("allow", user, "read", repo)) \
.with_context_facts([("has_role", user, "owner", repo)])
# Various evaluation formats
exists = query.evaluate() # Boolean
actions = query.evaluate(action) # Single variable
pairs = query.evaluate((action, repository)) # Tuple variables
mapped = query.evaluate({repository: action}) # Dictionary
from oso_cloud import ValueOfType, typed_var
# Use for type-constrained wildcards in delete and get operations
oso.delete(("has_role", user, None, ValueOfType("Repository")))
# Use for query variables in QueryBuilder
repos = typed_var("Repository")
oso.build_query(("allow", user, "read", repos)).evaluate(repos)
Go Migration (v1 → v2)
The v2 release represents a significant rethinking of the developer experience with structural changes and new APIs.
Key Changes:
- Import path changed to include /v2
- Instance struct replaced with Value struct
- Separate FactPattern type for patterns
- Helper functions for ergonomic construction
- API condensed from 6 methods to 4
Breaking Changes
// OLD: Import path
import (oso "github.com/osohq/go-oso-cloud")
// NEW: Import path with /v2
import (oso "github.com/osohq/go-oso-cloud/v2")
// OLD: Fact.Name field
// Fact.Name field
// NEW: Fact.Predicate field
// Fact.Predicate field
// OLD: Instance struct
oso.Instance{Type: "User", ID: "alice"}
// NEW: Value struct (cannot have zero-valued ID or Type)
oso.NewValue("User", "alice")
// OLD: Fact used for both concrete facts and patterns
// Fact used for both concrete facts and patterns
// NEW: Separate FactPattern for patterns
oso.NewFact("has_role", alice, oso.String("reader"), repo) // Concrete fact
oso.NewFactPattern("has_role", alice, nil, nil) // Pattern
// OLD: Tell() method
osoClient.Tell("has_role", oso.Instance{Type: "User", ID: "bob"}, oso.String("owner"), oso.Instance{Type: "Organization", ID: "acme"})
// NEW: Insert() with NewFact
osoClient.Insert(oso.NewFact("has_role", oso.NewValue("User", "bob"), oso.String("owner"), oso.NewValue("Organization", "acme")))
// OLD: Delete() method
osoClient.Delete("has_role", oso.Instance{Type: "User", ID: "bob"}, oso.String("maintainer"), oso.Instance{Type: "Repository", ID: "anvil"})
// NEW: Delete() with NewFactPattern
osoClient.Delete(oso.NewFactPattern("has_role", oso.NewValue("User", "bob"), oso.String("maintainer"), oso.NewValue("Repository", "anvil")))
// Wildcard deletion
osoClient.Delete(oso.NewFactPattern("has_role", user, nil, nil)) // Remove all roles
// OLD: Get() method
oso.Get("has_role", oso.Instance{Type: "User", ID: "bob"}, oso.String("admin"), oso.Instance{Type: "Repository", ID: "anvils"})
// NEW: Get() with NewFactPattern
oso.Get(oso.NewFactPattern("has_role", oso.NewValue("User", "bob"), oso.String("admin"), oso.NewValue("Repository", "anvils")))
// Use oso.NewValueOfType("User") instead of oso.Instance{Type: "User"}
// Use nil instead of oso.Instance{}
// OLD: Bulk() method
osoClient.Bulk([]oso.Fact{deletePattern}, []oso.Fact{insertFact})
// NEW: Batch() with callback
osoClient.Batch(func(tx oso.BatchTransaction) {
tx.Delete(deletePattern)
tx.Insert(insertFact)
})
// OLD: BulkTell() method
osoClient.BulkTell([]oso.Fact{fact1, fact2})
// NEW: Batch() with multiple inserts
osoClient.Batch(func(tx oso.BatchTransaction) {
tx.Insert(fact1)
tx.Insert(fact2)
})
// OLD: BulkDelete() method
osoClient.BulkDelete([]oso.Fact{fact1, fact2})
// NEW: Batch() with multiple deletes
osoClient.Batch(func(tx oso.BatchTransaction) {
tx.Delete(pattern1)
tx.Delete(pattern2)
})
// OLD: Query() for boolean checks
results, err := osoClient.Query("has_role", &oso.Instance{Type: "User", ID: "bob"}, &oso.String("reader"), &oso.Instance{Type: "Repository", ID: "acme"})
ok := err != nil && len(results) > 0
// NEW: BuildQuery() with EvaluateExists()
ok, err := osoClient.BuildQuery(oso.NewQueryFact("has_role", oso.NewValue("User", "bob"), oso.String("reader"), oso.NewValue("Repository", "acme"))).EvaluateExists()
// OLD: Query() with type-constrained wildcards
results, err := osoClient.Query("allow", &oso.Instance{Type: "User", ID: "bob"}, &oso.String("read"), &oso.Instance{Type: "Repository"})
// NEW: BuildQuery() with TypedVar
repos := oso.TypedVar("Repository")
repoIds, err := osoClient.BuildQuery(oso.NewQueryFact("allow", oso.NewValue("User", "bob"), oso.String("read"), repos)).EvaluateValues(repos)
// OLD: AuthorizeResources()
results, e := osoClient.AuthorizeResources(user, "read", []oso.Instance{repo1, repo2})
// NEW: BuildQuery() with In() method
repoVar := oso.TypedVar("Repository")
allowedRepoIds, e := osoClient.BuildQuery(oso.NewQueryFact("allow", user, oso.String("read"), repoVar)).In(repos, repoIds).EvaluateValues(repoVar)
// Wildcard results changed to "*" string
New Features
Helper Functions
QueryBuilder Methods
// Ergonomic Value construction
user := oso.NewValue("User", "alice")
// Ergonomic Fact construction
fact := oso.NewFact("has_role", user, oso.String("admin"), repo)
// Ergonomic FactPattern construction
pattern := oso.NewFactPattern("has_role", user, nil, nil)
// Ergonomic QueryFact construction
queryFact := oso.NewQueryFact("allow", user, oso.String("read"), repo)
query := osoClient.BuildQuery(oso.NewQueryFact("allow", user, oso.String("read"), repo))
// Chain additional conditions
constrainedQuery := query.And(oso.NewQueryFact("has_relation", repo, oso.String("folder"), folder))
// Constrain variables to specific value sets
filteredQuery := query.In(repo, repositories)
// Add context facts to queries
contextQuery := query.WithContextFacts([]oso.Fact{oso.NewFact("has_role", user, oso.String("owner"), repo)})
// Various evaluation methods
allowed, err := query.EvaluateExists() // Boolean
actions, err := query.EvaluateValues(action) // Values for variable
pairs, err := query.EvaluateCombinations([]oso.Variable{action, repo}) // Variable combinations
err = query.Evaluate(&repoActions, map[oso.Variable]oso.Variable{repo: action}) // Custom mapping
Java Migration (v0 → v1)
The v1 release represents a significant rethinking of the developer experience with the new Query Builder API and consolidated packages.
Key Changes:
- Public API consolidated under com.osohq.oso_cloud package
- Explicit wildcard behavior with ValuePattern types
- New QueryBuilder API with fluent interface
- Enhanced type safety with FactPattern
Breaking Changes
// OLD: Imports from api package
import com.osohq.oso_cloud.api.Value;
import com.osohq.oso_cloud.api.Fact;
// NEW: Imports from main package
import com.osohq.oso_cloud.Value;
import com.osohq.oso_cloud.Fact;
// OLD: Constructors accepted null
// Value and Fact constructors accepted null for type/id fields
// NEW: Explicit wildcards with ValuePattern
// Use ValuePattern.ANY and ValuePattern.ValueOfType for wildcards
// OLD: tell() method
oso.tell("has_role", user, new Value("member"), repo);
// NEW: insert() with Fact object
oso.insert(new Fact("has_role", user, new Value("member"), repo));
// OLD: delete() method
oso.delete("has_role", user, new Value("member"), repo);
// NEW: delete() with Fact object or FactPattern
oso.delete(new Fact("has_role", user, new Value("member"), repo));
// Pattern deletion
oso.delete(new FactPattern("has_role", user, ValuePattern.ANY, ValuePattern.ANY));
// OLD: get() with null wildcards
oso.get("has_role", userBob, null, new Value("Folder", null));
// NEW: get() with FactPattern and explicit wildcards
oso.get(new FactPattern("has_role", userBob, ValuePattern.ANY, new ValuePattern.ValueOfType("Folder")));
// OLD: bulk() method
oso.bulk(deleteList, insertList)
// NEW: batch() with Consumer<BatchTransaction>
oso.batch(tx -> {
factsToInsert.forEach(tx::insert);
factsToDelete.forEach(tx::delete);
tx.delete(patternToDelete);
});
// OLD: bulkTell() method
oso.bulkTell(facts)
// NEW: batch() with forEach insert
oso.batch(tx -> {
facts.forEach(tx::insert);
});
// OLD: bulkDelete() method
oso.bulkDelete(facts)
// NEW: batch() with forEach delete
oso.batch(tx -> {
facts.forEach(tx::delete);
});
// OLD: query() for boolean checks
Fact[] results = oso.query("has_role", user, role, repo);
boolean ok = results.length > 0;
// NEW: buildQuery() with EvaluateArgs.exists()
boolean ok = oso.buildQuery("has_role", user, role, repo).evaluate(EvaluateArgs.exists());
// OLD: query() with type-constrained wildcards
Fact[] results = oso.query("allow", user, action, new Value("Repository", null));
// NEW: buildQuery() with TypedVar
TypedVar repos = new TypedVar("Repository");
List<String> repoIds = oso.buildQuery("allow", user, action, repos).evaluate(EvaluateArgs.values(repos));
// OLD: authorizeResources()
Value[] authorizedRepos = oso.authorizeResources(user, action, repos);
// NEW: buildQuery() with in() method
TypedVar repoVar = new TypedVar("Repo");
List<String> authorizedRepoIds = oso.buildQuery("allow", user, action, repoVar)
.in(repoVar, repoIds)
.evaluate(EvaluateArgs.values(repoVar));
// Wildcard results changed from null to "*" string
New Features
QueryBuilder API
Value Patterns
QueryBuilder query = oso.buildQuery("allow", user, action, repo);
// Chain additional conditions
QueryBuilder constrainedQuery = query.and("has_relation", repo, new Value("folder"), folder);
// Constrain variables to specific value sets
QueryBuilder filteredQuery = query.in(repoVar, repoIds);
// Add context facts to queries
QueryBuilder contextQuery = query.withContextFacts(contextFacts);
// Various evaluation formats
Boolean allowed = query.evaluate(EvaluateArgs.exists()); // Boolean
List<String> actions = query.evaluate(EvaluateArgs.values(actionVar)); // Values
Map<String, List<String>> mapped = query.evaluate( // Map
EvaluateArgs.map(repositoryVar, EvaluateArgs.values(actionVar))
);
// Wildcard matching any value
oso.get(new FactPattern("has_role", user, ValuePattern.ANY, ValuePattern.ANY));
// Type-constrained wildcard
oso.get(new FactPattern("has_role", user, ValuePattern.ANY, new ValuePattern.ValueOfType("Repository")));
// Separate type for pattern matching
FactPattern pattern = new FactPattern("has_role", user, ValuePattern.ANY, repo);
Migration Testing
After completing your migration:
- Run your existing tests to ensure functionality is preserved
- Test edge cases with wildcards and batch operations
- Verify performance with the new QueryBuilder API
- Check type safety if using TypeScript or other typed languages
- Update documentation to reflect new API patterns