Skip to content

Conversation

@daniel-jonathan
Copy link

@daniel-jonathan daniel-jonathan commented Oct 28, 2025

Summary

Adds streamedListObjects method for retrieving unlimited objects via the streaming API. Node.js-only implementation with NDJSON parsing, error handling, and automatic resource cleanup.

Requires OpenFGA server v1.2.0+

Fixes #236

Changes

  • Add streaming.ts with NDJSON parser for Node.js streams
  • Add StreamedListObjectsResponse interface to apiModel.ts
  • Add OpenFgaClient.streamedListObjects() async generator method
  • Add createStreamingRequestFunction to common.ts for streaming requests
  • Export parseNDJSONStream from index.ts
  • Add streaming tests and examples
  • Update CHANGELOG with feature and version requirement

Implementation

  • Streams beyond 1000-object limit
  • Proper error propagation through async iterators
  • Automatic stream cleanup on early exit
  • Supports Readable, AsyncIterable, string, Buffer, and Uint8Array inputs
  • Telemetry integration via API layer

Usage

const fgaClient = new OpenFgaClient({ 
  apiUrl: "http://localhost:8080", 
  storeId: "01H0H015178Y2V4CX10C2KGHF4" 
});

for await (const response of fgaClient.streamedListObjects({
  user: "user:anne",
  relation: "owner",
  type: "document"
})) {
  console.log(response.object);
}

Testing

All 153 tests passing (10/10 suites)

  • 17 streaming tests covering parsing, errors, cleanup, edge cases
  • 90.8% coverage on streaming.ts
  • Live verified: 3-object and 2000-object streaming

Related

@daniel-jonathan daniel-jonathan requested review from a team as code owners October 28, 2025 04:29
@coderabbitai
Copy link

coderabbitai bot commented Oct 28, 2025

Walkthrough

This PR adds a new streaming variant of the ListObjects API to the JavaScript SDK. It includes a new streamedListObjects method that streams NDJSON-formatted results from the server, allowing retrieval of more than 1000 objects. The implementation spans the API layer, client wrapper, streaming parser, and includes comprehensive examples and tests.

Changes

Cohort / File(s) Summary
Core API Layer
src/api.ts
Adds streamedListObjects method across all API interfaces (AxiosParamCreator, Fp, Factory, and OpenFgaApi class) to support streaming requests via createStreamingRequestFunction.
API Model Types
src/apiModel.ts
Introduces new StreamedListObjectsResponse interface with object property for streamed response elements.
Client Wrapper
src/client.ts
Adds streamedListObjects method returning AsyncGenerator<StreamedListObjectsResponse> that parses NDJSON stream and yields results.
Streaming Support
src/common.ts, src/streaming.ts, src/index.ts
Implements createStreamingRequestFunction for axios streaming with telemetry; adds parseNDJSONStream utility to parse line-delimited JSON from Node.js Readable streams; exports parseNDJSONStream publicly.
Test Mocks & Coverage
tests/helpers/nocks.ts, tests/streaming.test.ts
Adds streamedListObjects mock helper for NDJSON endpoint responses; comprehensive test suite for NDJSON parser covering chunked data, various input types, and error handling.
Examples
example/streamed-list-objects/README.md, example/streamed-list-objects/model.json, example/streamed-list-objects/streamedListObjects.mjs
Complete example demonstrating streaming 2000 tuples, comparing streamed vs. standard endpoints, with access control model definition.
Local Example
example/streamed-list-objects-local/README.md, example/streamed-list-objects-local/streamedListObjectsLocal.mjs
Simplified local example showing streamedListObjects with user-relation queries and store lifecycle.
Documentation
CHANGELOG.md
Documents new streamedListObjects feature for Node.js environments with NDJSON parsing support.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Client as OpenFgaClient
    participant API as OpenFgaApi
    participant HTTP as axios
    participant Server as OpenFGA Server
    participant Parser as parseNDJSONStream

    User->>Client: streamedListObjects(request)
    Client->>API: streamedListObjects()
    API->>HTTP: GET /stores/{id}/streamed-list-objects<br/>(responseType: stream, telemetry)
    HTTP->>Server: Request with streaming
    Server-->>HTTP: NDJSON stream (line-delimited JSON)
    HTTP-->>API: Response stream
    API-->>Client: Response.data (stream)
    Client->>Parser: parseNDJSONStream(stream)
    
    loop For each line in stream
        Parser->>Parser: UTF-8 decode, buffer, split on \\n
        Parser->>Parser: Parse JSON object
        Parser-->>Client: yield StreamedListObjectsResponse
    end
    
    Client-->>User: AsyncGenerator<StreamedListObjectsResponse>
    User->>User: Iterate and process results
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~35 minutes

  • src/streaming.ts: The NDJSON parser handles multiple input modalities (Readable, Buffer, string, async iterable) with UTF-8 decoding, line buffering, and error handling. Verify edge cases around partial UTF-8 sequences, decoder state management, and listener cleanup.
  • src/common.ts: New createStreamingRequestFunction mirrors existing request flow but with streaming response. Verify telemetry capture (fromRequest/fromResponse) and histogram emission work correctly with streaming.
  • tests/streaming.test.ts: Extensive test suite validates chunked data, error scenarios, and various input types. Spot-check for completeness of edge cases (e.g., incomplete UTF-8 at buffer boundary).
  • Example files: Ensure model.json schema is valid and examples execute correctly with 2000 tuples to verify streaming advantage over standard pagination.

Possibly related issues

Suggested reviewers

  • jimmyjames

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "feat: add streamedListObjects for unlimited object retrieval" clearly and directly describes the primary change in the changeset. The title references the new streamedListObjects feature and explains its key benefit (unlimited object retrieval beyond the standard 1000-object limit). The title is specific, concise, and uses appropriate conventional prefix (feat:) without vague language or excessive noise. A developer reviewing the commit history would immediately understand that this PR adds streaming capability to retrieve objects without the standard limitation.
Linked Issues Check ✅ Passed The pull request successfully implements both primary requirements from linked issue #236. First, SDK methods to call the StreamedListObjects API have been added across all layers: the api.ts exposes the streamedListObjects method through OpenFgaApi, OpenFgaApiFp, OpenFgaApiFactory, and OpenFgaApiAxiosParamCreator; the client.ts provides OpenFgaClient.streamedListObjects() as an AsyncGenerator interface for consumers. Second, the implementation aligns with the Python SDK's async-generator pattern as noted in the PR objectives. The code includes supporting infrastructure (parseNDJSONStream for NDJSON parsing, createStreamingRequestFunction for streaming requests, StreamedListObjectsResponse model), comprehensive examples demonstrating usage, and test coverage validating the streaming behavior.
Out of Scope Changes Check ✅ Passed All changes in the pull request are directly scoped to the objectives of adding StreamedListObjects support. The modifications span necessary layers: API contract changes (api.ts), client-facing implementation (client.ts), data models (apiModel.ts), streaming utilities (streaming.ts), request infrastructure (common.ts), public exports (index.ts), test infrastructure (tests/helpers/nocks.ts, tests/streaming.test.ts), usage examples (example/ directory), and documentation (CHANGELOG.md). No unrelated bug fixes, general refactoring, or tangential features are present. The examples and supporting files are integral to demonstrating the feature rather than out-of-scope additions.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/streaming-only

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov-commenter
Copy link

codecov-commenter commented Oct 28, 2025

Codecov Report

❌ Patch coverage is 65.54054% with 51 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.62%. Comparing base (9b81c81) to head (0c2a0ab).

Files with missing lines Patch % Lines
api.ts 4.54% 21 Missing ⚠️
common.ts 36.36% 14 Missing ⚠️
streaming.ts 90.21% 7 Missing and 2 partials ⚠️
client.ts 36.36% 7 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #280      +/-   ##
==========================================
- Coverage   89.13%   86.62%   -2.51%     
==========================================
  Files          24       25       +1     
  Lines        1288     1436     +148     
  Branches      211      241      +30     
==========================================
+ Hits         1148     1244      +96     
- Misses         84      134      +50     
- Partials       56       58       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (10)
apiModel.ts (1)

864-876: Clarify docstring: SDK-yielded type vs wire shape.

The client parses NDJSON lines like { result: { object } } and yields { object }. Consider adjusting the comment to “Element yielded by streamedListObjects” to avoid implying this is the on-the-wire shape. Otherwise the interface looks good.

example/streamed-list-objects-local/README.md (1)

5-7: Align Node version and add browser note.

  • Change “Node.js 18+” to match SDK minimum (e.g., “Node.js ≥16.15.0”, unless 18+ is explicitly required here).
  • Add: “Not supported in browsers; uses Node Readable streams.”

Please confirm whether any example dependency requires Node 18+ specifically (e.g., APIs not in Node 16.15).

common.ts (1)

353-418: Harden streaming request: headers default, NDJSON Accept, cancellation, telemetry parity.

  • Ensure headers exists before auth header injection.
  • Set Accept: application/x-ndjson if not provided.
  • Allow AbortSignal passthrough (so callers can cancel).
  • Optionally record request-body attributes (parity with non-stream paths, if/when added there).

Apply this diff:

 export const createStreamingRequestFunction = function (axiosArgs: RequestArgs, axiosInstance: AxiosInstance, configuration: Configuration, credentials: Credentials, methodAttributes: Record<string, string | number> = {}) {
   configuration.isValid();
@@
-  return async (axios: AxiosInstance = axiosInstance): Promise<any> => {
-    await setBearerAuthToObject(axiosArgs.options.headers, credentials!);
+  return async (axios: AxiosInstance = axiosInstance): Promise<any> => {
+    // ensure headers object
+    axiosArgs.options.headers = axiosArgs.options.headers || {};
+    await setBearerAuthToObject(axiosArgs.options.headers, credentials!);
@@
-    const axiosRequestArgs = { ...axiosArgs.options, responseType: "stream", url: url };
+    // default NDJSON accept unless caller overrides
+    if (!axiosArgs.options.headers["Accept"]) {
+      axiosArgs.options.headers["Accept"] = "application/x-ndjson";
+    }
+    const axiosRequestArgs = { ...axiosArgs.options, responseType: "stream", url };
@@
-    attributes = TelemetryAttributes.fromResponse({
+    attributes = TelemetryAttributes.fromResponse({
       response,
       attributes,
     });

Optional: If you add signal?: AbortSignal to client options, pass it via axiosArgs.options.signal.

Please confirm the server’s preferred Accept header for streamed list objects (e.g., application/x-ndjson). If different, we should set it accordingly.

tests/helpers/nocks.ts (1)

249-264: Simulate chunked NDJSON to better exercise the parser.

Current mock emits a single large chunk. Emit smaller chunks to surface boundary/chunking bugs in tests.

-    return nock(basePath)
-      .post(`/stores/${storeId}/streamed-list-objects`)
-      .reply(200, () => Readable.from([ndjsonResponse]), {
-        "Content-Type": "application/x-ndjson"
-      });
+    return nock(basePath)
+      .post(`/stores/${storeId}/streamed-list-objects`)
+      .reply(200, () => {
+        // send ~32-byte chunks to simulate real streaming
+        const chunks = Array.from(ndjsonResponse.matchAll(/.{1,32}/gs), m => m[0]);
+        return Readable.from(chunks);
+      }, {
+        "Content-Type": "application/x-ndjson"
+      });
example/streamed-list-objects-local/streamedListObjectsLocal.mjs (1)

55-57: Ensure store cleanup in a finally block.

If an earlier step throws, the store may be orphaned. Move deleteStore into finally and guard on storeId.

Example:

let storeId;
try {
  // ... create store, set storeId, do work ...
} finally {
  if (storeId) {
    await new OpenFgaClient(new ClientConfiguration({ apiUrl, storeId })).deleteStore().catch(() => {});
  }
}
api.ts (2)

387-425: Be explicit about NDJSON in request headers.

Setting Accept helps interoperability and test clarity.

-      const localVarHeaderParameter = {} as any;
+      const localVarHeaderParameter = {} as any;
+      localVarHeaderParameter["Accept"] = "application/x-ndjson";

955-970: Align telemetry with other methods.

Include store id and body-derived attributes for consistency.

-      return createStreamingRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
-        [TelemetryAttribute.FgaClientRequestMethod]: "StreamedListObjects"
-      });
+      return createStreamingRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
+        [TelemetryAttribute.FgaClientRequestMethod]: "StreamedListObjects",
+        [TelemetryAttribute.FgaClientRequestStoreId]: storeId ?? "",
+        ...TelemetryAttributes.fromRequestBody(body),
+      });
example/streamed-list-objects/README.md (1)

16-22: Doc: mention building the SDK or using the published package.

Examples import from ../../dist; advise building first or installing from npm.

Suggested snippet:

# From repo root
pnpm install
pnpm build
cd example/streamed-list-objects
node streamedListObjects.mjs
example/streamed-list-objects/streamedListObjects.mjs (2)

19-36: writeTuples drops remainder; handle non-multiples of 100.

If quantity isn’t divisible by 100, the tail is lost.

-    const chunks = Math.floor(quantity / 100);
+    const chunks = Math.floor(quantity / 100);
+    const remainder = quantity % 100;
@@
     for (let chunk = 0; chunk < chunks; ++chunk) {
       const tuples = [];
       for (let t = 0; t < 100; ++t) {
         tuples.push({
           user: "user:anne",
           relation: "owner",
           object: `document:${chunk * 100 + t}`
         });
       }
       await fgaClient.writeTuples(tuples);
     }
+    if (remainder) {
+      const tuples = [];
+      for (let t = 0; t < remainder; ++t) {
+        tuples.push({
+          user: "user:anne",
+          relation: "owner",
+          object: `document:${chunks * 100 + t}`
+        });
+      }
+      await fgaClient.writeTuples(tuples);
+    }

94-99: Ensure cleanup in finally.

Move deleteStore into finally and guard on storeId to avoid orphaned stores on errors.

Example:

let storeId;
try {
  storeId = await createStore(fgaClient);
  // ...
} finally {
  if (storeId) await fgaClient.deleteStore().catch(() => {});
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9b81c81 and 055d1dd.

📒 Files selected for processing (14)
  • CHANGELOG.md (1 hunks)
  • api.ts (5 hunks)
  • apiModel.ts (1 hunks)
  • client.ts (3 hunks)
  • common.ts (1 hunks)
  • example/streamed-list-objects-local/README.md (1 hunks)
  • example/streamed-list-objects-local/streamedListObjectsLocal.mjs (1 hunks)
  • example/streamed-list-objects/README.md (1 hunks)
  • example/streamed-list-objects/model.json (1 hunks)
  • example/streamed-list-objects/streamedListObjects.mjs (1 hunks)
  • index.ts (1 hunks)
  • streaming.ts (1 hunks)
  • tests/helpers/nocks.ts (2 hunks)
  • tests/streaming.test.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (8)
example/streamed-list-objects/streamedListObjects.mjs (2)
client.ts (4)
  • createStore (324-326)
  • writeTuples (568-572)
  • streamedListObjects (825-845)
  • listObjects (797-807)
example/streamed-list-objects-local/streamedListObjectsLocal.mjs (2)
  • model (13-29)
  • storeId (9-9)
tests/helpers/nocks.ts (1)
tests/helpers/default-config.ts (1)
  • defaultConfiguration (56-56)
streaming.ts (1)
index.ts (1)
  • parseNDJSONStream (27-27)
client.ts (2)
apiModel.ts (1)
  • StreamedListObjectsResponse (869-876)
streaming.ts (1)
  • parseNDJSONStream (96-168)
api.ts (3)
apiModel.ts (1)
  • ListObjectsRequest (804-847)
common.ts (6)
  • RequestArgs (30-33)
  • DUMMY_BASE_URL (23-23)
  • setSearchParams (51-66)
  • serializeDataIfNeeded (88-96)
  • toPathString (102-104)
  • createStreamingRequestFunction (353-418)
validation.ts (1)
  • assertParamExists (8-12)
example/streamed-list-objects-local/streamedListObjectsLocal.mjs (1)
client.ts (2)
  • OpenFgaClient (233-945)
  • ClientConfiguration (59-92)
tests/streaming.test.ts (1)
streaming.ts (1)
  • parseNDJSONStream (96-168)
common.ts (4)
base.ts (1)
  • RequestArgs (15-18)
configuration.ts (1)
  • Configuration (58-202)
telemetry/attributes.ts (1)
  • TelemetryAttributes (35-130)
telemetry/histograms.ts (1)
  • TelemetryHistograms (20-32)
🔇 Additional comments (10)
client.ts (1)

24-25: Imports look correct.

Also applies to: 52-53

common.ts (1)

345-347: No functional change here.

tests/helpers/nocks.ts (1)

3-4: LGTM: Node stream import fits the streaming mock.

api.ts (1)

1442-1454: OO wrapper wiring looks correct.

index.ts (1)

27-27: LGTM: public export of parseNDJSONStream.

example/streamed-list-objects/model.json (1)

1-251: LGTM: model is coherent and matches example relations.

tests/streaming.test.ts (4)

14-19: LGTM! Well-organized test suite structure.

The imports and test suite organization are clean and appropriate for testing the NDJSON streaming parser.


20-96: Excellent coverage of core parsing scenarios.

These tests cover the fundamental NDJSON parsing cases effectively. The chunked data test (lines 48-65) is particularly valuable as it validates the buffering logic when JSON objects are split across stream chunks—a critical real-world scenario.


98-162: Comprehensive input type coverage.

These tests validate the parser's flexibility with different input types (Buffer, string, async iterable). The test for JSON without a trailing newline (lines 109-117) is particularly important as it validates the final buffer flush logic. The as any type casts are acceptable here to test the parser's runtime behavior with various input types.


164-257: Thorough async generator protocol and cleanup testing.

These tests rigorously validate the async iterator implementation, including error handling, early cancellation, buffering behavior, and proper cleanup of event listeners. The listener cleanup assertions (lines 193-195, 241-243) are particularly valuable for preventing memory leaks. While these tests exercise internal implementation details, the guarantees they provide about correctness and resource management justify their inclusion.

daniel-jonathan added a commit to openfga/sdk-generator that referenced this pull request Oct 28, 2025
Updates JavaScript SDK templates to support the streaming API endpoint
for unlimited object retrieval. Templates now handle streaming operations
differently using vendor extension conditionals.

Changes:
- Add streaming.mustache template with NDJSON parser for Node.js
- Update api.mustache to import createStreamingRequestFunction
- Update apiInner.mustache with x-fga-streaming vendor extension logic
  - Uses createStreamingRequestFunction for streaming ops
  - Returns Promise<any> instead of PromiseResult<T>
  - Simplified telemetry (method name only)
- Update index.mustache to export parseNDJSONStream
- Update config.overrides.json with streaming file + feature flag
- Add README documentation for Streamed List Objects API
- Update API endpoints table with streaming endpoint

Implementation:
- Conditional template logic based on x-fga-streaming vendor extension
- Preserves telemetry while returning raw Node.js stream
- Aligned with Python SDK template patterns

Dependencies:
- Requires x-fga-streaming: true in OpenAPI spec (openfga/api)

Related:
- Fixes #76 (JavaScript SDK)
- Implements openfga/js-sdk#236
- Related PR: openfga/js-sdk#280
daniel-jonathan added a commit to openfga/sdk-generator that referenced this pull request Oct 28, 2025
Updates JavaScript SDK templates to support the streaming API endpoint
for unlimited object retrieval. Templates now handle streaming operations
differently using vendor extension conditionals.

Changes:
- Add streaming.mustache template with NDJSON parser for Node.js
- Update api.mustache to import createStreamingRequestFunction
- Update apiInner.mustache with x-fga-streaming vendor extension logic
  - Uses createStreamingRequestFunction for streaming ops
  - Returns Promise<any> instead of PromiseResult<T>
  - Simplified telemetry (method name only)
- Update index.mustache to export parseNDJSONStream
- Update config.overrides.json with streaming file + feature flag
- Add README documentation for Streamed List Objects API
- Update API endpoints table with streaming endpoint

Implementation:
- Conditional template logic based on x-fga-streaming vendor extension
- Preserves telemetry while returning raw Node.js stream
- Aligned with Python SDK template patterns
- Fixed error propagation in async iterator adapter
- Widened parseNDJSONStream type signature for better DX

Dependencies:
- Requires x-fga-streaming: true in OpenAPI spec (openfga/api)

Related:
- Fixes #76 (JavaScript SDK)
- Implements openfga/js-sdk#236
- Related PR: openfga/js-sdk#280
daniel-jonathan added a commit to openfga/sdk-generator that referenced this pull request Oct 28, 2025
Updates JavaScript SDK templates to support the streaming API endpoint
for unlimited object retrieval. Templates now handle streaming operations
differently using vendor extension conditionals.

Templates Modified (7 files):
- Add streaming.mustache template with NDJSON parser for Node.js
- Update api.mustache to import createStreamingRequestFunction
- Update apiInner.mustache with x-fga-streaming vendor extension logic
  - Uses createStreamingRequestFunction for streaming ops
  - Returns Promise<any> instead of PromiseResult<T>
  - Simplified telemetry (method name only)
- Update index.mustache to export parseNDJSONStream
- Update config.overrides.json with streaming file + feature flag
- Add README_calling_api.mustache documentation for Streamed List Objects
- Add README_api_endpoints.mustache table entry for streaming endpoint

Implementation:
- Conditional template logic based on x-fga-streaming vendor extension
- Preserves telemetry while returning raw Node.js stream
- Aligned with Python SDK template patterns
- Fixed error propagation in async iterator adapter
- Widened parseNDJSONStream type signature for better DX
- Added guard to prevent onEnd processing after error state

Generated SDK Verification:
- ✅ streaming.ts generated with all error handling fixes
- ✅ parseNDJSONStream exported from index.ts
- ✅ StreamedListObjectsResponse interface in apiModel.ts
- ⚠️ streamedListObjects method uses regular handling (needs x-fga-streaming: true in spec)

Dependencies:
- Requires x-fga-streaming: true vendor extension in OpenAPI spec (openfga/api)
- Without vendor extension, method is generated but uses wrong request handler

Related:
- Fixes #76 (JavaScript SDK)
- Implements openfga/js-sdk#236
- Related PR: openfga/js-sdk#280
daniel-jonathan added a commit to openfga/sdk-generator that referenced this pull request Oct 28, 2025
Adds NDJSON streaming parser template to support streamedListObjects in
the JavaScript SDK. Templates provide the parsing utility; actual streaming
implementation remains in custom code (client.ts, common.ts) in js-sdk repo.

Templates Added/Modified (5 files):
- streaming.mustache (NEW) - NDJSON parser for Node.js streams
  - Proper error propagation (pending promises reject on error)
  - onEnd guard prevents processing after error state
  - Widened type signature (Readable|AsyncIterable|string|Buffer)
- index.mustache - Export parseNDJSONStream utility
- config.overrides.json - Register streaming.mustache + supportsStreamedListObjects flag
- README_calling_api.mustache - Add Streamed List Objects usage documentation
- README_api_endpoints.mustache - Add endpoint to API table

Architecture:
- Templates generate utilities and basic API methods
- Custom code in js-sdk handles actual streaming (client.ts, common.ts)
- No OpenAPI spec changes required

Tested:
- Generated streaming.ts includes all error handling fixes
- parseNDJSONStream exported correctly
- Custom js-sdk code works with generated utilities

Related:
- Fixes #76 (JavaScript SDK)
- Implements openfga/js-sdk#236
- Related PR: openfga/js-sdk#280
Adds streamedListObjects method for retrieving unlimited objects via the
streaming API endpoint. Node.js-only implementation with resilient NDJSON
parsing, proper error handling, and automatic resource cleanup.

Requires OpenFGA server v1.2.0+

Features:
- Streams beyond 1000-object limit (tested with 2000 objects)
- Memory-efficient incremental results via async generators
- Automatic stream cleanup prevents connection leaks
- Proper error propagation through async iterators
- Flexible input types (Readable|AsyncIterable|string|Buffer|Uint8Array)
- Telemetry maintained through streaming request helper

Implementation:
- streaming.ts: NDJSON parser with robust error handling
- common.ts: createStreamingRequestFunction for axios streaming
- client.ts: streamedListObjects() async generator wrapper
- Error handling: Pending promises reject on error, onEnd guarded
- Resource management: Stream destruction in return()/throw()/finally
- Type safety: Wide signature eliminates unnecessary casts

Testing (153/153 tests passing):
- 17 streaming tests (parsing, errors, cleanup, edge cases)
- 95% coverage on streaming.ts
- Live tested: 3-object and 2000-object streaming verified

Examples:
- example/streamed-list-objects: Full model with 2000 tuples
- example/streamed-list-objects-local: Minimal local setup

Related:
- Fixes #236
- Parent issue: openfga/sdk-generator#76
- Related PR: openfga/sdk-generator#654 (templates)
daniel-jonathan added a commit to openfga/sdk-generator that referenced this pull request Oct 28, 2025
Adds NDJSON streaming parser template to support streamedListObjects in the
JavaScript SDK. Templates provide parsing utilities; actual streaming logic
remains in custom code (client.ts, common.ts) maintained in js-sdk repository.

Templates Added/Modified (5 files):
- streaming.mustache (NEW): NDJSON parser for Node.js streams
  - Proper error propagation (reject pending promises on error)
  - onEnd guard prevents processing after error
  - Uint8Array handling alongside string/Buffer
  - Stream destruction in return()/throw() methods
  - Widened type signature for better DX
- index.mustache: Export parseNDJSONStream utility
- config.overrides.json: Register streaming + supportsStreamedListObjects flag
- README_calling_api.mustache: Usage documentation
- README_api_endpoints.mustache: API endpoint table entry

Architecture:
- Templates generate utilities (streaming.ts, exports)
- Custom js-sdk code implements streaming (common.ts, client.ts)
- No OpenAPI spec changes required

Generated & Verified:
- streaming.ts includes all error handling fixes
- parseNDJSONStream exported correctly
- Works with custom js-sdk streaming implementation

Related:
- Fixes #76 (JavaScript SDK)
- Implements openfga/js-sdk#236
- Related PR: openfga/js-sdk#280
- feat: add support for handling Retry-After header (#267)
- feat: streamedListObjects (streaming ListObjects) - Node.js only
- Enables retrieving >1000 objects beyond standard listObjects limit
- Requires OpenFGA server [v1.2.0+](https://github.com/openfga/openfga/releases/tag/v1.2.0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this requirement?

Comment on lines +7 to +14
- feat: streamedListObjects (streaming ListObjects) - Node.js only
- Enables retrieving >1000 objects beyond standard listObjects limit
- Requires OpenFGA server [v1.2.0+](https://github.com/openfga/openfga/releases/tag/v1.2.0)
- Uses axios streaming via API layer with preserved telemetry
- Resilient NDJSON parsing (supports async-iterable and event-based streams)
- Parses chunked data across multiple reads; handles Buffer/string inputs
- Adds example for usage: `example/streamed-list-objects`
- Adds example for local usage: `example/streamed-list-objects-local`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are too detailed for the changelog - replace them with a link to the documentation in Readme

E.g.

- feat: add support for [StreamedListObjects](https://openfga.dev/api/service#/Relationship%20Queries/StreamedListObjects) with streaming semantics. See [documentation](https://github.com/openfga/js-sdk/blob/main/README.md#streamed-list-objects) for more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also can you add that section to the README under List Objects


Streamed List Objects

The Streamed ListObjects API is very similar to the the ListObjects API, with two differences:

  • Instead of collecting all objects before returning a response, it streams them to the client as they are collected.
  • *The number of results returned is only limited by the execution timeout specified in the flag OPENFGA_LIST_OBJECTS_DEADLINE.

API Documentation

const options = {};

// To override the authorization model id for this request
options.authorizationModelId = "01GXSA8YR785C4FYS3C0RTG7B1";

const objects = [];
for await (const response of fga.streamedListObjects(
    { user: "user:anne", relation: "can_read", type: "document" },
    { consistency: ConsistencyPreference.HigherConsistency }
)) {
    objects.push(response.object);
}

// objects = ["document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a"]

@@ -0,0 +1,62 @@
import { ClientConfiguration, OpenFgaClient, ConsistencyPreference } from "../../dist/index.js";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need two examples for this, let's stick to this one only - or the other one only

Also can you add the package.json for the example?

Comment on lines +47 to +52
for await (const _ of fga.streamedListObjects(
{ user: "user:anne", relation: "can_read", type: "document" },
{ consistency: ConsistencyPreference.HigherConsistency }
)) {
count++;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of just appending the count, can you get the object? Just to showcase to folks looking at this example?

It can be something like:

Suggested change
for await (const _ of fga.streamedListObjects(
{ user: "user:anne", relation: "can_read", type: "document" },
{ consistency: ConsistencyPreference.HigherConsistency }
)) {
count++;
}
for await (const response of fga.streamedListObjects(
{ user: "user:anne", relation: "can_read", type: "document" },
{ consistency: ConsistencyPreference.HigherConsistency }
)) {
console.log(`- ${response.object}`)
count++;
}

Comment on lines +13 to +29
const model = {
schema_version: "1.1",
type_definitions: [
{ type: "user" },
{
type: "document",
relations: { can_read: { this: {} } },
metadata: {
relations: {
can_read: {
directly_related_user_types: [{ type: "user" }]
}
}
}
}
]
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the syntax transformer here:

Suggested change
const model = {
schema_version: "1.1",
type_definitions: [
{ type: "user" },
{
type: "document",
relations: { can_read: { this: {} } },
metadata: {
relations: {
can_read: {
directly_related_user_types: [{ type: "user" }]
}
}
}
}
]
};
const dslString = `
model
schema 1.1
type user
type document
relations
can_read: [user]
`;
const model = transformer.transformDSLToJSONObject(dslString);

@@ -0,0 +1,33 @@
# Streamed List Objects Example
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for this example I think

@@ -0,0 +1,251 @@
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need a complex model for this example? Can we stay simple like in the first one.

* @param {number} [options.retryParams.minWaitInMs] - Override the minimum wait before a retry is initiated
* @returns {AsyncGenerator<StreamedListObjectsResponse>} An async generator that yields objects as they are received
*/
async *streamedListObjects(body: ClientListObjectsRequest, options: ClientRequestOptsWithConsistency = {}): AsyncGenerator<StreamedListObjectsResponse> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add the relevant tests to client.test.js?

Note include in the tests, tests for:

  • retry handling
  • custom headers
  • error handling

This example demonstrates using the js-sdk `streamedListObjects` API against a locally running OpenFGA server that you manage yourself.

Prerequisites:
- Node.js 18+
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this?

@rhamzeh
Copy link
Member

rhamzeh commented Oct 29, 2025

Can you also review @SoulPancake's PR here: openfga/go-sdk#252

Ideally both would have the same semantics, example, tests, config options, README, etc..

- feat: add support for handling Retry-After header (#267)
- feat: streamedListObjects (streaming ListObjects) - Node.js only
- Enables retrieving >1000 objects beyond standard listObjects limit
- Requires OpenFGA server [v1.2.0+](https://github.com/openfga/openfga/releases/tag/v1.2.0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm not wrong, this was the release with the streamed list objects in the API
https://github.com/openfga/openfga/releases/tag/v0.2.0
openfga/api@75e70de
Maybe some improvements were added later in the release mentioned

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Support for StreamedListObjects Endpoint

5 participants