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

<Tabs>
  <Tab title="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>

  <Tab title="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>

  <Tab title="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>

  <Tab title="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>(...);
    ```
  </Tab>
</Tabs>

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

<Tabs>
  <Tab title="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>

  <Tab title="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>

  <Tab title="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>

  <Tab title="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>

  <Tab title="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
    ```
  </Tab>
</Tabs>

### New Features

<Tabs>
  <Tab title="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>

  <Tab title="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)
    ```
  </Tab>
</Tabs>

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

<Tabs>
  <Tab title="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>

  <Tab title="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>

  <Tab title="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>

  <Tab title="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
    ```
  </Tab>
</Tabs>

### New Features

<Tabs>
  <Tab title="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>

  <Tab title="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
    ```
  </Tab>
</Tabs>

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

<Tabs>
  <Tab title="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>

  <Tab title="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>

  <Tab title="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>

  <Tab title="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
    ```
  </Tab>
</Tabs>

### New Features

<Tabs>
  <Tab title="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>

  <Tab title="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);
    ```
  </Tab>
</Tabs>

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