Skip to content

Commit b5fa5ab

Browse files
feat(js): add streaming support for streamedListObjects
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
1 parent b411f38 commit b5fa5ab

File tree

1 file changed

+16
-15
lines changed

1 file changed

+16
-15
lines changed

config/clients/js/template/streaming.mustache

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ const createAsyncIterableFromReadable = (readable: any): AsyncIterable<any> => {
77
return {
88
[Symbol.asyncIterator](): AsyncIterator<any> {
99
const chunkQueue: any[] = [];
10-
const pendingResolvers: Array<(value: IteratorResult<any>) => void> = [];
10+
const pendings: Array<{ resolve: (v: IteratorResult<any>) => void; reject: (e?: any) => void }> = [];
1111
let ended = false;
1212
let error: any = null;
1313

1414
const onData = (chunk: any) => {
15-
if (pendingResolvers.length > 0) {
16-
const resolve = pendingResolvers.shift()!;
15+
if (pendings.length > 0) {
16+
const { resolve } = pendings.shift()!;
1717
resolve({ value: chunk, done: false });
1818
} else {
1919
chunkQueue.push(chunk);
@@ -22,18 +22,17 @@ const createAsyncIterableFromReadable = (readable: any): AsyncIterable<any> => {
2222

2323
const onEnd = () => {
2424
ended = true;
25-
while (pendingResolvers.length > 0) {
26-
const resolve = pendingResolvers.shift()!;
25+
while (pendings.length > 0) {
26+
const { resolve } = pendings.shift()!;
2727
resolve({ value: undefined, done: true });
2828
}
2929
};
3030

3131
const onError = (err: any) => {
3232
error = err;
33-
while (pendingResolvers.length > 0) {
34-
const resolve = pendingResolvers.shift()!;
35-
// Rejecting inside async iterator isn't straightforward; surface as end and throw later
36-
resolve({ value: undefined, done: true });
33+
while (pendings.length > 0) {
34+
const { reject } = pendings.shift()!;
35+
reject(err);
3736
}
3837
};
3938

@@ -61,8 +60,8 @@ const createAsyncIterableFromReadable = (readable: any): AsyncIterable<any> => {
6160
cleanup();
6261
return Promise.resolve({ value: undefined, done: true });
6362
}
64-
return new Promise<IteratorResult<any>>((resolve) => {
65-
pendingResolvers.push(resolve);
63+
return new Promise<IteratorResult<any>>((resolve, reject) => {
64+
pendings.push({ resolve, reject });
6665
});
6766
},
6867
return(): Promise<IteratorResult<any>> {
@@ -80,10 +79,12 @@ const createAsyncIterableFromReadable = (readable: any): AsyncIterable<any> => {
8079

8180
/**
8281
* Parse newline-delimited JSON (NDJSON) from a Node.js readable stream
83-
* @param stream - Node.js readable stream
82+
* @param stream - Node.js readable stream, AsyncIterable, string, or Buffer
8483
* @returns AsyncGenerator that yields parsed JSON objects
8584
*/
86-
export async function* parseNDJSONStream(stream: Readable): AsyncGenerator<any> {
85+
export async function* parseNDJSONStream(
86+
stream: Readable | AsyncIterable<Uint8Array | string | Buffer> | string | Uint8Array | Buffer
87+
): AsyncGenerator<any> {
8788
const decoder = new TextDecoder("utf-8");
8889
let buffer = "";
8990
@@ -110,8 +111,8 @@ export async function* parseNDJSONStream(stream: Readable): AsyncGenerator<any>
110111
return;
111112
}
112113

113-
const isAsyncIterable = stream && typeof stream[Symbol.asyncIterator] === "function";
114-
const source: AsyncIterable<any> = isAsyncIterable ? stream : createAsyncIterableFromReadable(stream);
114+
const isAsyncIterable = stream && typeof (stream as any)[Symbol.asyncIterator] === "function";
115+
const source: AsyncIterable<any> = isAsyncIterable ? (stream as any) : createAsyncIterableFromReadable(stream as any);
115116

116117
for await (const chunk of source) {
117118
// Node.js streams can return Buffer or string chunks

0 commit comments

Comments
 (0)