> ## 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.

# Migration Guide

> Migrate Oso Cloud client libraries to the latest versions.

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:

1. **Update imports and package references**
2. **Convert fact representations to new format**
3. **Replace bulk operations with batch API**
4. **Migrate query calls to QueryBuilder API**
5. **Update wildcard usage to explicit patterns**
6. **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

> [tab] Facts API
>
> ```javascript theme={null}
> // 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]);
> ```

> [tab] Batch Operations
>
> ```javascript theme={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)));
> ```

> [tab] Query API
>
> ```javascript theme={null}
> // 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]]));
> ```

> [tab] TypeScript Types
>
> ```typescript theme={null}
> // 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

```javascript theme={null}
// 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

> [tab] Fact Representation
>
> ```python theme={null}
> # 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")
> ```

> [tab] Input Types
>
> ```python theme={null}
> # 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
> ```

> [tab] Management API
>
> ```python theme={null}
> # 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")))
> ```

> [tab] Batch Operations
>
> ```python theme={null}
> # 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))
> ```

> [tab] Query API
>
> ```python theme={null}
> # 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

> [tab] QueryBuilder Methods
>
> ```python theme={null}
> # 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
> ```

> [tab] Value Types
>
> ```python theme={null}
> 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

> [tab] Imports & Structs
>
> ```go theme={null}
> // 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
> ```

> [tab] Centralized Authorization Data API
>
> ```go theme={null}
> // 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{}
> ```

> [tab] Batch Operations
>
> ```go theme={null}
> // 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)
> })
> ```

> [tab] Query API
>
> ```go theme={null}
> // 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

> [tab] Helper Functions
>
> ```go theme={null}
> // 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)
> ```

> [tab] QueryBuilder Methods
>
> ```go theme={null}
> 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

> [tab] Packages & Imports
>
> ```java theme={null}
> // 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
> ```

> [tab] Centralized Authorization Data API
>
> ```java theme={null}
> // 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")));
> ```

> [tab] Batch Operations
>
> ```java theme={null}
> // 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);
> });
> ```

> [tab] Query API
>
> ```java theme={null}
> // 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

> [tab] QueryBuilder API
>
> ```java theme={null}
> 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))
> );
> ```

> [tab] Value Patterns
>
> ```java theme={null}
> // 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:

1. **Run your existing tests** to ensure functionality is preserved
2. **Test edge cases** with wildcards and batch operations
3. **Verify performance** with the new QueryBuilder API
4. **Check type safety** if using TypeScript or other typed languages
5. **Update documentation** to reflect new API patterns


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.