diff --git a/docs/content/getting-started/perform-list-objects.mdx b/docs/content/getting-started/perform-list-objects.mdx index 5e3f919011..d8ab5a5197 100644 --- a/docs/content/getting-started/perform-list-objects.mdx +++ b/docs/content/getting-started/perform-list-objects.mdx @@ -9,6 +9,7 @@ import { SupportedLanguage, languageLabelMap, ListObjectsRequestViewer, + StreamedListObjectsRequestViewer, DocumentationNotice, ProductConcept, ProductName, @@ -182,6 +183,27 @@ The result `document:otherdoc` and `document:planning` are the document objects The performance characteristics of the ListObjects endpoint vary drastically depending on the model complexity, number of tuples, and the relations it needs to evaluate. Relations with 'and' or 'but not' are more expensive to evaluate than relations with 'or'. ::: +## Streamed List Objects + +The Streamed ListObjects API is similar to the ListObjects API, with two key differences: + +1. **Streaming Response**: Instead of collecting all objects before returning a response, it streams them to the client as they are collected. +2. **No Result Limit**: The number of results returned is only limited by the execution timeout specified in the flag `OPENFGA_LIST_OBJECTS_DEADLINE`, not by a fixed limit. + +:::info +The streaming functionality is currently available in the **Node.js SDK**, **Go SDK**, **.NET SDK**, **Python SDK**, and **Java SDK**. +::: + +### Using Streamed List Objects + + + ## Related Sections diff --git a/src/components/Docs/SnippetViewer/StreamedListObjectsRequestViewer.tsx b/src/components/Docs/SnippetViewer/StreamedListObjectsRequestViewer.tsx new file mode 100644 index 0000000000..fda566737e --- /dev/null +++ b/src/components/Docs/SnippetViewer/StreamedListObjectsRequestViewer.tsx @@ -0,0 +1,106 @@ +import { getFilteredAllowedLangs, SupportedLanguage } from './SupportedLanguage'; +import { defaultOperationsViewer } from './DefaultTabbedViewer'; +import assertNever from 'assert-never/index'; + +interface StreamedListObjectsRequestViewerOpts { + user: string; + relation: string; + objectType: string; + expectedResults: string[]; + skipSetup?: boolean; + allowedLanguages?: SupportedLanguage[]; +} + +function streamedListObjectsRequestViewer(lang: SupportedLanguage, opts: StreamedListObjectsRequestViewerOpts): string { + const { user, relation, objectType, expectedResults } = opts; + + switch (lang) { + case SupportedLanguage.PLAYGROUND: + return `# Note: Streamed List Objects is not currently supported on the playground`; + case SupportedLanguage.CLI: + return `# Note: Streamed List Objects is not currently supported in the CLI`; + case SupportedLanguage.CURL: + return `curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/streamed-list-objects \\ + -H "Authorization: Bearer $FGA_API_TOKEN" \\ # Not needed if service does not require authorization + -H "content-type: application/json" \\ + -d '{ + "type": "${objectType}", + "relation": "${relation}", + "user":"${user}" + }' + + +# Response: +${expectedResults.map((r) => `{"result":{"object":"${r}"}}`).join('\n')}`; + case SupportedLanguage.JS_SDK: + return `const objects = []; +for await (const response of fgaClient.streamedListObjects( + { user: "${user}", relation: "${relation}", type: "${objectType}" } +)) { + objects.push(response.object); +} +// objects = [${expectedResults.map((r) => `"${r}"`).join(', ')}]`; + case SupportedLanguage.DOTNET_SDK: + return `var objects = new List(); +await foreach (var response in fgaClient.StreamedListObjects( + new ClientListObjectsRequest { + User = "${user}", + Relation = "${relation}", + Type = "${objectType}" + })) { + objects.Add(response.Object); +} +// objects = [${expectedResults.map((r) => `"${r}"`).join(', ')}]`; + case SupportedLanguage.PYTHON_SDK: + return `objects = [] +async for response in fga_client.streamed_list_objects( + ClientListObjectsRequest( + user="${user}", + relation="${relation}", + type="${objectType}" + ) +): + objects.append(response.object) +# objects = [${expectedResults.map((r) => `"${r}"`).join(', ')}]`; + case SupportedLanguage.GO_SDK: + return `objects := []string{} +err := fgaClient.StreamedListObjects(context.Background()). + Body(client.ClientListObjectsRequest{ + User: "${user}", + Relation: "${relation}", + Type: "${objectType}", + }). + Execute(func(response *client.ClientStreamedListObjectsResponse) error { + objects = append(objects, response.Object) + return nil + }) +// objects = [${expectedResults.map((r) => `"${r}"`).join(', ')}]`; + case SupportedLanguage.JAVA_SDK: + return `var objects = new ArrayList(); +var request = new ClientListObjectsRequest() + .user("${user}") + .relation("${relation}") + .type("${objectType}"); + +fgaClient.streamedListObjects(request, new ClientStreamedListObjectsOptions(), response -> { + objects.add(response.getObject()); +}).get(); +// objects = [${expectedResults.map((r) => `"${r}"`).join(', ')}]`; + case SupportedLanguage.RPC: + return `# Note: Use CURL or SDK for streaming examples`; + default: + return assertNever(lang); + } +} + +export function StreamedListObjectsRequestViewer(opts: StreamedListObjectsRequestViewerOpts): JSX.Element { + const defaultLangs = [ + SupportedLanguage.JS_SDK, + SupportedLanguage.GO_SDK, + SupportedLanguage.DOTNET_SDK, + SupportedLanguage.PYTHON_SDK, + SupportedLanguage.JAVA_SDK, + ]; + const allowedLanguages = getFilteredAllowedLangs(opts.allowedLanguages, defaultLangs); + return defaultOperationsViewer(allowedLanguages, opts, streamedListObjectsRequestViewer); +} diff --git a/src/components/Docs/SnippetViewer/index.ts b/src/components/Docs/SnippetViewer/index.ts index 4f50acffa3..19efebe2d1 100644 --- a/src/components/Docs/SnippetViewer/index.ts +++ b/src/components/Docs/SnippetViewer/index.ts @@ -8,5 +8,6 @@ export * from './ReadChangesRequestViewer'; export * from './ReadRequestViewer'; export { SdkSetupHeader } from './SdkSetup'; export { SupportedLanguage, languageLabelMap } from './SupportedLanguage'; +export * from './StreamedListObjectsRequestViewer'; export * from './WriteRequestViewer'; export * from './WriteAuthzModelViewer';