Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
6719b0b
feat/Add raw request method to JavaScript SDK for calling arbitrary A…
Abishek-Newar Jan 2, 2026
41d7044
filed the gaps between go-sdk and js-sdk
Abishek-Newar Jan 6, 2026
f338e77
covering the failed test cases in github actions
Abishek-Newar Jan 8, 2026
836ecf6
updated the readme for rawRequests
Abishek-Newar Jan 14, 2026
ca0c8e8
Merge branch 'main' into main
SoulPancake Jan 14, 2026
3b830c3
removed the white spaces from the api.ts
Abishek-Newar Jan 14, 2026
ebcd4f5
removed white spaces from api.ts
Abishek-Newar Jan 14, 2026
0d6b33c
removed white spaces from api.ts
Abishek-Newar Jan 14, 2026
f18c2b9
removed white spaces from client.ts
Abishek-Newar Jan 14, 2026
55a78c0
removed white spaces from client.ts
Abishek-Newar Jan 14, 2026
02861ca
trying to fix the test failure
Abishek-Newar Jan 14, 2026
307e15a
Updated nocks.ts
Abishek-Newar Jan 14, 2026
97af6ab
update nocks.ts
Abishek-Newar Jan 14, 2026
d6d4484
updated nocks
Abishek-Newar Jan 14, 2026
1a1e8f6
Updated RawRequest.test.ts by comparing it to go-sdk
Abishek-Newar Jan 14, 2026
7e12939
url encoding fixed
Abishek-Newar Jan 14, 2026
1e4af9e
changeing the base path to match that of nock one
Abishek-Newar Jan 14, 2026
254db3c
changed the nock url
Abishek-Newar Jan 14, 2026
232a776
mew test changes
Abishek-Newar Jan 14, 2026
b0529ef
resolved the issues
Abishek-Newar Jan 14, 2026
7407e68
using regex based url matching in nocks
Abishek-Newar Jan 15, 2026
939abb3
fixing the regex in nocks
Abishek-Newar Jan 15, 2026
a05df74
updated nocks regex
Abishek-Newar Jan 15, 2026
55428ef
testing
Abishek-Newar Jan 15, 2026
cba2592
removed the rm
Abishek-Newar Jan 15, 2026
376ea1b
update nocks.ts
Abishek-Newar Jan 15, 2026
0afe5ac
modified the test and reveretd the nocks
Abishek-Newar Jan 15, 2026
cb35e1d
updated the path
Abishek-Newar Jan 15, 2026
68257d5
changed to default configuration instead of url
Abishek-Newar Jan 15, 2026
0a053a0
modified the rawrequests
Abishek-Newar Jan 15, 2026
e651fea
update sequential run test
Abishek-Newar Jan 15, 2026
84326cb
reverted main yml file
Abishek-Newar Jan 15, 2026
73021ff
changes in the configruation
Abishek-Newar Jan 15, 2026
bece098
changes in raw request test
Abishek-Newar Jan 15, 2026
24e1ecb
reqwrite of rawrequest acc to headers
Abishek-Newar Jan 15, 2026
21affd2
changes in rawrequest
Abishek-Newar Jan 15, 2026
80bd09f
Update rawRequests.ts
Abishek-Newar Jan 15, 2026
598de4b
Update rawRequests
Abishek-Newar Jan 16, 2026
200ad3e
update rawRequests.ts
Abishek-Newar Jan 16, 2026
ce389e6
Merge branch 'openfga:main' into main
Abishek-Newar Jan 20, 2026
37ad6f5
fix: changed the naming conventions
Abishek-Newar Jan 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ This is an autogenerated JavaScript SDK for OpenFGA. It provides a wrapper aroun
- [Assertions](#assertions)
- [Read Assertions](#read-assertions)
- [Write Assertions](#write-assertions)
- [Calling Other Endpoints](#calling-other-endpoints)
- [Retries](#retries)
- [API Endpoints](#api-endpoints)
- [Models](#models)
Expand Down Expand Up @@ -853,6 +854,74 @@ const response = await fgaClient.writeAssertions([{
}], options);
```

### Calling Other Endpoints

In certain cases you may want to call other APIs not yet wrapped by the SDK. You can do so by using the `apiExecutor` method available on the `OpenFgaClient`. The `apiExecutor` method allows you to make raw HTTP calls to any OpenFGA endpoint by specifying the HTTP method, path, body, query parameters, and path parameters, while still honoring the client configuration (authentication, telemetry, retries, and error handling).

This is useful when:
- You want to call a new endpoint that is not yet supported by the SDK
- You are using an earlier version of the SDK that doesn't yet support a particular endpoint
- You have a custom endpoint deployed that extends the OpenFGA API

#### Example: Calling a Custom Endpoint with POST

```javascript
const { OpenFgaClient } = require('@openfga/sdk');

const fgaClient = new OpenFgaClient({
apiUrl: process.env.FGA_API_URL,
storeId: process.env.FGA_STORE_ID,
});

// Call a custom endpoint using path parameters
const response = await fgaClient.apiExecutor({
operationName: 'CustomEndpoint', // For telemetry/logging
method: 'POST',
path: '/stores/{store_id}/custom-endpoint',
pathParams: { store_id: process.env.FGA_STORE_ID },
body: {
user: 'user:bob',
action: 'custom_action',
resource: 'resource:123',
},
queryParams: {
page_size: 20,
},
});

console.log('Response:', response);
```

#### Example: Calling an Existing Endpoint with GET

```javascript
// Get a list of stores with query parameters
const stores = await fgaClient.apiExecutor({
method: 'GET',
path: '/stores',
queryParams: {
page_size: 10,
continuation_token: 'eyJwayI6...',
},
});

console.log('Stores:', stores);
```

#### Example: Using Path Parameters

Path parameters are specified in the path using `{param_name}` syntax and are replaced with URL-encoded values from the `pathParams` object:

```javascript
const response = await fgaClient.apiExecutor({
method: 'GET',
path: '/stores/{store_id}/authorization-models/{model_id}',
pathParams: {
store_id: 'your-store-id',
model_id: 'your-model-id',
},
});
```

### Retries

Expand Down
124 changes: 124 additions & 0 deletions api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,74 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio
localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers};
localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions);

return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
* Make a raw HTTP request to an arbitrary API endpoint.
* This method provides an escape hatch for calling new or experimental endpoints
* that may not yet have dedicated SDK methods.
* @summary Make a raw HTTP request
* @param {'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'} method - HTTP method
* @param {string} path - API path with optional template parameters (e.g., '/stores/{store_id}/my-endpoint')
* @param {any} [body] - Optional request body
* @param {Record<string, any>} [queryParams] - Optional query parameters
* @param {Record<string, string>} [pathParams] - Optional path parameters to replace template variables
* @param {*} [options] Override http request option.
* @throws { FgaError }
*/
apiExecutor: (method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", path: string, body?: any, queryParams?: Record<string, any>, pathParams?: Record<string, string>, options: any = {}): RequestArgs => {
// Build path by replacing template parameters with URL-encoded values
let localVarPath = path;
if (pathParams) {
for (const [key, value] of Object.entries(pathParams)) {
// Use split/join to replace ALL occurrences of the parameter
localVarPath = localVarPath.split(`{${key}}`).join(encodeURIComponent(value));
}
}

// Validate that all path parameters have been replaced
if (localVarPath.includes("{") && localVarPath.includes("}")) {
const unresolvedMatch = localVarPath.match(/\{([^}]+)\}/);
if (unresolvedMatch) {
throw new Error(`Path parameter '${unresolvedMatch[1]}' was not provided for path: ${path}`);
}
}

// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}

const localVarRequestOptions = { method: method, ...baseOptions, ...options };
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;

// Add query parameters if provided
if (queryParams) {
for (const [key, value] of Object.entries(queryParams)) {
if (value !== undefined) {
localVarQueryParameter[key] = value;
}
}
}

// Set Content-Type for requests with body
if (body !== undefined && (method === "POST" || method === "PUT" || method === "PATCH")) {
localVarHeaderParameter["Content-Type"] = "application/json";
}

setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers };

if (body !== undefined) {
localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions);
}

return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
Expand Down Expand Up @@ -1072,6 +1140,26 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials:
...TelemetryAttributes.fromRequestBody(body)
});
},
/**
* Make a raw HTTP request to an arbitrary API endpoint.
* This method provides an escape hatch for calling new or experimental endpoints
* that may not yet have dedicated SDK methods.
* @summary Make a raw HTTP request
* @param {'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'} method - HTTP method
* @param {string} path - API path with optional template parameters (e.g., '/stores/{store_id}/my-endpoint')
* @param {any} [body] - Optional request body
* @param {Record<string, any>} [queryParams] - Optional query parameters
* @param {Record<string, string>} [pathParams] - Optional path parameters to replace template variables
* @param {string} [operationName] - Optional operation name for telemetry (defaults to "ApiExecutor")
* @param {*} [options] Override http request option.
* @throws { FgaError }
*/
async apiExecutor(method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", path: string, body?: any, queryParams?: Record<string, any>, pathParams?: Record<string, string>, operationName?: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult<any>> {
const localVarAxiosArgs = localVarAxiosParamCreator.apiExecutor(method, path, body, queryParams, pathParams, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, {
[TelemetryAttribute.FgaClientRequestMethod]: operationName || "ApiExecutor",
});
},
};
};

Expand Down Expand Up @@ -1272,6 +1360,23 @@ export const OpenFgaApiFactory = function (configuration: Configuration, credent
writeAuthorizationModel(storeId: string, body: WriteAuthorizationModelRequest, options?: any): PromiseResult<WriteAuthorizationModelResponse> {
return localVarFp.writeAuthorizationModel(storeId, body, options).then((request) => request(axios));
},
/**
* Make a raw HTTP request to an arbitrary API endpoint.
* This method provides an escape hatch for calling new or experimental endpoints
* that may not yet have dedicated SDK methods.
* @summary Make a raw HTTP request
* @param {'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'} method - HTTP method
* @param {string} path - API path with optional template parameters (e.g., '/stores/{store_id}/my-endpoint')
* @param {any} [body] - Optional request body
* @param {Record<string, any>} [queryParams] - Optional query parameters
* @param {Record<string, string>} [pathParams] - Optional path parameters to replace template variables
* @param {string} [operationName] - Optional operation name for telemetry (defaults to "ApiExecutor")
* @param {*} [options] Override http request option.
* @throws { FgaError }
*/
apiExecutor(method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", path: string, body?: any, queryParams?: Record<string, any>, pathParams?: Record<string, string>, operationName?: string, options?: any): PromiseResult<any> {
return localVarFp.apiExecutor(method, path, body, queryParams, pathParams, operationName, options).then((request) => request(axios));
},
};
};

Expand Down Expand Up @@ -1505,6 +1610,25 @@ export class OpenFgaApi extends BaseAPI {
public writeAuthorizationModel(storeId: string, body: WriteAuthorizationModelRequest, options?: any): Promise<CallResult<WriteAuthorizationModelResponse>> {
return OpenFgaApiFp(this.configuration, this.credentials).writeAuthorizationModel(storeId, body, options).then((request) => request(this.axios));
}

/**
* Make a raw HTTP request to an arbitrary API endpoint.
* This method provides an escape hatch for calling new or experimental endpoints
* that may not yet have dedicated SDK methods.
* @summary Make a raw HTTP request
* @param {'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'} method - HTTP method
* @param {string} path - API path with optional template parameters (e.g., '/stores/{store_id}/my-endpoint')
* @param {any} [body] - Optional request body
* @param {Record<string, any>} [queryParams] - Optional query parameters
* @param {Record<string, string>} [pathParams] - Optional path parameters to replace template variables
* @param {string} [operationName] - Optional operation name for telemetry (defaults to "ApiExecutor")
* @param {*} [options] Override http request option.
* @throws { FgaError }
* @memberof OpenFgaApi
*/
public apiExecutor(method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", path: string, body?: any, queryParams?: Record<string, any>, pathParams?: Record<string, string>, operationName?: string, options?: any): Promise<CallResult<any>> {
return OpenFgaApiFp(this.configuration, this.credentials).apiExecutor(method, path, body, queryParams, pathParams, operationName, options).then((request) => request(this.axios));
}
}


87 changes: 87 additions & 0 deletions client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,42 @@ export type ClientListRelationsRequest = Omit<ClientCheckRequest, "relation" | "
};
export type ClientWriteAssertionsRequest = (CheckRequestTupleKey & Pick<Assertion, "expectation">)[];

/**
* HTTP methods supported by apiExecutor
*/
export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";

/**
* Request parameters for apiExecutor method
*/
export interface ClientApiExecutorParams {
/**
* Operation name for telemetry and logging (e.g., "CustomCheck", "CustomEndpoint").
* Used for observability when calling new or experimental endpoints.
* Defaults to "ApiExecutor" if not provided.
*/
operationName?: string;
/** HTTP method */
method: HttpMethod;
/**
* API path with optional template parameters.
* Template parameters should be in the format {param_name} and will be replaced
* with URL-encoded values from pathParams.
* Example: '/stores/{store_id}/custom-endpoint'
*/
path: string;
/**
* Path parameters to replace template variables in the path.
* Values will be URL-encoded automatically.
* Example: { store_id: "abc123" } will replace {store_id} in the path.
*/
pathParams?: Record<string, string>;
/** Optional request body for POST/PUT/PATCH requests */
body?: any;
/** Optional query parameters */
queryParams?: Record<string, any>;
}

export class OpenFgaClient extends BaseAPI {
public api: OpenFgaApi;
public authorizationModelId?: string;
Expand Down Expand Up @@ -945,4 +981,55 @@ export class OpenFgaClient extends BaseAPI {
}))
}, options);
}


/**
* apiExecutor lets you send any HTTP request directly to an OpenFGA API endpoint.
* It’s useful when you need to call a new or experimental API that doesn’t yet have a built-in method in the SDK.
* You still get the benefits of the SDK, like authentication, configuration, and consistent error handling.
*
* @param {ClientApiExecutorParams} request - The request parameters
* @param {HttpMethod} request.method - HTTP method (GET, POST, PUT, DELETE, PATCH)
* @param {string} request.path - API path (e.g., '/stores/{store_id}/my-endpoint')
* @param {any} [request.body] - Optional request body for POST/PUT/PATCH requests
* @param {Record<string, any>} [request.queryParams] - Optional query parameters
* @param {ClientRequestOpts} [options] - Request options
* @param {object} [options.headers] - Custom headers to send alongside the request
* @param {object} [options.retryParams] - Override the retry parameters for this request
* @param {number} [options.retryParams.maxRetry] - Override the max number of retries on each API request
* @param {number} [options.retryParams.minWaitInMs] - Override the minimum wait before a retry is initiated
* @throws { FgaError }
*
* @example
* // Call a new endpoint using path parameters (recommended)
* const response = await client.apiExecutor({
* operationName: 'CustomCheck',
* method: 'POST',
* path: '/stores/{store_id}/custom-endpoint',
* pathParams: { store_id: 'my-store-id' },
* body: { foo: 'bar' },
* });
*
* @example
* // Call an existing endpoint with query parameters
* const stores = await client.apiExecutor({
* method: 'GET',
* path: '/stores',
* queryParams: { page_size: 10 },
* });
*/
async apiExecutor(
request: ClientApiExecutorParams,
options: ClientRequestOpts = {}
): PromiseResult<any> {
return this.api.apiExecutor(
request.method,
request.path,
request.body,
request.queryParams,
request.pathParams,
request.operationName,
options
);
}
}
Loading