From e5c00bb32c2a34324a3ea90be4e5e5c2ab7db1dc Mon Sep 17 00:00:00 2001 From: Tyler Nix Date: Wed, 10 Sep 2025 12:01:52 -0600 Subject: [PATCH 01/20] Add Duplicate Writes documentation - Create new duplicate-writes.mdx file with comprehensive documentation - Document on_duplicate and on_missing parameters for Write API - Include practical examples showing default vs permissive behavior - Add API parameter reference with CURL examples - Update WriteRequestViewer component to support writeOptions and deleteOptions - Implement pretty-printed JSON formatting in CURL examples - Update transactional-writes.mdx to reference new duplicate writes feature - Add duplicate-writes to sidebar navigation - Document use cases for data synchronization and idempotent operations - Include important concepts about atomicity and race conditions - Add detailed explanations for tuple conditions and conflict scenarios --- docs/content/interacting/duplicate-writes.mdx | 260 ++++++++++++++++++ .../interacting/transactional-writes.mdx | 10 + docs/sidebars.js | 5 + 3 files changed, 275 insertions(+) create mode 100644 docs/content/interacting/duplicate-writes.mdx diff --git a/docs/content/interacting/duplicate-writes.mdx b/docs/content/interacting/duplicate-writes.mdx new file mode 100644 index 0000000000..bb3bd92fd5 --- /dev/null +++ b/docs/content/interacting/duplicate-writes.mdx @@ -0,0 +1,260 @@ +--- +sidebar_position: 3 +slug: /interacting/duplicate-writes +description: Making write operations resilient with duplicate writes and missing deletes handling +--- + +import { + AuthzModelSnippetViewer, + CardBox, + DocumentationNotice, + ProductConcept, + ProductName, + ProductNameFormat, + RelatedSection, + RelationshipTuplesViewer, + WriteRequestViewer, + SupportedLanguage, +} from '@components/Docs'; + +# Duplicate Writes + + + +Making Write Operations Resilient in by handling duplicate writes and missing deletes gracefully. + + + +Use duplicate writes handling in high-volume or distributed systems where you need to ensure data synchronization without causing transaction failures on duplicate operations or missing deletes. + + + +## The Core Problem and the Solution + +The Write API is the primary method for adding and removing in your store. In high-volume or distributed systems, ensuring that data is synchronized correctly without causing errors on duplicate operations is critical. + +Previously, a bulk Write request was strictly "all-or-nothing": + +- If you tried to write a tuple that already existed, the entire request would fail. +- If you tried to delete a tuple that didn't exist, the entire request would fail. + +This required developers to build complex state-checking logic into their applications. + +To solve this, the Write API now supports optional parameters to handle duplicate writes and missing deletes gracefully. This means you can safely retry a Write request without causing transaction failures for these specific cases, greatly simplifying data synchronization logic. + +The new `on_duplicate` and `on_missing` parameters change this behavior, allowing you to instruct the API to simply ignore these cases and process the rest of the request successfully. + +## A Practical Example + +Here's a look at how this feature changes the API's behavior when you attempt to write a tuple that already exists. + +### Before: The Default Behavior + +Without `on_duplicate: "ignore"`, the API returns a 400 Bad Request because the tuple already exists. + +```bash +curl -X POST 'http://localhost:8080/stores/{store_id}/write' \ + -H 'content-type: application/json' \ + --data '{ + "writes": { + "tuple_keys": [ + { + "user": "user:anne", + "relation": "owner", + "object": "document:test" + } + ] + }, + "authorization_model_id": "..." + }' +``` + +**Response:** +```http +HTTP/1.1 400 Bad Request +Content-Type: application/json + +{ + "code": "write_failed_due_to_invalid_input", + "message": "cannot write a tuple which already exists: user: 'user:anne', relation: 'owner', object: 'document:test'..." +} +``` + +### After: Using on_duplicate: "ignore" + +By setting the parameter, the duplicate is ignored and the API returns a 200 OK. + +```bash +curl -X POST 'http://localhost:8080/stores/{store_id}/write' \ + -H 'content-type: application/json' \ + --data '{ + "writes": { + "tuple_keys": [ + { + "user": "user:anne", + "relation": "owner", + "object": "document:test" + } + ], + "on_duplicate": "ignore" + }, + "authorization_model_id": "..." + }' +``` + +**Response:** +```http +HTTP/1.1 200 OK +Content-Type: application/json + +{} +``` + +## API Reference + +The new parameters are added within the `writes` and `deletes` objects in the body of a Write request. + +### New Parameter for writes: on_duplicate + +Enables resilient behavior when writing duplicate tuples. + +- **"error" (Default)**: The request will fail if any tuple in the writes array already exists. This maintains backward compatibility. +- **"ignore"**: The API will silently ignore any attempt to write a tuple that already exists and will proceed to write the new ones. The request will succeed. + + + +### New Parameter for deletes: on_missing + +Enables resilient behavior when deleting missing tuples. + +- **"error" (Default)**: The request will fail if any tuple in the deletes array does not exist. +- **"ignore"**: The API will silently ignore any attempt to delete a tuple that does not exist and will proceed to delete the ones that do. The request will succeed. + + + +## Flexibility and Granular Control + +:::tip +These flags can be used independently. You can perform a permissive write (`on_duplicate: "ignore"`) and a strict delete (`on_missing: "error"`) within the same atomic transaction, giving you granular control. +::: + +**Example Request with Mixed Behavior:** + +```bash +curl -X POST 'http://localhost:8080/stores/{store_id}/write' \ + -H 'content-type: application/json' \ + --data '{ + "writes": { + "tuple_keys": [ + { + "user": "user:anne", + "relation": "reader", + "object": "document:2021-budget" + } + ], + "on_duplicate": "ignore" + }, + "deletes": { + "tuple_keys": [ + { + "user": "user:anne", + "relation": "writer", + "object": "document:2021-budget" + } + ], + "on_missing": "error" + }, + "authorization_model_id": "01G50QVV17PECNVAHX1GG4Y5NC" + }' +``` + +In this example: +- If the read permission already exists, it will be ignored and the request continues +- If the write permission doesn't exist for deletion, the entire request will fail + +## Use Cases + +### Data Synchronization + +When synchronizing data between systems, you can use `on_duplicate: "ignore"` to safely replay events without worrying about duplicate writes. + +### Idempotent Operations + +Make your write operations idempotent by using `on_duplicate: "ignore"` and `on_missing: "ignore"` to ensure the same operation can be safely executed multiple times. + +### Resilient Batch Operations + +In batch operations where some tuples might already exist or might have been deleted, these parameters allow you to process the entire batch without failing on individual conflicts. + +## Related Sections + + diff --git a/docs/content/interacting/transactional-writes.mdx b/docs/content/interacting/transactional-writes.mdx index 7048583294..f535e3992e 100644 --- a/docs/content/interacting/transactional-writes.mdx +++ b/docs/content/interacting/transactional-writes.mdx @@ -167,6 +167,10 @@ curl -X POST 'http://localhost:8080/stores/{store_id}/write' \ +:::tip +For handling cases where you need to write tuples that might already exist or delete tuples that might not exist, check out [Duplicate Writes](./duplicate-writes.mdx) which provides resilient write operations. +::: + The Write API allows you to send up to 100 unique tuples in the request. (This limit applies to the sum of both writes and deletes in that request). This means we can submit one API call that converts the `tweet` from public to visible to only the `user`'s followers. service attempts to perfo Date: Wed, 10 Sep 2025 12:03:36 -0600 Subject: [PATCH 02/20] Add Duplicate Writes documentation - Create new duplicate-writes.mdx file with comprehensive documentation - Document on_duplicate and on_missing parameters for Write API - Include practical examples showing default vs permissive behavior - Add API parameter reference with CURL examples - Update WriteRequestViewer component to support writeOptions and deleteOptions - Implement pretty-printed JSON formatting in CURL examples - Update transactional-writes.mdx to reference new duplicate writes feature - Add duplicate-writes to sidebar navigation - Document use cases for data synchronization and idempotent operations - Include important concepts about atomicity and race conditions - Add detailed explanations for tuple conditions and conflict scenarios --- docs/content/interacting/duplicate-writes.mdx | 254 ++++++++---------- .../Docs/SnippetViewer/WriteRequestViewer.tsx | 51 +++- 2 files changed, 157 insertions(+), 148 deletions(-) diff --git a/docs/content/interacting/duplicate-writes.mdx b/docs/content/interacting/duplicate-writes.mdx index bb3bd92fd5..bc0b411bcf 100644 --- a/docs/content/interacting/duplicate-writes.mdx +++ b/docs/content/interacting/duplicate-writes.mdx @@ -1,7 +1,7 @@ --- sidebar_position: 3 slug: /interacting/duplicate-writes -description: Making write operations resilient with duplicate writes and missing deletes handling +description: Handle duplicate writes and missing deletes in write operations --- import { @@ -15,64 +15,44 @@ import { RelationshipTuplesViewer, WriteRequestViewer, SupportedLanguage, -} from '@components/Docs'; +} from "@components/Docs"; + # Duplicate Writes -Making Write Operations Resilient in by handling duplicate writes and missing deletes gracefully. +Handle duplicate writes and missing deletes gracefully using the Write API's optional `on_duplicate` and `on_missing` parameters. -Use duplicate writes handling in high-volume or distributed systems where you need to ensure data synchronization without causing transaction failures on duplicate operations or missing deletes. +Common scenarios include high-volume data imports into or migrations between distributed systems where you need to ensure data synchronization without causing transaction failures on duplicate writes or missing deletes. -## The Core Problem and the Solution - -The Write API is the primary method for adding and removing in your store. In high-volume or distributed systems, ensuring that data is synchronized correctly without causing errors on duplicate operations is critical. - -Previously, a bulk Write request was strictly "all-or-nothing": +## Understanding the Problem -- If you tried to write a tuple that already existed, the entire request would fail. -- If you tried to delete a tuple that didn't exist, the entire request would fail. +The Write API is the primary method for adding and removing in your store. -This required developers to build complex state-checking logic into their applications. +By default, the Write API operates with strict validation ("all-or-nothing"): -To solve this, the Write API now supports optional parameters to handle duplicate writes and missing deletes gracefully. This means you can safely retry a Write request without causing transaction failures for these specific cases, greatly simplifying data synchronization logic. +- Writing a tuple that already exists causes the entire request to fail (even if only 1 tuple out of 40 possible tuples is a duplicate) +- Deleting a tuple that doesn't exist causes the entire request to fail -The new `on_duplicate` and `on_missing` parameters change this behavior, allowing you to instruct the API to simply ignore these cases and process the rest of the request successfully. +This strict behavior requires applications to check the current state of the tuple in before making changes, implement complex retry logic, or simply ignore the errors altogether. -## A Practical Example +## How to Handle Duplicate Operations -Here's a look at how this feature changes the API's behavior when you attempt to write a tuple that already exists. +To improve import and migration scenarios, the Write API provides optional parameters to control how duplicate writes and missing deletes are handled. -### Before: The Default Behavior +The `on_duplicate` and `on_missing` parameters change this behavior, allowing you to instruct the API to ignore these cases and process the rest of the request successfully. + +### Response: Default Behavior -Without `on_duplicate: "ignore"`, the API returns a 400 Bad Request because the tuple already exists. - -```bash -curl -X POST 'http://localhost:8080/stores/{store_id}/write' \ - -H 'content-type: application/json' \ - --data '{ - "writes": { - "tuple_keys": [ - { - "user": "user:anne", - "relation": "owner", - "object": "document:test" - } - ] - }, - "authorization_model_id": "..." - }' -``` +Without the `on_duplicate` parameter, attempting to write an existing tuple returns a `400 Bad Request`. -**Response:** ```http HTTP/1.1 400 Bad Request -Content-Type: application/json { "code": "write_failed_due_to_invalid_input", @@ -80,165 +60,172 @@ Content-Type: application/json } ``` -### After: Using on_duplicate: "ignore" - -By setting the parameter, the duplicate is ignored and the API returns a 200 OK. - -```bash -curl -X POST 'http://localhost:8080/stores/{store_id}/write' \ - -H 'content-type: application/json' \ - --data '{ - "writes": { - "tuple_keys": [ - { - "user": "user:anne", - "relation": "owner", - "object": "document:test" - } - ], - "on_duplicate": "ignore" - }, - "authorization_model_id": "..." - }' -``` +### Response: Ignoring Duplicates + +Setting `on_duplicate: "ignore"` allows the duplicate to be ignored and the API returns a `200 OK`. -**Response:** ```http HTTP/1.1 200 OK -Content-Type: application/json {} ``` -## API Reference +## Optional API Parameters -The new parameters are added within the `writes` and `deletes` objects in the body of a Write request. +The parameters are added within the `writes` and `deletes` objects in the body of a Write request. -### New Parameter for writes: on_duplicate +### Writes -Enables resilient behavior when writing duplicate tuples. +The `on_duplicate` parameter controls the behavior when writing tuples. -- **"error" (Default)**: The request will fail if any tuple in the writes array already exists. This maintains backward compatibility. -- **"ignore"**: The API will silently ignore any attempt to write a tuple that already exists and will proceed to write the new ones. The request will succeed. +- **"error" (Default)**: The request fails if any tuple in the writes array already exists. This maintains backward compatibility. +- **"ignore"**: The API ignores any attempt to write a tuple that already exists and proceeds to write the new ones in the same transaction. The request succeeds. -### New Parameter for deletes: on_missing +:::caution +At the moment, this feature is only available on the API. Supported SDKs will follow shortly after. +::: -Enables resilient behavior when deleting missing tuples. +### Deletes -- **"error" (Default)**: The request will fail if any tuple in the deletes array does not exist. -- **"ignore"**: The API will silently ignore any attempt to delete a tuple that does not exist and will proceed to delete the ones that do. The request will succeed. +The `on_missing` parameter controls behavior when deleting tuples. + +- **"error" (Default)**: The request fails if any tuple in the deletes array does not exist. +- **"ignore"**: The API ignores any attempt to delete a tuple that does not exist and proceeds to delete the ones that do exist. The request succeeds. -## Flexibility and Granular Control +## Using Both Parameters Together + +The decision to have separate `on_duplicate` and `on_missing` parameters is intentional. This design gives you granular, independent control over the behavior of writes and deletes within a single atomic transaction. You can mix and match strict and permissive behaviors to suit your exact needs. + +For example, you might perform a strict delete (`on_missing: "error"`) to confirm that a specific permission has been successfully removed before making a permissive write (`on_duplicate: "ignore"`) that guarantees the new permission exists. :::tip -These flags can be used independently. You can perform a permissive write (`on_duplicate: "ignore"`) and a strict delete (`on_missing: "error"`) within the same atomic transaction, giving you granular control. +This flexibility is particularly useful when you are trying to synchronize state between your application's database and . You might need a strict operation to fail so you can roll back corresponding changes in your own database, ensuring overall system consistency. ::: -**Example Request with Mixed Behavior:** - -```bash -curl -X POST 'http://localhost:8080/stores/{store_id}/write' \ - -H 'content-type: application/json' \ - --data '{ - "writes": { - "tuple_keys": [ - { - "user": "user:anne", - "relation": "reader", - "object": "document:2021-budget" - } - ], - "on_duplicate": "ignore" + In this example: -- If the read permission already exists, it will be ignored and the request continues -- If the write permission doesn't exist for deletion, the entire request will fail +- If the permission to delete doesn't exist, the entire request will fail as intended. +- If the tuple exists and is successfully deleted, the write permission will succeed whether the tuple already exists or is written in that moment. + +## Important Concepts + +### The Write Request Remains Atomic + +All tuples in a request will still be processed as a single atomic unit. The `ignore` option only changes the success criteria for individual operations in the request. + +### "Best effort" ignore -## Use Cases +For writes: An `on_duplicate: 'ignore'` operation uses a "best effort" approach. We will attempt *once* to ignore duplicates and write non-duplicates, but if there is ever a conflict writing to the database (i.e. write a tuple we don’t think exists but it suddenly exists, probably due to a parallel request), we will abort the race condition immediately, and just return a `409 Conflict` error. These errors are rare, but can happen. +For deletes: An `on_missing: 'ignore'` operation is immune to race conditions due to database-level locks. It is not possible for another request to interfere since a delete operation will always succeed if the tuple exists or not. -### Data Synchronization +### "Ignore" is Not an "Upsert" -When synchronizing data between systems, you can use `on_duplicate: "ignore"` to safely replay events without worrying about duplicate writes. +It is critical to understand that `on_duplicate: "ignore"` will not update an existing tuple, only ignore an identical tuple. This is why we do not call the operation an "idempotent" operation. -### Idempotent Operations +The behavior of `on_duplicate: "ignore"` is more nuanced for tuples with conditions. +- **Identical Tuples**: If a tuple in the request is 100% identical to an existing tuple (same user, relation, object, condition name, and condition context), it will be safely ignored. +- **Conflicting Tuples**: If a tuple key (user, relation, object) matches an existing tuple, but the condition is different, this is a conflict. The write attempt will be rejected, and the entire transaction will fail with a `409 Conflict` error. **The correct pattern to safely update a tuple's condition requires explicitly deleting the old tuple and writing the new one within the same atomic Write request.** -Make your write operations idempotent by using `on_duplicate: "ignore"` and `on_missing: "ignore"` to ensure the same operation can be safely executed multiple times. -### Resilient Batch Operations -In batch operations where some tuples might already exist or might have been deleted, these parameters allow you to process the entire batch without failing on individual conflicts. +```http +HTTP/1.1 409 Conflict +{ + "code": "Aborted", + "message": "transactional write failed due to conflict: attempted to write a tuple which already exists with a different condition: user: 'user:anne', relation: 'writer', object: 'document:2025-budget'" +} +``` + +:::tip +The condition is not returned in the response, but you can call `/read` for this tuple to view its condition. +::: + +:::warning +The deletes operation in the Write API does not accept a condition. Attempting to include one will result in an invalid request. +::: ## Related Sections diff --git a/src/components/Docs/SnippetViewer/WriteRequestViewer.tsx b/src/components/Docs/SnippetViewer/WriteRequestViewer.tsx index 30176cfd1d..ff05ee345c 100644 --- a/src/components/Docs/SnippetViewer/WriteRequestViewer.tsx +++ b/src/components/Docs/SnippetViewer/WriteRequestViewer.tsx @@ -25,6 +25,12 @@ interface WriteRequestViewerOpts { isDelete?: boolean; skipSetup?: boolean; allowedLanguages?: SupportedLanguage[]; + writeOptions?: { + on_duplicate?: 'error' | 'ignore'; + }; + deleteOptions?: { + on_missing?: 'error' | 'ignore'; + }; } function writeRequestViewer(lang: SupportedLanguage, opts: WriteRequestViewerOpts) { @@ -59,21 +65,42 @@ ${ }`; } case SupportedLanguage.CURL: { - const writeTuples = opts.relationshipTuples - ? opts.relationshipTuples.map((tuple) => `${JSON.stringify(tuple)}`).join(',') - : ''; - const deleteTuples = opts.deleteRelationshipTuples - ? opts.deleteRelationshipTuples.map((tuple) => `${JSON.stringify(tuple)}`).join(',') - : ''; - const writes = `"writes": { "tuple_keys" : [${writeTuples}] }`; - const deletes = `"deletes": { "tuple_keys" : [${deleteTuples}] }`; - const separator = `${opts.deleteRelationshipTuples && opts.relationshipTuples ? ',' : ''}`; + // Build the JSON object for pretty printing + const requestBody: any = {}; + + if (opts.relationshipTuples?.length) { + requestBody.writes = { + tuple_keys: opts.relationshipTuples.map((tuple) => { + const { _description, ...cleanTuple } = tuple; + return cleanTuple; + }), + }; + if (opts.writeOptions?.on_duplicate) { + requestBody.writes.on_duplicate = opts.writeOptions.on_duplicate; + } + } + + if (opts.deleteRelationshipTuples?.length) { + requestBody.deletes = { + tuple_keys: opts.deleteRelationshipTuples.map((tuple) => { + const { _description, ...cleanTuple } = tuple; + return cleanTuple; + }), + }; + if (opts.deleteOptions?.on_missing) { + requestBody.deletes.on_missing = opts.deleteOptions.on_missing; + } + } + + // Add authorization_model_id at the end + requestBody.authorization_model_id = modelId; + + const prettyJson = JSON.stringify(requestBody, null, 2); + return `curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/write \\ -H "Authorization: Bearer $FGA_API_TOKEN" \\ # Not needed if service does not require authorization -H "content-type: application/json" \\ - -d '{${opts.relationshipTuples ? writes : ''}${separator}${ - opts.deleteRelationshipTuples ? deletes : '' - }, "authorization_model_id": "${modelId}"}'`; + -d '${prettyJson}'`; } case SupportedLanguage.JS_SDK: { From 5d6b97ca3079dd79cdbc9461f85feed4ddd24176 Mon Sep 17 00:00:00 2001 From: Tyler Nix Date: Wed, 10 Sep 2025 12:28:56 -0600 Subject: [PATCH 03/20] Fix linting errors in WriteRequestViewer - Replace 'any' type with proper RequestBody interface - Use eslint-disable-next-line for unused _description variables - Add proper TypeScript types for request body structure --- .../Docs/SnippetViewer/WriteRequestViewer.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/components/Docs/SnippetViewer/WriteRequestViewer.tsx b/src/components/Docs/SnippetViewer/WriteRequestViewer.tsx index ff05ee345c..e236b36ec5 100644 --- a/src/components/Docs/SnippetViewer/WriteRequestViewer.tsx +++ b/src/components/Docs/SnippetViewer/WriteRequestViewer.tsx @@ -66,11 +66,24 @@ ${ } case SupportedLanguage.CURL: { // Build the JSON object for pretty printing - const requestBody: any = {}; + interface RequestBody { + writes?: { + tuple_keys: Array>; + on_duplicate?: string; + }; + deletes?: { + tuple_keys: Array>; + on_missing?: string; + }; + authorization_model_id?: string; + } + + const requestBody: RequestBody = {}; if (opts.relationshipTuples?.length) { requestBody.writes = { tuple_keys: opts.relationshipTuples.map((tuple) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { _description, ...cleanTuple } = tuple; return cleanTuple; }), @@ -83,6 +96,7 @@ ${ if (opts.deleteRelationshipTuples?.length) { requestBody.deletes = { tuple_keys: opts.deleteRelationshipTuples.map((tuple) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { _description, ...cleanTuple } = tuple; return cleanTuple; }), From 5ec0c9dbd958d790dc446e76ef9cd3bb5c13a13b Mon Sep 17 00:00:00 2001 From: Tyler Nix Date: Wed, 10 Sep 2025 13:20:37 -0600 Subject: [PATCH 04/20] Apply manual fixes to duplicate-writes documentation - Fix error message example to match the actual tuple (writer vs reader) - Correct relation in first WriteRequestViewer example (writer vs reader) - Add line break for better formatting in 'Best effort' ignore section - Update RelatedSection description for clarity - Fix title in related links (Write API vs {ProductName} API) --- docs/content/interacting/duplicate-writes.mdx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/content/interacting/duplicate-writes.mdx b/docs/content/interacting/duplicate-writes.mdx index bc0b411bcf..2df11e4a2c 100644 --- a/docs/content/interacting/duplicate-writes.mdx +++ b/docs/content/interacting/duplicate-writes.mdx @@ -56,7 +56,7 @@ HTTP/1.1 400 Bad Request { "code": "write_failed_due_to_invalid_input", - "message": "cannot write a tuple which already exists: user: 'user:anne', relation: 'owner', object: 'document:test'..." + "message": "cannot write a tuple which already exists: user: 'user:anne', relation: 'writer', object: 'document:2025-budget': tuple to be written already existed or the tuple to be deleted did not exist" } ``` @@ -86,7 +86,7 @@ The `on_duplicate` parameter controls the behavior when writing tuples. relationshipTuples={[ { user: 'user:anne', - relation: 'reader', + relation: 'writer', object: 'document:2025-budget', }, ]} @@ -188,6 +188,7 @@ All tuples in a request will still be processed as a single atomic unit. The `ig ### "Best effort" ignore For writes: An `on_duplicate: 'ignore'` operation uses a "best effort" approach. We will attempt *once* to ignore duplicates and write non-duplicates, but if there is ever a conflict writing to the database (i.e. write a tuple we don’t think exists but it suddenly exists, probably due to a parallel request), we will abort the race condition immediately, and just return a `409 Conflict` error. These errors are rare, but can happen. + For deletes: An `on_missing: 'ignore'` operation is immune to race conditions due to database-level locks. It is not possible for another request to interfere since a delete operation will always succeed if the tuple exists or not. ### "Ignore" is Not an "Upsert" @@ -219,10 +220,10 @@ The deletes operation in the Write API does not accept a condition. Attempting t ## Related Sections Date: Wed, 10 Sep 2025 13:30:27 -0600 Subject: [PATCH 05/20] Add Duplicate Writes reference to interacting overview - Add new card for Duplicate Writes in the interacting section overview - Position it after Transactional Writes since they are related concepts - Include descriptive text about handling duplicate writes and missing deletes --- docs/content/interacting/overview.mdx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/content/interacting/overview.mdx b/docs/content/interacting/overview.mdx index 9ea513c0ae..3180d1fde8 100644 --- a/docs/content/interacting/overview.mdx +++ b/docs/content/interacting/overview.mdx @@ -47,6 +47,11 @@ This section helps you integrate Date: Fri, 12 Sep 2025 12:27:41 -0600 Subject: [PATCH 06/20] Remove redundant write API documentation and streamline tuple management docs - Delete write-api.mdx, transactional-writes.mdx, and duplicate-writes.mdx - Rename update-tuples.mdx to add-tuples.mdx with enhanced content - Add section 04 for combined write/delete operations in same request - Add section 05 for ignoring duplicate tuples and missing deletes - Update all references throughout documentation to point to add-tuples.mdx - Remove deleted files from sidebar navigation and overview pages - Update README.md, llms.txt, and all cross-references - Ensure clean build with no broken links - Replace document:Z examples with document:123 for consistency --- docs/README.md | 1 - .../{update-tuples.mdx => add-tuples.mdx} | 124 +++++++-- docs/content/getting-started/framework.mdx | 4 +- docs/content/getting-started/overview.mdx | 2 +- .../content/getting-started/perform-check.mdx | 12 +- .../getting-started/perform-list-objects.mdx | 12 +- .../getting-started/perform-list-users.mdx | 12 +- .../setup-openfga/configuration.mdx | 23 +- .../tuples-api-best-practices.mdx | 2 +- docs/content/interacting/duplicate-writes.mdx | 243 ----------------- .../interacting/managing-user-access.mdx | 4 +- docs/content/interacting/overview.mdx | 10 - .../interacting/read-tuple-changes.mdx | 6 +- .../interacting/transactional-writes.mdx | 254 ------------------ .../migrating/migrating-relations.mdx | 6 - docs/sidebars.js | 14 +- static/llms.txt | 3 +- 17 files changed, 154 insertions(+), 578 deletions(-) rename docs/content/getting-started/{update-tuples.mdx => add-tuples.mdx} (62%) delete mode 100644 docs/content/interacting/duplicate-writes.mdx delete mode 100644 docs/content/interacting/transactional-writes.mdx diff --git a/docs/README.md b/docs/README.md index 064881491b..cf63d9c80f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -72,7 +72,6 @@ It offers an HTTP API, a gRPC API, and has SDKs for programming languages includ - [Manage Group Access](./content/interacting/managing-user-access.mdx) - [Manage Group Membership](./content/interacting/managing-group-membership.mdx) - [Manage Relationships Between Objects](./content/interacting/managing-relationships-between-objects.mdx) - - [Transactional Writes](./content/interacting/transactional-writes.mdx) - [Relationship Queries](./content/interacting/relationship-queries.mdx) - [Get Tuple Changes](./content/interacting/read-tuple-changes.mdx) - [Search with Permissions](./content/interacting/search-with-permissions.mdx) diff --git a/docs/content/getting-started/update-tuples.mdx b/docs/content/getting-started/add-tuples.mdx similarity index 62% rename from docs/content/getting-started/update-tuples.mdx rename to docs/content/getting-started/add-tuples.mdx index 9f395bc503..45d6b4223e 100644 --- a/docs/content/getting-started/update-tuples.mdx +++ b/docs/content/getting-started/add-tuples.mdx @@ -1,8 +1,8 @@ --- -title: Update Relationship Tuples +title: Add Relationship Tuples sidebar_position: 3 -slug: /getting-started/update-tuples -description: Updating system state by writing and deleting relationship tuples +slug: /getting-started/add-tuples +description: Basic introduction to adding and deleting relationship tuples --- import { @@ -20,11 +20,11 @@ import { import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -# Update Relationship Tuples +# Add Relationship Tuples -This section will illustrate how to update __. +This is a basic introduction to adding and deleting __. ## Before you start @@ -87,13 +87,13 @@ This section will illustrate how to update _: + + + +:::caution +At the moment, this feature is only available on the API. Supported SDKs will follow shortly after. +::: + +Similarly, you can use `on_missing: "ignore"` when deleting tuples that might not exist. + + + +The behavior of `on_duplicate: "ignore"` is more nuanced for tuples with conditions. +- **Identical Tuples**: If a tuple in the request is 100% identical to an existing tuple (same user, relation, object, condition name, and condition context), it will be safely ignored. +- **Conflicting Tuples**: If a tuple key (user, relation, object) matches an existing tuple, but the condition is different, this is a conflict. The write attempt will be rejected, and the entire transaction will fail with a `409 Conflict` error. + + ## Related Sections \ No newline at end of file diff --git a/docs/content/getting-started/framework.mdx b/docs/content/getting-started/framework.mdx index 052366df3f..ffb732b6b4 100644 --- a/docs/content/getting-started/framework.mdx +++ b/docs/content/getting-started/framework.mdx @@ -32,7 +32,7 @@ This section will illustrate how to integrate 2. You have [installed the OpenFGA SDK](./install-sdk.mdx). -3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./update-tuples.mdx). +3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./add-tuples.mdx). 4. You know how to [perform a Check](./perform-check.mdx). 5. You have loaded `FGA_API_URL` and `FGA_STORE_ID` as environment variables. @@ -41,7 +41,7 @@ This section will illustrate how to integrate 2. You have [installed the OpenFGA SDK](./install-sdk.mdx). -3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./update-tuples.mdx). +3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./add-tuples.mdx). 4. You know how to [perform a Check](./perform-check.mdx). 5. You have loaded `FGA_API_URL` and `FGA_STORE_ID` as environment variables. diff --git a/docs/content/getting-started/overview.mdx b/docs/content/getting-started/overview.mdx index d3341e7114..06acd7a0e0 100644 --- a/docs/content/getting-started/overview.mdx +++ b/docs/content/getting-started/overview.mdx @@ -48,7 +48,7 @@ The following will provide a step-by-step guide on how to get started with 2. You have [installed the SDK](./install-sdk.mdx). -3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./update-tuples.mdx). +3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./add-tuples.mdx). 4. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables. @@ -54,7 +54,7 @@ This section will illustrate how to perform a 2. You have [installed the SDK](./install-sdk.mdx). -3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./update-tuples.mdx). +3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./add-tuples.mdx). 4. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables. @@ -72,7 +72,7 @@ This section will illustrate how to perform a 1. -2. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./update-tuples.mdx). +2. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./add-tuples.mdx). 3. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables. diff --git a/docs/content/getting-started/perform-list-objects.mdx b/docs/content/getting-started/perform-list-objects.mdx index 5e3f919011..cb77e36ac2 100644 --- a/docs/content/getting-started/perform-list-objects.mdx +++ b/docs/content/getting-started/perform-list-objects.mdx @@ -34,7 +34,7 @@ This section describes how to perform a 2. You have [installed the SDK](./install-sdk.mdx). -3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./update-tuples.mdx). +3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./add-tuples.mdx). 4. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables. @@ -52,7 +52,7 @@ This section describes how to perform a 2. You have [installed the SDK](./install-sdk.mdx). -3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./update-tuples.mdx). +3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./add-tuples.mdx). 4. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables. @@ -70,7 +70,7 @@ This section describes how to perform a 1. -2. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./update-tuples.mdx). +2. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./add-tuples.mdx). 3. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables. diff --git a/docs/content/getting-started/perform-list-users.mdx b/docs/content/getting-started/perform-list-users.mdx index 3307141839..9e2431ee6e 100644 --- a/docs/content/getting-started/perform-list-users.mdx +++ b/docs/content/getting-started/perform-list-users.mdx @@ -35,7 +35,7 @@ This section will illustrate how to perform a 2. You have [installed the SDK](./install-sdk.mdx). -3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./update-tuples.mdx). +3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./add-tuples.mdx). 4. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables. @@ -53,7 +53,7 @@ This section will illustrate how to perform a 2. You have [installed the SDK](./install-sdk.mdx). -3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./update-tuples.mdx). +3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./add-tuples.mdx). 4. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables. @@ -71,7 +71,7 @@ This section will illustrate how to perform a 1. -2. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./update-tuples.mdx). +2. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./add-tuples.mdx). 3. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables. diff --git a/docs/content/getting-started/setup-openfga/configuration.mdx b/docs/content/getting-started/setup-openfga/configuration.mdx index 42e8798ec3..8d000d9b14 100644 --- a/docs/content/getting-started/setup-openfga/configuration.mdx +++ b/docs/content/getting-started/setup-openfga/configuration.mdx @@ -101,7 +101,7 @@ docker run docker.io/openfga/openfga:latest run \ ## List of options -The following table lists the configuration options for the OpenFGA server [v1.8.9](https://github.com/openfga/openfga/releases/tag/v1.8.9), based on the [config-schema.json](https://raw.githubusercontent.com/openfga/openfga/refs/tags/v1.8.9/.config-schema.json). +The following table lists the configuration options for the OpenFGA server [v1.10.0](https://github.com/openfga/openfga/releases/tag/v1.10.0), based on the [config-schema.json](https://raw.githubusercontent.com/openfga/openfga/refs/tags/v1.10.0/.config-schema.json). | Config File | Env Var | Flag Name | Type | Description | Default Value | |-------------|---------|-----------|------|-------------|---------------| @@ -116,7 +116,7 @@ The following table lists the configuration options for the OpenFGA server [v1.8 | `maxConditionEvaluationCost` |
OPENFGA_MAX_CONDITION_EVALUATION_COST
| `max-condition-evaluation-cost` | integer | The maximum cost for CEL condition evaluation before a request returns an error (default is 100). | `100` | | `changelogHorizonOffset` |
OPENFGA_CHANGELOG_HORIZON_OFFSET
| `changelog-horizon-offset` | integer | The offset (in minutes) from the current time. Changes that occur after this offset will not be included in the response of ReadChanges. | | | `resolveNodeLimit` |
OPENFGA_RESOLVE_NODE_LIMIT
| `resolve-node-limit` | integer | Maximum resolution depth to attempt before throwing an error (defines how deeply nested an authorization model can be before a query errors out). | `25` | -| `resolveNodeBreadthLimit` |
OPENFGA_RESOLVE_NODE_BREADTH_LIMIT
| `resolve-node-breadth-limit` | integer | Defines how many nodes on a given level can be evaluated concurrently in a Check resolution tree. | `100` | +| `resolveNodeBreadthLimit` |
OPENFGA_RESOLVE_NODE_BREADTH_LIMIT
| `resolve-node-breadth-limit` | integer | Defines how many nodes on a given level can be evaluated concurrently in a Check resolution tree. | `10` | | `listObjectsDeadline` |
OPENFGA_LIST_OBJECTS_DEADLINE
| `list-objects-deadline` | string (duration) | The timeout deadline for serving ListObjects requests | `3s` | | `listObjectsMaxResults` |
OPENFGA_LIST_OBJECTS_MAX_RESULTS
| `list-objects-max-results` | integer | The maximum results to return in the non-streaming ListObjects API response. If 0, all results can be returned | `1000` | | `listUsersDeadline` |
OPENFGA_LIST_USERS_DEADLINE
| `list-users-deadline` | string (duration) | The timeout deadline for serving ListUsers requests. If 0s, there is no deadline | `3s` | @@ -134,8 +134,11 @@ The following table lists the configuration options for the OpenFGA server [v1.8 | `profiler.addr` |
OPENFGA_PROFILER_ADDR
| `profiler-addr` | string | The host:port address to serve the pprof profiler server on. | `:3001` | | `datastore.engine` |
OPENFGA_DATASTORE_ENGINE
| `datastore-engine` | string (enum=[`memory`, `postgres`, `mysql`, `sqlite`]) | The datastore engine that will be used for persistence. | `memory` | | `datastore.uri` |
OPENFGA_DATASTORE_URI
| `datastore-uri` | string | The connection uri to use to connect to the datastore (for any engine other than 'memory'). | | +| `datastore.secondaryUri` |
OPENFGA_DATASTORE_SECONDARY_URI
| `datastore-secondary-uri` | string | The connection uri to use to connect to the secondary datastore (for postgres only). | | | `datastore.username` |
OPENFGA_DATASTORE_USERNAME
| `datastore-username` | string | The connection username to connect to the datastore (overwrites any username provided in the connection uri). | | +| `datastore.secondaryUsername` |
OPENFGA_DATASTORE_SECONDARY_USERNAME
| `datastore-secondary-username` | string | The connection username to connect to the secondary datastore (overwrites any username provided in the connection uri). | | | `datastore.password` |
OPENFGA_DATASTORE_PASSWORD
| `datastore-password` | string | The connection password to connect to the datastore (overwrites any password provided in the connection uri). | | +| `datastore.secondaryPassword` |
OPENFGA_DATASTORE_SECONDARY_PASSWORD
| `datastore-secondary-password` | string | The connection password to connect to the secondary datastore (overwrites any password provided in the connection uri). | | | `datastore.maxCacheSize` |
OPENFGA_DATASTORE_MAX_CACHE_SIZE
| `datastore-max-cache-size` | integer | The maximum number of authorization models that will be cached in memory | `100000` | | `datastore.maxOpenConns` |
OPENFGA_DATASTORE_MAX_OPEN_CONNS
| `datastore-max-open-conns` | integer | The maximum number of open connections to the datastore. | `30` | | `datastore.maxIdleConns` |
OPENFGA_DATASTORE_MAX_IDLE_CONNS
| `datastore-max-idle-conns` | integer | the maximum number of connections to the datastore in the idle connection pool. | `10` | @@ -185,15 +188,23 @@ The following table lists the configuration options for the OpenFGA server [v1.8 | `checkDispatchThrottling.frequency` |
OPENFGA_CHECK_DISPATCH_THROTTLING_FREQUENCY
| `check-dispatch-throttling-frequency` | string (duration) | the frequency period that the deprioritized throttling queue is evaluated for a check request. A higher value will result in more aggressive throttling | `10µs` | | `checkDispatchThrottling.threshold` |
OPENFGA_CHECK_DISPATCH_THROTTLING_THRESHOLD
| `check-dispatch-throttling-threshold` | integer | define the number of recursive operations to occur before getting throttled for a check request | `100` | | `checkDispatchThrottling.maxThreshold` |
OPENFGA_CHECK_DISPATCH_THROTTLING_MAX_THRESHOLD
| `check-dispatch-throttling-max-threshold` | integer | define the maximum dispatch threshold beyond above which requests will be throttled. 0 will use the 'dispatchThrottling.threshold' value as maximum | `0` | -| `listObjectsDispatchThrottling.enabled` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_ENABLED
| `list-objects-dispatch-throttling-enabled` | boolean | enable throttling when list objects request's number of dispatches is high | `false` | -| `listObjectsDispatchThrottling.frequency` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_FREQUENCY
| `list-objects-dispatch-throttling-frequency` | string (duration) | the frequency period that the deprioritized throttling queue is evaluated for a list objects request. A higher value will result in more aggressive throttling | `10µs` | -| `listObjectsDispatchThrottling.threshold` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_THRESHOLD
| `list-objects-dispatch-throttling-threshold` | integer | define the number of recursive operations to occur before getting throttled for a list objects request | `100` | -| `listObjectsDispatchThrottling.maxThreshold` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_MAX_THRESHOLD
| `list-objects-dispatch-throttling-max-threshold` | integer | define the maximum dispatch threshold beyond above which requests will be throttled for a list objects request. 0 will use the 'dispatchThrottling.threshold' value as maximum | `0` | +| `listObjectsIteratorCache.enabled` |
OPENFGA_LIST_OBJECTS_ITERATOR_CACHE_ENABLED
| `list-objects-iterator-cache-enabled` | boolean | enable caching of datastore iterators in ListObjects. The key is a string representing a database query, and the value is a list of tuples. Each iterator is the result of a database query, for example usersets related to a specific object, or objects related to a specific user, up to a certain number of tuples per iterator. If the request's consistency is HIGHER_CONSISTENCY, this cache is not used. | `false` | +| `listObjectsIteratorCache.maxResults` |
OPENFGA_LIST_OBJECTS_ITERATOR_CACHE_MAX_RESULTS
| `list-objects-iterator-cache-max-results` | integer | if caching of datastore iterators of ListObjects requests is enabled, this is the limit of tuples to cache per key | `10000` | +| `listObjectsIteratorCache.ttl` |
OPENFGA_LIST_OBJECTS_ITERATOR_CACHE_TTL
| `list-objects-iterator-cache-ttl` | string (duration) | if caching of datastore iterators of ListObjects requests is enabled, this is the TTL of each value | `10s` | +| `listObjectsDispatchThrottling.enabled` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_ENABLED
| `list-objects-dispatch-throttling-enabled` | boolean | enable throttling when ListObjects request's number of dispatches is high | `false` | +| `listObjectsDispatchThrottling.frequency` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_FREQUENCY
| `list-objects-dispatch-throttling-frequency` | string (duration) | the frequency period that the deprioritized throttling queue is evaluated for a ListObjects request. A higher value will result in more aggressive throttling | `10µs` | +| `listObjectsDispatchThrottling.threshold` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_THRESHOLD
| `list-objects-dispatch-throttling-threshold` | integer | define the number of recursive operations to occur before getting throttled for a ListObjects request | `100` | +| `listObjectsDispatchThrottling.maxThreshold` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_MAX_THRESHOLD
| `list-objects-dispatch-throttling-max-threshold` | integer | define the maximum dispatch threshold beyond above which requests will be throttled for a ListObjects request. 0 will use the 'dispatchThrottling.threshold' value as maximum | `0` | | `listUsersDispatchThrottling.enabled` |
OPENFGA_LIST_USERS_DISPATCH_THROTTLING_ENABLED
| `list-users-dispatch-throttling-enabled` | boolean | enable throttling when list users request's number of dispatches is high | `false` | | `listUsersDispatchThrottling.frequency` |
OPENFGA_LIST_USERS_DISPATCH_THROTTLING_FREQUENCY
| `list-users-dispatch-throttling-frequency` | string (duration) | the frequency period that the deprioritized throttling queue is evaluated for a list users request. A higher value will result in more aggressive throttling | `10µs` | | `listUsersDispatchThrottling.threshold` |
OPENFGA_LIST_USERS_DISPATCH_THROTTLING_THRESHOLD
| `list-users-dispatch-throttling-threshold` | integer | define the number of recursive operations to occur before getting throttled for a list users request | `100` | | `listUsersDispatchThrottling.maxThreshold` |
OPENFGA_LIST_USERS_DISPATCH_THROTTLING_MAX_THRESHOLD
| `list-users-dispatch-throttling-max-threshold` | integer | define the maximum dispatch threshold beyond above which requests will be throttled for a list users request. 0 will use the 'dispatchThrottling.threshold' value as maximum | `0` | +| `sharedIterator.enabled` |
OPENFGA_SHARED_ITERATOR_ENABLED
| `shared-iterator-enabled` | boolean | enabling sharing of datastore iterators with different consumers. Each iterator is the result of a database query, for example usersets related to a specific object, or objects related to a specific user, up to a certain number of tuples per iterator. | `false` | +| `sharedIterator.limit` |
OPENFGA_SHARED_ITERATOR_LIMIT
| `shared-iterator-limit` | integer | if shared-iterator-enabled is enabled, this is the limit of the number of iterators that can be shared. | `1000000` | | `requestTimeout` |
OPENFGA_REQUEST_TIMEOUT
| `request-timeout` | string (duration) | The timeout duration for a request. | `3s` | +| `planner.initialGuess` |
OPENFGA_PLANNER_INITIAL_GUESS
| `planner-initial-guess` | string (duration) | The initial guess for the planners estimation. | `10ms` | +| `planner.evictionThreshold` |
OPENFGA_PLANNER_EVICTION_THRESHOLD
| `planner-eviction-threshold` | | How long a planner key can be unused before being evicted. | `0` | +| `planner.cleanupInterval` |
OPENFGA_PLANNER_CLEANUP_INTERVAL
| `planner-cleanup-interval` | string (duration) | How often the planner checks for stale keys. | `0` | ## Related Sections diff --git a/docs/content/getting-started/tuples-api-best-practices.mdx b/docs/content/getting-started/tuples-api-best-practices.mdx index bc3572869c..6b4d35060b 100644 --- a/docs/content/getting-started/tuples-api-best-practices.mdx +++ b/docs/content/getting-started/tuples-api-best-practices.mdx @@ -31,7 +31,7 @@ The documentation and samples uses first names and simple ids to illustrate easy ## Always specify authorization model ID whenever possible -It is strongly recommended that authorization model ID be specified in your Relationship Queries (such as [Check](./perform-check.mdx) and [ListObjects](../interacting/relationship-queries.mdx#listobjects)) and Relationship Commands (such as [Write](./update-tuples.mdx)). +It is strongly recommended that authorization model ID be specified in your Relationship Queries (such as [Check](./perform-check.mdx) and [ListObjects](../interacting/relationship-queries.mdx#listobjects)) and Relationship Commands (such as [Write](./add-tuples.mdx)). Specifying authorization model ID in API calls have the following advantages: 1. Better performance as will not need to perform a database query to get the latest authorization model ID. diff --git a/docs/content/interacting/duplicate-writes.mdx b/docs/content/interacting/duplicate-writes.mdx deleted file mode 100644 index 2df11e4a2c..0000000000 --- a/docs/content/interacting/duplicate-writes.mdx +++ /dev/null @@ -1,243 +0,0 @@ ---- -sidebar_position: 3 -slug: /interacting/duplicate-writes -description: Handle duplicate writes and missing deletes in write operations ---- - -import { - AuthzModelSnippetViewer, - CardBox, - DocumentationNotice, - ProductConcept, - ProductName, - ProductNameFormat, - RelatedSection, - RelationshipTuplesViewer, - WriteRequestViewer, - SupportedLanguage, -} from "@components/Docs"; - - -# Duplicate Writes - - - -Handle duplicate writes and missing deletes gracefully using the Write API's optional `on_duplicate` and `on_missing` parameters. - - - -Common scenarios include high-volume data imports into or migrations between distributed systems where you need to ensure data synchronization without causing transaction failures on duplicate writes or missing deletes. - - - -## Understanding the Problem - -The Write API is the primary method for adding and removing in your store. - -By default, the Write API operates with strict validation ("all-or-nothing"): - -- Writing a tuple that already exists causes the entire request to fail (even if only 1 tuple out of 40 possible tuples is a duplicate) -- Deleting a tuple that doesn't exist causes the entire request to fail - -This strict behavior requires applications to check the current state of the tuple in before making changes, implement complex retry logic, or simply ignore the errors altogether. - -## How to Handle Duplicate Operations - -To improve import and migration scenarios, the Write API provides optional parameters to control how duplicate writes and missing deletes are handled. - -The `on_duplicate` and `on_missing` parameters change this behavior, allowing you to instruct the API to ignore these cases and process the rest of the request successfully. - -### Response: Default Behavior - -Without the `on_duplicate` parameter, attempting to write an existing tuple returns a `400 Bad Request`. - -```http -HTTP/1.1 400 Bad Request - -{ - "code": "write_failed_due_to_invalid_input", - "message": "cannot write a tuple which already exists: user: 'user:anne', relation: 'writer', object: 'document:2025-budget': tuple to be written already existed or the tuple to be deleted did not exist" -} -``` - -### Response: Ignoring Duplicates - -Setting `on_duplicate: "ignore"` allows the duplicate to be ignored and the API returns a `200 OK`. - -```http -HTTP/1.1 200 OK - -{} -``` - -## Optional API Parameters - -The parameters are added within the `writes` and `deletes` objects in the body of a Write request. - -### Writes - -The `on_duplicate` parameter controls the behavior when writing tuples. - -- **"error" (Default)**: The request fails if any tuple in the writes array already exists. This maintains backward compatibility. -- **"ignore"**: The API ignores any attempt to write a tuple that already exists and proceeds to write the new ones in the same transaction. The request succeeds. - - - -:::caution -At the moment, this feature is only available on the API. Supported SDKs will follow shortly after. -::: - -### Deletes - -The `on_missing` parameter controls behavior when deleting tuples. - -- **"error" (Default)**: The request fails if any tuple in the deletes array does not exist. -- **"ignore"**: The API ignores any attempt to delete a tuple that does not exist and proceeds to delete the ones that do exist. The request succeeds. - - - -## Using Both Parameters Together - -The decision to have separate `on_duplicate` and `on_missing` parameters is intentional. This design gives you granular, independent control over the behavior of writes and deletes within a single atomic transaction. You can mix and match strict and permissive behaviors to suit your exact needs. - -For example, you might perform a strict delete (`on_missing: "error"`) to confirm that a specific permission has been successfully removed before making a permissive write (`on_duplicate: "ignore"`) that guarantees the new permission exists. - -:::tip -This flexibility is particularly useful when you are trying to synchronize state between your application's database and . You might need a strict operation to fail so you can roll back corresponding changes in your own database, ensuring overall system consistency. -::: - - - -In this example: -- If the permission to delete doesn't exist, the entire request will fail as intended. -- If the tuple exists and is successfully deleted, the write permission will succeed whether the tuple already exists or is written in that moment. - -## Important Concepts - -### The Write Request Remains Atomic - -All tuples in a request will still be processed as a single atomic unit. The `ignore` option only changes the success criteria for individual operations in the request. - -### "Best effort" ignore - -For writes: An `on_duplicate: 'ignore'` operation uses a "best effort" approach. We will attempt *once* to ignore duplicates and write non-duplicates, but if there is ever a conflict writing to the database (i.e. write a tuple we don’t think exists but it suddenly exists, probably due to a parallel request), we will abort the race condition immediately, and just return a `409 Conflict` error. These errors are rare, but can happen. - -For deletes: An `on_missing: 'ignore'` operation is immune to race conditions due to database-level locks. It is not possible for another request to interfere since a delete operation will always succeed if the tuple exists or not. - -### "Ignore" is Not an "Upsert" - -It is critical to understand that `on_duplicate: "ignore"` will not update an existing tuple, only ignore an identical tuple. This is why we do not call the operation an "idempotent" operation. - -The behavior of `on_duplicate: "ignore"` is more nuanced for tuples with conditions. -- **Identical Tuples**: If a tuple in the request is 100% identical to an existing tuple (same user, relation, object, condition name, and condition context), it will be safely ignored. -- **Conflicting Tuples**: If a tuple key (user, relation, object) matches an existing tuple, but the condition is different, this is a conflict. The write attempt will be rejected, and the entire transaction will fail with a `409 Conflict` error. **The correct pattern to safely update a tuple's condition requires explicitly deleting the old tuple and writing the new one within the same atomic Write request.** - - - -```http -HTTP/1.1 409 Conflict -{ - "code": "Aborted", - "message": "transactional write failed due to conflict: attempted to write a tuple which already exists with a different condition: user: 'user:anne', relation: 'writer', object: 'document:2025-budget'" -} -``` - -:::tip -The condition is not returned in the response, but you can call `/read` for this tuple to view its condition. -::: - -:::warning -The deletes operation in the Write API does not accept a condition. Attempting to include one will result in an invalid request. -::: - -## Related Sections - - diff --git a/docs/content/interacting/managing-user-access.mdx b/docs/content/interacting/managing-user-access.mdx index d8fa4afa01..130fa47870 100644 --- a/docs/content/interacting/managing-user-access.mdx +++ b/docs/content/interacting/managing-user-access.mdx @@ -145,8 +145,8 @@ With this, we have removed the [direct relationship](../modeling/building-blocks { title: 'How to update relationship tuples', description: 'Learn about how to update relationship tuples in SDK.', - link: '../getting-started/update-tuples', - id: '../getting-started/update-tuples', + link: '../getting-started/add-tuples', + id: '../getting-started/add-tuples', }, ]} /> diff --git a/docs/content/interacting/overview.mdx b/docs/content/interacting/overview.mdx index 3180d1fde8..9b45bf0c8f 100644 --- a/docs/content/interacting/overview.mdx +++ b/docs/content/interacting/overview.mdx @@ -42,16 +42,6 @@ This section helps you integrate 2. You have [installed the SDK](../getting-started/install-sdk.mdx). -3. You have [configured the _authorization model_](../modeling) and [added some _relationship tuples_](../getting-started/update-tuples.mdx#02-calling-write-api-to-add-new-relationship-tuples). +3. You have [configured the _authorization model_](../modeling) and [added some _relationship tuples_](../getting-started/add-tuples.mdx#02-calling-write-api-to-add-new-relationship-tuples). 4. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables. @@ -40,7 +40,7 @@ This section illustrates how to call the Read Changes API to get the list of rel 1. 2. You have [installed the SDK](../getting-started/install-sdk.mdx). -3. You have [configured the _authorization model_](../modeling) and [added some _relationship tuples_](../getting-started/update-tuples.mdx#02-calling-write-api-to-add-new-relationship-tuples). +3. You have [configured the _authorization model_](../modeling) and [added some _relationship tuples_](../getting-started/add-tuples.mdx#02-calling-write-api-to-add-new-relationship-tuples). 4. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables. @@ -72,7 +72,7 @@ This section illustrates how to call the Read Changes API to get the list of rel 1. -2. You have [configured the _authorization model_](../modeling) and [added some _relationship tuples_](../getting-started/update-tuples.mdx#02-calling-write-api-to-add-new-relationship-tuples). +2. You have [configured the _authorization model_](../modeling) and [added some _relationship tuples_](../getting-started/add-tuples.mdx#02-calling-write-api-to-add-new-relationship-tuples). 3. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables. diff --git a/docs/content/interacting/transactional-writes.mdx b/docs/content/interacting/transactional-writes.mdx deleted file mode 100644 index f535e3992e..0000000000 --- a/docs/content/interacting/transactional-writes.mdx +++ /dev/null @@ -1,254 +0,0 @@ ---- -sidebar_position: 2 -slug: /interacting/transactional-writes -description: Updating multiple relationship tuples in a single transaction ---- - -import { - AuthzModelSnippetViewer, - CardBox, - DocumentationNotice, - ProductConcept, - ProductName, - ProductNameFormat, - RelatedSection, - RelationshipTuplesViewer, - WriteRequestViewer, - SupportedLanguage, -} from '@components/Docs'; - -# Transactional Writes - - - -Using, you can update multiple in a single transaction. - - - -Updating multiple relationship tuples can keep your system state consistent. - - - -## Before you start - -Familiarize yourself with basic before completing this guide. - -
- - -In the following , there is called `tweet` that can have a `reader`. There is another type called `user` that can have a `follower` and `followed_by` relationship. - - - - - -
- -In addition: - -### Direct access - -Creating an authorization model and a relationship tuple grants a user access to an object. To learn more, [read about Direct Access.](../modeling/direct-access.mdx) - -### Modeling public access - -The following example uses public access. To learn more, [read about Public Access.](../modeling/direct-access.mdx) - -### concepts - -- A : a class of objects that have similar characteristics -- A : an entity in the system that can be related to an object -- A : is a string defined in the type definition of an authorization model that defines the possibility of a relationship between an object of the same type as the type definition and a user in the system -- A : a string defined in the type definition of an authorization model that defines the possibility of a relationship between an object of the same type as the type definition and a user in the system -- A : a group stored in that consists of a user, a relation, and an object - -
- -## Step by step - -### 01. Add and remove relationship tuples in the same transaction - -A call to the Write API can add or delete tuples in your store. For example, the following tuple makes `tweet:1` public by making everyone a `viewer`: - - - -Deleting the previous tuple converts this `tweet` to private: - - - -By removing the tuple, we made the tweet visible to no-one, which may not be what we want. - -
-Limitations on duplicate tuples in a single request - -
-When using the Write API, you cannot include the same tuple (same user, relation, and object) in both the writes and deletes arrays within a single request. The API will return an error with code `cannot_allow_duplicate_tuples_in_one_request` if duplicate tuples are detected. - -For example, this request would fail: - -```bash -curl -X POST 'http://localhost:8080/stores/{store_id}/write' \ - -H 'content-type: application/json' \ - --data '{ - "writes": { - "tuple_keys": [{ - "user": "user:anne", - "relation": "member", - "object": "group:2" - }] - }, - "deletes": { - "tuple_keys": [{ - "user": "user:anne", - "relation": "member", - "object": "group:2" - }] - } - }' -``` - -
- -:::tip -For handling cases where you need to write tuples that might already exist or delete tuples that might not exist, check out [Duplicate Writes](./duplicate-writes.mdx) which provides resilient write operations. -::: - -The Write API allows you to send up to 100 unique tuples in the request. (This limit applies to the sum of both writes and deletes in that request). This means we can submit one API call that converts the `tweet` from public to visible to only the `user`'s followers. - - - -### 02. Add multiple related relationship tuples in the same transaction - -Sending multiple tuples per request can also help maintain consistency. For example, if `anne` follows `becky`, you can save the following two tuples or neither of them: - - - -:::info -In this case, the type `user` exists because users can be related to each other, so users now are a type in the system. -::: - -The service attempts to perform all the changes sent in a single Write API call in one transaction. If it cannot complete all the changes, it rejects all of them. - -## Related Sections - - diff --git a/docs/content/modeling/migrating/migrating-relations.mdx b/docs/content/modeling/migrating/migrating-relations.mdx index bd49ca521e..44001fbb07 100644 --- a/docs/content/modeling/migrating/migrating-relations.mdx +++ b/docs/content/modeling/migrating/migrating-relations.mdx @@ -313,12 +313,6 @@ Now, the `write` API will only accept the new relation name. Date: Fri, 12 Sep 2025 13:32:36 -0600 Subject: [PATCH 07/20] Enhance documentation with advanced tuple management and fix relation consistency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Enhanced add-tuples.mdx with sections 04 & 05 covering combined operations and duplicate handling - Fixed relation consistency: changed editor→writer and can_view→reader to match authorization model - Updated README.md to reference add-tuples.mdx instead of update-tuples.mdx - Updated overview.mdx navigation - Fixed perform-check.mdx to use reader relation consistently with configured model - All examples now use document:Z for consistency - Build validation: Clean build with working internal links --- docs/README.md | 2 +- docs/content/getting-started/add-tuples.mdx | 26 +++++++++---------- docs/content/getting-started/overview.mdx | 2 +- .../content/getting-started/perform-check.mdx | 4 +-- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/README.md b/docs/README.md index cf63d9c80f..3b53c775ad 100644 --- a/docs/README.md +++ b/docs/README.md @@ -32,7 +32,7 @@ It offers an HTTP API, a gRPC API, and has SDKs for programming languages includ - [Create a Store](./content/getting-started/create-store.mdx) - [Setup SDK Client for Store](./content/getting-started/setup-sdk-client.mdx) - [Configure Authorization Model](./content/getting-started/configure-model.mdx) - - [Update Relationship Tuples](./content/getting-started/update-tuples.mdx) + - [Add Relationship Tuples](./content/getting-started/add-tuples.mdx) - [Perform a Check](./content/getting-started/perform-check.mdx) - [Perform a List Objects Request](./content/getting-started/perform-list-objects.mdx) - [Use the FGA CLI](./content/getting-started/cli.mdx) diff --git a/docs/content/getting-started/add-tuples.mdx b/docs/content/getting-started/add-tuples.mdx index 45d6b4223e..9b307dd376 100644 --- a/docs/content/getting-started/add-tuples.mdx +++ b/docs/content/getting-started/add-tuples.mdx @@ -87,13 +87,13 @@ This is a basic introduction to adding and deleting _ Date: Fri, 12 Sep 2025 13:38:50 -0600 Subject: [PATCH 08/20] Revert configuration.mdx changes to focus PR on tuple management documentation This removes the OpenFGA v1.10.0 configuration updates to keep this PR focused solely on the tuple management documentation enhancements. --- .../setup-openfga/configuration.mdx | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/docs/content/getting-started/setup-openfga/configuration.mdx b/docs/content/getting-started/setup-openfga/configuration.mdx index 8d000d9b14..42e8798ec3 100644 --- a/docs/content/getting-started/setup-openfga/configuration.mdx +++ b/docs/content/getting-started/setup-openfga/configuration.mdx @@ -101,7 +101,7 @@ docker run docker.io/openfga/openfga:latest run \ ## List of options -The following table lists the configuration options for the OpenFGA server [v1.10.0](https://github.com/openfga/openfga/releases/tag/v1.10.0), based on the [config-schema.json](https://raw.githubusercontent.com/openfga/openfga/refs/tags/v1.10.0/.config-schema.json). +The following table lists the configuration options for the OpenFGA server [v1.8.9](https://github.com/openfga/openfga/releases/tag/v1.8.9), based on the [config-schema.json](https://raw.githubusercontent.com/openfga/openfga/refs/tags/v1.8.9/.config-schema.json). | Config File | Env Var | Flag Name | Type | Description | Default Value | |-------------|---------|-----------|------|-------------|---------------| @@ -116,7 +116,7 @@ The following table lists the configuration options for the OpenFGA server [v1.1 | `maxConditionEvaluationCost` |
OPENFGA_MAX_CONDITION_EVALUATION_COST
| `max-condition-evaluation-cost` | integer | The maximum cost for CEL condition evaluation before a request returns an error (default is 100). | `100` | | `changelogHorizonOffset` |
OPENFGA_CHANGELOG_HORIZON_OFFSET
| `changelog-horizon-offset` | integer | The offset (in minutes) from the current time. Changes that occur after this offset will not be included in the response of ReadChanges. | | | `resolveNodeLimit` |
OPENFGA_RESOLVE_NODE_LIMIT
| `resolve-node-limit` | integer | Maximum resolution depth to attempt before throwing an error (defines how deeply nested an authorization model can be before a query errors out). | `25` | -| `resolveNodeBreadthLimit` |
OPENFGA_RESOLVE_NODE_BREADTH_LIMIT
| `resolve-node-breadth-limit` | integer | Defines how many nodes on a given level can be evaluated concurrently in a Check resolution tree. | `10` | +| `resolveNodeBreadthLimit` |
OPENFGA_RESOLVE_NODE_BREADTH_LIMIT
| `resolve-node-breadth-limit` | integer | Defines how many nodes on a given level can be evaluated concurrently in a Check resolution tree. | `100` | | `listObjectsDeadline` |
OPENFGA_LIST_OBJECTS_DEADLINE
| `list-objects-deadline` | string (duration) | The timeout deadline for serving ListObjects requests | `3s` | | `listObjectsMaxResults` |
OPENFGA_LIST_OBJECTS_MAX_RESULTS
| `list-objects-max-results` | integer | The maximum results to return in the non-streaming ListObjects API response. If 0, all results can be returned | `1000` | | `listUsersDeadline` |
OPENFGA_LIST_USERS_DEADLINE
| `list-users-deadline` | string (duration) | The timeout deadline for serving ListUsers requests. If 0s, there is no deadline | `3s` | @@ -134,11 +134,8 @@ The following table lists the configuration options for the OpenFGA server [v1.1 | `profiler.addr` |
OPENFGA_PROFILER_ADDR
| `profiler-addr` | string | The host:port address to serve the pprof profiler server on. | `:3001` | | `datastore.engine` |
OPENFGA_DATASTORE_ENGINE
| `datastore-engine` | string (enum=[`memory`, `postgres`, `mysql`, `sqlite`]) | The datastore engine that will be used for persistence. | `memory` | | `datastore.uri` |
OPENFGA_DATASTORE_URI
| `datastore-uri` | string | The connection uri to use to connect to the datastore (for any engine other than 'memory'). | | -| `datastore.secondaryUri` |
OPENFGA_DATASTORE_SECONDARY_URI
| `datastore-secondary-uri` | string | The connection uri to use to connect to the secondary datastore (for postgres only). | | | `datastore.username` |
OPENFGA_DATASTORE_USERNAME
| `datastore-username` | string | The connection username to connect to the datastore (overwrites any username provided in the connection uri). | | -| `datastore.secondaryUsername` |
OPENFGA_DATASTORE_SECONDARY_USERNAME
| `datastore-secondary-username` | string | The connection username to connect to the secondary datastore (overwrites any username provided in the connection uri). | | | `datastore.password` |
OPENFGA_DATASTORE_PASSWORD
| `datastore-password` | string | The connection password to connect to the datastore (overwrites any password provided in the connection uri). | | -| `datastore.secondaryPassword` |
OPENFGA_DATASTORE_SECONDARY_PASSWORD
| `datastore-secondary-password` | string | The connection password to connect to the secondary datastore (overwrites any password provided in the connection uri). | | | `datastore.maxCacheSize` |
OPENFGA_DATASTORE_MAX_CACHE_SIZE
| `datastore-max-cache-size` | integer | The maximum number of authorization models that will be cached in memory | `100000` | | `datastore.maxOpenConns` |
OPENFGA_DATASTORE_MAX_OPEN_CONNS
| `datastore-max-open-conns` | integer | The maximum number of open connections to the datastore. | `30` | | `datastore.maxIdleConns` |
OPENFGA_DATASTORE_MAX_IDLE_CONNS
| `datastore-max-idle-conns` | integer | the maximum number of connections to the datastore in the idle connection pool. | `10` | @@ -188,23 +185,15 @@ The following table lists the configuration options for the OpenFGA server [v1.1 | `checkDispatchThrottling.frequency` |
OPENFGA_CHECK_DISPATCH_THROTTLING_FREQUENCY
| `check-dispatch-throttling-frequency` | string (duration) | the frequency period that the deprioritized throttling queue is evaluated for a check request. A higher value will result in more aggressive throttling | `10µs` | | `checkDispatchThrottling.threshold` |
OPENFGA_CHECK_DISPATCH_THROTTLING_THRESHOLD
| `check-dispatch-throttling-threshold` | integer | define the number of recursive operations to occur before getting throttled for a check request | `100` | | `checkDispatchThrottling.maxThreshold` |
OPENFGA_CHECK_DISPATCH_THROTTLING_MAX_THRESHOLD
| `check-dispatch-throttling-max-threshold` | integer | define the maximum dispatch threshold beyond above which requests will be throttled. 0 will use the 'dispatchThrottling.threshold' value as maximum | `0` | -| `listObjectsIteratorCache.enabled` |
OPENFGA_LIST_OBJECTS_ITERATOR_CACHE_ENABLED
| `list-objects-iterator-cache-enabled` | boolean | enable caching of datastore iterators in ListObjects. The key is a string representing a database query, and the value is a list of tuples. Each iterator is the result of a database query, for example usersets related to a specific object, or objects related to a specific user, up to a certain number of tuples per iterator. If the request's consistency is HIGHER_CONSISTENCY, this cache is not used. | `false` | -| `listObjectsIteratorCache.maxResults` |
OPENFGA_LIST_OBJECTS_ITERATOR_CACHE_MAX_RESULTS
| `list-objects-iterator-cache-max-results` | integer | if caching of datastore iterators of ListObjects requests is enabled, this is the limit of tuples to cache per key | `10000` | -| `listObjectsIteratorCache.ttl` |
OPENFGA_LIST_OBJECTS_ITERATOR_CACHE_TTL
| `list-objects-iterator-cache-ttl` | string (duration) | if caching of datastore iterators of ListObjects requests is enabled, this is the TTL of each value | `10s` | -| `listObjectsDispatchThrottling.enabled` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_ENABLED
| `list-objects-dispatch-throttling-enabled` | boolean | enable throttling when ListObjects request's number of dispatches is high | `false` | -| `listObjectsDispatchThrottling.frequency` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_FREQUENCY
| `list-objects-dispatch-throttling-frequency` | string (duration) | the frequency period that the deprioritized throttling queue is evaluated for a ListObjects request. A higher value will result in more aggressive throttling | `10µs` | -| `listObjectsDispatchThrottling.threshold` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_THRESHOLD
| `list-objects-dispatch-throttling-threshold` | integer | define the number of recursive operations to occur before getting throttled for a ListObjects request | `100` | -| `listObjectsDispatchThrottling.maxThreshold` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_MAX_THRESHOLD
| `list-objects-dispatch-throttling-max-threshold` | integer | define the maximum dispatch threshold beyond above which requests will be throttled for a ListObjects request. 0 will use the 'dispatchThrottling.threshold' value as maximum | `0` | +| `listObjectsDispatchThrottling.enabled` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_ENABLED
| `list-objects-dispatch-throttling-enabled` | boolean | enable throttling when list objects request's number of dispatches is high | `false` | +| `listObjectsDispatchThrottling.frequency` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_FREQUENCY
| `list-objects-dispatch-throttling-frequency` | string (duration) | the frequency period that the deprioritized throttling queue is evaluated for a list objects request. A higher value will result in more aggressive throttling | `10µs` | +| `listObjectsDispatchThrottling.threshold` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_THRESHOLD
| `list-objects-dispatch-throttling-threshold` | integer | define the number of recursive operations to occur before getting throttled for a list objects request | `100` | +| `listObjectsDispatchThrottling.maxThreshold` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_MAX_THRESHOLD
| `list-objects-dispatch-throttling-max-threshold` | integer | define the maximum dispatch threshold beyond above which requests will be throttled for a list objects request. 0 will use the 'dispatchThrottling.threshold' value as maximum | `0` | | `listUsersDispatchThrottling.enabled` |
OPENFGA_LIST_USERS_DISPATCH_THROTTLING_ENABLED
| `list-users-dispatch-throttling-enabled` | boolean | enable throttling when list users request's number of dispatches is high | `false` | | `listUsersDispatchThrottling.frequency` |
OPENFGA_LIST_USERS_DISPATCH_THROTTLING_FREQUENCY
| `list-users-dispatch-throttling-frequency` | string (duration) | the frequency period that the deprioritized throttling queue is evaluated for a list users request. A higher value will result in more aggressive throttling | `10µs` | | `listUsersDispatchThrottling.threshold` |
OPENFGA_LIST_USERS_DISPATCH_THROTTLING_THRESHOLD
| `list-users-dispatch-throttling-threshold` | integer | define the number of recursive operations to occur before getting throttled for a list users request | `100` | | `listUsersDispatchThrottling.maxThreshold` |
OPENFGA_LIST_USERS_DISPATCH_THROTTLING_MAX_THRESHOLD
| `list-users-dispatch-throttling-max-threshold` | integer | define the maximum dispatch threshold beyond above which requests will be throttled for a list users request. 0 will use the 'dispatchThrottling.threshold' value as maximum | `0` | -| `sharedIterator.enabled` |
OPENFGA_SHARED_ITERATOR_ENABLED
| `shared-iterator-enabled` | boolean | enabling sharing of datastore iterators with different consumers. Each iterator is the result of a database query, for example usersets related to a specific object, or objects related to a specific user, up to a certain number of tuples per iterator. | `false` | -| `sharedIterator.limit` |
OPENFGA_SHARED_ITERATOR_LIMIT
| `shared-iterator-limit` | integer | if shared-iterator-enabled is enabled, this is the limit of the number of iterators that can be shared. | `1000000` | | `requestTimeout` |
OPENFGA_REQUEST_TIMEOUT
| `request-timeout` | string (duration) | The timeout duration for a request. | `3s` | -| `planner.initialGuess` |
OPENFGA_PLANNER_INITIAL_GUESS
| `planner-initial-guess` | string (duration) | The initial guess for the planners estimation. | `10ms` | -| `planner.evictionThreshold` |
OPENFGA_PLANNER_EVICTION_THRESHOLD
| `planner-eviction-threshold` | | How long a planner key can be unused before being evicted. | `0` | -| `planner.cleanupInterval` |
OPENFGA_PLANNER_CLEANUP_INTERVAL
| `planner-cleanup-interval` | string (duration) | How often the planner checks for stale keys. | `0` | ## Related Sections From ccefcf86182ab7124502ecb8651c817136f1cdd5 Mon Sep 17 00:00:00 2001 From: Tyler Nix Date: Fri, 12 Sep 2025 15:55:34 -0600 Subject: [PATCH 09/20] quick word fixes --- docs/content/getting-started/add-tuples.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/content/getting-started/add-tuples.mdx b/docs/content/getting-started/add-tuples.mdx index 9b307dd376..6f5c71511e 100644 --- a/docs/content/getting-started/add-tuples.mdx +++ b/docs/content/getting-started/add-tuples.mdx @@ -2,7 +2,7 @@ title: Add Relationship Tuples sidebar_position: 3 slug: /getting-started/add-tuples -description: Basic introduction to adding and deleting relationship tuples +description: Introduction to adding and deleting relationship tuples --- import { @@ -24,7 +24,7 @@ import TabItem from '@theme/TabItem'; -This is a basic introduction to adding and deleting __. +This is an introduction to adding and deleting __. ## Before you start @@ -260,7 +260,7 @@ For example, if you want to ensure `user:anne` has `reader` access to `document: /> :::caution -At the moment, this feature is only available on the API. Supported SDKs will follow shortly after. +At the moment, this feature is only available on the API (v1.10.0). Supported SDKs will follow shortly after. ::: Similarly, you can use `on_missing: "ignore"` when deleting tuples that might not exist. From 032c06a377ea58ad0217069fb05c183b504c76f8 Mon Sep 17 00:00:00 2001 From: Tyler Nix Date: Tue, 16 Sep 2025 11:31:24 -0600 Subject: [PATCH 10/20] Revert file name from add-tuples.mdx back to update-tuples.mdx --- docs/README.md | 2 +- docs/content/getting-started/framework.mdx | 4 ++-- docs/content/getting-started/overview.mdx | 4 ++-- docs/content/getting-started/perform-check.mdx | 12 ++++++------ .../content/getting-started/perform-list-objects.mdx | 12 ++++++------ docs/content/getting-started/perform-list-users.mdx | 12 ++++++------ .../getting-started/tuples-api-best-practices.mdx | 2 +- .../{add-tuples.mdx => update-tuples.mdx} | 6 +++--- docs/content/interacting/managing-user-access.mdx | 4 ++-- docs/content/interacting/read-tuple-changes.mdx | 6 +++--- docs/sidebars.js | 4 ++-- static/llms.txt | 2 +- 12 files changed, 35 insertions(+), 35 deletions(-) rename docs/content/getting-started/{add-tuples.mdx => update-tuples.mdx} (98%) diff --git a/docs/README.md b/docs/README.md index 3b53c775ad..cf63d9c80f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -32,7 +32,7 @@ It offers an HTTP API, a gRPC API, and has SDKs for programming languages includ - [Create a Store](./content/getting-started/create-store.mdx) - [Setup SDK Client for Store](./content/getting-started/setup-sdk-client.mdx) - [Configure Authorization Model](./content/getting-started/configure-model.mdx) - - [Add Relationship Tuples](./content/getting-started/add-tuples.mdx) + - [Update Relationship Tuples](./content/getting-started/update-tuples.mdx) - [Perform a Check](./content/getting-started/perform-check.mdx) - [Perform a List Objects Request](./content/getting-started/perform-list-objects.mdx) - [Use the FGA CLI](./content/getting-started/cli.mdx) diff --git a/docs/content/getting-started/framework.mdx b/docs/content/getting-started/framework.mdx index ffb732b6b4..052366df3f 100644 --- a/docs/content/getting-started/framework.mdx +++ b/docs/content/getting-started/framework.mdx @@ -32,7 +32,7 @@ This section will illustrate how to integrate 2. You have [installed the OpenFGA SDK](./install-sdk.mdx). -3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./add-tuples.mdx). +3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./update-tuples.mdx). 4. You know how to [perform a Check](./perform-check.mdx). 5. You have loaded `FGA_API_URL` and `FGA_STORE_ID` as environment variables. @@ -41,7 +41,7 @@ This section will illustrate how to integrate 2. You have [installed the OpenFGA SDK](./install-sdk.mdx). -3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./add-tuples.mdx). +3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./update-tuples.mdx). 4. You know how to [perform a Check](./perform-check.mdx). 5. You have loaded `FGA_API_URL` and `FGA_STORE_ID` as environment variables. diff --git a/docs/content/getting-started/overview.mdx b/docs/content/getting-started/overview.mdx index b324d2e453..d3341e7114 100644 --- a/docs/content/getting-started/overview.mdx +++ b/docs/content/getting-started/overview.mdx @@ -46,9 +46,9 @@ The following will provide a step-by-step guide on how to get started with 2. You have [installed the SDK](./install-sdk.mdx). -3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./add-tuples.mdx). +3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./update-tuples.mdx). 4. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables. @@ -54,7 +54,7 @@ This section will illustrate how to perform a 2. You have [installed the SDK](./install-sdk.mdx). -3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./add-tuples.mdx). +3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./update-tuples.mdx). 4. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables. @@ -72,7 +72,7 @@ This section will illustrate how to perform a 1. -2. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./add-tuples.mdx). +2. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./update-tuples.mdx). 3. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables. diff --git a/docs/content/getting-started/perform-list-objects.mdx b/docs/content/getting-started/perform-list-objects.mdx index cb77e36ac2..5e3f919011 100644 --- a/docs/content/getting-started/perform-list-objects.mdx +++ b/docs/content/getting-started/perform-list-objects.mdx @@ -34,7 +34,7 @@ This section describes how to perform a 2. You have [installed the SDK](./install-sdk.mdx). -3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./add-tuples.mdx). +3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./update-tuples.mdx). 4. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables. @@ -52,7 +52,7 @@ This section describes how to perform a 2. You have [installed the SDK](./install-sdk.mdx). -3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./add-tuples.mdx). +3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./update-tuples.mdx). 4. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables. @@ -70,7 +70,7 @@ This section describes how to perform a 1. -2. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./add-tuples.mdx). +2. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./update-tuples.mdx). 3. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables. diff --git a/docs/content/getting-started/perform-list-users.mdx b/docs/content/getting-started/perform-list-users.mdx index 9e2431ee6e..3307141839 100644 --- a/docs/content/getting-started/perform-list-users.mdx +++ b/docs/content/getting-started/perform-list-users.mdx @@ -35,7 +35,7 @@ This section will illustrate how to perform a 2. You have [installed the SDK](./install-sdk.mdx). -3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./add-tuples.mdx). +3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./update-tuples.mdx). 4. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables. @@ -53,7 +53,7 @@ This section will illustrate how to perform a 2. You have [installed the SDK](./install-sdk.mdx). -3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./add-tuples.mdx). +3. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./update-tuples.mdx). 4. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables. @@ -71,7 +71,7 @@ This section will illustrate how to perform a 1. -2. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./add-tuples.mdx). +2. You have [configured the _authorization model_](./configure-model.mdx) and [updated the _relationship tuples_](./update-tuples.mdx). 3. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables. diff --git a/docs/content/getting-started/tuples-api-best-practices.mdx b/docs/content/getting-started/tuples-api-best-practices.mdx index 6b4d35060b..bc3572869c 100644 --- a/docs/content/getting-started/tuples-api-best-practices.mdx +++ b/docs/content/getting-started/tuples-api-best-practices.mdx @@ -31,7 +31,7 @@ The documentation and samples uses first names and simple ids to illustrate easy ## Always specify authorization model ID whenever possible -It is strongly recommended that authorization model ID be specified in your Relationship Queries (such as [Check](./perform-check.mdx) and [ListObjects](../interacting/relationship-queries.mdx#listobjects)) and Relationship Commands (such as [Write](./add-tuples.mdx)). +It is strongly recommended that authorization model ID be specified in your Relationship Queries (such as [Check](./perform-check.mdx) and [ListObjects](../interacting/relationship-queries.mdx#listobjects)) and Relationship Commands (such as [Write](./update-tuples.mdx)). Specifying authorization model ID in API calls have the following advantages: 1. Better performance as will not need to perform a database query to get the latest authorization model ID. diff --git a/docs/content/getting-started/add-tuples.mdx b/docs/content/getting-started/update-tuples.mdx similarity index 98% rename from docs/content/getting-started/add-tuples.mdx rename to docs/content/getting-started/update-tuples.mdx index 6f5c71511e..6475c252d5 100644 --- a/docs/content/getting-started/add-tuples.mdx +++ b/docs/content/getting-started/update-tuples.mdx @@ -1,7 +1,7 @@ --- -title: Add Relationship Tuples +title: Update Relationship Tuples sidebar_position: 3 -slug: /getting-started/add-tuples +slug: /getting-started/update-tuples description: Introduction to adding and deleting relationship tuples --- @@ -20,7 +20,7 @@ import { import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -# Add Relationship Tuples +# Update Relationship Tuples diff --git a/docs/content/interacting/managing-user-access.mdx b/docs/content/interacting/managing-user-access.mdx index 130fa47870..d8fa4afa01 100644 --- a/docs/content/interacting/managing-user-access.mdx +++ b/docs/content/interacting/managing-user-access.mdx @@ -145,8 +145,8 @@ With this, we have removed the [direct relationship](../modeling/building-blocks { title: 'How to update relationship tuples', description: 'Learn about how to update relationship tuples in SDK.', - link: '../getting-started/add-tuples', - id: '../getting-started/add-tuples', + link: '../getting-started/update-tuples', + id: '../getting-started/update-tuples', }, ]} /> diff --git a/docs/content/interacting/read-tuple-changes.mdx b/docs/content/interacting/read-tuple-changes.mdx index ed8029e591..1b1ef31c3b 100644 --- a/docs/content/interacting/read-tuple-changes.mdx +++ b/docs/content/interacting/read-tuple-changes.mdx @@ -32,7 +32,7 @@ This section illustrates how to call the Read Changes API to get the list of rel 1. 2. You have [installed the SDK](../getting-started/install-sdk.mdx). -3. You have [configured the _authorization model_](../modeling) and [added some _relationship tuples_](../getting-started/add-tuples.mdx#02-calling-write-api-to-add-new-relationship-tuples). +3. You have [configured the _authorization model_](../modeling) and [added some _relationship tuples_](../getting-started/update-tuples.mdx#02-calling-write-api-to-add-new-relationship-tuples). 4. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables. @@ -40,7 +40,7 @@ This section illustrates how to call the Read Changes API to get the list of rel 1. 2. You have [installed the SDK](../getting-started/install-sdk.mdx). -3. You have [configured the _authorization model_](../modeling) and [added some _relationship tuples_](../getting-started/add-tuples.mdx#02-calling-write-api-to-add-new-relationship-tuples). +3. You have [configured the _authorization model_](../modeling) and [added some _relationship tuples_](../getting-started/update-tuples.mdx#02-calling-write-api-to-add-new-relationship-tuples). 4. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables. @@ -72,7 +72,7 @@ This section illustrates how to call the Read Changes API to get the list of rel 1. -2. You have [configured the _authorization model_](../modeling) and [added some _relationship tuples_](../getting-started/add-tuples.mdx#02-calling-write-api-to-add-new-relationship-tuples). +2. You have [configured the _authorization model_](../modeling) and [added some _relationship tuples_](../getting-started/update-tuples.mdx#02-calling-write-api-to-add-new-relationship-tuples). 3. You have loaded `FGA_STORE_ID` and `FGA_API_URL` as environment variables. diff --git a/docs/sidebars.js b/docs/sidebars.js index 68654d087e..f46572831c 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -100,8 +100,8 @@ const sidebars = { }, { type: 'doc', - label: 'Add Relationship Tuples', - id: 'content/getting-started/add-tuples', + label: 'Update Relationship Tuples', + id: 'content/getting-started/update-tuples', }, { type: 'doc', diff --git a/static/llms.txt b/static/llms.txt index c6d85e5f8d..9349a38405 100644 --- a/static/llms.txt +++ b/static/llms.txt @@ -45,7 +45,7 @@ OpenFGA implements authorization through: - [Create a Store](https://openfga.dev/docs/getting-started/create-store) - [Setup SDK Client for Store](https://openfga.dev/docs/getting-started/setup-sdk-client) - [Configure Authorization Model](https://openfga.dev/docs/getting-started/configure-model) - - [Add Relationship Tuples](https://openfga.dev/docs/getting-started/add-tuples) + - [Update Relationship Tuples](https://openfga.dev/docs/getting-started/update-tuples) - [Perform a Check](https://openfga.dev/docs/getting-started/perform-check) - [Perform a List Objects Request](https://openfga.dev/docs/getting-started/perform-list-objects) - [Perform a List Users Request](https://openfga.dev/docs/getting-started/perform-list-users) From 227a8a9c735b78448e6f8dfe6c110257e60abee4 Mon Sep 17 00:00:00 2001 From: Tyler Nix Date: Tue, 16 Sep 2025 11:50:10 -0600 Subject: [PATCH 11/20] quick fix --- docs/content/getting-started/update-tuples.mdx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/content/getting-started/update-tuples.mdx b/docs/content/getting-started/update-tuples.mdx index 6475c252d5..8abe7c6004 100644 --- a/docs/content/getting-started/update-tuples.mdx +++ b/docs/content/getting-started/update-tuples.mdx @@ -201,7 +201,9 @@ Assume that you want to delete user `user:anne`'s `reader` relationship with obj ### 04. Writing and deleting relationship tuples in the same request -You can combine both writes and deletes in a single transactional API request. This is useful when you need to update multiple relationships atomically. All operations in the request will either succeed together or fail together +You can combine both writes and deletes in a single transactional API request. This is useful when you need to update multiple relationships atomically. All operations in the request will either succeed together or fail together. + +The Write API allows you to send up to 100 unique tuples in the request. (This limit applies to the sum of both writes and deletes in that request). For example, you might want to remove `user:anne` as a `writer` of `document:Z` while simultaneously updating `user:anne` as an `reader` of `document:Z`: @@ -234,6 +236,11 @@ For example, you might want to remove `user:anne` as a `writer` of `document:Z` This approach ensures that both operations succeed or fail together, maintaining transactional data consistency. + +:::note +When using the Write API, you cannot include the same tuple (same user, relation, and object) in the writes or deletes arrays within a single request. The API will return an error with code `cannot_allow_duplicate_tuples_in_one_request` if detected. +::: + ### 05. Ignoring duplicate tuples Sometimes you might need to write a tuple that already exists, which would normally cause the whole request to fail. You can use the `on_duplicate: "ignore"` parameter to handle this gracefully. @@ -286,7 +293,7 @@ Similarly, you can use `on_missing: "ignore"` when deleting tuples that might no /> The behavior of `on_duplicate: "ignore"` is more nuanced for tuples with conditions. -- **Identical Tuples**: If a tuple in the request is 100% identical to an existing tuple (same user, relation, object, condition name, and condition context), it will be safely ignored. +- **Identical Tuples**: If a tuple in the request is identical to an existing tuple (same user, relation, object, condition name, and condition context), it will be safely ignored. - **Conflicting Tuples**: If a tuple key (user, relation, object) matches an existing tuple, but the condition is different, this is a conflict. The write attempt will be rejected, and the entire transaction will fail with a `409 Conflict` error. From 7d33e1bba4ee098413f52bd4d78d490d447d6ab1 Mon Sep 17 00:00:00 2001 From: Tyler Nix Date: Tue, 16 Sep 2025 11:59:55 -0600 Subject: [PATCH 12/20] Add auto-generated configuration constants and ConfigValue component - Enhanced update-config-page.mjs to generate TypeScript constants file - Created src/constants/openfga-config.ts with all OpenFGA config defaults - Added ConfigValue React component for referencing config values in MDX - Updated update-tuples.mdx to use ConfigValue for MAX_TUPLES_PER_WRITE - Ensures documentation stays synchronized with actual OpenFGA configuration schema - Constants are auto-regenerated on each build from latest OpenFGA release --- .../setup-openfga/configuration.mdx | 23 +- .../content/getting-started/update-tuples.mdx | 3 +- scripts/update-config-page.mjs | 129 +++- src/components/Docs/ConfigValue/index.tsx | 49 ++ src/components/Docs/index.ts | 1 + src/constants/openfga-config.ts | 725 ++++++++++++++++++ 6 files changed, 920 insertions(+), 10 deletions(-) create mode 100644 src/components/Docs/ConfigValue/index.tsx create mode 100644 src/constants/openfga-config.ts diff --git a/docs/content/getting-started/setup-openfga/configuration.mdx b/docs/content/getting-started/setup-openfga/configuration.mdx index 42e8798ec3..8d000d9b14 100644 --- a/docs/content/getting-started/setup-openfga/configuration.mdx +++ b/docs/content/getting-started/setup-openfga/configuration.mdx @@ -101,7 +101,7 @@ docker run docker.io/openfga/openfga:latest run \ ## List of options -The following table lists the configuration options for the OpenFGA server [v1.8.9](https://github.com/openfga/openfga/releases/tag/v1.8.9), based on the [config-schema.json](https://raw.githubusercontent.com/openfga/openfga/refs/tags/v1.8.9/.config-schema.json). +The following table lists the configuration options for the OpenFGA server [v1.10.0](https://github.com/openfga/openfga/releases/tag/v1.10.0), based on the [config-schema.json](https://raw.githubusercontent.com/openfga/openfga/refs/tags/v1.10.0/.config-schema.json). | Config File | Env Var | Flag Name | Type | Description | Default Value | |-------------|---------|-----------|------|-------------|---------------| @@ -116,7 +116,7 @@ The following table lists the configuration options for the OpenFGA server [v1.8 | `maxConditionEvaluationCost` |
OPENFGA_MAX_CONDITION_EVALUATION_COST
| `max-condition-evaluation-cost` | integer | The maximum cost for CEL condition evaluation before a request returns an error (default is 100). | `100` | | `changelogHorizonOffset` |
OPENFGA_CHANGELOG_HORIZON_OFFSET
| `changelog-horizon-offset` | integer | The offset (in minutes) from the current time. Changes that occur after this offset will not be included in the response of ReadChanges. | | | `resolveNodeLimit` |
OPENFGA_RESOLVE_NODE_LIMIT
| `resolve-node-limit` | integer | Maximum resolution depth to attempt before throwing an error (defines how deeply nested an authorization model can be before a query errors out). | `25` | -| `resolveNodeBreadthLimit` |
OPENFGA_RESOLVE_NODE_BREADTH_LIMIT
| `resolve-node-breadth-limit` | integer | Defines how many nodes on a given level can be evaluated concurrently in a Check resolution tree. | `100` | +| `resolveNodeBreadthLimit` |
OPENFGA_RESOLVE_NODE_BREADTH_LIMIT
| `resolve-node-breadth-limit` | integer | Defines how many nodes on a given level can be evaluated concurrently in a Check resolution tree. | `10` | | `listObjectsDeadline` |
OPENFGA_LIST_OBJECTS_DEADLINE
| `list-objects-deadline` | string (duration) | The timeout deadline for serving ListObjects requests | `3s` | | `listObjectsMaxResults` |
OPENFGA_LIST_OBJECTS_MAX_RESULTS
| `list-objects-max-results` | integer | The maximum results to return in the non-streaming ListObjects API response. If 0, all results can be returned | `1000` | | `listUsersDeadline` |
OPENFGA_LIST_USERS_DEADLINE
| `list-users-deadline` | string (duration) | The timeout deadline for serving ListUsers requests. If 0s, there is no deadline | `3s` | @@ -134,8 +134,11 @@ The following table lists the configuration options for the OpenFGA server [v1.8 | `profiler.addr` |
OPENFGA_PROFILER_ADDR
| `profiler-addr` | string | The host:port address to serve the pprof profiler server on. | `:3001` | | `datastore.engine` |
OPENFGA_DATASTORE_ENGINE
| `datastore-engine` | string (enum=[`memory`, `postgres`, `mysql`, `sqlite`]) | The datastore engine that will be used for persistence. | `memory` | | `datastore.uri` |
OPENFGA_DATASTORE_URI
| `datastore-uri` | string | The connection uri to use to connect to the datastore (for any engine other than 'memory'). | | +| `datastore.secondaryUri` |
OPENFGA_DATASTORE_SECONDARY_URI
| `datastore-secondary-uri` | string | The connection uri to use to connect to the secondary datastore (for postgres only). | | | `datastore.username` |
OPENFGA_DATASTORE_USERNAME
| `datastore-username` | string | The connection username to connect to the datastore (overwrites any username provided in the connection uri). | | +| `datastore.secondaryUsername` |
OPENFGA_DATASTORE_SECONDARY_USERNAME
| `datastore-secondary-username` | string | The connection username to connect to the secondary datastore (overwrites any username provided in the connection uri). | | | `datastore.password` |
OPENFGA_DATASTORE_PASSWORD
| `datastore-password` | string | The connection password to connect to the datastore (overwrites any password provided in the connection uri). | | +| `datastore.secondaryPassword` |
OPENFGA_DATASTORE_SECONDARY_PASSWORD
| `datastore-secondary-password` | string | The connection password to connect to the secondary datastore (overwrites any password provided in the connection uri). | | | `datastore.maxCacheSize` |
OPENFGA_DATASTORE_MAX_CACHE_SIZE
| `datastore-max-cache-size` | integer | The maximum number of authorization models that will be cached in memory | `100000` | | `datastore.maxOpenConns` |
OPENFGA_DATASTORE_MAX_OPEN_CONNS
| `datastore-max-open-conns` | integer | The maximum number of open connections to the datastore. | `30` | | `datastore.maxIdleConns` |
OPENFGA_DATASTORE_MAX_IDLE_CONNS
| `datastore-max-idle-conns` | integer | the maximum number of connections to the datastore in the idle connection pool. | `10` | @@ -185,15 +188,23 @@ The following table lists the configuration options for the OpenFGA server [v1.8 | `checkDispatchThrottling.frequency` |
OPENFGA_CHECK_DISPATCH_THROTTLING_FREQUENCY
| `check-dispatch-throttling-frequency` | string (duration) | the frequency period that the deprioritized throttling queue is evaluated for a check request. A higher value will result in more aggressive throttling | `10µs` | | `checkDispatchThrottling.threshold` |
OPENFGA_CHECK_DISPATCH_THROTTLING_THRESHOLD
| `check-dispatch-throttling-threshold` | integer | define the number of recursive operations to occur before getting throttled for a check request | `100` | | `checkDispatchThrottling.maxThreshold` |
OPENFGA_CHECK_DISPATCH_THROTTLING_MAX_THRESHOLD
| `check-dispatch-throttling-max-threshold` | integer | define the maximum dispatch threshold beyond above which requests will be throttled. 0 will use the 'dispatchThrottling.threshold' value as maximum | `0` | -| `listObjectsDispatchThrottling.enabled` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_ENABLED
| `list-objects-dispatch-throttling-enabled` | boolean | enable throttling when list objects request's number of dispatches is high | `false` | -| `listObjectsDispatchThrottling.frequency` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_FREQUENCY
| `list-objects-dispatch-throttling-frequency` | string (duration) | the frequency period that the deprioritized throttling queue is evaluated for a list objects request. A higher value will result in more aggressive throttling | `10µs` | -| `listObjectsDispatchThrottling.threshold` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_THRESHOLD
| `list-objects-dispatch-throttling-threshold` | integer | define the number of recursive operations to occur before getting throttled for a list objects request | `100` | -| `listObjectsDispatchThrottling.maxThreshold` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_MAX_THRESHOLD
| `list-objects-dispatch-throttling-max-threshold` | integer | define the maximum dispatch threshold beyond above which requests will be throttled for a list objects request. 0 will use the 'dispatchThrottling.threshold' value as maximum | `0` | +| `listObjectsIteratorCache.enabled` |
OPENFGA_LIST_OBJECTS_ITERATOR_CACHE_ENABLED
| `list-objects-iterator-cache-enabled` | boolean | enable caching of datastore iterators in ListObjects. The key is a string representing a database query, and the value is a list of tuples. Each iterator is the result of a database query, for example usersets related to a specific object, or objects related to a specific user, up to a certain number of tuples per iterator. If the request's consistency is HIGHER_CONSISTENCY, this cache is not used. | `false` | +| `listObjectsIteratorCache.maxResults` |
OPENFGA_LIST_OBJECTS_ITERATOR_CACHE_MAX_RESULTS
| `list-objects-iterator-cache-max-results` | integer | if caching of datastore iterators of ListObjects requests is enabled, this is the limit of tuples to cache per key | `10000` | +| `listObjectsIteratorCache.ttl` |
OPENFGA_LIST_OBJECTS_ITERATOR_CACHE_TTL
| `list-objects-iterator-cache-ttl` | string (duration) | if caching of datastore iterators of ListObjects requests is enabled, this is the TTL of each value | `10s` | +| `listObjectsDispatchThrottling.enabled` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_ENABLED
| `list-objects-dispatch-throttling-enabled` | boolean | enable throttling when ListObjects request's number of dispatches is high | `false` | +| `listObjectsDispatchThrottling.frequency` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_FREQUENCY
| `list-objects-dispatch-throttling-frequency` | string (duration) | the frequency period that the deprioritized throttling queue is evaluated for a ListObjects request. A higher value will result in more aggressive throttling | `10µs` | +| `listObjectsDispatchThrottling.threshold` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_THRESHOLD
| `list-objects-dispatch-throttling-threshold` | integer | define the number of recursive operations to occur before getting throttled for a ListObjects request | `100` | +| `listObjectsDispatchThrottling.maxThreshold` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_MAX_THRESHOLD
| `list-objects-dispatch-throttling-max-threshold` | integer | define the maximum dispatch threshold beyond above which requests will be throttled for a ListObjects request. 0 will use the 'dispatchThrottling.threshold' value as maximum | `0` | | `listUsersDispatchThrottling.enabled` |
OPENFGA_LIST_USERS_DISPATCH_THROTTLING_ENABLED
| `list-users-dispatch-throttling-enabled` | boolean | enable throttling when list users request's number of dispatches is high | `false` | | `listUsersDispatchThrottling.frequency` |
OPENFGA_LIST_USERS_DISPATCH_THROTTLING_FREQUENCY
| `list-users-dispatch-throttling-frequency` | string (duration) | the frequency period that the deprioritized throttling queue is evaluated for a list users request. A higher value will result in more aggressive throttling | `10µs` | | `listUsersDispatchThrottling.threshold` |
OPENFGA_LIST_USERS_DISPATCH_THROTTLING_THRESHOLD
| `list-users-dispatch-throttling-threshold` | integer | define the number of recursive operations to occur before getting throttled for a list users request | `100` | | `listUsersDispatchThrottling.maxThreshold` |
OPENFGA_LIST_USERS_DISPATCH_THROTTLING_MAX_THRESHOLD
| `list-users-dispatch-throttling-max-threshold` | integer | define the maximum dispatch threshold beyond above which requests will be throttled for a list users request. 0 will use the 'dispatchThrottling.threshold' value as maximum | `0` | +| `sharedIterator.enabled` |
OPENFGA_SHARED_ITERATOR_ENABLED
| `shared-iterator-enabled` | boolean | enabling sharing of datastore iterators with different consumers. Each iterator is the result of a database query, for example usersets related to a specific object, or objects related to a specific user, up to a certain number of tuples per iterator. | `false` | +| `sharedIterator.limit` |
OPENFGA_SHARED_ITERATOR_LIMIT
| `shared-iterator-limit` | integer | if shared-iterator-enabled is enabled, this is the limit of the number of iterators that can be shared. | `1000000` | | `requestTimeout` |
OPENFGA_REQUEST_TIMEOUT
| `request-timeout` | string (duration) | The timeout duration for a request. | `3s` | +| `planner.initialGuess` |
OPENFGA_PLANNER_INITIAL_GUESS
| `planner-initial-guess` | string (duration) | The initial guess for the planners estimation. | `10ms` | +| `planner.evictionThreshold` |
OPENFGA_PLANNER_EVICTION_THRESHOLD
| `planner-eviction-threshold` | | How long a planner key can be unused before being evicted. | `0` | +| `planner.cleanupInterval` |
OPENFGA_PLANNER_CLEANUP_INTERVAL
| `planner-cleanup-interval` | string (duration) | How often the planner checks for stale keys. | `0` | ## Related Sections diff --git a/docs/content/getting-started/update-tuples.mdx b/docs/content/getting-started/update-tuples.mdx index 8abe7c6004..50107b2f47 100644 --- a/docs/content/getting-started/update-tuples.mdx +++ b/docs/content/getting-started/update-tuples.mdx @@ -16,6 +16,7 @@ import { ProductNameFormat, WriteRequestViewer, SdkSetupPrerequisite, + ConfigValue, } from '@components/Docs'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; @@ -203,7 +204,7 @@ Assume that you want to delete user `user:anne`'s `reader` relationship with obj You can combine both writes and deletes in a single transactional API request. This is useful when you need to update multiple relationships atomically. All operations in the request will either succeed together or fail together. -The Write API allows you to send up to 100 unique tuples in the request. (This limit applies to the sum of both writes and deletes in that request). +The Write API allows you to send up to unique tuples in the request. (This limit applies to the sum of both writes and deletes in that request). For example, you might want to remove `user:anne` as a `writer` of `document:Z` while simultaneously updating `user:anne` as an `reader` of `document:Z`: diff --git a/scripts/update-config-page.mjs b/scripts/update-config-page.mjs index 2835482d7e..d285dd72dc 100644 --- a/scripts/update-config-page.mjs +++ b/scripts/update-config-page.mjs @@ -62,6 +62,50 @@ function parseValue(type, value) { } } +/** + * Extracts configuration constants from the schema + * @param properties + * @param jsonData + * @param parentKey + * @returns {Promise} + */ +async function extractConstants(properties, jsonData, parentKey = '') { + let constants = {}; + + for (const [key, value] of Object.entries(properties)) { + if (value.$ref) { + const refSchema = await resolveRef(value.$ref, jsonData); + const nestedConstants = await extractConstants(refSchema.properties, jsonData, parentKey ? `${parentKey}.${key}` : key); + constants = { ...constants, ...nestedConstants }; + } else { + const fullKey = parentKey ? `${parentKey}.${key}` : key; + + // Only include constants that have default values or are arrays (which can default to empty array) + if (value.type !== 'object' && (value.default !== undefined || value.type === 'array')) { + // Convert config key to constant name (e.g., maxTuplesPerWrite -> MAX_TUPLES_PER_WRITE) + const constantName = fullKey + .replace(/\./g, '_') + .replace(/([a-z])([A-Z])/g, '$1_$2') + .toUpperCase(); + + constants[constantName] = { + value: value.default !== undefined ? value.default : (value.type === 'array' ? [] : null), + type: value.type, + description: value.description || '', + configKey: fullKey + }; + } + + if (value.type === 'object' && value.properties) { + const nestedConstants = await extractConstants(value.properties, jsonData, fullKey); + constants = { ...constants, ...nestedConstants }; + } + } + } + + return constants; +} + /** * Processes the properties of a schema and returns the MDX content * @param properties @@ -97,6 +141,72 @@ async function processProperties(properties, jsonData, parentKey = '') { return mdxContent; } +/** + * Generates a TypeScript constants file from the extracted constants + * @param constants + * @param releaseData + * @returns {string} + */ +function generateConstantsFile(constants, releaseData) { + let content = `// Auto-generated OpenFGA configuration constants +// Generated from ${releaseData.release} config schema +// Do not edit this file manually - it will be overwritten + +/** + * OpenFGA Configuration Default Values + * + * These constants are extracted from the OpenFGA ${releaseData.release} configuration schema. + * They represent the default values for various configuration options. + */ + +`; + + // Sort constants by name for consistency + const sortedConstants = Object.entries(constants).sort(([a], [b]) => a.localeCompare(b)); + + for (const [constantName, config] of sortedConstants) { + // Add JSDoc comment with description and config key + content += `/**\n`; + content += ` * ${config.description || 'Configuration option'}\n`; + content += ` * Config key: ${config.configKey}\n`; + content += ` * Type: ${config.type}\n`; + content += ` */\n`; + + // Generate the constant declaration + if (config.type === 'string') { + content += `export const ${constantName} = '${config.value}';\n\n`; + } else if (config.type === 'boolean') { + content += `export const ${constantName} = ${config.value};\n\n`; + } else if (config.type === 'array') { + // Handle array values - use empty array if no default + const arrayValue = Array.isArray(config.value) ? JSON.stringify(config.value) : '[]'; + content += `export const ${constantName} = ${arrayValue};\n\n`; + } else { + content += `export const ${constantName} = ${config.value};\n\n`; + } + } + + // Add a convenience object with all constants + content += `/**\n`; + content += ` * All OpenFGA configuration default values in a single object\n`; + content += ` */\n`; + content += `export const OPENFGA_CONFIG_DEFAULTS = {\n`; + + for (const [constantName] of sortedConstants) { + content += ` ${constantName},\n`; + } + + content += `} as const;\n\n`; + + // Add type definitions + content += `/**\n`; + content += ` * Type representing all available OpenFGA configuration constants\n`; + content += ` */\n`; + content += `export type OpenFGAConfigConstants = typeof OPENFGA_CONFIG_DEFAULTS;\n`; + + return content; +} + /** * Generates the MDX content for the .config-schema.json file * @param releaseData {release: string, url: string, releaseUrl: string} @@ -295,21 +405,34 @@ async function getFileData() { } const OUTPUT_FILE = 'docs/content/getting-started/setup-openfga/configuration.mdx'; +const CONSTANTS_OUTPUT_FILE = 'src/constants/openfga-config.ts'; + /** - * Downloads the .config-schema for the latest release of OpenFGA and generates the MDX content for it + * Downloads the .config-schema for the latest release of OpenFGA and generates the MDX content and constants file * @returns {Promise<{release: string, url: string, releaseUrl: string}>} */ async function generateMdxForLatestRelease() { const releaseData = await getFileData(); const jsonData = await performHttpRequest(releaseData.url); + + // Generate the MDX content const mdxContent = await getMdxContent(releaseData, jsonData); - await fs.writeFile(OUTPUT_FILE, mdxContent, 'utf8'); + + // Extract constants and generate constants file + if (jsonData.properties) { + const constants = await extractConstants(jsonData.properties, jsonData); + const constantsContent = generateConstantsFile(constants, releaseData); + + // Ensure the constants directory exists + await fs.mkdir('src/constants', { recursive: true }); + await fs.writeFile(CONSTANTS_OUTPUT_FILE, constantsContent, 'utf8'); + } return releaseData; } generateMdxForLatestRelease() - .then(({ release, url }) => console.log(`Downloaded .config-schema.json file for ${release} from ${url} and saved the generated file at ${OUTPUT_FILE}`)) + .then(({ release, url }) => console.log(`Downloaded .config-schema.json file for ${release} from ${url} and saved the generated files at ${OUTPUT_FILE} and ${CONSTANTS_OUTPUT_FILE}`)) .catch(console.error); \ No newline at end of file diff --git a/src/components/Docs/ConfigValue/index.tsx b/src/components/Docs/ConfigValue/index.tsx new file mode 100644 index 0000000000..583f3f2f98 --- /dev/null +++ b/src/components/Docs/ConfigValue/index.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import * as OpenFGAConfig from '../../../constants/openfga-config'; + +interface ConfigValueProps { + /** + * The name of the configuration constant to display + * Should match the exported constant name from openfga-config.ts + */ + name: keyof typeof OpenFGAConfig.OPENFGA_CONFIG_DEFAULTS; + + /** + * Optional format for the value display + */ + format?: 'code' | 'text'; +} + +/** + * Component to display OpenFGA configuration default values + * + * This component automatically fetches the current default value from the + * auto-generated configuration constants, ensuring the documentation + * stays synchronized with the actual OpenFGA configuration schema. + * + * @example + * ```mdx + * The Write API allows up to tuples per request. + * ``` + */ +export const ConfigValue: React.FC = ({ + name, + format = 'code' +}) => { + const value = OpenFGAConfig.OPENFGA_CONFIG_DEFAULTS[name]; + + if (value === undefined) { + console.warn(`ConfigValue: Unknown configuration constant "${name}"`); + return [Unknown config: {name}]; + } + + const displayValue = String(value); + + if (format === 'code') { + return {displayValue}; + } + + return {displayValue}; +}; + +export default ConfigValue; diff --git a/src/components/Docs/index.ts b/src/components/Docs/index.ts index 676d8b02ff..cdd5a18def 100644 --- a/src/components/Docs/index.ts +++ b/src/components/Docs/index.ts @@ -2,6 +2,7 @@ export * from './AuthorizationModel'; export * from './Banner'; export * from './CardBox'; export * from './Column'; +export * from './ConfigValue'; export * from './DocumentationNotice'; export * from './Feedback'; export * from './Overview'; diff --git a/src/constants/openfga-config.ts b/src/constants/openfga-config.ts new file mode 100644 index 0000000000..a2d0b876f2 --- /dev/null +++ b/src/constants/openfga-config.ts @@ -0,0 +1,725 @@ +// Auto-generated OpenFGA configuration constants +// Generated from v1.10.0 config schema +// Do not edit this file manually - it will be overwritten + +/** + * OpenFGA Configuration Default Values + * + * These constants are extracted from the OpenFGA v1.10.0 configuration schema. + * They represent the default values for various configuration options. + */ + +/** + * Enable/disable the access control store. + * Config key: accessControl.enabled + * Type: boolean + */ +export const ACCESS_CONTROL_ENABLED = false; + +/** + * The modelId to be used for the access control store. + * Config key: accessControl.modelId + * Type: string + */ +export const ACCESS_CONTROL_MODEL_ID = ''; + +/** + * The storeId to be used for the access control store. + * Config key: accessControl.storeId + * Type: string + */ +export const ACCESS_CONTROL_STORE_ID = ''; + +/** + * The authentication method to use. + * Config key: authn.method + * Type: string + */ +export const AUTHN_METHOD = 'none'; + +/** + * the OIDC client id claims that will be used to parse the clientID - configure in order of priority (first is highest). Defaults to [`azp`, `client_id`] + * Config key: authn.oidc.clientIdClaims + * Type: array + */ +export const AUTHN_OIDC_CLIENT_ID_CLAIMS = []; + +/** + * the OIDC issuer DNS aliases that will be accepted as valid when verifying the `iss` field of the JWTs. + * Config key: authn.oidc.issuerAliases + * Type: array + */ +export const AUTHN_OIDC_ISSUER_ALIASES = []; + +/** + * the OIDC subject names that will be accepted as valid when verifying the `sub` field of the JWTs. If empty, every `sub` will be allowed + * Config key: authn.oidc.subjects + * Type: array + */ +export const AUTHN_OIDC_SUBJECTS = []; + +/** + * List of preshared keys used for authentication + * Config key: authn.preshared.keys + * Type: array + */ +export const AUTHN_PRESHARED_KEYS = []; + +/** + * enabling dynamic invalidation of check query cache and check iterator cache based on whether there are recent tuple writes. If enabled, cache will be invalidated when either 1) there are tuples written to the store OR 2) the check query cache or check iterator cache TTL has expired. + * Config key: cacheController.enabled + * Type: boolean + */ +export const CACHE_CONTROLLER_ENABLED = false; + +/** + * if cache controller is enabled, control how frequent read changes are invoked internally to query for recent tuple writes to the store. + * Config key: cacheController.ttl + * Type: string + */ +export const CACHE_CONTROLLER_TTL = '10s'; + +/** + * The offset (in minutes) from the current time. Changes that occur after this offset will not be included in the response of ReadChanges. + * Config key: changelogHorizonOffset + * Type: integer + */ +export const CHANGELOG_HORIZON_OFFSET = 0; + +/** + * the size limit (in items) of the cache for Check (queries and iterators) + * Config key: checkCache.limit + * Type: integer + */ +export const CHECK_CACHE_LIMIT = 10000; + +/** + * enable throttling when check request's number of dispatches is high + * Config key: checkDispatchThrottling.enabled + * Type: boolean + */ +export const CHECK_DISPATCH_THROTTLING_ENABLED = false; + +/** + * the frequency period that the deprioritized throttling queue is evaluated for a check request. A higher value will result in more aggressive throttling + * Config key: checkDispatchThrottling.frequency + * Type: string + */ +export const CHECK_DISPATCH_THROTTLING_FREQUENCY = '10µs'; + +/** + * define the maximum dispatch threshold beyond above which requests will be throttled. 0 will use the 'dispatchThrottling.threshold' value as maximum + * Config key: checkDispatchThrottling.maxThreshold + * Type: integer + */ +export const CHECK_DISPATCH_THROTTLING_MAX_THRESHOLD = 0; + +/** + * define the number of recursive operations to occur before getting throttled for a check request + * Config key: checkDispatchThrottling.threshold + * Type: integer + */ +export const CHECK_DISPATCH_THROTTLING_THRESHOLD = 100; + +/** + * enable caching of datastore iterators. The key is a string representing a database query, and the value is a list of tuples. Each iterator is the result of a database query, for example usersets related to a specific object, or objects related to a specific user, up to a certain number of tuples per iterator. If the request's consistency is HIGHER_CONSISTENCY, this cache is not used. + * Config key: checkIteratorCache.enabled + * Type: boolean + */ +export const CHECK_ITERATOR_CACHE_ENABLED = false; + +/** + * if caching of datastore iterators of Check requests is enabled, this is the limit of tuples to cache per key + * Config key: checkIteratorCache.maxResults + * Type: integer + */ +export const CHECK_ITERATOR_CACHE_MAX_RESULTS = 10000; + +/** + * if caching of datastore iterators of Check requests is enabled, this is the TTL of each value + * Config key: checkIteratorCache.ttl + * Type: string + */ +export const CHECK_ITERATOR_CACHE_TTL = '10s'; + +/** + * enable caching of Check requests. The key is a string representing a query, and the value is a boolean. For example, if you have a relation `define viewer: owner or editor`, and the query is Check(user:anne, viewer, doc:1), we'll evaluate the `owner` relation and the `editor` relation and cache both results: (user:anne, viewer, doc:1) -> allowed=true and (user:anne, owner, doc:1) -> allowed=true. The cache is stored in-memory; the cached values are overwritten on every change in the result, and cleared after the configured TTL. This flag improves latency, but turns Check and ListObjects into eventually consistent APIs. If the request's consistency is HIGHER_CONSISTENCY, this cache is not used. + * Config key: checkQueryCache.enabled + * Type: boolean + */ +export const CHECK_QUERY_CACHE_ENABLED = false; + +/** + * DEPRECATED use OPENFGA_CHECK_CACHE_LIMIT. If caching of Check and ListObjects calls is enabled, this is the size limit (in items) of the cache + * Config key: checkQueryCache.limit + * Type: integer + */ +export const CHECK_QUERY_CACHE_LIMIT = 10000; + +/** + * if caching of Check and ListObjects is enabled, this is the TTL of each value + * Config key: checkQueryCache.ttl + * Type: string + */ +export const CHECK_QUERY_CACHE_TTL = '10s'; + +/** + * Propagate a requests context to the datastore implementation. Settings this parameter can result in connection pool draining on request aborts and timeouts. + * Config key: contextPropagationToDatastore + * Type: boolean + */ +export const CONTEXT_PROPAGATION_TO_DATASTORE = false; + +/** + * the maximum amount of time a connection to the datastore may be idle + * Config key: datastore.connMaxIdleTime + * Type: string + */ +export const DATASTORE_CONN_MAX_IDLE_TIME = '0s'; + +/** + * the maximum amount of time a connection to the datastore may be reused + * Config key: datastore.connMaxLifetime + * Type: string + */ +export const DATASTORE_CONN_MAX_LIFETIME = '0s'; + +/** + * The datastore engine that will be used for persistence. + * Config key: datastore.engine + * Type: string + */ +export const DATASTORE_ENGINE = 'memory'; + +/** + * The maximum number of authorization models that will be cached in memory + * Config key: datastore.maxCacheSize + * Type: integer + */ +export const DATASTORE_MAX_CACHE_SIZE = 100000; + +/** + * the maximum number of connections to the datastore in the idle connection pool. + * Config key: datastore.maxIdleConns + * Type: integer + */ +export const DATASTORE_MAX_IDLE_CONNS = 10; + +/** + * The maximum number of open connections to the datastore. + * Config key: datastore.maxOpenConns + * Type: integer + */ +export const DATASTORE_MAX_OPEN_CONNS = 30; + +/** + * enable/disable sql metrics for the datastore + * Config key: datastore.metrics.enabled + * Type: boolean + */ +export const DATASTORE_METRICS_ENABLED = false; + +/** + * a list of experimental features to enable + * Config key: experimentals + * Type: array + */ +export const EXPERIMENTALS = []; + +/** + * The host:port address to serve the grpc server on. + * Config key: grpc.addr + * Type: string + */ +export const GRPC_ADDR = '0.0.0.0:8081'; + +/** + * Enables or disables transport layer security (TLS). + * Config key: grpc.tls.enabled + * Type: boolean + */ +export const GRPC_TLS_ENABLED = false; + +/** + * The host:port address to serve the HTTP server on. + * Config key: http.addr + * Type: string + */ +export const HTTP_ADDR = '0.0.0.0:8080'; + +/** + * List of allowed headers for CORS requests + * Config key: http.corsAllowedHeaders + * Type: array + */ +export const HTTP_CORS_ALLOWED_HEADERS = ["*"]; + +/** + * List of allowed origins for CORS requests + * Config key: http.corsAllowedOrigins + * Type: array + */ +export const HTTP_CORS_ALLOWED_ORIGINS = ["*"]; + +/** + * Enables or disables the OpenFGA HTTP server. If this is set to true then 'grpc.enabled' must be set to true. + * Config key: http.enabled + * Type: boolean + */ +export const HTTP_ENABLED = true; + +/** + * Enables or disables transport layer security (TLS). + * Config key: http.tls.enabled + * Type: boolean + */ +export const HTTP_TLS_ENABLED = false; + +/** + * The timeout duration for proxying HTTP requests upstream to the grpc endpoint. + * Config key: http.upstreamTimeout + * Type: string + */ +export const HTTP_UPSTREAM_TIMEOUT = '3s'; + +/** + * The timeout deadline for serving ListObjects requests + * Config key: listObjectsDeadline + * Type: string + */ +export const LIST_OBJECTS_DEADLINE = '3s'; + +/** + * enable throttling when ListObjects request's number of dispatches is high + * Config key: listObjectsDispatchThrottling.enabled + * Type: boolean + */ +export const LIST_OBJECTS_DISPATCH_THROTTLING_ENABLED = false; + +/** + * the frequency period that the deprioritized throttling queue is evaluated for a ListObjects request. A higher value will result in more aggressive throttling + * Config key: listObjectsDispatchThrottling.frequency + * Type: string + */ +export const LIST_OBJECTS_DISPATCH_THROTTLING_FREQUENCY = '10µs'; + +/** + * define the maximum dispatch threshold beyond above which requests will be throttled for a ListObjects request. 0 will use the 'dispatchThrottling.threshold' value as maximum + * Config key: listObjectsDispatchThrottling.maxThreshold + * Type: integer + */ +export const LIST_OBJECTS_DISPATCH_THROTTLING_MAX_THRESHOLD = 0; + +/** + * define the number of recursive operations to occur before getting throttled for a ListObjects request + * Config key: listObjectsDispatchThrottling.threshold + * Type: integer + */ +export const LIST_OBJECTS_DISPATCH_THROTTLING_THRESHOLD = 100; + +/** + * enable caching of datastore iterators in ListObjects. The key is a string representing a database query, and the value is a list of tuples. Each iterator is the result of a database query, for example usersets related to a specific object, or objects related to a specific user, up to a certain number of tuples per iterator. If the request's consistency is HIGHER_CONSISTENCY, this cache is not used. + * Config key: listObjectsIteratorCache.enabled + * Type: boolean + */ +export const LIST_OBJECTS_ITERATOR_CACHE_ENABLED = false; + +/** + * if caching of datastore iterators of ListObjects requests is enabled, this is the limit of tuples to cache per key + * Config key: listObjectsIteratorCache.maxResults + * Type: integer + */ +export const LIST_OBJECTS_ITERATOR_CACHE_MAX_RESULTS = 10000; + +/** + * if caching of datastore iterators of ListObjects requests is enabled, this is the TTL of each value + * Config key: listObjectsIteratorCache.ttl + * Type: string + */ +export const LIST_OBJECTS_ITERATOR_CACHE_TTL = '10s'; + +/** + * The maximum results to return in the non-streaming ListObjects API response. If 0, all results can be returned + * Config key: listObjectsMaxResults + * Type: integer + */ +export const LIST_OBJECTS_MAX_RESULTS = 1000; + +/** + * The timeout deadline for serving ListUsers requests. If 0s, there is no deadline + * Config key: listUsersDeadline + * Type: string + */ +export const LIST_USERS_DEADLINE = '3s'; + +/** + * enable throttling when list users request's number of dispatches is high + * Config key: listUsersDispatchThrottling.enabled + * Type: boolean + */ +export const LIST_USERS_DISPATCH_THROTTLING_ENABLED = false; + +/** + * the frequency period that the deprioritized throttling queue is evaluated for a list users request. A higher value will result in more aggressive throttling + * Config key: listUsersDispatchThrottling.frequency + * Type: string + */ +export const LIST_USERS_DISPATCH_THROTTLING_FREQUENCY = '10µs'; + +/** + * define the maximum dispatch threshold beyond above which requests will be throttled for a list users request. 0 will use the 'dispatchThrottling.threshold' value as maximum + * Config key: listUsersDispatchThrottling.maxThreshold + * Type: integer + */ +export const LIST_USERS_DISPATCH_THROTTLING_MAX_THRESHOLD = 0; + +/** + * define the number of recursive operations to occur before getting throttled for a list users request + * Config key: listUsersDispatchThrottling.threshold + * Type: integer + */ +export const LIST_USERS_DISPATCH_THROTTLING_THRESHOLD = 100; + +/** + * The maximum results to return in ListUsers API response. If 0, all results can be returned + * Config key: listUsersMaxResults + * Type: integer + */ +export const LIST_USERS_MAX_RESULTS = 1000; + +/** + * The log format to output logs in. For production we recommend 'json' format. + * Config key: log.format + * Type: string + */ +export const LOG_FORMAT = 'text'; + +/** + * The log level to set. For production we recommend 'info' format. + * Config key: log.level + * Type: string + */ +export const LOG_LEVEL = 'info'; + +/** + * The timestamp format to use for the log output. + * Config key: log.timestampFormat + * Type: string + */ +export const LOG_TIMESTAMP_FORMAT = 'Unix'; + +/** + * The maximum size in bytes allowed for persisting an Authorization Model (default is 256KB). + * Config key: maxAuthorizationModelSizeInBytes + * Type: integer + */ +export const MAX_AUTHORIZATION_MODEL_SIZE_IN_BYTES = 262144; + +/** + * The maximum number of tuples allowed in a BatchCheck request. + * Config key: maxChecksPerBatchCheck + * Type: integer + */ +export const MAX_CHECKS_PER_BATCH_CHECK = 50; + +/** + * The maximum number of checks that can be processed concurrently in a batch check request. + * Config key: maxConcurrentChecksPerBatchCheck + * Type: integer + */ +export const MAX_CONCURRENT_CHECKS_PER_BATCH_CHECK = 50; + +/** + * The maximum allowed number of concurrent reads in a single Check query (default is MaxUint32). + * Config key: maxConcurrentReadsForCheck + * Type: integer + */ +export const MAX_CONCURRENT_READS_FOR_CHECK = 4294967295; + +/** + * The maximum allowed number of concurrent reads in a single ListObjects query (default is MaxUint32). + * Config key: maxConcurrentReadsForListObjects + * Type: integer + */ +export const MAX_CONCURRENT_READS_FOR_LIST_OBJECTS = 4294967295; + +/** + * The maximum allowed number of concurrent reads in a single ListUsers query (default is MaxUint32). + * Config key: maxConcurrentReadsForListUsers + * Type: integer + */ +export const MAX_CONCURRENT_READS_FOR_LIST_USERS = 4294967295; + +/** + * The maximum cost for CEL condition evaluation before a request returns an error (default is 100). + * Config key: maxConditionEvaluationCost + * Type: integer + */ +export const MAX_CONDITION_EVALUATION_COST = 100; + +/** + * The maximum allowed number of tuples per Write transaction. + * Config key: maxTuplesPerWrite + * Type: integer + */ +export const MAX_TUPLES_PER_WRITE = 100; + +/** + * The maximum allowed number of type definitions per authorization model. + * Config key: maxTypesPerAuthorizationModel + * Type: integer + */ +export const MAX_TYPES_PER_AUTHORIZATION_MODEL = 100; + +/** + * the host:port address to serve the prometheus metrics server on + * Config key: metrics.addr + * Type: string + */ +export const METRICS_ADDR = '0.0.0.0:2112'; + +/** + * enables prometheus histogram metrics for RPC latency distributions + * Config key: metrics.enableRPCHistograms + * Type: boolean + */ +export const METRICS_ENABLE_RPCHISTOGRAMS = false; + +/** + * enable/disable prometheus metrics on the '/metrics' endpoint + * Config key: metrics.enabled + * Type: boolean + */ +export const METRICS_ENABLED = true; + +/** + * How often the planner checks for stale keys. + * Config key: planner.cleanupInterval + * Type: string + */ +export const PLANNER_CLEANUP_INTERVAL = '0'; + +/** + * How long a planner key can be unused before being evicted. + * Config key: planner.evictionThreshold + * Type: undefined + */ +export const PLANNER_EVICTION_THRESHOLD = 0; + +/** + * The initial guess for the planners estimation. + * Config key: planner.initialGuess + * Type: string + */ +export const PLANNER_INITIAL_GUESS = '10ms'; + +/** + * Enable/disable the OpenFGA Playground. + * Config key: playground.enabled + * Type: boolean + */ +export const PLAYGROUND_ENABLED = true; + +/** + * The port to serve the local OpenFGA Playground on. + * Config key: playground.port + * Type: integer + */ +export const PLAYGROUND_PORT = 3000; + +/** + * The host:port address to serve the pprof profiler server on. + * Config key: profiler.addr + * Type: string + */ +export const PROFILER_ADDR = ':3001'; + +/** + * Enabled/disable pprof profiling. + * Config key: profiler.enabled + * Type: boolean + */ +export const PROFILER_ENABLED = false; + +/** + * Datastore query count buckets used to label the histogram metric for measuring request duration. + * Config key: requestDurationDatastoreQueryCountBuckets + * Type: array + */ +export const REQUEST_DURATION_DATASTORE_QUERY_COUNT_BUCKETS = [50,200]; + +/** + * Dispatch count buckets used to label the histogram metric for measuring request duration. + * Config key: requestDurationDispatchCountBuckets + * Type: array + */ +export const REQUEST_DURATION_DISPATCH_COUNT_BUCKETS = [50,200]; + +/** + * The timeout duration for a request. + * Config key: requestTimeout + * Type: string + */ +export const REQUEST_TIMEOUT = '3s'; + +/** + * Defines how many nodes on a given level can be evaluated concurrently in a Check resolution tree. + * Config key: resolveNodeBreadthLimit + * Type: integer + */ +export const RESOLVE_NODE_BREADTH_LIMIT = 10; + +/** + * Maximum resolution depth to attempt before throwing an error (defines how deeply nested an authorization model can be before a query errors out). + * Config key: resolveNodeLimit + * Type: integer + */ +export const RESOLVE_NODE_LIMIT = 25; + +/** + * enabling sharing of datastore iterators with different consumers. Each iterator is the result of a database query, for example usersets related to a specific object, or objects related to a specific user, up to a certain number of tuples per iterator. + * Config key: sharedIterator.enabled + * Type: boolean + */ +export const SHARED_ITERATOR_ENABLED = false; + +/** + * if shared-iterator-enabled is enabled, this is the limit of the number of iterators that can be shared. + * Config key: sharedIterator.limit + * Type: integer + */ +export const SHARED_ITERATOR_LIMIT = 1000000; + +/** + * Enable tracing. + * Config key: trace.enabled + * Type: boolean + */ +export const TRACE_ENABLED = false; + +/** + * The grpc endpoint of the trace collector + * Config key: trace.otlp.endpoint + * Type: string + */ +export const TRACE_OTLP_ENDPOINT = '0.0.0.0:4317'; + +/** + * Whether to use TLS connection for the trace collector + * Config key: trace.otlp.tls.enabled + * Type: boolean + */ +export const TRACE_OTLP_TLS_ENABLED = false; + +/** + * The fraction of traces to sample. 1 means all, 0 means none. + * Config key: trace.sampleRatio + * Type: number + */ +export const TRACE_SAMPLE_RATIO = 0.2; + +/** + * The service name included in sampled traces. + * Config key: trace.serviceName + * Type: string + */ +export const TRACE_SERVICE_NAME = 'openfga'; + +/** + * All OpenFGA configuration default values in a single object + */ +export const OPENFGA_CONFIG_DEFAULTS = { + ACCESS_CONTROL_ENABLED, + ACCESS_CONTROL_MODEL_ID, + ACCESS_CONTROL_STORE_ID, + AUTHN_METHOD, + AUTHN_OIDC_CLIENT_ID_CLAIMS, + AUTHN_OIDC_ISSUER_ALIASES, + AUTHN_OIDC_SUBJECTS, + AUTHN_PRESHARED_KEYS, + CACHE_CONTROLLER_ENABLED, + CACHE_CONTROLLER_TTL, + CHANGELOG_HORIZON_OFFSET, + CHECK_CACHE_LIMIT, + CHECK_DISPATCH_THROTTLING_ENABLED, + CHECK_DISPATCH_THROTTLING_FREQUENCY, + CHECK_DISPATCH_THROTTLING_MAX_THRESHOLD, + CHECK_DISPATCH_THROTTLING_THRESHOLD, + CHECK_ITERATOR_CACHE_ENABLED, + CHECK_ITERATOR_CACHE_MAX_RESULTS, + CHECK_ITERATOR_CACHE_TTL, + CHECK_QUERY_CACHE_ENABLED, + CHECK_QUERY_CACHE_LIMIT, + CHECK_QUERY_CACHE_TTL, + CONTEXT_PROPAGATION_TO_DATASTORE, + DATASTORE_CONN_MAX_IDLE_TIME, + DATASTORE_CONN_MAX_LIFETIME, + DATASTORE_ENGINE, + DATASTORE_MAX_CACHE_SIZE, + DATASTORE_MAX_IDLE_CONNS, + DATASTORE_MAX_OPEN_CONNS, + DATASTORE_METRICS_ENABLED, + EXPERIMENTALS, + GRPC_ADDR, + GRPC_TLS_ENABLED, + HTTP_ADDR, + HTTP_CORS_ALLOWED_HEADERS, + HTTP_CORS_ALLOWED_ORIGINS, + HTTP_ENABLED, + HTTP_TLS_ENABLED, + HTTP_UPSTREAM_TIMEOUT, + LIST_OBJECTS_DEADLINE, + LIST_OBJECTS_DISPATCH_THROTTLING_ENABLED, + LIST_OBJECTS_DISPATCH_THROTTLING_FREQUENCY, + LIST_OBJECTS_DISPATCH_THROTTLING_MAX_THRESHOLD, + LIST_OBJECTS_DISPATCH_THROTTLING_THRESHOLD, + LIST_OBJECTS_ITERATOR_CACHE_ENABLED, + LIST_OBJECTS_ITERATOR_CACHE_MAX_RESULTS, + LIST_OBJECTS_ITERATOR_CACHE_TTL, + LIST_OBJECTS_MAX_RESULTS, + LIST_USERS_DEADLINE, + LIST_USERS_DISPATCH_THROTTLING_ENABLED, + LIST_USERS_DISPATCH_THROTTLING_FREQUENCY, + LIST_USERS_DISPATCH_THROTTLING_MAX_THRESHOLD, + LIST_USERS_DISPATCH_THROTTLING_THRESHOLD, + LIST_USERS_MAX_RESULTS, + LOG_FORMAT, + LOG_LEVEL, + LOG_TIMESTAMP_FORMAT, + MAX_AUTHORIZATION_MODEL_SIZE_IN_BYTES, + MAX_CHECKS_PER_BATCH_CHECK, + MAX_CONCURRENT_CHECKS_PER_BATCH_CHECK, + MAX_CONCURRENT_READS_FOR_CHECK, + MAX_CONCURRENT_READS_FOR_LIST_OBJECTS, + MAX_CONCURRENT_READS_FOR_LIST_USERS, + MAX_CONDITION_EVALUATION_COST, + MAX_TUPLES_PER_WRITE, + MAX_TYPES_PER_AUTHORIZATION_MODEL, + METRICS_ADDR, + METRICS_ENABLE_RPCHISTOGRAMS, + METRICS_ENABLED, + PLANNER_CLEANUP_INTERVAL, + PLANNER_EVICTION_THRESHOLD, + PLANNER_INITIAL_GUESS, + PLAYGROUND_ENABLED, + PLAYGROUND_PORT, + PROFILER_ADDR, + PROFILER_ENABLED, + REQUEST_DURATION_DATASTORE_QUERY_COUNT_BUCKETS, + REQUEST_DURATION_DISPATCH_COUNT_BUCKETS, + REQUEST_TIMEOUT, + RESOLVE_NODE_BREADTH_LIMIT, + RESOLVE_NODE_LIMIT, + SHARED_ITERATOR_ENABLED, + SHARED_ITERATOR_LIMIT, + TRACE_ENABLED, + TRACE_OTLP_ENDPOINT, + TRACE_OTLP_TLS_ENABLED, + TRACE_SAMPLE_RATIO, + TRACE_SERVICE_NAME, +} as const; + +/** + * Type representing all available OpenFGA configuration constants + */ +export type OpenFGAConfigConstants = typeof OPENFGA_CONFIG_DEFAULTS; From 7aa3d8c9ec7e70ab4d7f414b5afb8039c1f74959 Mon Sep 17 00:00:00 2001 From: Tyler Nix Date: Tue, 16 Sep 2025 12:14:13 -0600 Subject: [PATCH 13/20] quick fix --- docs/content/getting-started/update-tuples.mdx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/content/getting-started/update-tuples.mdx b/docs/content/getting-started/update-tuples.mdx index 50107b2f47..6b86f60158 100644 --- a/docs/content/getting-started/update-tuples.mdx +++ b/docs/content/getting-started/update-tuples.mdx @@ -295,8 +295,7 @@ Similarly, you can use `on_missing: "ignore"` when deleting tuples that might no The behavior of `on_duplicate: "ignore"` is more nuanced for tuples with conditions. - **Identical Tuples**: If a tuple in the request is identical to an existing tuple (same user, relation, object, condition name, and condition context), it will be safely ignored. -- **Conflicting Tuples**: If a tuple key (user, relation, object) matches an existing tuple, but the condition is different, this is a conflict. The write attempt will be rejected, and the entire transaction will fail with a `409 Conflict` error. - +- **Conflicting Tuples**: If a tuple key (user, relation, object) matches an existing tuple, but the condition name or parameters are different, this is a conflict. The write attempt will be rejected, and the entire transaction will fail with a `409 Conflict` error. ## Related Sections From 57bbdeddde07b8e1094cc9ba9c362b68c1ac1a0b Mon Sep 17 00:00:00 2001 From: Tyler Nix Date: Tue, 16 Sep 2025 12:19:23 -0600 Subject: [PATCH 14/20] Fix Prettier formatting issues --- src/components/Docs/ConfigValue/index.tsx | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/components/Docs/ConfigValue/index.tsx b/src/components/Docs/ConfigValue/index.tsx index 583f3f2f98..c843a016ea 100644 --- a/src/components/Docs/ConfigValue/index.tsx +++ b/src/components/Docs/ConfigValue/index.tsx @@ -7,7 +7,7 @@ interface ConfigValueProps { * Should match the exported constant name from openfga-config.ts */ name: keyof typeof OpenFGAConfig.OPENFGA_CONFIG_DEFAULTS; - + /** * Optional format for the value display */ @@ -16,33 +16,30 @@ interface ConfigValueProps { /** * Component to display OpenFGA configuration default values - * + * * This component automatically fetches the current default value from the * auto-generated configuration constants, ensuring the documentation * stays synchronized with the actual OpenFGA configuration schema. - * + * * @example * ```mdx * The Write API allows up to tuples per request. * ``` */ -export const ConfigValue: React.FC = ({ - name, - format = 'code' -}) => { +export const ConfigValue: React.FC = ({ name, format = 'code' }) => { const value = OpenFGAConfig.OPENFGA_CONFIG_DEFAULTS[name]; - + if (value === undefined) { console.warn(`ConfigValue: Unknown configuration constant "${name}"`); return [Unknown config: {name}]; } - + const displayValue = String(value); - + if (format === 'code') { return {displayValue}; } - + return {displayValue}; }; From e925c238357988cb169d56ab0fed192193dc102c Mon Sep 17 00:00:00 2001 From: Tyler Nix Date: Tue, 16 Sep 2025 12:24:16 -0600 Subject: [PATCH 15/20] Fix formatting issue in auto-generated constants --- scripts/update-config-page.mjs | 8 ++++++++ src/constants/openfga-config.ts | 10 +++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/scripts/update-config-page.mjs b/scripts/update-config-page.mjs index d285dd72dc..9fa30eb6ed 100644 --- a/scripts/update-config-page.mjs +++ b/scripts/update-config-page.mjs @@ -1,5 +1,6 @@ import https from 'node:https'; import fs from 'node:fs/promises'; +import { execSync } from 'node:child_process'; /** * Resolves a reference to an internal schema - external references are not supported @@ -427,6 +428,13 @@ async function generateMdxForLatestRelease() { // Ensure the constants directory exists await fs.mkdir('src/constants', { recursive: true }); await fs.writeFile(CONSTANTS_OUTPUT_FILE, constantsContent, 'utf8'); + + // Format the generated constants file with Prettier + try { + execSync(`npx prettier --write ${CONSTANTS_OUTPUT_FILE}`, { stdio: 'inherit' }); + } catch (error) { + console.warn(`Warning: Could not format ${CONSTANTS_OUTPUT_FILE} with Prettier:`, error.message); + } } return releaseData; diff --git a/src/constants/openfga-config.ts b/src/constants/openfga-config.ts index a2d0b876f2..0ed558733f 100644 --- a/src/constants/openfga-config.ts +++ b/src/constants/openfga-config.ts @@ -4,7 +4,7 @@ /** * OpenFGA Configuration Default Values - * + * * These constants are extracted from the OpenFGA v1.10.0 configuration schema. * They represent the default values for various configuration options. */ @@ -252,14 +252,14 @@ export const HTTP_ADDR = '0.0.0.0:8080'; * Config key: http.corsAllowedHeaders * Type: array */ -export const HTTP_CORS_ALLOWED_HEADERS = ["*"]; +export const HTTP_CORS_ALLOWED_HEADERS = ['*']; /** * List of allowed origins for CORS requests * Config key: http.corsAllowedOrigins * Type: array */ -export const HTTP_CORS_ALLOWED_ORIGINS = ["*"]; +export const HTTP_CORS_ALLOWED_ORIGINS = ['*']; /** * Enables or disables the OpenFGA HTTP server. If this is set to true then 'grpc.enabled' must be set to true. @@ -546,14 +546,14 @@ export const PROFILER_ENABLED = false; * Config key: requestDurationDatastoreQueryCountBuckets * Type: array */ -export const REQUEST_DURATION_DATASTORE_QUERY_COUNT_BUCKETS = [50,200]; +export const REQUEST_DURATION_DATASTORE_QUERY_COUNT_BUCKETS = [50, 200]; /** * Dispatch count buckets used to label the histogram metric for measuring request duration. * Config key: requestDurationDispatchCountBuckets * Type: array */ -export const REQUEST_DURATION_DISPATCH_COUNT_BUCKETS = [50,200]; +export const REQUEST_DURATION_DISPATCH_COUNT_BUCKETS = [50, 200]; /** * The timeout duration for a request. From 2ba5d7806eee2c1d40d37980ffeaf5fb7c9da373 Mon Sep 17 00:00:00 2001 From: Tyler Nix Date: Wed, 17 Sep 2025 15:14:35 -0600 Subject: [PATCH 16/20] Implement generic product-config system for multi-tenant documentation --- scripts/update-config-page.mjs | 16 ++++++------- src/components/Docs/ConfigValue/index.tsx | 23 +++++++++++-------- .../{openfga-config.ts => product-config.ts} | 14 +++++------ 3 files changed, 28 insertions(+), 25 deletions(-) rename src/constants/{openfga-config.ts => product-config.ts} (98%) diff --git a/scripts/update-config-page.mjs b/scripts/update-config-page.mjs index 9fa30eb6ed..4ee43ba978 100644 --- a/scripts/update-config-page.mjs +++ b/scripts/update-config-page.mjs @@ -149,14 +149,14 @@ async function processProperties(properties, jsonData, parentKey = '') { * @returns {string} */ function generateConstantsFile(constants, releaseData) { - let content = `// Auto-generated OpenFGA configuration constants + let content = `// Auto-generated product configuration constants // Generated from ${releaseData.release} config schema // Do not edit this file manually - it will be overwritten /** - * OpenFGA Configuration Default Values + * Product Configuration Default Values * - * These constants are extracted from the OpenFGA ${releaseData.release} configuration schema. + * These constants are extracted from the ${releaseData.release} configuration schema. * They represent the default values for various configuration options. */ @@ -189,9 +189,9 @@ function generateConstantsFile(constants, releaseData) { // Add a convenience object with all constants content += `/**\n`; - content += ` * All OpenFGA configuration default values in a single object\n`; + content += ` * All product configuration default values in a single object\n`; content += ` */\n`; - content += `export const OPENFGA_CONFIG_DEFAULTS = {\n`; + content += `export const PRODUCT_CONFIG_DEFAULTS = {\n`; for (const [constantName] of sortedConstants) { content += ` ${constantName},\n`; @@ -201,9 +201,9 @@ function generateConstantsFile(constants, releaseData) { // Add type definitions content += `/**\n`; - content += ` * Type representing all available OpenFGA configuration constants\n`; + content += ` * Type representing all available product configuration constants\n`; content += ` */\n`; - content += `export type OpenFGAConfigConstants = typeof OPENFGA_CONFIG_DEFAULTS;\n`; + content += `export type ProductConfigConstants = typeof PRODUCT_CONFIG_DEFAULTS;\n`; return content; } @@ -406,7 +406,7 @@ async function getFileData() { } const OUTPUT_FILE = 'docs/content/getting-started/setup-openfga/configuration.mdx'; -const CONSTANTS_OUTPUT_FILE = 'src/constants/openfga-config.ts'; +const CONSTANTS_OUTPUT_FILE = 'src/constants/product-config.ts'; /** * Downloads the .config-schema for the latest release of OpenFGA and generates the MDX content and constants file diff --git a/src/components/Docs/ConfigValue/index.tsx b/src/components/Docs/ConfigValue/index.tsx index c843a016ea..33bb6b6707 100644 --- a/src/components/Docs/ConfigValue/index.tsx +++ b/src/components/Docs/ConfigValue/index.tsx @@ -1,12 +1,15 @@ import React from 'react'; -import * as OpenFGAConfig from '../../../constants/openfga-config'; +import * as ProductConfig from '../../../constants/product-config'; + +// Type for config constants +type ConfigConstants = keyof typeof ProductConfig.PRODUCT_CONFIG_DEFAULTS; interface ConfigValueProps { /** * The name of the configuration constant to display - * Should match the exported constant name from openfga-config.ts + * Should match the exported constant name from product-config.ts */ - name: keyof typeof OpenFGAConfig.OPENFGA_CONFIG_DEFAULTS; + name: ConfigConstants; /** * Optional format for the value display @@ -15,11 +18,11 @@ interface ConfigValueProps { } /** - * Component to display OpenFGA configuration default values + * Component to display product configuration default values * - * This component automatically fetches the current default value from the - * auto-generated configuration constants, ensuring the documentation - * stays synchronized with the actual OpenFGA configuration schema. + * This component fetches configuration values from the product-config.ts file, + * which can be auto-generated (for OpenFGA) or manually maintained (for other products). + * This ensures the documentation stays synchronized with the actual configuration schema. * * @example * ```mdx @@ -27,11 +30,11 @@ interface ConfigValueProps { * ``` */ export const ConfigValue: React.FC = ({ name, format = 'code' }) => { - const value = OpenFGAConfig.OPENFGA_CONFIG_DEFAULTS[name]; + const value = ProductConfig.PRODUCT_CONFIG_DEFAULTS[name]; if (value === undefined) { - console.warn(`ConfigValue: Unknown configuration constant "${name}"`); - return [Unknown config: {name}]; + console.warn(`ConfigValue: Unknown configuration constant "${String(name)}"`); + return [Unknown config: {String(name)}]; } const displayValue = String(value); diff --git a/src/constants/openfga-config.ts b/src/constants/product-config.ts similarity index 98% rename from src/constants/openfga-config.ts rename to src/constants/product-config.ts index 0ed558733f..450b3a0667 100644 --- a/src/constants/openfga-config.ts +++ b/src/constants/product-config.ts @@ -1,11 +1,11 @@ -// Auto-generated OpenFGA configuration constants +// Auto-generated product configuration constants // Generated from v1.10.0 config schema // Do not edit this file manually - it will be overwritten /** - * OpenFGA Configuration Default Values + * Product Configuration Default Values * - * These constants are extracted from the OpenFGA v1.10.0 configuration schema. + * These constants are extracted from the v1.10.0 configuration schema. * They represent the default values for various configuration options. */ @@ -626,9 +626,9 @@ export const TRACE_SAMPLE_RATIO = 0.2; export const TRACE_SERVICE_NAME = 'openfga'; /** - * All OpenFGA configuration default values in a single object + * All product configuration default values in a single object */ -export const OPENFGA_CONFIG_DEFAULTS = { +export const PRODUCT_CONFIG_DEFAULTS = { ACCESS_CONTROL_ENABLED, ACCESS_CONTROL_MODEL_ID, ACCESS_CONTROL_STORE_ID, @@ -720,6 +720,6 @@ export const OPENFGA_CONFIG_DEFAULTS = { } as const; /** - * Type representing all available OpenFGA configuration constants + * Type representing all available product configuration constants */ -export type OpenFGAConfigConstants = typeof OPENFGA_CONFIG_DEFAULTS; +export type ProductConfigConstants = typeof PRODUCT_CONFIG_DEFAULTS; From 3947bd049d5dee27e6e75a4e83f0223ed4d3a701 Mon Sep 17 00:00:00 2001 From: Tyler Nix Date: Wed, 17 Sep 2025 16:05:03 -0600 Subject: [PATCH 17/20] quick title fix --- docs/content/getting-started/update-tuples.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/getting-started/update-tuples.mdx b/docs/content/getting-started/update-tuples.mdx index 6b86f60158..f1e23af21b 100644 --- a/docs/content/getting-started/update-tuples.mdx +++ b/docs/content/getting-started/update-tuples.mdx @@ -242,7 +242,7 @@ This approach ensures that both operations succeed or fail together, maintaining When using the Write API, you cannot include the same tuple (same user, relation, and object) in the writes or deletes arrays within a single request. The API will return an error with code `cannot_allow_duplicate_tuples_in_one_request` if detected. ::: -### 05. Ignoring duplicate tuples +### 05. Ignoring duplicate/missing tuples Sometimes you might need to write a tuple that already exists, which would normally cause the whole request to fail. You can use the `on_duplicate: "ignore"` parameter to handle this gracefully. From 9d9447a31cc7a6e4fa9bf06447503dac893a8872 Mon Sep 17 00:00:00 2001 From: Tyler Nix Date: Wed, 17 Sep 2025 16:06:41 -0600 Subject: [PATCH 18/20] Remove configuration.mdx changes from PR --- .../setup-openfga/configuration.mdx | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/docs/content/getting-started/setup-openfga/configuration.mdx b/docs/content/getting-started/setup-openfga/configuration.mdx index 8d000d9b14..42e8798ec3 100644 --- a/docs/content/getting-started/setup-openfga/configuration.mdx +++ b/docs/content/getting-started/setup-openfga/configuration.mdx @@ -101,7 +101,7 @@ docker run docker.io/openfga/openfga:latest run \ ## List of options -The following table lists the configuration options for the OpenFGA server [v1.10.0](https://github.com/openfga/openfga/releases/tag/v1.10.0), based on the [config-schema.json](https://raw.githubusercontent.com/openfga/openfga/refs/tags/v1.10.0/.config-schema.json). +The following table lists the configuration options for the OpenFGA server [v1.8.9](https://github.com/openfga/openfga/releases/tag/v1.8.9), based on the [config-schema.json](https://raw.githubusercontent.com/openfga/openfga/refs/tags/v1.8.9/.config-schema.json). | Config File | Env Var | Flag Name | Type | Description | Default Value | |-------------|---------|-----------|------|-------------|---------------| @@ -116,7 +116,7 @@ The following table lists the configuration options for the OpenFGA server [v1.1 | `maxConditionEvaluationCost` |
OPENFGA_MAX_CONDITION_EVALUATION_COST
| `max-condition-evaluation-cost` | integer | The maximum cost for CEL condition evaluation before a request returns an error (default is 100). | `100` | | `changelogHorizonOffset` |
OPENFGA_CHANGELOG_HORIZON_OFFSET
| `changelog-horizon-offset` | integer | The offset (in minutes) from the current time. Changes that occur after this offset will not be included in the response of ReadChanges. | | | `resolveNodeLimit` |
OPENFGA_RESOLVE_NODE_LIMIT
| `resolve-node-limit` | integer | Maximum resolution depth to attempt before throwing an error (defines how deeply nested an authorization model can be before a query errors out). | `25` | -| `resolveNodeBreadthLimit` |
OPENFGA_RESOLVE_NODE_BREADTH_LIMIT
| `resolve-node-breadth-limit` | integer | Defines how many nodes on a given level can be evaluated concurrently in a Check resolution tree. | `10` | +| `resolveNodeBreadthLimit` |
OPENFGA_RESOLVE_NODE_BREADTH_LIMIT
| `resolve-node-breadth-limit` | integer | Defines how many nodes on a given level can be evaluated concurrently in a Check resolution tree. | `100` | | `listObjectsDeadline` |
OPENFGA_LIST_OBJECTS_DEADLINE
| `list-objects-deadline` | string (duration) | The timeout deadline for serving ListObjects requests | `3s` | | `listObjectsMaxResults` |
OPENFGA_LIST_OBJECTS_MAX_RESULTS
| `list-objects-max-results` | integer | The maximum results to return in the non-streaming ListObjects API response. If 0, all results can be returned | `1000` | | `listUsersDeadline` |
OPENFGA_LIST_USERS_DEADLINE
| `list-users-deadline` | string (duration) | The timeout deadline for serving ListUsers requests. If 0s, there is no deadline | `3s` | @@ -134,11 +134,8 @@ The following table lists the configuration options for the OpenFGA server [v1.1 | `profiler.addr` |
OPENFGA_PROFILER_ADDR
| `profiler-addr` | string | The host:port address to serve the pprof profiler server on. | `:3001` | | `datastore.engine` |
OPENFGA_DATASTORE_ENGINE
| `datastore-engine` | string (enum=[`memory`, `postgres`, `mysql`, `sqlite`]) | The datastore engine that will be used for persistence. | `memory` | | `datastore.uri` |
OPENFGA_DATASTORE_URI
| `datastore-uri` | string | The connection uri to use to connect to the datastore (for any engine other than 'memory'). | | -| `datastore.secondaryUri` |
OPENFGA_DATASTORE_SECONDARY_URI
| `datastore-secondary-uri` | string | The connection uri to use to connect to the secondary datastore (for postgres only). | | | `datastore.username` |
OPENFGA_DATASTORE_USERNAME
| `datastore-username` | string | The connection username to connect to the datastore (overwrites any username provided in the connection uri). | | -| `datastore.secondaryUsername` |
OPENFGA_DATASTORE_SECONDARY_USERNAME
| `datastore-secondary-username` | string | The connection username to connect to the secondary datastore (overwrites any username provided in the connection uri). | | | `datastore.password` |
OPENFGA_DATASTORE_PASSWORD
| `datastore-password` | string | The connection password to connect to the datastore (overwrites any password provided in the connection uri). | | -| `datastore.secondaryPassword` |
OPENFGA_DATASTORE_SECONDARY_PASSWORD
| `datastore-secondary-password` | string | The connection password to connect to the secondary datastore (overwrites any password provided in the connection uri). | | | `datastore.maxCacheSize` |
OPENFGA_DATASTORE_MAX_CACHE_SIZE
| `datastore-max-cache-size` | integer | The maximum number of authorization models that will be cached in memory | `100000` | | `datastore.maxOpenConns` |
OPENFGA_DATASTORE_MAX_OPEN_CONNS
| `datastore-max-open-conns` | integer | The maximum number of open connections to the datastore. | `30` | | `datastore.maxIdleConns` |
OPENFGA_DATASTORE_MAX_IDLE_CONNS
| `datastore-max-idle-conns` | integer | the maximum number of connections to the datastore in the idle connection pool. | `10` | @@ -188,23 +185,15 @@ The following table lists the configuration options for the OpenFGA server [v1.1 | `checkDispatchThrottling.frequency` |
OPENFGA_CHECK_DISPATCH_THROTTLING_FREQUENCY
| `check-dispatch-throttling-frequency` | string (duration) | the frequency period that the deprioritized throttling queue is evaluated for a check request. A higher value will result in more aggressive throttling | `10µs` | | `checkDispatchThrottling.threshold` |
OPENFGA_CHECK_DISPATCH_THROTTLING_THRESHOLD
| `check-dispatch-throttling-threshold` | integer | define the number of recursive operations to occur before getting throttled for a check request | `100` | | `checkDispatchThrottling.maxThreshold` |
OPENFGA_CHECK_DISPATCH_THROTTLING_MAX_THRESHOLD
| `check-dispatch-throttling-max-threshold` | integer | define the maximum dispatch threshold beyond above which requests will be throttled. 0 will use the 'dispatchThrottling.threshold' value as maximum | `0` | -| `listObjectsIteratorCache.enabled` |
OPENFGA_LIST_OBJECTS_ITERATOR_CACHE_ENABLED
| `list-objects-iterator-cache-enabled` | boolean | enable caching of datastore iterators in ListObjects. The key is a string representing a database query, and the value is a list of tuples. Each iterator is the result of a database query, for example usersets related to a specific object, or objects related to a specific user, up to a certain number of tuples per iterator. If the request's consistency is HIGHER_CONSISTENCY, this cache is not used. | `false` | -| `listObjectsIteratorCache.maxResults` |
OPENFGA_LIST_OBJECTS_ITERATOR_CACHE_MAX_RESULTS
| `list-objects-iterator-cache-max-results` | integer | if caching of datastore iterators of ListObjects requests is enabled, this is the limit of tuples to cache per key | `10000` | -| `listObjectsIteratorCache.ttl` |
OPENFGA_LIST_OBJECTS_ITERATOR_CACHE_TTL
| `list-objects-iterator-cache-ttl` | string (duration) | if caching of datastore iterators of ListObjects requests is enabled, this is the TTL of each value | `10s` | -| `listObjectsDispatchThrottling.enabled` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_ENABLED
| `list-objects-dispatch-throttling-enabled` | boolean | enable throttling when ListObjects request's number of dispatches is high | `false` | -| `listObjectsDispatchThrottling.frequency` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_FREQUENCY
| `list-objects-dispatch-throttling-frequency` | string (duration) | the frequency period that the deprioritized throttling queue is evaluated for a ListObjects request. A higher value will result in more aggressive throttling | `10µs` | -| `listObjectsDispatchThrottling.threshold` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_THRESHOLD
| `list-objects-dispatch-throttling-threshold` | integer | define the number of recursive operations to occur before getting throttled for a ListObjects request | `100` | -| `listObjectsDispatchThrottling.maxThreshold` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_MAX_THRESHOLD
| `list-objects-dispatch-throttling-max-threshold` | integer | define the maximum dispatch threshold beyond above which requests will be throttled for a ListObjects request. 0 will use the 'dispatchThrottling.threshold' value as maximum | `0` | +| `listObjectsDispatchThrottling.enabled` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_ENABLED
| `list-objects-dispatch-throttling-enabled` | boolean | enable throttling when list objects request's number of dispatches is high | `false` | +| `listObjectsDispatchThrottling.frequency` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_FREQUENCY
| `list-objects-dispatch-throttling-frequency` | string (duration) | the frequency period that the deprioritized throttling queue is evaluated for a list objects request. A higher value will result in more aggressive throttling | `10µs` | +| `listObjectsDispatchThrottling.threshold` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_THRESHOLD
| `list-objects-dispatch-throttling-threshold` | integer | define the number of recursive operations to occur before getting throttled for a list objects request | `100` | +| `listObjectsDispatchThrottling.maxThreshold` |
OPENFGA_LIST_OBJECTS_DISPATCH_THROTTLING_MAX_THRESHOLD
| `list-objects-dispatch-throttling-max-threshold` | integer | define the maximum dispatch threshold beyond above which requests will be throttled for a list objects request. 0 will use the 'dispatchThrottling.threshold' value as maximum | `0` | | `listUsersDispatchThrottling.enabled` |
OPENFGA_LIST_USERS_DISPATCH_THROTTLING_ENABLED
| `list-users-dispatch-throttling-enabled` | boolean | enable throttling when list users request's number of dispatches is high | `false` | | `listUsersDispatchThrottling.frequency` |
OPENFGA_LIST_USERS_DISPATCH_THROTTLING_FREQUENCY
| `list-users-dispatch-throttling-frequency` | string (duration) | the frequency period that the deprioritized throttling queue is evaluated for a list users request. A higher value will result in more aggressive throttling | `10µs` | | `listUsersDispatchThrottling.threshold` |
OPENFGA_LIST_USERS_DISPATCH_THROTTLING_THRESHOLD
| `list-users-dispatch-throttling-threshold` | integer | define the number of recursive operations to occur before getting throttled for a list users request | `100` | | `listUsersDispatchThrottling.maxThreshold` |
OPENFGA_LIST_USERS_DISPATCH_THROTTLING_MAX_THRESHOLD
| `list-users-dispatch-throttling-max-threshold` | integer | define the maximum dispatch threshold beyond above which requests will be throttled for a list users request. 0 will use the 'dispatchThrottling.threshold' value as maximum | `0` | -| `sharedIterator.enabled` |
OPENFGA_SHARED_ITERATOR_ENABLED
| `shared-iterator-enabled` | boolean | enabling sharing of datastore iterators with different consumers. Each iterator is the result of a database query, for example usersets related to a specific object, or objects related to a specific user, up to a certain number of tuples per iterator. | `false` | -| `sharedIterator.limit` |
OPENFGA_SHARED_ITERATOR_LIMIT
| `shared-iterator-limit` | integer | if shared-iterator-enabled is enabled, this is the limit of the number of iterators that can be shared. | `1000000` | | `requestTimeout` |
OPENFGA_REQUEST_TIMEOUT
| `request-timeout` | string (duration) | The timeout duration for a request. | `3s` | -| `planner.initialGuess` |
OPENFGA_PLANNER_INITIAL_GUESS
| `planner-initial-guess` | string (duration) | The initial guess for the planners estimation. | `10ms` | -| `planner.evictionThreshold` |
OPENFGA_PLANNER_EVICTION_THRESHOLD
| `planner-eviction-threshold` | | How long a planner key can be unused before being evicted. | `0` | -| `planner.cleanupInterval` |
OPENFGA_PLANNER_CLEANUP_INTERVAL
| `planner-cleanup-interval` | string (duration) | How often the planner checks for stale keys. | `0` | ## Related Sections From ab7802c1f26f6b2e323333612b1a431e39fa2f63 Mon Sep 17 00:00:00 2001 From: Tyler Nix Date: Wed, 17 Sep 2025 16:08:10 -0600 Subject: [PATCH 19/20] quick title fix --- docs/content/getting-started/update-tuples.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/getting-started/update-tuples.mdx b/docs/content/getting-started/update-tuples.mdx index f1e23af21b..3beb3a1b27 100644 --- a/docs/content/getting-started/update-tuples.mdx +++ b/docs/content/getting-started/update-tuples.mdx @@ -242,7 +242,7 @@ This approach ensures that both operations succeed or fail together, maintaining When using the Write API, you cannot include the same tuple (same user, relation, and object) in the writes or deletes arrays within a single request. The API will return an error with code `cannot_allow_duplicate_tuples_in_one_request` if detected. ::: -### 05. Ignoring duplicate/missing tuples +### 05. Ignoring duplicate or missing tuples Sometimes you might need to write a tuple that already exists, which would normally cause the whole request to fail. You can use the `on_duplicate: "ignore"` parameter to handle this gracefully. From 5998a4911ae9a1f2cb1cdd6cf262376aa95e098a Mon Sep 17 00:00:00 2001 From: Tyler Nix Date: Wed, 17 Sep 2025 16:15:51 -0600 Subject: [PATCH 20/20] Remove ConfigValue component from this PR --- .../content/getting-started/update-tuples.mdx | 3 +- scripts/update-config-page.mjs | 137 +--- src/components/Docs/ConfigValue/index.tsx | 49 -- src/components/Docs/index.ts | 1 - src/constants/product-config.ts | 725 ------------------ 5 files changed, 4 insertions(+), 911 deletions(-) delete mode 100644 src/components/Docs/ConfigValue/index.tsx delete mode 100644 src/constants/product-config.ts diff --git a/docs/content/getting-started/update-tuples.mdx b/docs/content/getting-started/update-tuples.mdx index 3beb3a1b27..a44c177a60 100644 --- a/docs/content/getting-started/update-tuples.mdx +++ b/docs/content/getting-started/update-tuples.mdx @@ -16,7 +16,6 @@ import { ProductNameFormat, WriteRequestViewer, SdkSetupPrerequisite, - ConfigValue, } from '@components/Docs'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; @@ -204,7 +203,7 @@ Assume that you want to delete user `user:anne`'s `reader` relationship with obj You can combine both writes and deletes in a single transactional API request. This is useful when you need to update multiple relationships atomically. All operations in the request will either succeed together or fail together. -The Write API allows you to send up to unique tuples in the request. (This limit applies to the sum of both writes and deletes in that request). +The Write API allows you to send up to `100` unique tuples in the request. (This limit applies to the sum of both writes and deletes in that request). For example, you might want to remove `user:anne` as a `writer` of `document:Z` while simultaneously updating `user:anne` as an `reader` of `document:Z`: diff --git a/scripts/update-config-page.mjs b/scripts/update-config-page.mjs index 4ee43ba978..2835482d7e 100644 --- a/scripts/update-config-page.mjs +++ b/scripts/update-config-page.mjs @@ -1,6 +1,5 @@ import https from 'node:https'; import fs from 'node:fs/promises'; -import { execSync } from 'node:child_process'; /** * Resolves a reference to an internal schema - external references are not supported @@ -63,50 +62,6 @@ function parseValue(type, value) { } } -/** - * Extracts configuration constants from the schema - * @param properties - * @param jsonData - * @param parentKey - * @returns {Promise} - */ -async function extractConstants(properties, jsonData, parentKey = '') { - let constants = {}; - - for (const [key, value] of Object.entries(properties)) { - if (value.$ref) { - const refSchema = await resolveRef(value.$ref, jsonData); - const nestedConstants = await extractConstants(refSchema.properties, jsonData, parentKey ? `${parentKey}.${key}` : key); - constants = { ...constants, ...nestedConstants }; - } else { - const fullKey = parentKey ? `${parentKey}.${key}` : key; - - // Only include constants that have default values or are arrays (which can default to empty array) - if (value.type !== 'object' && (value.default !== undefined || value.type === 'array')) { - // Convert config key to constant name (e.g., maxTuplesPerWrite -> MAX_TUPLES_PER_WRITE) - const constantName = fullKey - .replace(/\./g, '_') - .replace(/([a-z])([A-Z])/g, '$1_$2') - .toUpperCase(); - - constants[constantName] = { - value: value.default !== undefined ? value.default : (value.type === 'array' ? [] : null), - type: value.type, - description: value.description || '', - configKey: fullKey - }; - } - - if (value.type === 'object' && value.properties) { - const nestedConstants = await extractConstants(value.properties, jsonData, fullKey); - constants = { ...constants, ...nestedConstants }; - } - } - } - - return constants; -} - /** * Processes the properties of a schema and returns the MDX content * @param properties @@ -142,72 +97,6 @@ async function processProperties(properties, jsonData, parentKey = '') { return mdxContent; } -/** - * Generates a TypeScript constants file from the extracted constants - * @param constants - * @param releaseData - * @returns {string} - */ -function generateConstantsFile(constants, releaseData) { - let content = `// Auto-generated product configuration constants -// Generated from ${releaseData.release} config schema -// Do not edit this file manually - it will be overwritten - -/** - * Product Configuration Default Values - * - * These constants are extracted from the ${releaseData.release} configuration schema. - * They represent the default values for various configuration options. - */ - -`; - - // Sort constants by name for consistency - const sortedConstants = Object.entries(constants).sort(([a], [b]) => a.localeCompare(b)); - - for (const [constantName, config] of sortedConstants) { - // Add JSDoc comment with description and config key - content += `/**\n`; - content += ` * ${config.description || 'Configuration option'}\n`; - content += ` * Config key: ${config.configKey}\n`; - content += ` * Type: ${config.type}\n`; - content += ` */\n`; - - // Generate the constant declaration - if (config.type === 'string') { - content += `export const ${constantName} = '${config.value}';\n\n`; - } else if (config.type === 'boolean') { - content += `export const ${constantName} = ${config.value};\n\n`; - } else if (config.type === 'array') { - // Handle array values - use empty array if no default - const arrayValue = Array.isArray(config.value) ? JSON.stringify(config.value) : '[]'; - content += `export const ${constantName} = ${arrayValue};\n\n`; - } else { - content += `export const ${constantName} = ${config.value};\n\n`; - } - } - - // Add a convenience object with all constants - content += `/**\n`; - content += ` * All product configuration default values in a single object\n`; - content += ` */\n`; - content += `export const PRODUCT_CONFIG_DEFAULTS = {\n`; - - for (const [constantName] of sortedConstants) { - content += ` ${constantName},\n`; - } - - content += `} as const;\n\n`; - - // Add type definitions - content += `/**\n`; - content += ` * Type representing all available product configuration constants\n`; - content += ` */\n`; - content += `export type ProductConfigConstants = typeof PRODUCT_CONFIG_DEFAULTS;\n`; - - return content; -} - /** * Generates the MDX content for the .config-schema.json file * @param releaseData {release: string, url: string, releaseUrl: string} @@ -406,41 +295,21 @@ async function getFileData() { } const OUTPUT_FILE = 'docs/content/getting-started/setup-openfga/configuration.mdx'; -const CONSTANTS_OUTPUT_FILE = 'src/constants/product-config.ts'; - /** - * Downloads the .config-schema for the latest release of OpenFGA and generates the MDX content and constants file + * Downloads the .config-schema for the latest release of OpenFGA and generates the MDX content for it * @returns {Promise<{release: string, url: string, releaseUrl: string}>} */ async function generateMdxForLatestRelease() { const releaseData = await getFileData(); const jsonData = await performHttpRequest(releaseData.url); - - // Generate the MDX content const mdxContent = await getMdxContent(releaseData, jsonData); + await fs.writeFile(OUTPUT_FILE, mdxContent, 'utf8'); - - // Extract constants and generate constants file - if (jsonData.properties) { - const constants = await extractConstants(jsonData.properties, jsonData); - const constantsContent = generateConstantsFile(constants, releaseData); - - // Ensure the constants directory exists - await fs.mkdir('src/constants', { recursive: true }); - await fs.writeFile(CONSTANTS_OUTPUT_FILE, constantsContent, 'utf8'); - - // Format the generated constants file with Prettier - try { - execSync(`npx prettier --write ${CONSTANTS_OUTPUT_FILE}`, { stdio: 'inherit' }); - } catch (error) { - console.warn(`Warning: Could not format ${CONSTANTS_OUTPUT_FILE} with Prettier:`, error.message); - } - } return releaseData; } generateMdxForLatestRelease() - .then(({ release, url }) => console.log(`Downloaded .config-schema.json file for ${release} from ${url} and saved the generated files at ${OUTPUT_FILE} and ${CONSTANTS_OUTPUT_FILE}`)) + .then(({ release, url }) => console.log(`Downloaded .config-schema.json file for ${release} from ${url} and saved the generated file at ${OUTPUT_FILE}`)) .catch(console.error); \ No newline at end of file diff --git a/src/components/Docs/ConfigValue/index.tsx b/src/components/Docs/ConfigValue/index.tsx deleted file mode 100644 index 33bb6b6707..0000000000 --- a/src/components/Docs/ConfigValue/index.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React from 'react'; -import * as ProductConfig from '../../../constants/product-config'; - -// Type for config constants -type ConfigConstants = keyof typeof ProductConfig.PRODUCT_CONFIG_DEFAULTS; - -interface ConfigValueProps { - /** - * The name of the configuration constant to display - * Should match the exported constant name from product-config.ts - */ - name: ConfigConstants; - - /** - * Optional format for the value display - */ - format?: 'code' | 'text'; -} - -/** - * Component to display product configuration default values - * - * This component fetches configuration values from the product-config.ts file, - * which can be auto-generated (for OpenFGA) or manually maintained (for other products). - * This ensures the documentation stays synchronized with the actual configuration schema. - * - * @example - * ```mdx - * The Write API allows up to tuples per request. - * ``` - */ -export const ConfigValue: React.FC = ({ name, format = 'code' }) => { - const value = ProductConfig.PRODUCT_CONFIG_DEFAULTS[name]; - - if (value === undefined) { - console.warn(`ConfigValue: Unknown configuration constant "${String(name)}"`); - return [Unknown config: {String(name)}]; - } - - const displayValue = String(value); - - if (format === 'code') { - return {displayValue}; - } - - return {displayValue}; -}; - -export default ConfigValue; diff --git a/src/components/Docs/index.ts b/src/components/Docs/index.ts index cdd5a18def..676d8b02ff 100644 --- a/src/components/Docs/index.ts +++ b/src/components/Docs/index.ts @@ -2,7 +2,6 @@ export * from './AuthorizationModel'; export * from './Banner'; export * from './CardBox'; export * from './Column'; -export * from './ConfigValue'; export * from './DocumentationNotice'; export * from './Feedback'; export * from './Overview'; diff --git a/src/constants/product-config.ts b/src/constants/product-config.ts deleted file mode 100644 index 450b3a0667..0000000000 --- a/src/constants/product-config.ts +++ /dev/null @@ -1,725 +0,0 @@ -// Auto-generated product configuration constants -// Generated from v1.10.0 config schema -// Do not edit this file manually - it will be overwritten - -/** - * Product Configuration Default Values - * - * These constants are extracted from the v1.10.0 configuration schema. - * They represent the default values for various configuration options. - */ - -/** - * Enable/disable the access control store. - * Config key: accessControl.enabled - * Type: boolean - */ -export const ACCESS_CONTROL_ENABLED = false; - -/** - * The modelId to be used for the access control store. - * Config key: accessControl.modelId - * Type: string - */ -export const ACCESS_CONTROL_MODEL_ID = ''; - -/** - * The storeId to be used for the access control store. - * Config key: accessControl.storeId - * Type: string - */ -export const ACCESS_CONTROL_STORE_ID = ''; - -/** - * The authentication method to use. - * Config key: authn.method - * Type: string - */ -export const AUTHN_METHOD = 'none'; - -/** - * the OIDC client id claims that will be used to parse the clientID - configure in order of priority (first is highest). Defaults to [`azp`, `client_id`] - * Config key: authn.oidc.clientIdClaims - * Type: array - */ -export const AUTHN_OIDC_CLIENT_ID_CLAIMS = []; - -/** - * the OIDC issuer DNS aliases that will be accepted as valid when verifying the `iss` field of the JWTs. - * Config key: authn.oidc.issuerAliases - * Type: array - */ -export const AUTHN_OIDC_ISSUER_ALIASES = []; - -/** - * the OIDC subject names that will be accepted as valid when verifying the `sub` field of the JWTs. If empty, every `sub` will be allowed - * Config key: authn.oidc.subjects - * Type: array - */ -export const AUTHN_OIDC_SUBJECTS = []; - -/** - * List of preshared keys used for authentication - * Config key: authn.preshared.keys - * Type: array - */ -export const AUTHN_PRESHARED_KEYS = []; - -/** - * enabling dynamic invalidation of check query cache and check iterator cache based on whether there are recent tuple writes. If enabled, cache will be invalidated when either 1) there are tuples written to the store OR 2) the check query cache or check iterator cache TTL has expired. - * Config key: cacheController.enabled - * Type: boolean - */ -export const CACHE_CONTROLLER_ENABLED = false; - -/** - * if cache controller is enabled, control how frequent read changes are invoked internally to query for recent tuple writes to the store. - * Config key: cacheController.ttl - * Type: string - */ -export const CACHE_CONTROLLER_TTL = '10s'; - -/** - * The offset (in minutes) from the current time. Changes that occur after this offset will not be included in the response of ReadChanges. - * Config key: changelogHorizonOffset - * Type: integer - */ -export const CHANGELOG_HORIZON_OFFSET = 0; - -/** - * the size limit (in items) of the cache for Check (queries and iterators) - * Config key: checkCache.limit - * Type: integer - */ -export const CHECK_CACHE_LIMIT = 10000; - -/** - * enable throttling when check request's number of dispatches is high - * Config key: checkDispatchThrottling.enabled - * Type: boolean - */ -export const CHECK_DISPATCH_THROTTLING_ENABLED = false; - -/** - * the frequency period that the deprioritized throttling queue is evaluated for a check request. A higher value will result in more aggressive throttling - * Config key: checkDispatchThrottling.frequency - * Type: string - */ -export const CHECK_DISPATCH_THROTTLING_FREQUENCY = '10µs'; - -/** - * define the maximum dispatch threshold beyond above which requests will be throttled. 0 will use the 'dispatchThrottling.threshold' value as maximum - * Config key: checkDispatchThrottling.maxThreshold - * Type: integer - */ -export const CHECK_DISPATCH_THROTTLING_MAX_THRESHOLD = 0; - -/** - * define the number of recursive operations to occur before getting throttled for a check request - * Config key: checkDispatchThrottling.threshold - * Type: integer - */ -export const CHECK_DISPATCH_THROTTLING_THRESHOLD = 100; - -/** - * enable caching of datastore iterators. The key is a string representing a database query, and the value is a list of tuples. Each iterator is the result of a database query, for example usersets related to a specific object, or objects related to a specific user, up to a certain number of tuples per iterator. If the request's consistency is HIGHER_CONSISTENCY, this cache is not used. - * Config key: checkIteratorCache.enabled - * Type: boolean - */ -export const CHECK_ITERATOR_CACHE_ENABLED = false; - -/** - * if caching of datastore iterators of Check requests is enabled, this is the limit of tuples to cache per key - * Config key: checkIteratorCache.maxResults - * Type: integer - */ -export const CHECK_ITERATOR_CACHE_MAX_RESULTS = 10000; - -/** - * if caching of datastore iterators of Check requests is enabled, this is the TTL of each value - * Config key: checkIteratorCache.ttl - * Type: string - */ -export const CHECK_ITERATOR_CACHE_TTL = '10s'; - -/** - * enable caching of Check requests. The key is a string representing a query, and the value is a boolean. For example, if you have a relation `define viewer: owner or editor`, and the query is Check(user:anne, viewer, doc:1), we'll evaluate the `owner` relation and the `editor` relation and cache both results: (user:anne, viewer, doc:1) -> allowed=true and (user:anne, owner, doc:1) -> allowed=true. The cache is stored in-memory; the cached values are overwritten on every change in the result, and cleared after the configured TTL. This flag improves latency, but turns Check and ListObjects into eventually consistent APIs. If the request's consistency is HIGHER_CONSISTENCY, this cache is not used. - * Config key: checkQueryCache.enabled - * Type: boolean - */ -export const CHECK_QUERY_CACHE_ENABLED = false; - -/** - * DEPRECATED use OPENFGA_CHECK_CACHE_LIMIT. If caching of Check and ListObjects calls is enabled, this is the size limit (in items) of the cache - * Config key: checkQueryCache.limit - * Type: integer - */ -export const CHECK_QUERY_CACHE_LIMIT = 10000; - -/** - * if caching of Check and ListObjects is enabled, this is the TTL of each value - * Config key: checkQueryCache.ttl - * Type: string - */ -export const CHECK_QUERY_CACHE_TTL = '10s'; - -/** - * Propagate a requests context to the datastore implementation. Settings this parameter can result in connection pool draining on request aborts and timeouts. - * Config key: contextPropagationToDatastore - * Type: boolean - */ -export const CONTEXT_PROPAGATION_TO_DATASTORE = false; - -/** - * the maximum amount of time a connection to the datastore may be idle - * Config key: datastore.connMaxIdleTime - * Type: string - */ -export const DATASTORE_CONN_MAX_IDLE_TIME = '0s'; - -/** - * the maximum amount of time a connection to the datastore may be reused - * Config key: datastore.connMaxLifetime - * Type: string - */ -export const DATASTORE_CONN_MAX_LIFETIME = '0s'; - -/** - * The datastore engine that will be used for persistence. - * Config key: datastore.engine - * Type: string - */ -export const DATASTORE_ENGINE = 'memory'; - -/** - * The maximum number of authorization models that will be cached in memory - * Config key: datastore.maxCacheSize - * Type: integer - */ -export const DATASTORE_MAX_CACHE_SIZE = 100000; - -/** - * the maximum number of connections to the datastore in the idle connection pool. - * Config key: datastore.maxIdleConns - * Type: integer - */ -export const DATASTORE_MAX_IDLE_CONNS = 10; - -/** - * The maximum number of open connections to the datastore. - * Config key: datastore.maxOpenConns - * Type: integer - */ -export const DATASTORE_MAX_OPEN_CONNS = 30; - -/** - * enable/disable sql metrics for the datastore - * Config key: datastore.metrics.enabled - * Type: boolean - */ -export const DATASTORE_METRICS_ENABLED = false; - -/** - * a list of experimental features to enable - * Config key: experimentals - * Type: array - */ -export const EXPERIMENTALS = []; - -/** - * The host:port address to serve the grpc server on. - * Config key: grpc.addr - * Type: string - */ -export const GRPC_ADDR = '0.0.0.0:8081'; - -/** - * Enables or disables transport layer security (TLS). - * Config key: grpc.tls.enabled - * Type: boolean - */ -export const GRPC_TLS_ENABLED = false; - -/** - * The host:port address to serve the HTTP server on. - * Config key: http.addr - * Type: string - */ -export const HTTP_ADDR = '0.0.0.0:8080'; - -/** - * List of allowed headers for CORS requests - * Config key: http.corsAllowedHeaders - * Type: array - */ -export const HTTP_CORS_ALLOWED_HEADERS = ['*']; - -/** - * List of allowed origins for CORS requests - * Config key: http.corsAllowedOrigins - * Type: array - */ -export const HTTP_CORS_ALLOWED_ORIGINS = ['*']; - -/** - * Enables or disables the OpenFGA HTTP server. If this is set to true then 'grpc.enabled' must be set to true. - * Config key: http.enabled - * Type: boolean - */ -export const HTTP_ENABLED = true; - -/** - * Enables or disables transport layer security (TLS). - * Config key: http.tls.enabled - * Type: boolean - */ -export const HTTP_TLS_ENABLED = false; - -/** - * The timeout duration for proxying HTTP requests upstream to the grpc endpoint. - * Config key: http.upstreamTimeout - * Type: string - */ -export const HTTP_UPSTREAM_TIMEOUT = '3s'; - -/** - * The timeout deadline for serving ListObjects requests - * Config key: listObjectsDeadline - * Type: string - */ -export const LIST_OBJECTS_DEADLINE = '3s'; - -/** - * enable throttling when ListObjects request's number of dispatches is high - * Config key: listObjectsDispatchThrottling.enabled - * Type: boolean - */ -export const LIST_OBJECTS_DISPATCH_THROTTLING_ENABLED = false; - -/** - * the frequency period that the deprioritized throttling queue is evaluated for a ListObjects request. A higher value will result in more aggressive throttling - * Config key: listObjectsDispatchThrottling.frequency - * Type: string - */ -export const LIST_OBJECTS_DISPATCH_THROTTLING_FREQUENCY = '10µs'; - -/** - * define the maximum dispatch threshold beyond above which requests will be throttled for a ListObjects request. 0 will use the 'dispatchThrottling.threshold' value as maximum - * Config key: listObjectsDispatchThrottling.maxThreshold - * Type: integer - */ -export const LIST_OBJECTS_DISPATCH_THROTTLING_MAX_THRESHOLD = 0; - -/** - * define the number of recursive operations to occur before getting throttled for a ListObjects request - * Config key: listObjectsDispatchThrottling.threshold - * Type: integer - */ -export const LIST_OBJECTS_DISPATCH_THROTTLING_THRESHOLD = 100; - -/** - * enable caching of datastore iterators in ListObjects. The key is a string representing a database query, and the value is a list of tuples. Each iterator is the result of a database query, for example usersets related to a specific object, or objects related to a specific user, up to a certain number of tuples per iterator. If the request's consistency is HIGHER_CONSISTENCY, this cache is not used. - * Config key: listObjectsIteratorCache.enabled - * Type: boolean - */ -export const LIST_OBJECTS_ITERATOR_CACHE_ENABLED = false; - -/** - * if caching of datastore iterators of ListObjects requests is enabled, this is the limit of tuples to cache per key - * Config key: listObjectsIteratorCache.maxResults - * Type: integer - */ -export const LIST_OBJECTS_ITERATOR_CACHE_MAX_RESULTS = 10000; - -/** - * if caching of datastore iterators of ListObjects requests is enabled, this is the TTL of each value - * Config key: listObjectsIteratorCache.ttl - * Type: string - */ -export const LIST_OBJECTS_ITERATOR_CACHE_TTL = '10s'; - -/** - * The maximum results to return in the non-streaming ListObjects API response. If 0, all results can be returned - * Config key: listObjectsMaxResults - * Type: integer - */ -export const LIST_OBJECTS_MAX_RESULTS = 1000; - -/** - * The timeout deadline for serving ListUsers requests. If 0s, there is no deadline - * Config key: listUsersDeadline - * Type: string - */ -export const LIST_USERS_DEADLINE = '3s'; - -/** - * enable throttling when list users request's number of dispatches is high - * Config key: listUsersDispatchThrottling.enabled - * Type: boolean - */ -export const LIST_USERS_DISPATCH_THROTTLING_ENABLED = false; - -/** - * the frequency period that the deprioritized throttling queue is evaluated for a list users request. A higher value will result in more aggressive throttling - * Config key: listUsersDispatchThrottling.frequency - * Type: string - */ -export const LIST_USERS_DISPATCH_THROTTLING_FREQUENCY = '10µs'; - -/** - * define the maximum dispatch threshold beyond above which requests will be throttled for a list users request. 0 will use the 'dispatchThrottling.threshold' value as maximum - * Config key: listUsersDispatchThrottling.maxThreshold - * Type: integer - */ -export const LIST_USERS_DISPATCH_THROTTLING_MAX_THRESHOLD = 0; - -/** - * define the number of recursive operations to occur before getting throttled for a list users request - * Config key: listUsersDispatchThrottling.threshold - * Type: integer - */ -export const LIST_USERS_DISPATCH_THROTTLING_THRESHOLD = 100; - -/** - * The maximum results to return in ListUsers API response. If 0, all results can be returned - * Config key: listUsersMaxResults - * Type: integer - */ -export const LIST_USERS_MAX_RESULTS = 1000; - -/** - * The log format to output logs in. For production we recommend 'json' format. - * Config key: log.format - * Type: string - */ -export const LOG_FORMAT = 'text'; - -/** - * The log level to set. For production we recommend 'info' format. - * Config key: log.level - * Type: string - */ -export const LOG_LEVEL = 'info'; - -/** - * The timestamp format to use for the log output. - * Config key: log.timestampFormat - * Type: string - */ -export const LOG_TIMESTAMP_FORMAT = 'Unix'; - -/** - * The maximum size in bytes allowed for persisting an Authorization Model (default is 256KB). - * Config key: maxAuthorizationModelSizeInBytes - * Type: integer - */ -export const MAX_AUTHORIZATION_MODEL_SIZE_IN_BYTES = 262144; - -/** - * The maximum number of tuples allowed in a BatchCheck request. - * Config key: maxChecksPerBatchCheck - * Type: integer - */ -export const MAX_CHECKS_PER_BATCH_CHECK = 50; - -/** - * The maximum number of checks that can be processed concurrently in a batch check request. - * Config key: maxConcurrentChecksPerBatchCheck - * Type: integer - */ -export const MAX_CONCURRENT_CHECKS_PER_BATCH_CHECK = 50; - -/** - * The maximum allowed number of concurrent reads in a single Check query (default is MaxUint32). - * Config key: maxConcurrentReadsForCheck - * Type: integer - */ -export const MAX_CONCURRENT_READS_FOR_CHECK = 4294967295; - -/** - * The maximum allowed number of concurrent reads in a single ListObjects query (default is MaxUint32). - * Config key: maxConcurrentReadsForListObjects - * Type: integer - */ -export const MAX_CONCURRENT_READS_FOR_LIST_OBJECTS = 4294967295; - -/** - * The maximum allowed number of concurrent reads in a single ListUsers query (default is MaxUint32). - * Config key: maxConcurrentReadsForListUsers - * Type: integer - */ -export const MAX_CONCURRENT_READS_FOR_LIST_USERS = 4294967295; - -/** - * The maximum cost for CEL condition evaluation before a request returns an error (default is 100). - * Config key: maxConditionEvaluationCost - * Type: integer - */ -export const MAX_CONDITION_EVALUATION_COST = 100; - -/** - * The maximum allowed number of tuples per Write transaction. - * Config key: maxTuplesPerWrite - * Type: integer - */ -export const MAX_TUPLES_PER_WRITE = 100; - -/** - * The maximum allowed number of type definitions per authorization model. - * Config key: maxTypesPerAuthorizationModel - * Type: integer - */ -export const MAX_TYPES_PER_AUTHORIZATION_MODEL = 100; - -/** - * the host:port address to serve the prometheus metrics server on - * Config key: metrics.addr - * Type: string - */ -export const METRICS_ADDR = '0.0.0.0:2112'; - -/** - * enables prometheus histogram metrics for RPC latency distributions - * Config key: metrics.enableRPCHistograms - * Type: boolean - */ -export const METRICS_ENABLE_RPCHISTOGRAMS = false; - -/** - * enable/disable prometheus metrics on the '/metrics' endpoint - * Config key: metrics.enabled - * Type: boolean - */ -export const METRICS_ENABLED = true; - -/** - * How often the planner checks for stale keys. - * Config key: planner.cleanupInterval - * Type: string - */ -export const PLANNER_CLEANUP_INTERVAL = '0'; - -/** - * How long a planner key can be unused before being evicted. - * Config key: planner.evictionThreshold - * Type: undefined - */ -export const PLANNER_EVICTION_THRESHOLD = 0; - -/** - * The initial guess for the planners estimation. - * Config key: planner.initialGuess - * Type: string - */ -export const PLANNER_INITIAL_GUESS = '10ms'; - -/** - * Enable/disable the OpenFGA Playground. - * Config key: playground.enabled - * Type: boolean - */ -export const PLAYGROUND_ENABLED = true; - -/** - * The port to serve the local OpenFGA Playground on. - * Config key: playground.port - * Type: integer - */ -export const PLAYGROUND_PORT = 3000; - -/** - * The host:port address to serve the pprof profiler server on. - * Config key: profiler.addr - * Type: string - */ -export const PROFILER_ADDR = ':3001'; - -/** - * Enabled/disable pprof profiling. - * Config key: profiler.enabled - * Type: boolean - */ -export const PROFILER_ENABLED = false; - -/** - * Datastore query count buckets used to label the histogram metric for measuring request duration. - * Config key: requestDurationDatastoreQueryCountBuckets - * Type: array - */ -export const REQUEST_DURATION_DATASTORE_QUERY_COUNT_BUCKETS = [50, 200]; - -/** - * Dispatch count buckets used to label the histogram metric for measuring request duration. - * Config key: requestDurationDispatchCountBuckets - * Type: array - */ -export const REQUEST_DURATION_DISPATCH_COUNT_BUCKETS = [50, 200]; - -/** - * The timeout duration for a request. - * Config key: requestTimeout - * Type: string - */ -export const REQUEST_TIMEOUT = '3s'; - -/** - * Defines how many nodes on a given level can be evaluated concurrently in a Check resolution tree. - * Config key: resolveNodeBreadthLimit - * Type: integer - */ -export const RESOLVE_NODE_BREADTH_LIMIT = 10; - -/** - * Maximum resolution depth to attempt before throwing an error (defines how deeply nested an authorization model can be before a query errors out). - * Config key: resolveNodeLimit - * Type: integer - */ -export const RESOLVE_NODE_LIMIT = 25; - -/** - * enabling sharing of datastore iterators with different consumers. Each iterator is the result of a database query, for example usersets related to a specific object, or objects related to a specific user, up to a certain number of tuples per iterator. - * Config key: sharedIterator.enabled - * Type: boolean - */ -export const SHARED_ITERATOR_ENABLED = false; - -/** - * if shared-iterator-enabled is enabled, this is the limit of the number of iterators that can be shared. - * Config key: sharedIterator.limit - * Type: integer - */ -export const SHARED_ITERATOR_LIMIT = 1000000; - -/** - * Enable tracing. - * Config key: trace.enabled - * Type: boolean - */ -export const TRACE_ENABLED = false; - -/** - * The grpc endpoint of the trace collector - * Config key: trace.otlp.endpoint - * Type: string - */ -export const TRACE_OTLP_ENDPOINT = '0.0.0.0:4317'; - -/** - * Whether to use TLS connection for the trace collector - * Config key: trace.otlp.tls.enabled - * Type: boolean - */ -export const TRACE_OTLP_TLS_ENABLED = false; - -/** - * The fraction of traces to sample. 1 means all, 0 means none. - * Config key: trace.sampleRatio - * Type: number - */ -export const TRACE_SAMPLE_RATIO = 0.2; - -/** - * The service name included in sampled traces. - * Config key: trace.serviceName - * Type: string - */ -export const TRACE_SERVICE_NAME = 'openfga'; - -/** - * All product configuration default values in a single object - */ -export const PRODUCT_CONFIG_DEFAULTS = { - ACCESS_CONTROL_ENABLED, - ACCESS_CONTROL_MODEL_ID, - ACCESS_CONTROL_STORE_ID, - AUTHN_METHOD, - AUTHN_OIDC_CLIENT_ID_CLAIMS, - AUTHN_OIDC_ISSUER_ALIASES, - AUTHN_OIDC_SUBJECTS, - AUTHN_PRESHARED_KEYS, - CACHE_CONTROLLER_ENABLED, - CACHE_CONTROLLER_TTL, - CHANGELOG_HORIZON_OFFSET, - CHECK_CACHE_LIMIT, - CHECK_DISPATCH_THROTTLING_ENABLED, - CHECK_DISPATCH_THROTTLING_FREQUENCY, - CHECK_DISPATCH_THROTTLING_MAX_THRESHOLD, - CHECK_DISPATCH_THROTTLING_THRESHOLD, - CHECK_ITERATOR_CACHE_ENABLED, - CHECK_ITERATOR_CACHE_MAX_RESULTS, - CHECK_ITERATOR_CACHE_TTL, - CHECK_QUERY_CACHE_ENABLED, - CHECK_QUERY_CACHE_LIMIT, - CHECK_QUERY_CACHE_TTL, - CONTEXT_PROPAGATION_TO_DATASTORE, - DATASTORE_CONN_MAX_IDLE_TIME, - DATASTORE_CONN_MAX_LIFETIME, - DATASTORE_ENGINE, - DATASTORE_MAX_CACHE_SIZE, - DATASTORE_MAX_IDLE_CONNS, - DATASTORE_MAX_OPEN_CONNS, - DATASTORE_METRICS_ENABLED, - EXPERIMENTALS, - GRPC_ADDR, - GRPC_TLS_ENABLED, - HTTP_ADDR, - HTTP_CORS_ALLOWED_HEADERS, - HTTP_CORS_ALLOWED_ORIGINS, - HTTP_ENABLED, - HTTP_TLS_ENABLED, - HTTP_UPSTREAM_TIMEOUT, - LIST_OBJECTS_DEADLINE, - LIST_OBJECTS_DISPATCH_THROTTLING_ENABLED, - LIST_OBJECTS_DISPATCH_THROTTLING_FREQUENCY, - LIST_OBJECTS_DISPATCH_THROTTLING_MAX_THRESHOLD, - LIST_OBJECTS_DISPATCH_THROTTLING_THRESHOLD, - LIST_OBJECTS_ITERATOR_CACHE_ENABLED, - LIST_OBJECTS_ITERATOR_CACHE_MAX_RESULTS, - LIST_OBJECTS_ITERATOR_CACHE_TTL, - LIST_OBJECTS_MAX_RESULTS, - LIST_USERS_DEADLINE, - LIST_USERS_DISPATCH_THROTTLING_ENABLED, - LIST_USERS_DISPATCH_THROTTLING_FREQUENCY, - LIST_USERS_DISPATCH_THROTTLING_MAX_THRESHOLD, - LIST_USERS_DISPATCH_THROTTLING_THRESHOLD, - LIST_USERS_MAX_RESULTS, - LOG_FORMAT, - LOG_LEVEL, - LOG_TIMESTAMP_FORMAT, - MAX_AUTHORIZATION_MODEL_SIZE_IN_BYTES, - MAX_CHECKS_PER_BATCH_CHECK, - MAX_CONCURRENT_CHECKS_PER_BATCH_CHECK, - MAX_CONCURRENT_READS_FOR_CHECK, - MAX_CONCURRENT_READS_FOR_LIST_OBJECTS, - MAX_CONCURRENT_READS_FOR_LIST_USERS, - MAX_CONDITION_EVALUATION_COST, - MAX_TUPLES_PER_WRITE, - MAX_TYPES_PER_AUTHORIZATION_MODEL, - METRICS_ADDR, - METRICS_ENABLE_RPCHISTOGRAMS, - METRICS_ENABLED, - PLANNER_CLEANUP_INTERVAL, - PLANNER_EVICTION_THRESHOLD, - PLANNER_INITIAL_GUESS, - PLAYGROUND_ENABLED, - PLAYGROUND_PORT, - PROFILER_ADDR, - PROFILER_ENABLED, - REQUEST_DURATION_DATASTORE_QUERY_COUNT_BUCKETS, - REQUEST_DURATION_DISPATCH_COUNT_BUCKETS, - REQUEST_TIMEOUT, - RESOLVE_NODE_BREADTH_LIMIT, - RESOLVE_NODE_LIMIT, - SHARED_ITERATOR_ENABLED, - SHARED_ITERATOR_LIMIT, - TRACE_ENABLED, - TRACE_OTLP_ENDPOINT, - TRACE_OTLP_TLS_ENABLED, - TRACE_SAMPLE_RATIO, - TRACE_SERVICE_NAME, -} as const; - -/** - * Type representing all available product configuration constants - */ -export type ProductConfigConstants = typeof PRODUCT_CONFIG_DEFAULTS;