From 6719b0bbb23986da1562c5a9451c6df8711c0c26 Mon Sep 17 00:00:00 2001 From: Abishek Newar <97790157+Abishek-Newar@users.noreply.github.com> Date: Fri, 2 Jan 2026 19:02:42 +0530 Subject: [PATCH 01/44] feat/Add raw request method to JavaScript SDK for calling arbitrary API endpoints --- api.ts | 213 ++++++++++++++++++++++++--------- client.ts | 98 ++++++++++++--- tests/rawRequest.test.ts | 252 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 491 insertions(+), 72 deletions(-) create mode 100644 tests/rawRequest.test.ts diff --git a/api.ts b/api.ts index bee86ba7..8f2035c6 100644 --- a/api.ts +++ b/api.ts @@ -147,16 +147,16 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: "POST", ...baseOptions, ...options}; + const localVarRequestOptions = { method: "POST", ...baseOptions, ...options }; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - + localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; + localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions); return { @@ -186,16 +186,16 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: "POST", ...baseOptions, ...options}; + const localVarRequestOptions = { method: "POST", ...baseOptions, ...options }; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - + localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; + localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions); return { @@ -214,24 +214,24 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio // verify required parameter 'body' is not null or undefined assertParamExists("createStore", "body", body); const localVarPath = "/stores" - ; - // use dummy base URL string because the URL constructor only accepts absolute URLs. + ; + // 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: "POST", ...baseOptions, ...options}; + const localVarRequestOptions = { method: "POST", ...baseOptions, ...options }; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - + localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; + localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions); return { @@ -258,14 +258,14 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: "DELETE", ...baseOptions, ...options}; + const localVarRequestOptions = { method: "DELETE", ...baseOptions, ...options }; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; + localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; return { url: toPathString(localVarUrlObj), @@ -294,16 +294,16 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: "POST", ...baseOptions, ...options}; + const localVarRequestOptions = { method: "POST", ...baseOptions, ...options }; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - + localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; + localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions); return { @@ -330,14 +330,14 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: "GET", ...baseOptions, ...options}; + const localVarRequestOptions = { method: "GET", ...baseOptions, ...options }; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; + localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; return { url: toPathString(localVarUrlObj), @@ -366,16 +366,16 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: "POST", ...baseOptions, ...options}; + const localVarRequestOptions = { method: "POST", ...baseOptions, ...options }; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - + localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; + localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions); return { @@ -394,15 +394,15 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio */ listStores: (pageSize?: number, continuationToken?: string, name?: string, options: any = {}): RequestArgs => { const localVarPath = "/stores" - ; - // use dummy base URL string because the URL constructor only accepts absolute URLs. + ; + // 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: "GET", ...baseOptions, ...options}; + const localVarRequestOptions = { method: "GET", ...baseOptions, ...options }; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; @@ -419,9 +419,9 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio } - + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; + localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; return { url: toPathString(localVarUrlObj), @@ -450,16 +450,16 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: "POST", ...baseOptions, ...options}; + const localVarRequestOptions = { method: "POST", ...baseOptions, ...options }; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - + localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; + localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions); return { @@ -489,16 +489,16 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: "POST", ...baseOptions, ...options}; + const localVarRequestOptions = { method: "POST", ...baseOptions, ...options }; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - + localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; + localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions); return { @@ -528,14 +528,14 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: "GET", ...baseOptions, ...options}; + const localVarRequestOptions = { method: "GET", ...baseOptions, ...options }; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; + localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; return { url: toPathString(localVarUrlObj), @@ -564,14 +564,14 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: "GET", ...baseOptions, ...options}; + const localVarRequestOptions = { method: "GET", ...baseOptions, ...options }; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; + localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; return { url: toPathString(localVarUrlObj), @@ -599,7 +599,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: "GET", ...baseOptions, ...options}; + const localVarRequestOptions = { method: "GET", ...baseOptions, ...options }; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; @@ -612,9 +612,9 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio } - + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; + localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; return { url: toPathString(localVarUrlObj), @@ -644,7 +644,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: "GET", ...baseOptions, ...options}; + const localVarRequestOptions = { method: "GET", ...baseOptions, ...options }; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; @@ -667,9 +667,9 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio } - + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; + localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; return { url: toPathString(localVarUrlObj), @@ -698,16 +698,16 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: "POST", ...baseOptions, ...options}; + const localVarRequestOptions = { method: "POST", ...baseOptions, ...options }; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - + localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; + localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions); return { @@ -740,16 +740,16 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: "PUT", ...baseOptions, ...options}; + const localVarRequestOptions = { method: "PUT", ...baseOptions, ...options }; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - + localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; + localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions); return { @@ -779,18 +779,69 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: "POST", ...baseOptions, ...options}; + const localVarRequestOptions = { method: "POST", ...baseOptions, ...options }; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; - + localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; + 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 (e.g., '/stores/{store_id}/my-endpoint') + * @param {any} [body] - Optional request body + * @param {Record} [queryParams] - Optional query parameters + * @param {*} [options] Override http request option. + * @throws { FgaError } + */ + rawRequest: (method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", path: string, body?: any, queryParams?: Record, options: any = {}): RequestArgs => { + const localVarPath = 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, @@ -803,7 +854,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio * OpenFgaApi - functional programming interface * @export */ -export const OpenFgaApiFp = function(configuration: Configuration, credentials: Credentials) { +export const OpenFgaApiFp = function (configuration: Configuration, credentials: Credentials) { const localVarAxiosParamCreator = OpenFgaApiAxiosParamCreator(configuration, credentials); return { /** @@ -1072,6 +1123,24 @@ 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 (e.g., '/stores/{store_id}/my-endpoint') + * @param {any} [body] - Optional request body + * @param {Record} [queryParams] - Optional query parameters + * @param {*} [options] Override http request option. + * @throws { FgaError } + */ + async rawRequest(method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", path: string, body?: any, queryParams?: Record, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { + const localVarAxiosArgs = localVarAxiosParamCreator.rawRequest(method, path, body, queryParams, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, { + [TelemetryAttribute.FgaClientRequestMethod]: "RawRequest", + }); + }, }; }; @@ -1272,6 +1341,21 @@ export const OpenFgaApiFactory = function (configuration: Configuration, credent writeAuthorizationModel(storeId: string, body: WriteAuthorizationModelRequest, options?: any): PromiseResult { 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 (e.g., '/stores/{store_id}/my-endpoint') + * @param {any} [body] - Optional request body + * @param {Record} [queryParams] - Optional query parameters + * @param {*} [options] Override http request option. + * @throws { FgaError } + */ + rawRequest(method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", path: string, body?: any, queryParams?: Record, options?: any): PromiseResult { + return localVarFp.rawRequest(method, path, body, queryParams, options).then((request) => request(axios)); + }, }; }; @@ -1505,6 +1589,23 @@ export class OpenFgaApi extends BaseAPI { public writeAuthorizationModel(storeId: string, body: WriteAuthorizationModelRequest, options?: any): Promise> { 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 (e.g., '/stores/{store_id}/my-endpoint') + * @param {any} [body] - Optional request body + * @param {Record} [queryParams] - Optional query parameters + * @param {*} [options] Override http request option. + * @throws { FgaError } + * @memberof OpenFgaApi + */ + public rawRequest(method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", path: string, body?: any, queryParams?: Record, options?: any): Promise> { + return OpenFgaApiFp(this.configuration, this.credentials).rawRequest(method, path, body, queryParams, options).then((request) => request(this.axios)); + } } diff --git a/client.ts b/client.ts index f55b301e..6e7310a6 100644 --- a/client.ts +++ b/client.ts @@ -120,8 +120,8 @@ export type ClientRequestOptsWithConsistency = ClientRequestOpts & StoreIdOpts & export type PaginationOptions = { pageSize?: number, continuationToken?: string, name?: string; }; export type ClientCheckRequest = CheckRequestTupleKey & - Pick & - { contextualTuples?: Array }; + Pick & +{ contextualTuples?: Array }; export type ClientBatchCheckClientRequest = ClientCheckRequest[]; @@ -160,17 +160,17 @@ export type ClientBatchCheckRequest = { // for server batch check export interface ClientBatchCheckRequestOpts { - maxParallelRequests?: number; - maxBatchSize?: number; + maxParallelRequests?: number; + maxBatchSize?: number; } // for server batch check export type ClientBatchCheckSingleResponse = { - allowed: boolean; - request: ClientBatchCheckItem; - correlationId: string; - error?: CheckError; + allowed: boolean; + request: ClientBatchCheckItem; + correlationId: string; + error?: CheckError; } export interface ClientBatchCheckResponse { @@ -244,20 +244,39 @@ export interface ClientReadChangesRequest { } export type ClientExpandRequest = ExpandRequestTupleKey & Omit & { - contextualTuples?: Array + contextualTuples?: Array }; export type ClientReadRequest = ReadRequestTupleKey; export type ClientListObjectsRequest = Omit & { - contextualTuples?: Array + contextualTuples?: Array }; export type ClientListUsersRequest = Omit & { - contextualTuples?: Array + contextualTuples?: Array }; export type ClientListRelationsRequest = Omit & { - relations?: string[], + relations?: string[], }; export type ClientWriteAssertionsRequest = (CheckRequestTupleKey & Pick)[]; +/** + * HTTP methods supported by rawRequest + */ +export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; + +/** + * Request parameters for rawRequest method + */ +export interface ClientRawRequestParams { + /** HTTP method */ + method: HttpMethod; + /** API path (e.g., '/stores/{store_id}/custom-endpoint') */ + path: string; + /** Optional request body for POST/PUT/PATCH requests */ + body?: any; + /** Optional query parameters */ + queryParams?: Record; +} + export class OpenFgaClient extends BaseAPI { public api: OpenFgaApi; public authorizationModelId?: string; @@ -548,7 +567,7 @@ export class OpenFgaClient extends BaseAPI { const writeResponses: ClientWriteSingleResponse[][] = []; if (writes?.length) { for await (const singleChunkResponse of asyncPool(maxParallelRequests, chunkArray(writes, maxPerChunk), - (chunk) => this.writeTuples(chunk,{ ...options, headers, conflict, transaction: undefined }).catch(err => { + (chunk) => this.writeTuples(chunk, { ...options, headers, conflict, transaction: undefined }).catch(err => { if (err instanceof FgaApiAuthenticationError) { throw err; } @@ -703,7 +722,7 @@ export class OpenFgaClient extends BaseAPI { - private singleBatchCheck(body: BatchCheckRequest, options: ClientRequestOptsWithConsistency & ClientBatchCheckRequestOpts = {}): Promise { + private singleBatchCheck(body: BatchCheckRequest, options: ClientRequestOptsWithConsistency & ClientBatchCheckRequestOpts = {}): Promise { return this.api.batchCheck(this.getStoreId(options)!, body, options); } @@ -781,7 +800,7 @@ export class OpenFgaClient extends BaseAPI { // Collect the responses and associate them with their correlation IDs for await (const response of batchResponses) { - if (response) { + if (response) { for (const [correlationId, result] of Object.entries(response)) { const check = correlationIdToCheck.get(correlationId); if (check && result) { @@ -797,7 +816,7 @@ export class OpenFgaClient extends BaseAPI { } return { result: results }; - } + } /** * Expand - Expands the relationships in userset tree format (evaluates) @@ -945,4 +964,51 @@ export class OpenFgaClient extends BaseAPI { })) }, options); } + + + /** + * RawRequest 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 {ClientRawRequestParams} 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} [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 that isn't yet in the SDK + * const response = await client.rawRequest({ + * method: 'POST', + * path: '/stores/my-store-id/some-new-endpoint', + * body: { foo: 'bar' }, + * }); + * + * @example + * // Call an existing endpoint with query parameters + * const stores = await client.rawRequest({ + * method: 'GET', + * path: '/stores', + * queryParams: { page_size: 10 }, + * }); + */ + async rawRequest( + request: ClientRawRequestParams, + options: ClientRequestOpts = {} + ): PromiseResult { + return this.api.rawRequest( + request.method, + request.path, + request.body, + request.queryParams, + options + ); + } } diff --git a/tests/rawRequest.test.ts b/tests/rawRequest.test.ts new file mode 100644 index 00000000..71e9d007 --- /dev/null +++ b/tests/rawRequest.test.ts @@ -0,0 +1,252 @@ +import * as nock from "nock"; + +import { + OpenFgaClient, + FgaApiNotFoundError, + FgaApiValidationError, +} from "../index"; +import { + baseConfig, + defaultConfiguration, + OPENFGA_API_TOKEN_ISSUER, + OPENFGA_STORE_ID, +} from "./helpers/default-config"; +import { getNocks } from "./helpers/nocks"; + +const nocks = getNocks(nock); +nock.disableNetConnect(); + +describe("OpenFgaClient.rawRequest", () => { + let fgaClient: OpenFgaClient; + const basePath = defaultConfiguration.getBasePath(); + + beforeEach(() => { + fgaClient = new OpenFgaClient({ ...baseConfig }); + nocks.tokenExchange(OPENFGA_API_TOKEN_ISSUER, "test-token"); + }); + + afterEach(() => { + nock.cleanAll(); + }); + + describe("GET requests", () => { + it("should make GET requests successfully", async () => { + const responseData = { stores: [{ id: "store-1", name: "Test Store" }] }; + + nock(basePath) + .get("/stores") + .reply(200, responseData); + + const result = await fgaClient.rawRequest({ + method: "GET", + path: "/stores", + }); + + expect(result.stores).toEqual(responseData.stores); + }); + + it("should include query parameters in GET requests", async () => { + const responseData = { stores: [] }; + + nock(basePath) + .get("/stores") + .query({ page_size: 10, name: "test" }) + .reply(200, responseData); + + const result = await fgaClient.rawRequest({ + method: "GET", + path: "/stores", + queryParams: { page_size: 10, name: "test" }, + }); + + expect(result.stores).toEqual([]); + }); + + it("should return $response with the full Axios response", async () => { + const responseData = { stores: [] }; + + nock(basePath) + .get("/stores") + .reply(200, responseData); + + const result = await fgaClient.rawRequest({ + method: "GET", + path: "/stores", + }); + + expect(result.$response).toBeDefined(); + expect(result.$response.status).toBe(200); + }); + }); + + describe("POST requests", () => { + it("should make POST requests with body", async () => { + const requestBody = { name: "New Store" }; + const responseData = { id: "new-store-id", name: "New Store" }; + + nock(basePath) + .post("/stores", requestBody) + .reply(201, responseData); + + const result = await fgaClient.rawRequest({ + method: "POST", + path: "/stores", + body: requestBody, + }); + + expect(result.id).toBe("new-store-id"); + }); + + it("should set Content-Type header for POST requests with body", async () => { + const requestBody = { foo: "bar" }; + + nock(basePath, { + reqheaders: { + "Content-Type": "application/json", + }, + }) + .post("/stores", requestBody) + .reply(200, {}); + + await fgaClient.rawRequest({ + method: "POST", + path: "/stores", + body: requestBody, + }); + + // If we get here without error, the Content-Type header was correctly applied + expect(true).toBe(true); + }); + }); + + describe("PUT requests", () => { + it("should make PUT requests with body", async () => { + const requestBody = { name: "Updated Store" }; + + nock(basePath) + .put(`/stores/${OPENFGA_STORE_ID}`, requestBody) + .reply(200, { success: true }); + + const result = await fgaClient.rawRequest({ + method: "PUT", + path: `/stores/${OPENFGA_STORE_ID}`, + body: requestBody, + }); + + expect(result.success).toBe(true); + }); + }); + + describe("DELETE requests", () => { + it("should make DELETE requests", async () => { + nock(basePath) + .delete(`/stores/${OPENFGA_STORE_ID}`) + .reply(204, {}); + + const result = await fgaClient.rawRequest({ + method: "DELETE", + path: `/stores/${OPENFGA_STORE_ID}`, + }); + + expect(result.$response.status).toBe(204); + }); + }); + + describe("PATCH requests", () => { + it("should make PATCH requests with body", async () => { + const requestBody = { name: "Patched Store" }; + + nock(basePath) + .patch(`/stores/${OPENFGA_STORE_ID}`, requestBody) + .reply(200, { success: true }); + + const result = await fgaClient.rawRequest({ + method: "PATCH", + path: `/stores/${OPENFGA_STORE_ID}`, + body: requestBody, + }); + + expect(result.success).toBe(true); + }); + }); + + describe("custom headers", () => { + it("should include custom headers in requests", async () => { + nock(basePath, { + reqheaders: { + "X-Custom-Header": "custom-value", + }, + }) + .get("/stores") + .reply(200, {}); + + await fgaClient.rawRequest( + { + method: "GET", + path: "/stores", + }, + { + headers: { "X-Custom-Header": "custom-value" }, + } + ); + + // If we get here without error, the custom header was correctly applied + expect(true).toBe(true); + }); + }); + + describe("error handling", () => { + it("should throw FgaApiNotFoundError for 404 responses", async () => { + nock(basePath) + .get("/nonexistent-endpoint") + .reply(404, { + code: "undefined_endpoint", + message: "Not found", + }); + + await expect( + fgaClient.rawRequest({ + method: "GET", + path: "/nonexistent-endpoint", + }) + ).rejects.toThrow(FgaApiNotFoundError); + }); + + it("should throw FgaApiValidationError for 400 responses", async () => { + nock(basePath) + .post("/stores") + .reply(400, { + code: "validation_error", + message: "Invalid request", + }); + + await expect( + fgaClient.rawRequest({ + method: "POST", + path: "/stores", + body: { invalid: "data" }, + }) + ).rejects.toThrow(FgaApiValidationError); + }); + }); + + describe("authentication", () => { + it("should include authentication headers", async () => { + nock(basePath, { + reqheaders: { + Authorization: "Bearer test-token", + }, + }) + .get("/stores") + .reply(200, {}); + + await fgaClient.rawRequest({ + method: "GET", + path: "/stores", + }); + + // If we get here without error, the auth header was correctly applied + expect(true).toBe(true); + }); + }); +}); From 41d70445db29c6c184b1abf490700625b8a872bd Mon Sep 17 00:00:00 2001 From: Abishek Newar Date: Wed, 7 Jan 2026 00:40:26 +0530 Subject: [PATCH 02/44] filed the gaps between go-sdk and js-sdk --- api.ts | 49 ++++++--- client.ts | 27 ++++- tests/rawRequest.test.ts | 214 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 274 insertions(+), 16 deletions(-) diff --git a/api.ts b/api.ts index 8f2035c6..be9ef619 100644 --- a/api.ts +++ b/api.ts @@ -802,14 +802,31 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio * 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 (e.g., '/stores/{store_id}/my-endpoint') + * @param {string} path - API path with optional template parameters (e.g., '/stores/{store_id}/my-endpoint') * @param {any} [body] - Optional request body * @param {Record} [queryParams] - Optional query parameters + * @param {Record} [pathParams] - Optional path parameters to replace template variables * @param {*} [options] Override http request option. * @throws { FgaError } */ - rawRequest: (method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", path: string, body?: any, queryParams?: Record, options: any = {}): RequestArgs => { - const localVarPath = path; + rawRequest: (method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", path: string, body?: any, queryParams?: Record, pathParams?: Record, 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; @@ -1129,16 +1146,18 @@ export const OpenFgaApiFp = function (configuration: Configuration, credentials: * 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 (e.g., '/stores/{store_id}/my-endpoint') + * @param {string} path - API path with optional template parameters (e.g., '/stores/{store_id}/my-endpoint') * @param {any} [body] - Optional request body * @param {Record} [queryParams] - Optional query parameters + * @param {Record} [pathParams] - Optional path parameters to replace template variables + * @param {string} [operationName] - Optional operation name for telemetry (defaults to "RawRequest") * @param {*} [options] Override http request option. * @throws { FgaError } */ - async rawRequest(method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", path: string, body?: any, queryParams?: Record, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { - const localVarAxiosArgs = localVarAxiosParamCreator.rawRequest(method, path, body, queryParams, options); + async rawRequest(method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", path: string, body?: any, queryParams?: Record, pathParams?: Record, operationName?: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { + const localVarAxiosArgs = localVarAxiosParamCreator.rawRequest(method, path, body, queryParams, pathParams, options); return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, { - [TelemetryAttribute.FgaClientRequestMethod]: "RawRequest", + [TelemetryAttribute.FgaClientRequestMethod]: operationName || "RawRequest", }); }, }; @@ -1347,14 +1366,16 @@ export const OpenFgaApiFactory = function (configuration: Configuration, credent * 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 (e.g., '/stores/{store_id}/my-endpoint') + * @param {string} path - API path with optional template parameters (e.g., '/stores/{store_id}/my-endpoint') * @param {any} [body] - Optional request body * @param {Record} [queryParams] - Optional query parameters + * @param {Record} [pathParams] - Optional path parameters to replace template variables + * @param {string} [operationName] - Optional operation name for telemetry (defaults to "RawRequest") * @param {*} [options] Override http request option. * @throws { FgaError } */ - rawRequest(method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", path: string, body?: any, queryParams?: Record, options?: any): PromiseResult { - return localVarFp.rawRequest(method, path, body, queryParams, options).then((request) => request(axios)); + rawRequest(method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", path: string, body?: any, queryParams?: Record, pathParams?: Record, operationName?: string, options?: any): PromiseResult { + return localVarFp.rawRequest(method, path, body, queryParams, pathParams, operationName, options).then((request) => request(axios)); }, }; }; @@ -1596,15 +1617,17 @@ export class OpenFgaApi extends BaseAPI { * 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 (e.g., '/stores/{store_id}/my-endpoint') + * @param {string} path - API path with optional template parameters (e.g., '/stores/{store_id}/my-endpoint') * @param {any} [body] - Optional request body * @param {Record} [queryParams] - Optional query parameters + * @param {Record} [pathParams] - Optional path parameters to replace template variables + * @param {string} [operationName] - Optional operation name for telemetry (defaults to "RawRequest") * @param {*} [options] Override http request option. * @throws { FgaError } * @memberof OpenFgaApi */ - public rawRequest(method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", path: string, body?: any, queryParams?: Record, options?: any): Promise> { - return OpenFgaApiFp(this.configuration, this.credentials).rawRequest(method, path, body, queryParams, options).then((request) => request(this.axios)); + public rawRequest(method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", path: string, body?: any, queryParams?: Record, pathParams?: Record, operationName?: string, options?: any): Promise> { + return OpenFgaApiFp(this.configuration, this.credentials).rawRequest(method, path, body, queryParams, pathParams, operationName, options).then((request) => request(this.axios)); } } diff --git a/client.ts b/client.ts index 6e7310a6..8126574c 100644 --- a/client.ts +++ b/client.ts @@ -267,10 +267,27 @@ export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; * Request parameters for rawRequest method */ export interface ClientRawRequestParams { + /** + * Operation name for telemetry and logging (e.g., "CustomCheck", "CustomEndpoint"). + * Used for observability when calling new or experimental endpoints. + * Defaults to "RawRequest" if not provided. + */ + operationName?: string; /** HTTP method */ method: HttpMethod; - /** API path (e.g., '/stores/{store_id}/custom-endpoint') */ + /** + * 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; /** Optional request body for POST/PUT/PATCH requests */ body?: any; /** Optional query parameters */ @@ -984,10 +1001,12 @@ export class OpenFgaClient extends BaseAPI { * @throws { FgaError } * * @example - * // Call a new endpoint that isn't yet in the SDK + * // Call a new endpoint using path parameters (recommended) * const response = await client.rawRequest({ + * operationName: 'CustomCheck', * method: 'POST', - * path: '/stores/my-store-id/some-new-endpoint', + * path: '/stores/{store_id}/custom-endpoint', + * pathParams: { store_id: 'my-store-id' }, * body: { foo: 'bar' }, * }); * @@ -1008,6 +1027,8 @@ export class OpenFgaClient extends BaseAPI { request.path, request.body, request.queryParams, + request.pathParams, + request.operationName, options ); } diff --git a/tests/rawRequest.test.ts b/tests/rawRequest.test.ts index 71e9d007..dfd1b5e3 100644 --- a/tests/rawRequest.test.ts +++ b/tests/rawRequest.test.ts @@ -250,3 +250,217 @@ describe("OpenFgaClient.rawRequest", () => { }); }); }); + +describe("OpenFgaClient.rawRequest - path parameters", () => { + let fgaClient: OpenFgaClient; + const basePath = defaultConfiguration.getBasePath(); + + beforeEach(() => { + fgaClient = new OpenFgaClient({ ...baseConfig }); + nocks.tokenExchange(OPENFGA_API_TOKEN_ISSUER, "test-token"); + }); + + afterEach(() => { + nock.cleanAll(); + }); + + describe("path parameter replacement", () => { + it("should replace path parameters with values (single_parameter)", async () => { + const storeId = "01ARZ3NDEKTSV4RRFFQ69G5FAV"; + const responseData = { id: storeId, name: "Test Store" }; + + nock(basePath) + .get(`/stores/${storeId}`) + .reply(200, responseData); + + const result = await fgaClient.rawRequest({ + method: "GET", + path: "/stores/{store_id}", + pathParams: { store_id: storeId }, + }); + + expect(result.id).toBe(storeId); + }); + + it("should replace multiple path parameters (multiple_parameters)", async () => { + const storeId = "store-123"; + const modelId = "model-456"; + const responseData = { id: modelId }; + + nock(basePath) + .get(`/stores/${storeId}/authorization-models/${modelId}`) + .reply(200, responseData); + + const result = await fgaClient.rawRequest({ + method: "GET", + path: "/stores/{store_id}/authorization-models/{model_id}", + pathParams: { store_id: storeId, model_id: modelId }, + }); + + expect(result.id).toBe(modelId); + }); + + it("should URL-encode path parameter values with spaces (parameter_with_special_characters)", async () => { + const storeId = "store id with spaces"; + const encodedStoreId = "store%20id%20with%20spaces"; + + nock(basePath) + .get(`/stores/${encodedStoreId}`) + .reply(200, { id: storeId }); + + const result = await fgaClient.rawRequest({ + method: "GET", + path: "/stores/{store_id}", + pathParams: { store_id: storeId }, + }); + + expect(result.id).toBe(storeId); + }); + + it("should URL-encode path parameter values with URL-unsafe characters (parameter_with_url_unsafe_characters)", async () => { + const id = "test/with?special&chars"; + const encodedId = encodeURIComponent(id); + + nock(basePath) + .get(`/items/${encodedId}`) + .reply(200, { id: id }); + + const result = await fgaClient.rawRequest({ + method: "GET", + path: "/items/{id}", + pathParams: { id: id }, + }); + + expect(result.id).toBe(id); + }); + + it("should URL-encode unicode characters in path parameters (parameter_with_unicode)", async () => { + const name = "用户"; + const encodedName = encodeURIComponent(name); // %E7%94%A8%E6%88%B7 + + nock(basePath) + .get(`/users/${encodedName}`) + .reply(200, { name: name }); + + const result = await fgaClient.rawRequest({ + method: "GET", + path: "/users/{name}", + pathParams: { name: name }, + }); + + expect(result.name).toBe(name); + }); + + it("should ignore unused path parameters (unused_parameters_ignored)", async () => { + const storeId = "123"; + + nock(basePath) + .get(`/stores/${storeId}`) + .reply(200, { id: storeId }); + + const result = await fgaClient.rawRequest({ + method: "GET", + path: "/stores/{store_id}", + pathParams: { + store_id: storeId, + unused: "value" // Should be ignored + }, + }); + + expect(result.id).toBe(storeId); + }); + + it("should replace parameter appearing multiple times in path (parameter_appears_multiple_times)", async () => { + const id = "abc"; + + nock(basePath) + .get(`/stores/${id}/check/${id}`) + .reply(200, { id: id }); + + const result = await fgaClient.rawRequest({ + method: "GET", + path: "/stores/{id}/check/{id}", + pathParams: { id: id }, + }); + + expect(result.id).toBe(id); + }); + + it("should allow empty parameter value (empty_parameter_value)", async () => { + nock(basePath) + .get("/stores/") + .reply(200, { id: "" }); + + const result = await fgaClient.rawRequest({ + method: "GET", + path: "/stores/{store_id}", + pathParams: { store_id: "" }, + }); + + expect(result.id).toBe(""); + }); + + it("should work without pathParams for paths with no template (no_parameters)", async () => { + nock(basePath) + .get("/stores") + .reply(200, { stores: [] }); + + const result = await fgaClient.rawRequest({ + method: "GET", + path: "/stores", + }); + + expect(result.stores).toEqual([]); + }); + + it("should throw error for unresolved path parameters", async () => { + await expect( + fgaClient.rawRequest({ + method: "GET", + path: "/stores/{store_id}/check", + // pathParams intentionally omitted + }) + ).rejects.toThrow("Path parameter 'store_id' was not provided for path: /stores/{store_id}/check"); + }); + + it("should throw error when some path parameters are missing", async () => { + await expect( + fgaClient.rawRequest({ + method: "GET", + path: "/stores/{store_id}/authorization-models/{model_id}", + pathParams: { store_id: "abc" }, // model_id is missing + }) + ).rejects.toThrow("Path parameter 'model_id' was not provided"); + }); + }); + + describe("operationName for telemetry", () => { + it("should accept operationName parameter", async () => { + nock(basePath) + .get("/stores") + .reply(200, { stores: [] }); + + // Should complete without error when operationName is provided + const result = await fgaClient.rawRequest({ + operationName: "CustomListStores", + method: "GET", + path: "/stores", + }); + + expect(result.stores).toEqual([]); + }); + + it("should work without operationName (backward compatible)", async () => { + nock(basePath) + .get("/stores") + .reply(200, { stores: [] }); + + const result = await fgaClient.rawRequest({ + method: "GET", + path: "/stores", + }); + + expect(result.stores).toEqual([]); + }); + }); +}); From f338e77fca404cc87b2c6226ed845913353a8581 Mon Sep 17 00:00:00 2001 From: Abishek Newar <97790157+Abishek-Newar@users.noreply.github.com> Date: Thu, 8 Jan 2026 10:31:40 +0530 Subject: [PATCH 03/44] covering the failed test cases in github actions --- tests/helpers/nocks.ts | 26 ++++++++++++++------------ tests/rawRequest.test.ts | 4 ++-- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/tests/helpers/nocks.ts b/tests/helpers/nocks.ts index bc461236..9e5c830e 100644 --- a/tests/helpers/nocks.ts +++ b/tests/helpers/nocks.ts @@ -34,13 +34,15 @@ export const getNocks = ((nock: typeof Nock) => ({ expiresIn = 300, statusCode = 200, headers = {}, + persist = false, ) => { - return nock(`https://${apiTokenIssuer}`, { reqheaders: { "Content-Type": "application/x-www-form-urlencoded"} }) + const scope = nock(`https://${apiTokenIssuer}`, { reqheaders: { "Content-Type": "application/x-www-form-urlencoded" } }) .post("/oauth/token") .reply(statusCode, { access_token: accessToken, expires_in: expiresIn, }, headers); + return persist ? scope.persist() : scope; }, listStores: ( basePath = defaultConfiguration.getBasePath(), @@ -142,8 +144,8 @@ export const getNocks = ((nock: typeof Nock) => ({ .query({ page_size: pageSize, continuation_token: contToken, - ...(type ? { type } : { }), - ...(startTime ? {start_time: startTime } :{}) + ...(type ? { type } : {}), + ...(startTime ? { start_time: startTime } : {}) }) .reply(200, { changes: [{ @@ -162,10 +164,10 @@ export const getNocks = ((nock: typeof Nock) => ({ storeId: string, tuple: TupleKey, basePath = defaultConfiguration.getBasePath(), - consistency: ConsistencyPreference|undefined = undefined, + consistency: ConsistencyPreference | undefined = undefined, ) => { return nock(basePath) - .post(`/stores/${storeId}/read`, (body: ReadRequest) => + .post(`/stores/${storeId}/read`, (body: ReadRequest) => body.consistency === consistency ) .reply(200, { tuples: [], continuation_token: "" } as ReadResponse); @@ -195,7 +197,7 @@ export const getNocks = ((nock: typeof Nock) => ({ basePath = defaultConfiguration.getBasePath(), response: { allowed: boolean } | { code: string, message: string } = { allowed: true }, statusCode = 200, - consistency: ConsistencyPreference|undefined = undefined, + consistency: ConsistencyPreference | undefined = undefined, ) => { return nock(basePath) .post(`/stores/${storeId}/check`, (body: CheckRequest) => @@ -210,7 +212,7 @@ export const getNocks = ((nock: typeof Nock) => ({ storeId: string, responseBody: BatchCheckResponse, basePath = defaultConfiguration.getBasePath(), - consistency: ConsistencyPreference|undefined | undefined, + consistency: ConsistencyPreference | undefined | undefined, authorizationModelId = "auth-model-id", ) => { return nock(basePath) @@ -224,10 +226,10 @@ export const getNocks = ((nock: typeof Nock) => ({ storeId: string, tuple: TupleKey, basePath = defaultConfiguration.getBasePath(), - consistency: ConsistencyPreference|undefined = undefined, + consistency: ConsistencyPreference | undefined = undefined, ) => { return nock(basePath) - .post(`/stores/${storeId}/expand`, (body: ExpandRequest) => + .post(`/stores/${storeId}/expand`, (body: ExpandRequest) => body.consistency === consistency ) .reply(200, { tree: {} } as ExpandResponse); @@ -236,7 +238,7 @@ export const getNocks = ((nock: typeof Nock) => ({ storeId: string, responseBody: ListObjectsResponse, basePath = defaultConfiguration.getBasePath(), - consistency: ConsistencyPreference|undefined = undefined, + consistency: ConsistencyPreference | undefined = undefined, ) => { return nock(basePath) .post(`/stores/${storeId}/list-objects`, (body: ListUsersRequest) => @@ -248,10 +250,10 @@ export const getNocks = ((nock: typeof Nock) => ({ storeId: string, responseBody: ListUsersResponse, basePath = defaultConfiguration.getBasePath(), - consistency: ConsistencyPreference|undefined = undefined + consistency: ConsistencyPreference | undefined = undefined ) => { return nock(basePath) - .post(`/stores/${storeId}/list-users`, (body: ListUsersRequest) => + .post(`/stores/${storeId}/list-users`, (body: ListUsersRequest) => body.consistency === consistency ) .reply(200, responseBody); diff --git a/tests/rawRequest.test.ts b/tests/rawRequest.test.ts index dfd1b5e3..4fc8f608 100644 --- a/tests/rawRequest.test.ts +++ b/tests/rawRequest.test.ts @@ -22,7 +22,7 @@ describe("OpenFgaClient.rawRequest", () => { beforeEach(() => { fgaClient = new OpenFgaClient({ ...baseConfig }); - nocks.tokenExchange(OPENFGA_API_TOKEN_ISSUER, "test-token"); + nocks.tokenExchange(OPENFGA_API_TOKEN_ISSUER, "test-token", 300, 200, {}, true); }); afterEach(() => { @@ -257,7 +257,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { beforeEach(() => { fgaClient = new OpenFgaClient({ ...baseConfig }); - nocks.tokenExchange(OPENFGA_API_TOKEN_ISSUER, "test-token"); + nocks.tokenExchange(OPENFGA_API_TOKEN_ISSUER, "test-token", 300, 200, {}, true); }); afterEach(() => { From 836ecf6dcb2d617aee9821b990e3d57304079c94 Mon Sep 17 00:00:00 2001 From: Abishek Newar <97790157+Abishek-Newar@users.noreply.github.com> Date: Wed, 14 Jan 2026 11:10:14 +0530 Subject: [PATCH 04/44] updated the readme for rawRequests --- README.md | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/README.md b/README.md index 70c5b631..97be835a 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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 `rawRequest` method available on the `OpenFgaClient`. The `rawRequest` 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.rawRequest({ + 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.rawRequest({ + 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.rawRequest({ + method: 'GET', + path: '/stores/{store_id}/authorization-models/{model_id}', + pathParams: { + store_id: 'your-store-id', + model_id: 'your-model-id', + }, +}); +``` ### Retries From 3b830c32816ac7bca2f952363463b50204a8059a Mon Sep 17 00:00:00 2001 From: Abishek Newar <97790157+Abishek-Newar@users.noreply.github.com> Date: Wed, 14 Jan 2026 12:03:26 +0530 Subject: [PATCH 05/44] removed the white spaces from the api.ts --- api.ts | 78 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/api.ts b/api.ts index be9ef619..f9e2e9cf 100644 --- a/api.ts +++ b/api.ts @@ -147,7 +147,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: "POST", ...baseOptions, ...options }; + const localVarRequestOptions = { method: "POST", ...baseOptions, ...options}; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; @@ -156,7 +156,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers }; localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions); return { @@ -186,7 +186,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: "POST", ...baseOptions, ...options }; + const localVarRequestOptions = { method: "POST", ...baseOptions, ...options}; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; @@ -195,7 +195,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers }; localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions); return { @@ -214,15 +214,15 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio // verify required parameter 'body' is not null or undefined assertParamExists("createStore", "body", body); const localVarPath = "/stores" - ; - // use dummy base URL string because the URL constructor only accepts absolute URLs. + ; + // 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: "POST", ...baseOptions, ...options }; + const localVarRequestOptions = { method: "POST", ...baseOptions, ...options}; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; @@ -231,7 +231,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers }; localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions); return { @@ -258,14 +258,14 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: "DELETE", ...baseOptions, ...options }; + const localVarRequestOptions = { method: "DELETE", ...baseOptions, ...options}; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers }; return { url: toPathString(localVarUrlObj), @@ -294,7 +294,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: "POST", ...baseOptions, ...options }; + const localVarRequestOptions = { method: "POST", ...baseOptions, ...options}; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; @@ -303,7 +303,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions); return { @@ -330,14 +330,14 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: "GET", ...baseOptions, ...options }; + const localVarRequestOptions = { method: "GET", ...baseOptions, ...options}; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; return { url: toPathString(localVarUrlObj), @@ -366,7 +366,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: "POST", ...baseOptions, ...options }; + const localVarRequestOptions = { method: "POST", ...baseOptions, ...options}; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; @@ -375,7 +375,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions); return { @@ -394,15 +394,15 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio */ listStores: (pageSize?: number, continuationToken?: string, name?: string, options: any = {}): RequestArgs => { const localVarPath = "/stores" - ; - // use dummy base URL string because the URL constructor only accepts absolute URLs. + ; + // 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: "GET", ...baseOptions, ...options }; + const localVarRequestOptions = { method: "GET", ...baseOptions, ...options}; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; @@ -421,7 +421,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; return { url: toPathString(localVarUrlObj), @@ -450,7 +450,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: "POST", ...baseOptions, ...options }; + const localVarRequestOptions = { method: "POST", ...baseOptions, ...options}; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; @@ -459,7 +459,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions); return { @@ -489,7 +489,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: "POST", ...baseOptions, ...options }; + const localVarRequestOptions = { method: "POST", ...baseOptions, ...options}; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; @@ -498,7 +498,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions); return { @@ -528,14 +528,14 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: "GET", ...baseOptions, ...options }; + const localVarRequestOptions = { method: "GET", ...baseOptions, ...options}; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; return { url: toPathString(localVarUrlObj), @@ -564,14 +564,14 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: "GET", ...baseOptions, ...options }; + const localVarRequestOptions = { method: "GET", ...baseOptions, ...options}; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; return { url: toPathString(localVarUrlObj), @@ -599,7 +599,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: "GET", ...baseOptions, ...options }; + const localVarRequestOptions = { method: "GET", ...baseOptions, ...options}; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; @@ -614,7 +614,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; return { url: toPathString(localVarUrlObj), @@ -644,7 +644,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: "GET", ...baseOptions, ...options }; + const localVarRequestOptions = { method: "GET", ...baseOptions, ...options}; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; @@ -669,7 +669,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; return { url: toPathString(localVarUrlObj), @@ -698,7 +698,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: "POST", ...baseOptions, ...options }; + const localVarRequestOptions = { method: "POST", ...baseOptions, ...options}; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; @@ -707,7 +707,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions); return { @@ -740,7 +740,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: "PUT", ...baseOptions, ...options }; + const localVarRequestOptions = { method: "PUT", ...baseOptions, ...options}; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; @@ -749,7 +749,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions); return { @@ -779,7 +779,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: "POST", ...baseOptions, ...options }; + const localVarRequestOptions = { method: "POST", ...baseOptions, ...options}; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; @@ -788,7 +788,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = { ...localVarHeaderParameter, ...options.headers }; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions); return { @@ -871,7 +871,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio * OpenFgaApi - functional programming interface * @export */ -export const OpenFgaApiFp = function (configuration: Configuration, credentials: Credentials) { +export const OpenFgaApiFp = function(configuration: Configuration, credentials: Credentials) { const localVarAxiosParamCreator = OpenFgaApiAxiosParamCreator(configuration, credentials); return { /** From ebcd4f5b7b8713502c65386bd00c2822c1b5a43e Mon Sep 17 00:00:00 2001 From: Abishek Newar <97790157+Abishek-Newar@users.noreply.github.com> Date: Wed, 14 Jan 2026 12:04:53 +0530 Subject: [PATCH 06/44] removed white spaces from api.ts --- api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api.ts b/api.ts index f9e2e9cf..55b537fd 100644 --- a/api.ts +++ b/api.ts @@ -152,7 +152,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio const localVarQueryParameter = {} as any; - + localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); From 0d6b33cdf04d485d433f8c3be0614c6f3c296aa3 Mon Sep 17 00:00:00 2001 From: Abishek Newar <97790157+Abishek-Newar@users.noreply.github.com> Date: Wed, 14 Jan 2026 12:09:34 +0530 Subject: [PATCH 07/44] removed white spaces from api.ts --- api.ts | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/api.ts b/api.ts index 55b537fd..0a534e93 100644 --- a/api.ts +++ b/api.ts @@ -152,11 +152,11 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio const localVarQueryParameter = {} as any; - + localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers }; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions); return { @@ -191,11 +191,11 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio const localVarQueryParameter = {} as any; - + localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers }; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions); return { @@ -227,11 +227,11 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio const localVarQueryParameter = {} as any; - + localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers }; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions); return { @@ -263,9 +263,9 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio const localVarQueryParameter = {} as any; - + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); - localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers }; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; return { url: toPathString(localVarUrlObj), @@ -299,7 +299,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio const localVarQueryParameter = {} as any; - + localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); @@ -335,7 +335,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio const localVarQueryParameter = {} as any; - + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; @@ -371,7 +371,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio const localVarQueryParameter = {} as any; - + localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); @@ -419,7 +419,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio } - + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; @@ -455,7 +455,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio const localVarQueryParameter = {} as any; - + localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); @@ -494,7 +494,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio const localVarQueryParameter = {} as any; - + localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); @@ -533,7 +533,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio const localVarQueryParameter = {} as any; - + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; @@ -569,7 +569,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio const localVarQueryParameter = {} as any; - + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; @@ -612,7 +612,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio } - + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; @@ -667,7 +667,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio } - + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; @@ -703,7 +703,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio const localVarQueryParameter = {} as any; - + localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); @@ -745,7 +745,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio const localVarQueryParameter = {} as any; - + localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); @@ -784,7 +784,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio const localVarQueryParameter = {} as any; - + localVarHeaderParameter["Content-Type"] = "application/json"; setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); From f18c2b93d2268b9dadced98fde429c54df8db09c Mon Sep 17 00:00:00 2001 From: Abishek Newar <97790157+Abishek-Newar@users.noreply.github.com> Date: Wed, 14 Jan 2026 12:13:38 +0530 Subject: [PATCH 08/44] removed white spaces from client.ts --- client.ts | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/client.ts b/client.ts index 8126574c..c3cffccd 100644 --- a/client.ts +++ b/client.ts @@ -120,8 +120,8 @@ export type ClientRequestOptsWithConsistency = ClientRequestOpts & StoreIdOpts & export type PaginationOptions = { pageSize?: number, continuationToken?: string, name?: string; }; export type ClientCheckRequest = CheckRequestTupleKey & - Pick & -{ contextualTuples?: Array }; + Pick & + { contextualTuples?: Array }; export type ClientBatchCheckClientRequest = ClientCheckRequest[]; @@ -160,17 +160,17 @@ export type ClientBatchCheckRequest = { // for server batch check export interface ClientBatchCheckRequestOpts { - maxParallelRequests?: number; - maxBatchSize?: number; + maxParallelRequests?: number; + maxBatchSize?: number; } // for server batch check export type ClientBatchCheckSingleResponse = { - allowed: boolean; - request: ClientBatchCheckItem; - correlationId: string; - error?: CheckError; + allowed: boolean; + request: ClientBatchCheckItem; + correlationId: string; + error?: CheckError; } export interface ClientBatchCheckResponse { @@ -244,17 +244,17 @@ export interface ClientReadChangesRequest { } export type ClientExpandRequest = ExpandRequestTupleKey & Omit & { - contextualTuples?: Array + contextualTuples?: Array }; export type ClientReadRequest = ReadRequestTupleKey; export type ClientListObjectsRequest = Omit & { - contextualTuples?: Array + contextualTuples?: Array }; export type ClientListUsersRequest = Omit & { - contextualTuples?: Array + contextualTuples?: Array }; export type ClientListRelationsRequest = Omit & { - relations?: string[], + relations?: string[], }; export type ClientWriteAssertionsRequest = (CheckRequestTupleKey & Pick)[]; @@ -584,7 +584,7 @@ export class OpenFgaClient extends BaseAPI { const writeResponses: ClientWriteSingleResponse[][] = []; if (writes?.length) { for await (const singleChunkResponse of asyncPool(maxParallelRequests, chunkArray(writes, maxPerChunk), - (chunk) => this.writeTuples(chunk, { ...options, headers, conflict, transaction: undefined }).catch(err => { + (chunk) => this.writeTuples(chunk,{ ...options, headers, conflict, transaction: undefined }).catch(err => { if (err instanceof FgaApiAuthenticationError) { throw err; } @@ -739,7 +739,7 @@ export class OpenFgaClient extends BaseAPI { - private singleBatchCheck(body: BatchCheckRequest, options: ClientRequestOptsWithConsistency & ClientBatchCheckRequestOpts = {}): Promise { + private singleBatchCheck(body: BatchCheckRequest, options: ClientRequestOptsWithConsistency & ClientBatchCheckRequestOpts = {}): Promise{ return this.api.batchCheck(this.getStoreId(options)!, body, options); } @@ -817,7 +817,7 @@ export class OpenFgaClient extends BaseAPI { // Collect the responses and associate them with their correlation IDs for await (const response of batchResponses) { - if (response) { + if (response) { for (const [correlationId, result] of Object.entries(response)) { const check = correlationIdToCheck.get(correlationId); if (check && result) { @@ -833,7 +833,7 @@ export class OpenFgaClient extends BaseAPI { } return { result: results }; - } + } /** * Expand - Expands the relationships in userset tree format (evaluates) From 55a78c0b1ab08efa10ecbfa4ec4ac27ad8645dd8 Mon Sep 17 00:00:00 2001 From: Abishek Newar <97790157+Abishek-Newar@users.noreply.github.com> Date: Wed, 14 Jan 2026 12:14:47 +0530 Subject: [PATCH 09/44] removed white spaces from client.ts --- client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client.ts b/client.ts index c3cffccd..828740a0 100644 --- a/client.ts +++ b/client.ts @@ -739,7 +739,7 @@ export class OpenFgaClient extends BaseAPI { - private singleBatchCheck(body: BatchCheckRequest, options: ClientRequestOptsWithConsistency & ClientBatchCheckRequestOpts = {}): Promise{ + private singleBatchCheck(body: BatchCheckRequest, options: ClientRequestOptsWithConsistency & ClientBatchCheckRequestOpts = {}): Promise { return this.api.batchCheck(this.getStoreId(options)!, body, options); } From 02861caca2bba4da0221a3ec448044aa0a08a75b Mon Sep 17 00:00:00 2001 From: Abishek Newar <97790157+Abishek-Newar@users.noreply.github.com> Date: Wed, 14 Jan 2026 12:43:07 +0530 Subject: [PATCH 10/44] trying to fix the test failure --- tests/rawRequest.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/rawRequest.test.ts b/tests/rawRequest.test.ts index 4fc8f608..dcebd4dd 100644 --- a/tests/rawRequest.test.ts +++ b/tests/rawRequest.test.ts @@ -19,10 +19,11 @@ nock.disableNetConnect(); describe("OpenFgaClient.rawRequest", () => { let fgaClient: OpenFgaClient; const basePath = defaultConfiguration.getBasePath(); + let tokenScope: nock.Scope; beforeEach(() => { + tokenScope = nocks.tokenExchange(OPENFGA_API_TOKEN_ISSUER, "test-token", 300, 200, {}, true); fgaClient = new OpenFgaClient({ ...baseConfig }); - nocks.tokenExchange(OPENFGA_API_TOKEN_ISSUER, "test-token", 300, 200, {}, true); }); afterEach(() => { @@ -254,10 +255,11 @@ describe("OpenFgaClient.rawRequest", () => { describe("OpenFgaClient.rawRequest - path parameters", () => { let fgaClient: OpenFgaClient; const basePath = defaultConfiguration.getBasePath(); + let tokenScope: nock.Scope; beforeEach(() => { + tokenScope = nocks.tokenExchange(OPENFGA_API_TOKEN_ISSUER, "test-token", 300, 200, {}, true); fgaClient = new OpenFgaClient({ ...baseConfig }); - nocks.tokenExchange(OPENFGA_API_TOKEN_ISSUER, "test-token", 300, 200, {}, true); }); afterEach(() => { From 307e15a93d55a2c1041e3dc6ec48dada8815abb4 Mon Sep 17 00:00:00 2001 From: detailswes <97790157+Abishek-Newar@users.noreply.github.com> Date: Wed, 14 Jan 2026 15:10:44 +0530 Subject: [PATCH 11/44] Updated nocks.ts --- tests/helpers/nocks.ts | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/tests/helpers/nocks.ts b/tests/helpers/nocks.ts index 9e5c830e..c218f878 100644 --- a/tests/helpers/nocks.ts +++ b/tests/helpers/nocks.ts @@ -36,13 +36,7 @@ export const getNocks = ((nock: typeof Nock) => ({ headers = {}, persist = false, ) => { - const scope = nock(`https://${apiTokenIssuer}`, { reqheaders: { "Content-Type": "application/x-www-form-urlencoded" } }) - .post("/oauth/token") - .reply(statusCode, { - access_token: accessToken, - expires_in: expiresIn, - }, headers); - return persist ? scope.persist() : scope; + return nock(`https://${apiTokenIssuer}`, { reqheaders: { "Content-Type": "application/x-www-form-urlencoded"} }) }, listStores: ( basePath = defaultConfiguration.getBasePath(), @@ -144,8 +138,8 @@ export const getNocks = ((nock: typeof Nock) => ({ .query({ page_size: pageSize, continuation_token: contToken, - ...(type ? { type } : {}), - ...(startTime ? { start_time: startTime } : {}) + ...(type ? { type } : { }), + ...(startTime ? {start_time: startTime } :{}) }) .reply(200, { changes: [{ @@ -164,10 +158,10 @@ export const getNocks = ((nock: typeof Nock) => ({ storeId: string, tuple: TupleKey, basePath = defaultConfiguration.getBasePath(), - consistency: ConsistencyPreference | undefined = undefined, + consistency: ConsistencyPreference|undefined = undefined, ) => { return nock(basePath) - .post(`/stores/${storeId}/read`, (body: ReadRequest) => + .post(`/stores/${storeId}/read`, (body: ReadRequest) => body.consistency === consistency ) .reply(200, { tuples: [], continuation_token: "" } as ReadResponse); @@ -197,7 +191,7 @@ export const getNocks = ((nock: typeof Nock) => ({ basePath = defaultConfiguration.getBasePath(), response: { allowed: boolean } | { code: string, message: string } = { allowed: true }, statusCode = 200, - consistency: ConsistencyPreference | undefined = undefined, + consistency: ConsistencyPreference|undefined = undefined, ) => { return nock(basePath) .post(`/stores/${storeId}/check`, (body: CheckRequest) => @@ -226,7 +220,7 @@ export const getNocks = ((nock: typeof Nock) => ({ storeId: string, tuple: TupleKey, basePath = defaultConfiguration.getBasePath(), - consistency: ConsistencyPreference | undefined = undefined, + consistency: ConsistencyPreference|undefined = undefined, ) => { return nock(basePath) .post(`/stores/${storeId}/expand`, (body: ExpandRequest) => @@ -238,10 +232,10 @@ export const getNocks = ((nock: typeof Nock) => ({ storeId: string, responseBody: ListObjectsResponse, basePath = defaultConfiguration.getBasePath(), - consistency: ConsistencyPreference | undefined = undefined, + consistency: ConsistencyPreference|undefined = undefined, ) => { return nock(basePath) - .post(`/stores/${storeId}/list-objects`, (body: ListUsersRequest) => + .post(`/stores/${storeId}/list-objects`, (body: ListUsersRequest) => body.consistency === consistency ) .reply(200, responseBody); @@ -250,7 +244,7 @@ export const getNocks = ((nock: typeof Nock) => ({ storeId: string, responseBody: ListUsersResponse, basePath = defaultConfiguration.getBasePath(), - consistency: ConsistencyPreference | undefined = undefined + consistency: ConsistencyPreference|undefined = undefined ) => { return nock(basePath) .post(`/stores/${storeId}/list-users`, (body: ListUsersRequest) => From 97af6ab77dd2a654136050fdca45ea113de7c029 Mon Sep 17 00:00:00 2001 From: detailswes <97790157+Abishek-Newar@users.noreply.github.com> Date: Wed, 14 Jan 2026 15:14:02 +0530 Subject: [PATCH 12/44] update nocks.ts --- tests/helpers/nocks.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/helpers/nocks.ts b/tests/helpers/nocks.ts index c218f878..792cff1f 100644 --- a/tests/helpers/nocks.ts +++ b/tests/helpers/nocks.ts @@ -37,6 +37,11 @@ export const getNocks = ((nock: typeof Nock) => ({ persist = false, ) => { return nock(`https://${apiTokenIssuer}`, { reqheaders: { "Content-Type": "application/x-www-form-urlencoded"} }) + .post("/oauth/token") + .reply(statusCode, { + access_token: accessToken, + expires_in: expiresIn, + }, headers); }, listStores: ( basePath = defaultConfiguration.getBasePath(), @@ -206,11 +211,11 @@ export const getNocks = ((nock: typeof Nock) => ({ storeId: string, responseBody: BatchCheckResponse, basePath = defaultConfiguration.getBasePath(), - consistency: ConsistencyPreference | undefined | undefined, + consistency: ConsistencyPreference|undefined | undefined, authorizationModelId = "auth-model-id", ) => { return nock(basePath) - .post(`/stores/${storeId}/batch-check`, (body: BatchCheckRequest) => + .post(`/stores/${storeId}/batch-check`, (body: BatchCheckRequest) => body.consistency === consistency && body.authorization_model_id === authorizationModelId ) @@ -223,7 +228,7 @@ export const getNocks = ((nock: typeof Nock) => ({ consistency: ConsistencyPreference|undefined = undefined, ) => { return nock(basePath) - .post(`/stores/${storeId}/expand`, (body: ExpandRequest) => + .post(`/stores/${storeId}/expand`, (body: ExpandRequest) => body.consistency === consistency ) .reply(200, { tree: {} } as ExpandResponse); @@ -247,7 +252,7 @@ export const getNocks = ((nock: typeof Nock) => ({ consistency: ConsistencyPreference|undefined = undefined ) => { return nock(basePath) - .post(`/stores/${storeId}/list-users`, (body: ListUsersRequest) => + .post(`/stores/${storeId}/list-users`, (body: ListUsersRequest) => body.consistency === consistency ) .reply(200, responseBody); From d6d4484a867446c49b0159bdba3e7369551848ca Mon Sep 17 00:00:00 2001 From: detailswes <97790157+Abishek-Newar@users.noreply.github.com> Date: Wed, 14 Jan 2026 15:17:41 +0530 Subject: [PATCH 13/44] updated nocks --- tests/helpers/nocks.ts | 5 ++--- tests/rawRequest.test.ts | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/helpers/nocks.ts b/tests/helpers/nocks.ts index 792cff1f..bc461236 100644 --- a/tests/helpers/nocks.ts +++ b/tests/helpers/nocks.ts @@ -34,7 +34,6 @@ export const getNocks = ((nock: typeof Nock) => ({ expiresIn = 300, statusCode = 200, headers = {}, - persist = false, ) => { return nock(`https://${apiTokenIssuer}`, { reqheaders: { "Content-Type": "application/x-www-form-urlencoded"} }) .post("/oauth/token") @@ -215,7 +214,7 @@ export const getNocks = ((nock: typeof Nock) => ({ authorizationModelId = "auth-model-id", ) => { return nock(basePath) - .post(`/stores/${storeId}/batch-check`, (body: BatchCheckRequest) => + .post(`/stores/${storeId}/batch-check`, (body: BatchCheckRequest) => body.consistency === consistency && body.authorization_model_id === authorizationModelId ) @@ -240,7 +239,7 @@ export const getNocks = ((nock: typeof Nock) => ({ consistency: ConsistencyPreference|undefined = undefined, ) => { return nock(basePath) - .post(`/stores/${storeId}/list-objects`, (body: ListUsersRequest) => + .post(`/stores/${storeId}/list-objects`, (body: ListUsersRequest) => body.consistency === consistency ) .reply(200, responseBody); diff --git a/tests/rawRequest.test.ts b/tests/rawRequest.test.ts index dcebd4dd..6c2d0575 100644 --- a/tests/rawRequest.test.ts +++ b/tests/rawRequest.test.ts @@ -22,7 +22,7 @@ describe("OpenFgaClient.rawRequest", () => { let tokenScope: nock.Scope; beforeEach(() => { - tokenScope = nocks.tokenExchange(OPENFGA_API_TOKEN_ISSUER, "test-token", 300, 200, {}, true); + tokenScope = nocks.tokenExchange(OPENFGA_API_TOKEN_ISSUER, "test-token", 300, 200, {},); fgaClient = new OpenFgaClient({ ...baseConfig }); }); @@ -258,7 +258,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { let tokenScope: nock.Scope; beforeEach(() => { - tokenScope = nocks.tokenExchange(OPENFGA_API_TOKEN_ISSUER, "test-token", 300, 200, {}, true); + tokenScope = nocks.tokenExchange(OPENFGA_API_TOKEN_ISSUER, "test-token", 300, 200, {}); fgaClient = new OpenFgaClient({ ...baseConfig }); }); From 1a1e8f64224ff6168821524eaf6116d12dfa76e6 Mon Sep 17 00:00:00 2001 From: detailswes <97790157+Abishek-Newar@users.noreply.github.com> Date: Wed, 14 Jan 2026 18:24:58 +0530 Subject: [PATCH 14/44] Updated RawRequest.test.ts by comparing it to go-sdk --- tests/rawRequest.test.ts | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/tests/rawRequest.test.ts b/tests/rawRequest.test.ts index 6c2d0575..283fbad8 100644 --- a/tests/rawRequest.test.ts +++ b/tests/rawRequest.test.ts @@ -4,26 +4,25 @@ import { OpenFgaClient, FgaApiNotFoundError, FgaApiValidationError, + CredentialsMethod, } from "../index"; import { baseConfig, defaultConfiguration, - OPENFGA_API_TOKEN_ISSUER, OPENFGA_STORE_ID, } from "./helpers/default-config"; -import { getNocks } from "./helpers/nocks"; -const nocks = getNocks(nock); nock.disableNetConnect(); describe("OpenFgaClient.rawRequest", () => { let fgaClient: OpenFgaClient; const basePath = defaultConfiguration.getBasePath(); - let tokenScope: nock.Scope; beforeEach(() => { - tokenScope = nocks.tokenExchange(OPENFGA_API_TOKEN_ISSUER, "test-token", 300, 200, {},); - fgaClient = new OpenFgaClient({ ...baseConfig }); + fgaClient = new OpenFgaClient({ + ...baseConfig, + credentials: { method: CredentialsMethod.None } + }); }); afterEach(() => { @@ -232,16 +231,27 @@ describe("OpenFgaClient.rawRequest", () => { }); describe("authentication", () => { - it("should include authentication headers", async () => { + it("should include authentication headers when configured", async () => { + // Create a client with ApiToken authentication + const authenticatedClient = new OpenFgaClient({ + ...baseConfig, + credentials: { + method: CredentialsMethod.ApiToken, + config: { + token: "test-api-token", + }, + }, + }); + nock(basePath, { reqheaders: { - Authorization: "Bearer test-token", + Authorization: "Bearer test-api-token", }, }) .get("/stores") .reply(200, {}); - await fgaClient.rawRequest({ + await authenticatedClient.rawRequest({ method: "GET", path: "/stores", }); @@ -255,11 +265,12 @@ describe("OpenFgaClient.rawRequest", () => { describe("OpenFgaClient.rawRequest - path parameters", () => { let fgaClient: OpenFgaClient; const basePath = defaultConfiguration.getBasePath(); - let tokenScope: nock.Scope; beforeEach(() => { - tokenScope = nocks.tokenExchange(OPENFGA_API_TOKEN_ISSUER, "test-token", 300, 200, {}); - fgaClient = new OpenFgaClient({ ...baseConfig }); + fgaClient = new OpenFgaClient({ + ...baseConfig, + credentials: { method: CredentialsMethod.None } + }); }); afterEach(() => { From 7e12939b1af185fb1b27faa193688658adc48dad Mon Sep 17 00:00:00 2001 From: detailswes <97790157+Abishek-Newar@users.noreply.github.com> Date: Wed, 14 Jan 2026 19:07:50 +0530 Subject: [PATCH 15/44] url encoding fixed --- tests/rawRequest.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/rawRequest.test.ts b/tests/rawRequest.test.ts index 283fbad8..57a1f612 100644 --- a/tests/rawRequest.test.ts +++ b/tests/rawRequest.test.ts @@ -334,8 +334,9 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { const id = "test/with?special&chars"; const encodedId = encodeURIComponent(id); + // Use regex matching to handle URL-encoded special characters properly nock(basePath) - .get(`/items/${encodedId}`) + .get(new RegExp(`/items/${encodedId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`)) .reply(200, { id: id }); const result = await fgaClient.rawRequest({ @@ -351,8 +352,9 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { const name = "用户"; const encodedName = encodeURIComponent(name); // %E7%94%A8%E6%88%B7 + // Use regex matching to handle URL-encoded unicode characters properly nock(basePath) - .get(`/users/${encodedName}`) + .get(new RegExp(`/users/${encodedName}`)) .reply(200, { name: name }); const result = await fgaClient.rawRequest({ From 1e4af9e01d5852aace18036eb7bfc0163a414f72 Mon Sep 17 00:00:00 2001 From: detailswes <97790157+Abishek-Newar@users.noreply.github.com> Date: Wed, 14 Jan 2026 19:17:36 +0530 Subject: [PATCH 16/44] changeing the base path to match that of nock one --- tests/rawRequest.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/rawRequest.test.ts b/tests/rawRequest.test.ts index 57a1f612..d43f7c77 100644 --- a/tests/rawRequest.test.ts +++ b/tests/rawRequest.test.ts @@ -16,7 +16,8 @@ nock.disableNetConnect(); describe("OpenFgaClient.rawRequest", () => { let fgaClient: OpenFgaClient; - const basePath = defaultConfiguration.getBasePath(); + // Use basePath with explicit port for nock to match requests in CI where axios includes :443 + const basePath = "https://api.fga.example:443"; beforeEach(() => { fgaClient = new OpenFgaClient({ @@ -264,7 +265,8 @@ describe("OpenFgaClient.rawRequest", () => { describe("OpenFgaClient.rawRequest - path parameters", () => { let fgaClient: OpenFgaClient; - const basePath = defaultConfiguration.getBasePath(); + // Use basePath with explicit port for nock to match requests in CI where axios includes :443 + const basePath = "https://api.fga.example:443"; beforeEach(() => { fgaClient = new OpenFgaClient({ From 254db3cbf86a3d665ed520125b246684e30dcf9b Mon Sep 17 00:00:00 2001 From: detailswes <97790157+Abishek-Newar@users.noreply.github.com> Date: Wed, 14 Jan 2026 19:24:57 +0530 Subject: [PATCH 17/44] changed the nock url --- tests/rawRequest.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/rawRequest.test.ts b/tests/rawRequest.test.ts index d43f7c77..70a2106d 100644 --- a/tests/rawRequest.test.ts +++ b/tests/rawRequest.test.ts @@ -16,8 +16,8 @@ nock.disableNetConnect(); describe("OpenFgaClient.rawRequest", () => { let fgaClient: OpenFgaClient; - // Use basePath with explicit port for nock to match requests in CI where axios includes :443 - const basePath = "https://api.fga.example:443"; + // Use regex for nock to match requests with or without explicit :443 port + const basePath = /https:\/\/api\.fga\.example/; beforeEach(() => { fgaClient = new OpenFgaClient({ @@ -265,8 +265,8 @@ describe("OpenFgaClient.rawRequest", () => { describe("OpenFgaClient.rawRequest - path parameters", () => { let fgaClient: OpenFgaClient; - // Use basePath with explicit port for nock to match requests in CI where axios includes :443 - const basePath = "https://api.fga.example:443"; + // Use regex for nock to match requests with or without explicit :443 port + const basePath = /https:\/\/api\.fga\.example/; beforeEach(() => { fgaClient = new OpenFgaClient({ From 232a776427f657c7a3f9808b507a9adc715ecc0c Mon Sep 17 00:00:00 2001 From: detailswes <97790157+Abishek-Newar@users.noreply.github.com> Date: Wed, 14 Jan 2026 19:26:58 +0530 Subject: [PATCH 18/44] mew test changes --- tests/rawRequest.test.ts | 43 +++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/tests/rawRequest.test.ts b/tests/rawRequest.test.ts index 70a2106d..e8df60a9 100644 --- a/tests/rawRequest.test.ts +++ b/tests/rawRequest.test.ts @@ -4,26 +4,31 @@ import { OpenFgaClient, FgaApiNotFoundError, FgaApiValidationError, - CredentialsMethod, } from "../index"; import { baseConfig, defaultConfiguration, + OPENFGA_API_TOKEN_ISSUER, OPENFGA_STORE_ID, } from "./helpers/default-config"; +import { getNocks } from "./helpers/nocks"; +const nocks = getNocks(nock); nock.disableNetConnect(); describe("OpenFgaClient.rawRequest", () => { let fgaClient: OpenFgaClient; +<<<<<<< HEAD // Use regex for nock to match requests with or without explicit :443 port const basePath = /https:\/\/api\.fga\.example/; +======= + const basePath = defaultConfiguration.getBasePath(); + let tokenScope: nock.Scope; +>>>>>>> parent of 1a1e8f6 (Updated RawRequest.test.ts by comparing it to go-sdk) beforeEach(() => { - fgaClient = new OpenFgaClient({ - ...baseConfig, - credentials: { method: CredentialsMethod.None } - }); + tokenScope = nocks.tokenExchange(OPENFGA_API_TOKEN_ISSUER, "test-token", 300, 200, {},); + fgaClient = new OpenFgaClient({ ...baseConfig }); }); afterEach(() => { @@ -232,27 +237,16 @@ describe("OpenFgaClient.rawRequest", () => { }); describe("authentication", () => { - it("should include authentication headers when configured", async () => { - // Create a client with ApiToken authentication - const authenticatedClient = new OpenFgaClient({ - ...baseConfig, - credentials: { - method: CredentialsMethod.ApiToken, - config: { - token: "test-api-token", - }, - }, - }); - + it("should include authentication headers", async () => { nock(basePath, { reqheaders: { - Authorization: "Bearer test-api-token", + Authorization: "Bearer test-token", }, }) .get("/stores") .reply(200, {}); - await authenticatedClient.rawRequest({ + await fgaClient.rawRequest({ method: "GET", path: "/stores", }); @@ -265,14 +259,17 @@ describe("OpenFgaClient.rawRequest", () => { describe("OpenFgaClient.rawRequest - path parameters", () => { let fgaClient: OpenFgaClient; +<<<<<<< HEAD // Use regex for nock to match requests with or without explicit :443 port const basePath = /https:\/\/api\.fga\.example/; +======= + const basePath = defaultConfiguration.getBasePath(); + let tokenScope: nock.Scope; +>>>>>>> parent of 1a1e8f6 (Updated RawRequest.test.ts by comparing it to go-sdk) beforeEach(() => { - fgaClient = new OpenFgaClient({ - ...baseConfig, - credentials: { method: CredentialsMethod.None } - }); + tokenScope = nocks.tokenExchange(OPENFGA_API_TOKEN_ISSUER, "test-token", 300, 200, {}); + fgaClient = new OpenFgaClient({ ...baseConfig }); }); afterEach(() => { From b0529ef7b5ec3b94328ff8c34d7ed5ce3ccdf21b Mon Sep 17 00:00:00 2001 From: detailswes <97790157+Abishek-Newar@users.noreply.github.com> Date: Wed, 14 Jan 2026 19:31:50 +0530 Subject: [PATCH 19/44] resolved the issues --- tests/rawRequest.test.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/rawRequest.test.ts b/tests/rawRequest.test.ts index e8df60a9..56728f98 100644 --- a/tests/rawRequest.test.ts +++ b/tests/rawRequest.test.ts @@ -18,13 +18,9 @@ nock.disableNetConnect(); describe("OpenFgaClient.rawRequest", () => { let fgaClient: OpenFgaClient; -<<<<<<< HEAD // Use regex for nock to match requests with or without explicit :443 port const basePath = /https:\/\/api\.fga\.example/; -======= - const basePath = defaultConfiguration.getBasePath(); let tokenScope: nock.Scope; ->>>>>>> parent of 1a1e8f6 (Updated RawRequest.test.ts by comparing it to go-sdk) beforeEach(() => { tokenScope = nocks.tokenExchange(OPENFGA_API_TOKEN_ISSUER, "test-token", 300, 200, {},); @@ -259,13 +255,9 @@ describe("OpenFgaClient.rawRequest", () => { describe("OpenFgaClient.rawRequest - path parameters", () => { let fgaClient: OpenFgaClient; -<<<<<<< HEAD // Use regex for nock to match requests with or without explicit :443 port const basePath = /https:\/\/api\.fga\.example/; -======= - const basePath = defaultConfiguration.getBasePath(); let tokenScope: nock.Scope; ->>>>>>> parent of 1a1e8f6 (Updated RawRequest.test.ts by comparing it to go-sdk) beforeEach(() => { tokenScope = nocks.tokenExchange(OPENFGA_API_TOKEN_ISSUER, "test-token", 300, 200, {}); From 7407e6828aaf828f5410bd601db81fa29e259753 Mon Sep 17 00:00:00 2001 From: detailswes <97790157+Abishek-Newar@users.noreply.github.com> Date: Thu, 15 Jan 2026 10:47:59 +0530 Subject: [PATCH 20/44] using regex based url matching in nocks --- tests/helpers/nocks.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/helpers/nocks.ts b/tests/helpers/nocks.ts index bc461236..80608669 100644 --- a/tests/helpers/nocks.ts +++ b/tests/helpers/nocks.ts @@ -35,7 +35,9 @@ export const getNocks = ((nock: typeof Nock) => ({ statusCode = 200, headers = {}, ) => { - return nock(`https://${apiTokenIssuer}`, { reqheaders: { "Content-Type": "application/x-www-form-urlencoded"} }) + // Use regex to match URLs with or without explicit :443 port (CI environments may include the port) + const tokenIssuerPattern = new RegExp(`https://${apiTokenIssuer.replace(/\./g, "\\.")}(:\\d+)?`); + return nock(tokenIssuerPattern, { reqheaders: { "Content-Type": "application/x-www-form-urlencoded" } }) .post("/oauth/token") .reply(statusCode, { access_token: accessToken, From 939abb3d2f55ab973bf0eef06e1c9dba1e3ef76f Mon Sep 17 00:00:00 2001 From: detailswes <97790157+Abishek-Newar@users.noreply.github.com> Date: Thu, 15 Jan 2026 11:24:17 +0530 Subject: [PATCH 21/44] fixing the regex in nocks --- tests/helpers/nocks.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/helpers/nocks.ts b/tests/helpers/nocks.ts index 80608669..b67afa0c 100644 --- a/tests/helpers/nocks.ts +++ b/tests/helpers/nocks.ts @@ -36,7 +36,8 @@ export const getNocks = ((nock: typeof Nock) => ({ headers = {}, ) => { // Use regex to match URLs with or without explicit :443 port (CI environments may include the port) - const tokenIssuerPattern = new RegExp(`https://${apiTokenIssuer.replace(/\./g, "\\.")}(:\\d+)?`); + const escapedIssuer = apiTokenIssuer.replace(/\./g, "\\."); + const tokenIssuerPattern = new RegExp("^https://" + escapedIssuer + "(:\\d+)?$"); return nock(tokenIssuerPattern, { reqheaders: { "Content-Type": "application/x-www-form-urlencoded" } }) .post("/oauth/token") .reply(statusCode, { From a05df742b9d9985fd12fdd224977dd56c155c9ce Mon Sep 17 00:00:00 2001 From: detailswes <97790157+Abishek-Newar@users.noreply.github.com> Date: Thu, 15 Jan 2026 11:30:48 +0530 Subject: [PATCH 22/44] updated nocks regex --- tests/helpers/nocks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/helpers/nocks.ts b/tests/helpers/nocks.ts index b67afa0c..dcb25b34 100644 --- a/tests/helpers/nocks.ts +++ b/tests/helpers/nocks.ts @@ -37,7 +37,7 @@ export const getNocks = ((nock: typeof Nock) => ({ ) => { // Use regex to match URLs with or without explicit :443 port (CI environments may include the port) const escapedIssuer = apiTokenIssuer.replace(/\./g, "\\."); - const tokenIssuerPattern = new RegExp("^https://" + escapedIssuer + "(:\\d+)?$"); + const tokenIssuerPattern = new RegExp("https://" + escapedIssuer + "(:\\d+)?"); return nock(tokenIssuerPattern, { reqheaders: { "Content-Type": "application/x-www-form-urlencoded" } }) .post("/oauth/token") .reply(statusCode, { From 55428ef1bde02d0f14737c6a38868582d85a2028 Mon Sep 17 00:00:00 2001 From: detailswes <97790157+Abishek-Newar@users.noreply.github.com> Date: Thu, 15 Jan 2026 11:55:58 +0530 Subject: [PATCH 23/44] testing --- .github/workflows/main.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 2870f934..22da91c5 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -32,7 +32,7 @@ jobs: cache: "npm" - name: Install dependencies - run: npm ci + run: rm -rf node_modules && npm ci - name: Build run: npm run build From cba2592b5e4a41d68e32566f4259326c01d3176e Mon Sep 17 00:00:00 2001 From: detailswes <97790157+Abishek-Newar@users.noreply.github.com> Date: Thu, 15 Jan 2026 12:02:31 +0530 Subject: [PATCH 24/44] removed the rm --- .github/workflows/main.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 22da91c5..2870f934 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -32,7 +32,7 @@ jobs: cache: "npm" - name: Install dependencies - run: rm -rf node_modules && npm ci + run: npm ci - name: Build run: npm run build From 376ea1be20ff423bac4f39d46bf447c1ac0d4631 Mon Sep 17 00:00:00 2001 From: detailswes <97790157+Abishek-Newar@users.noreply.github.com> Date: Thu, 15 Jan 2026 12:10:38 +0530 Subject: [PATCH 25/44] update nocks.ts --- tests/helpers/nocks.ts | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/tests/helpers/nocks.ts b/tests/helpers/nocks.ts index dcb25b34..0c67e0a3 100644 --- a/tests/helpers/nocks.ts +++ b/tests/helpers/nocks.ts @@ -35,15 +35,30 @@ export const getNocks = ((nock: typeof Nock) => ({ statusCode = 200, headers = {}, ) => { - // Use regex to match URLs with or without explicit :443 port (CI environments may include the port) - const escapedIssuer = apiTokenIssuer.replace(/\./g, "\\."); - const tokenIssuerPattern = new RegExp("https://" + escapedIssuer + "(:\\d+)?"); - return nock(tokenIssuerPattern, { reqheaders: { "Content-Type": "application/x-www-form-urlencoded" } }) + // Set up TWO nock scopes to handle both URL formats: + // - Without port: https://tokenissuer.fga.example (local environment) + // - With port: https://tokenissuer.fga.example:443 (CI environment) + const replyBody = { + access_token: accessToken, + expires_in: expiresIn, + }; + const reqheaders = { "Content-Type": "application/x-www-form-urlencoded" }; + + // Scope 1: Without explicit port + const scope1 = nock(`https://${apiTokenIssuer}`, { reqheaders }) .post("/oauth/token") - .reply(statusCode, { - access_token: accessToken, - expires_in: expiresIn, - }, headers); + .reply(statusCode, replyBody, headers); + + // Scope 2: With explicit :443 port (for CI environments) + const scope2 = nock(`https://${apiTokenIssuer}:443`, { reqheaders }) + .post("/oauth/token") + .reply(statusCode, replyBody, headers); + + // Return a proxy object that considers the mock "done" if EITHER scope was used + return { + isDone: () => scope1.isDone() || scope2.isDone(), + pendingMocks: () => [...scope1.pendingMocks(), ...scope2.pendingMocks()], + } as Nock.Scope; }, listStores: ( basePath = defaultConfiguration.getBasePath(), From 0afe5ac356d4538752d38f16694dab1b404d5574 Mon Sep 17 00:00:00 2001 From: detailswes <97790157+Abishek-Newar@users.noreply.github.com> Date: Thu, 15 Jan 2026 12:22:04 +0530 Subject: [PATCH 26/44] modified the test and reveretd the nocks --- tests/helpers/nocks.ts | 30 ++++++------------------------ tests/rawRequest.test.ts | 34 +++++----------------------------- 2 files changed, 11 insertions(+), 53 deletions(-) diff --git a/tests/helpers/nocks.ts b/tests/helpers/nocks.ts index 0c67e0a3..5be29de3 100644 --- a/tests/helpers/nocks.ts +++ b/tests/helpers/nocks.ts @@ -35,30 +35,12 @@ export const getNocks = ((nock: typeof Nock) => ({ statusCode = 200, headers = {}, ) => { - // Set up TWO nock scopes to handle both URL formats: - // - Without port: https://tokenissuer.fga.example (local environment) - // - With port: https://tokenissuer.fga.example:443 (CI environment) - const replyBody = { - access_token: accessToken, - expires_in: expiresIn, - }; - const reqheaders = { "Content-Type": "application/x-www-form-urlencoded" }; - - // Scope 1: Without explicit port - const scope1 = nock(`https://${apiTokenIssuer}`, { reqheaders }) - .post("/oauth/token") - .reply(statusCode, replyBody, headers); - - // Scope 2: With explicit :443 port (for CI environments) - const scope2 = nock(`https://${apiTokenIssuer}:443`, { reqheaders }) + return nock(`https://${apiTokenIssuer}`, { reqheaders: { "Content-Type": "application/x-www-form-urlencoded"} }) .post("/oauth/token") - .reply(statusCode, replyBody, headers); - - // Return a proxy object that considers the mock "done" if EITHER scope was used - return { - isDone: () => scope1.isDone() || scope2.isDone(), - pendingMocks: () => [...scope1.pendingMocks(), ...scope2.pendingMocks()], - } as Nock.Scope; + .reply(statusCode, { + access_token: accessToken, + expires_in: expiresIn, + }, headers); }, listStores: ( basePath = defaultConfiguration.getBasePath(), @@ -287,4 +269,4 @@ export const getNocks = ((nock: typeof Nock) => ({ .put(`/stores/${storeId}/assertions/${modelId}`) .reply(responseStatus); }, -})); +})); \ No newline at end of file diff --git a/tests/rawRequest.test.ts b/tests/rawRequest.test.ts index 56728f98..8ac73915 100644 --- a/tests/rawRequest.test.ts +++ b/tests/rawRequest.test.ts @@ -4,27 +4,24 @@ import { OpenFgaClient, FgaApiNotFoundError, FgaApiValidationError, + CredentialsMethod, } from "../index"; import { baseConfig, defaultConfiguration, - OPENFGA_API_TOKEN_ISSUER, OPENFGA_STORE_ID, } from "./helpers/default-config"; -import { getNocks } from "./helpers/nocks"; -const nocks = getNocks(nock); nock.disableNetConnect(); describe("OpenFgaClient.rawRequest", () => { let fgaClient: OpenFgaClient; // Use regex for nock to match requests with or without explicit :443 port const basePath = /https:\/\/api\.fga\.example/; - let tokenScope: nock.Scope; beforeEach(() => { - tokenScope = nocks.tokenExchange(OPENFGA_API_TOKEN_ISSUER, "test-token", 300, 200, {},); - fgaClient = new OpenFgaClient({ ...baseConfig }); + // Use CredentialsMethod.None to bypass OAuth token exchange + fgaClient = new OpenFgaClient({ ...baseConfig, credentials: { method: CredentialsMethod.None } }); }); afterEach(() => { @@ -231,37 +228,16 @@ describe("OpenFgaClient.rawRequest", () => { ).rejects.toThrow(FgaApiValidationError); }); }); - - describe("authentication", () => { - it("should include authentication headers", async () => { - nock(basePath, { - reqheaders: { - Authorization: "Bearer test-token", - }, - }) - .get("/stores") - .reply(200, {}); - - await fgaClient.rawRequest({ - method: "GET", - path: "/stores", - }); - - // If we get here without error, the auth header was correctly applied - expect(true).toBe(true); - }); - }); }); describe("OpenFgaClient.rawRequest - path parameters", () => { let fgaClient: OpenFgaClient; // Use regex for nock to match requests with or without explicit :443 port const basePath = /https:\/\/api\.fga\.example/; - let tokenScope: nock.Scope; beforeEach(() => { - tokenScope = nocks.tokenExchange(OPENFGA_API_TOKEN_ISSUER, "test-token", 300, 200, {}); - fgaClient = new OpenFgaClient({ ...baseConfig }); + // Use CredentialsMethod.None to bypass OAuth token exchange + fgaClient = new OpenFgaClient({ ...baseConfig, credentials: { method: CredentialsMethod.None } }); }); afterEach(() => { From cb35e1dd57013f6463cc43c55d7af828f0e1185f Mon Sep 17 00:00:00 2001 From: detailswes <97790157+Abishek-Newar@users.noreply.github.com> Date: Thu, 15 Jan 2026 12:30:08 +0530 Subject: [PATCH 27/44] updated the path --- tests/rawRequest.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/rawRequest.test.ts b/tests/rawRequest.test.ts index 8ac73915..b32fd516 100644 --- a/tests/rawRequest.test.ts +++ b/tests/rawRequest.test.ts @@ -16,8 +16,8 @@ nock.disableNetConnect(); describe("OpenFgaClient.rawRequest", () => { let fgaClient: OpenFgaClient; - // Use regex for nock to match requests with or without explicit :443 port - const basePath = /https:\/\/api\.fga\.example/; + // Use regex for nock to match requests with or without explicit :443 port (CI environments may include the port) + const basePath = /https:\/\/api\.fga\.example(:\d+)?/; beforeEach(() => { // Use CredentialsMethod.None to bypass OAuth token exchange @@ -232,8 +232,8 @@ describe("OpenFgaClient.rawRequest", () => { describe("OpenFgaClient.rawRequest - path parameters", () => { let fgaClient: OpenFgaClient; - // Use regex for nock to match requests with or without explicit :443 port - const basePath = /https:\/\/api\.fga\.example/; + // Use regex for nock to match requests with or without explicit :443 port (CI environments may include the port) + const basePath = /https:\/\/api\.fga\.example(:\d+)?/; beforeEach(() => { // Use CredentialsMethod.None to bypass OAuth token exchange From 68257d5f42249ca89110d1bdd4fcec5a1bf9efd1 Mon Sep 17 00:00:00 2001 From: detailswes <97790157+Abishek-Newar@users.noreply.github.com> Date: Thu, 15 Jan 2026 12:36:38 +0530 Subject: [PATCH 28/44] changed to default configuration instead of url --- tests/rawRequest.test.ts | 42 +++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/tests/rawRequest.test.ts b/tests/rawRequest.test.ts index b32fd516..e6e934c7 100644 --- a/tests/rawRequest.test.ts +++ b/tests/rawRequest.test.ts @@ -4,24 +4,27 @@ import { OpenFgaClient, FgaApiNotFoundError, FgaApiValidationError, - CredentialsMethod, } from "../index"; import { baseConfig, defaultConfiguration, + OPENFGA_API_TOKEN_ISSUER, OPENFGA_STORE_ID, } from "./helpers/default-config"; +import { getNocks } from "./helpers/nocks"; +const nocks = getNocks(nock); nock.disableNetConnect(); describe("OpenFgaClient.rawRequest", () => { let fgaClient: OpenFgaClient; - // Use regex for nock to match requests with or without explicit :443 port (CI environments may include the port) - const basePath = /https:\/\/api\.fga\.example(:\d+)?/; + // Use defaultConfiguration.getBasePath() like other tests + const basePath = defaultConfiguration.getBasePath(); + let tokenScope: nock.Scope; beforeEach(() => { - // Use CredentialsMethod.None to bypass OAuth token exchange - fgaClient = new OpenFgaClient({ ...baseConfig, credentials: { method: CredentialsMethod.None } }); + tokenScope = nocks.tokenExchange(OPENFGA_API_TOKEN_ISSUER, "test-token", 300, 200, {},); + fgaClient = new OpenFgaClient({ ...baseConfig }); }); afterEach(() => { @@ -228,16 +231,37 @@ describe("OpenFgaClient.rawRequest", () => { ).rejects.toThrow(FgaApiValidationError); }); }); + + describe("authentication", () => { + it("should include authentication headers", async () => { + nock(basePath, { + reqheaders: { + Authorization: "Bearer test-token", + }, + }) + .get("/stores") + .reply(200, {}); + + await fgaClient.rawRequest({ + method: "GET", + path: "/stores", + }); + + // If we get here without error, the auth header was correctly applied + expect(true).toBe(true); + }); + }); }); describe("OpenFgaClient.rawRequest - path parameters", () => { let fgaClient: OpenFgaClient; - // Use regex for nock to match requests with or without explicit :443 port (CI environments may include the port) - const basePath = /https:\/\/api\.fga\.example(:\d+)?/; + // Use defaultConfiguration.getBasePath() like other tests + const basePath = defaultConfiguration.getBasePath(); + let tokenScope: nock.Scope; beforeEach(() => { - // Use CredentialsMethod.None to bypass OAuth token exchange - fgaClient = new OpenFgaClient({ ...baseConfig, credentials: { method: CredentialsMethod.None } }); + tokenScope = nocks.tokenExchange(OPENFGA_API_TOKEN_ISSUER, "test-token", 300, 200, {}); + fgaClient = new OpenFgaClient({ ...baseConfig }); }); afterEach(() => { From 0a053a07e6c503b20a579ba1849fee4bb1733d40 Mon Sep 17 00:00:00 2001 From: detailswes <97790157+Abishek-Newar@users.noreply.github.com> Date: Thu, 15 Jan 2026 12:49:45 +0530 Subject: [PATCH 29/44] modified the rawrequests --- tests/rawRequest.test.ts | 35 +++++------------------------------ 1 file changed, 5 insertions(+), 30 deletions(-) diff --git a/tests/rawRequest.test.ts b/tests/rawRequest.test.ts index e6e934c7..7b13c9dd 100644 --- a/tests/rawRequest.test.ts +++ b/tests/rawRequest.test.ts @@ -4,27 +4,22 @@ import { OpenFgaClient, FgaApiNotFoundError, FgaApiValidationError, + CredentialsMethod, } from "../index"; import { baseConfig, defaultConfiguration, - OPENFGA_API_TOKEN_ISSUER, OPENFGA_STORE_ID, } from "./helpers/default-config"; -import { getNocks } from "./helpers/nocks"; - -const nocks = getNocks(nock); nock.disableNetConnect(); describe("OpenFgaClient.rawRequest", () => { let fgaClient: OpenFgaClient; // Use defaultConfiguration.getBasePath() like other tests const basePath = defaultConfiguration.getBasePath(); - let tokenScope: nock.Scope; - beforeEach(() => { - tokenScope = nocks.tokenExchange(OPENFGA_API_TOKEN_ISSUER, "test-token", 300, 200, {},); - fgaClient = new OpenFgaClient({ ...baseConfig }); + // Use CredentialsMethod.None to bypass OAuth token exchange + fgaClient = new OpenFgaClient({ ...baseConfig, credentials: { method: CredentialsMethod.None } }); }); afterEach(() => { @@ -232,36 +227,16 @@ describe("OpenFgaClient.rawRequest", () => { }); }); - describe("authentication", () => { - it("should include authentication headers", async () => { - nock(basePath, { - reqheaders: { - Authorization: "Bearer test-token", - }, - }) - .get("/stores") - .reply(200, {}); - await fgaClient.rawRequest({ - method: "GET", - path: "/stores", - }); - - // If we get here without error, the auth header was correctly applied - expect(true).toBe(true); - }); - }); }); describe("OpenFgaClient.rawRequest - path parameters", () => { let fgaClient: OpenFgaClient; // Use defaultConfiguration.getBasePath() like other tests const basePath = defaultConfiguration.getBasePath(); - let tokenScope: nock.Scope; - beforeEach(() => { - tokenScope = nocks.tokenExchange(OPENFGA_API_TOKEN_ISSUER, "test-token", 300, 200, {}); - fgaClient = new OpenFgaClient({ ...baseConfig }); + // Use CredentialsMethod.None to bypass OAuth token exchange + fgaClient = new OpenFgaClient({ ...baseConfig, credentials: { method: CredentialsMethod.None } }); }); afterEach(() => { From e651fea7e10ba16de0dcf7157ea17c537296bc58 Mon Sep 17 00:00:00 2001 From: detailswes <97790157+Abishek-Newar@users.noreply.github.com> Date: Thu, 15 Jan 2026 13:19:09 +0530 Subject: [PATCH 30/44] update sequential run test --- .github/workflows/main.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 2870f934..59891de5 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -61,7 +61,7 @@ jobs: run: npx madge --circular . --extensions ts,js - name: Run tests - run: npm test + run: npm test -- --runInBand - name: Upload coverage to Codecov uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 From 84326cb6758583367787cf08b98514ea1378ab34 Mon Sep 17 00:00:00 2001 From: detailswes <97790157+Abishek-Newar@users.noreply.github.com> Date: Thu, 15 Jan 2026 13:23:22 +0530 Subject: [PATCH 31/44] reverted main yml file --- .github/workflows/main.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 59891de5..2870f934 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -61,7 +61,7 @@ jobs: run: npx madge --circular . --extensions ts,js - name: Run tests - run: npm test -- --runInBand + run: npm test - name: Upload coverage to Codecov uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 From 73021ff13074ee134239f775f1afd47580abd783 Mon Sep 17 00:00:00 2001 From: detailswes <97790157+Abishek-Newar@users.noreply.github.com> Date: Thu, 15 Jan 2026 13:28:20 +0530 Subject: [PATCH 32/44] changes in the configruation --- tests/rawRequest.test.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/rawRequest.test.ts b/tests/rawRequest.test.ts index 7b13c9dd..de4f9d18 100644 --- a/tests/rawRequest.test.ts +++ b/tests/rawRequest.test.ts @@ -8,15 +8,13 @@ import { } from "../index"; import { baseConfig, - defaultConfiguration, OPENFGA_STORE_ID, } from "./helpers/default-config"; nock.disableNetConnect(); describe("OpenFgaClient.rawRequest", () => { let fgaClient: OpenFgaClient; - // Use defaultConfiguration.getBasePath() like other tests - const basePath = defaultConfiguration.getBasePath(); + const basePath = baseConfig.apiUrl!; beforeEach(() => { // Use CredentialsMethod.None to bypass OAuth token exchange fgaClient = new OpenFgaClient({ ...baseConfig, credentials: { method: CredentialsMethod.None } }); @@ -232,8 +230,7 @@ describe("OpenFgaClient.rawRequest", () => { describe("OpenFgaClient.rawRequest - path parameters", () => { let fgaClient: OpenFgaClient; - // Use defaultConfiguration.getBasePath() like other tests - const basePath = defaultConfiguration.getBasePath(); + const basePath = baseConfig.apiUrl!; beforeEach(() => { // Use CredentialsMethod.None to bypass OAuth token exchange fgaClient = new OpenFgaClient({ ...baseConfig, credentials: { method: CredentialsMethod.None } }); From bece098f858d7493553a60f2352f89c7d5a0f86d Mon Sep 17 00:00:00 2001 From: detailswes <97790157+Abishek-Newar@users.noreply.github.com> Date: Thu, 15 Jan 2026 13:38:35 +0530 Subject: [PATCH 33/44] changes in raw request test --- tests/helpers/index.ts | 2 +- tests/rawRequest.test.ts | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/helpers/index.ts b/tests/helpers/index.ts index 3a37f229..9eeba356 100644 --- a/tests/helpers/index.ts +++ b/tests/helpers/index.ts @@ -1,2 +1,2 @@ export { getNocks } from "./nocks"; -export { baseConfig, defaultConfiguration } from "./default-config"; +export { baseConfig, defaultConfiguration, OPENFGA_STORE_ID } from "./default-config"; diff --git a/tests/rawRequest.test.ts b/tests/rawRequest.test.ts index de4f9d18..dafd7c8b 100644 --- a/tests/rawRequest.test.ts +++ b/tests/rawRequest.test.ts @@ -9,8 +9,15 @@ import { import { baseConfig, OPENFGA_STORE_ID, -} from "./helpers/default-config"; -nock.disableNetConnect(); +} from "./helpers"; + +beforeAll(() => { + nock.disableNetConnect(); +}); + +afterAll(() => { + nock.restore(); +}); describe("OpenFgaClient.rawRequest", () => { let fgaClient: OpenFgaClient; From 24e1ecbd6be3cd6935987e72236ff1729bbd144d Mon Sep 17 00:00:00 2001 From: detailswes <97790157+Abishek-Newar@users.noreply.github.com> Date: Thu, 15 Jan 2026 13:49:12 +0530 Subject: [PATCH 34/44] reqwrite of rawrequest acc to headers --- tests/rawRequest.test.ts | 78 ++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 40 deletions(-) diff --git a/tests/rawRequest.test.ts b/tests/rawRequest.test.ts index dafd7c8b..7878b016 100644 --- a/tests/rawRequest.test.ts +++ b/tests/rawRequest.test.ts @@ -2,29 +2,24 @@ import * as nock from "nock"; import { OpenFgaClient, + UserClientConfigurationParams, FgaApiNotFoundError, FgaApiValidationError, - CredentialsMethod, } from "../index"; -import { - baseConfig, - OPENFGA_STORE_ID, -} from "./helpers"; - -beforeAll(() => { - nock.disableNetConnect(); -}); +import { CredentialsMethod } from "../credentials"; +import { baseConfig, OPENFGA_STORE_ID } from "./helpers/default-config"; -afterAll(() => { - nock.restore(); -}); +nock.disableNetConnect(); describe("OpenFgaClient.rawRequest", () => { + const testConfig: UserClientConfigurationParams = { + ...baseConfig, + credentials: { method: CredentialsMethod.None } + }; let fgaClient: OpenFgaClient; - const basePath = baseConfig.apiUrl!; + beforeEach(() => { - // Use CredentialsMethod.None to bypass OAuth token exchange - fgaClient = new OpenFgaClient({ ...baseConfig, credentials: { method: CredentialsMethod.None } }); + fgaClient = new OpenFgaClient(testConfig); }); afterEach(() => { @@ -35,7 +30,7 @@ describe("OpenFgaClient.rawRequest", () => { it("should make GET requests successfully", async () => { const responseData = { stores: [{ id: "store-1", name: "Test Store" }] }; - nock(basePath) + nock(testConfig.apiUrl!) .get("/stores") .reply(200, responseData); @@ -50,7 +45,7 @@ describe("OpenFgaClient.rawRequest", () => { it("should include query parameters in GET requests", async () => { const responseData = { stores: [] }; - nock(basePath) + nock(testConfig.apiUrl!) .get("/stores") .query({ page_size: 10, name: "test" }) .reply(200, responseData); @@ -67,7 +62,7 @@ describe("OpenFgaClient.rawRequest", () => { it("should return $response with the full Axios response", async () => { const responseData = { stores: [] }; - nock(basePath) + nock(testConfig.apiUrl!) .get("/stores") .reply(200, responseData); @@ -86,7 +81,7 @@ describe("OpenFgaClient.rawRequest", () => { const requestBody = { name: "New Store" }; const responseData = { id: "new-store-id", name: "New Store" }; - nock(basePath) + nock(testConfig.apiUrl!) .post("/stores", requestBody) .reply(201, responseData); @@ -102,7 +97,7 @@ describe("OpenFgaClient.rawRequest", () => { it("should set Content-Type header for POST requests with body", async () => { const requestBody = { foo: "bar" }; - nock(basePath, { + nock(testConfig.apiUrl!, { reqheaders: { "Content-Type": "application/json", }, @@ -125,7 +120,7 @@ describe("OpenFgaClient.rawRequest", () => { it("should make PUT requests with body", async () => { const requestBody = { name: "Updated Store" }; - nock(basePath) + nock(testConfig.apiUrl!) .put(`/stores/${OPENFGA_STORE_ID}`, requestBody) .reply(200, { success: true }); @@ -141,7 +136,7 @@ describe("OpenFgaClient.rawRequest", () => { describe("DELETE requests", () => { it("should make DELETE requests", async () => { - nock(basePath) + nock(testConfig.apiUrl!) .delete(`/stores/${OPENFGA_STORE_ID}`) .reply(204, {}); @@ -158,7 +153,7 @@ describe("OpenFgaClient.rawRequest", () => { it("should make PATCH requests with body", async () => { const requestBody = { name: "Patched Store" }; - nock(basePath) + nock(testConfig.apiUrl!) .patch(`/stores/${OPENFGA_STORE_ID}`, requestBody) .reply(200, { success: true }); @@ -174,7 +169,7 @@ describe("OpenFgaClient.rawRequest", () => { describe("custom headers", () => { it("should include custom headers in requests", async () => { - nock(basePath, { + nock(testConfig.apiUrl!, { reqheaders: { "X-Custom-Header": "custom-value", }, @@ -199,7 +194,7 @@ describe("OpenFgaClient.rawRequest", () => { describe("error handling", () => { it("should throw FgaApiNotFoundError for 404 responses", async () => { - nock(basePath) + nock(testConfig.apiUrl!) .get("/nonexistent-endpoint") .reply(404, { code: "undefined_endpoint", @@ -215,7 +210,7 @@ describe("OpenFgaClient.rawRequest", () => { }); it("should throw FgaApiValidationError for 400 responses", async () => { - nock(basePath) + nock(testConfig.apiUrl!) .post("/stores") .reply(400, { code: "validation_error", @@ -236,11 +231,14 @@ describe("OpenFgaClient.rawRequest", () => { }); describe("OpenFgaClient.rawRequest - path parameters", () => { + const testConfig: UserClientConfigurationParams = { + ...baseConfig, + credentials: { method: CredentialsMethod.None } + }; let fgaClient: OpenFgaClient; - const basePath = baseConfig.apiUrl!; + beforeEach(() => { - // Use CredentialsMethod.None to bypass OAuth token exchange - fgaClient = new OpenFgaClient({ ...baseConfig, credentials: { method: CredentialsMethod.None } }); + fgaClient = new OpenFgaClient(testConfig); }); afterEach(() => { @@ -252,7 +250,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { const storeId = "01ARZ3NDEKTSV4RRFFQ69G5FAV"; const responseData = { id: storeId, name: "Test Store" }; - nock(basePath) + nock(testConfig.apiUrl!) .get(`/stores/${storeId}`) .reply(200, responseData); @@ -270,7 +268,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { const modelId = "model-456"; const responseData = { id: modelId }; - nock(basePath) + nock(testConfig.apiUrl!) .get(`/stores/${storeId}/authorization-models/${modelId}`) .reply(200, responseData); @@ -287,7 +285,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { const storeId = "store id with spaces"; const encodedStoreId = "store%20id%20with%20spaces"; - nock(basePath) + nock(testConfig.apiUrl!) .get(`/stores/${encodedStoreId}`) .reply(200, { id: storeId }); @@ -305,7 +303,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { const encodedId = encodeURIComponent(id); // Use regex matching to handle URL-encoded special characters properly - nock(basePath) + nock(testConfig.apiUrl!) .get(new RegExp(`/items/${encodedId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`)) .reply(200, { id: id }); @@ -323,7 +321,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { const encodedName = encodeURIComponent(name); // %E7%94%A8%E6%88%B7 // Use regex matching to handle URL-encoded unicode characters properly - nock(basePath) + nock(testConfig.apiUrl!) .get(new RegExp(`/users/${encodedName}`)) .reply(200, { name: name }); @@ -339,7 +337,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { it("should ignore unused path parameters (unused_parameters_ignored)", async () => { const storeId = "123"; - nock(basePath) + nock(testConfig.apiUrl!) .get(`/stores/${storeId}`) .reply(200, { id: storeId }); @@ -358,7 +356,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { it("should replace parameter appearing multiple times in path (parameter_appears_multiple_times)", async () => { const id = "abc"; - nock(basePath) + nock(testConfig.apiUrl!) .get(`/stores/${id}/check/${id}`) .reply(200, { id: id }); @@ -372,7 +370,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { }); it("should allow empty parameter value (empty_parameter_value)", async () => { - nock(basePath) + nock(testConfig.apiUrl!) .get("/stores/") .reply(200, { id: "" }); @@ -386,7 +384,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { }); it("should work without pathParams for paths with no template (no_parameters)", async () => { - nock(basePath) + nock(testConfig.apiUrl!) .get("/stores") .reply(200, { stores: [] }); @@ -421,7 +419,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { describe("operationName for telemetry", () => { it("should accept operationName parameter", async () => { - nock(basePath) + nock(testConfig.apiUrl!) .get("/stores") .reply(200, { stores: [] }); @@ -436,7 +434,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { }); it("should work without operationName (backward compatible)", async () => { - nock(basePath) + nock(testConfig.apiUrl!) .get("/stores") .reply(200, { stores: [] }); From 21affd2f5fd71a0c7c8139645da40a013811d891 Mon Sep 17 00:00:00 2001 From: detailswes <97790157+Abishek-Newar@users.noreply.github.com> Date: Thu, 15 Jan 2026 13:55:01 +0530 Subject: [PATCH 35/44] changes in rawrequest --- tests/rawRequest.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/rawRequest.test.ts b/tests/rawRequest.test.ts index 7878b016..8d959927 100644 --- a/tests/rawRequest.test.ts +++ b/tests/rawRequest.test.ts @@ -18,7 +18,7 @@ describe("OpenFgaClient.rawRequest", () => { }; let fgaClient: OpenFgaClient; - beforeEach(() => { + beforeAll(() => { fgaClient = new OpenFgaClient(testConfig); }); @@ -237,7 +237,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { }; let fgaClient: OpenFgaClient; - beforeEach(() => { + beforeAll(() => { fgaClient = new OpenFgaClient(testConfig); }); From 80bd09f1ae7ed1e0f0e5d78ced61f43551d7a348 Mon Sep 17 00:00:00 2001 From: detailswes <97790157+Abishek-Newar@users.noreply.github.com> Date: Thu, 15 Jan 2026 13:58:43 +0530 Subject: [PATCH 36/44] Update rawRequests.ts --- tests/rawRequest.test.ts | 44 +++++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/tests/rawRequest.test.ts b/tests/rawRequest.test.ts index 8d959927..4738e844 100644 --- a/tests/rawRequest.test.ts +++ b/tests/rawRequest.test.ts @@ -16,11 +16,6 @@ describe("OpenFgaClient.rawRequest", () => { ...baseConfig, credentials: { method: CredentialsMethod.None } }; - let fgaClient: OpenFgaClient; - - beforeAll(() => { - fgaClient = new OpenFgaClient(testConfig); - }); afterEach(() => { nock.cleanAll(); @@ -28,6 +23,7 @@ describe("OpenFgaClient.rawRequest", () => { describe("GET requests", () => { it("should make GET requests successfully", async () => { + const fgaClient = new OpenFgaClient(testConfig); const responseData = { stores: [{ id: "store-1", name: "Test Store" }] }; nock(testConfig.apiUrl!) @@ -43,6 +39,7 @@ describe("OpenFgaClient.rawRequest", () => { }); it("should include query parameters in GET requests", async () => { + const fgaClient = new OpenFgaClient(testConfig); const responseData = { stores: [] }; nock(testConfig.apiUrl!) @@ -60,6 +57,7 @@ describe("OpenFgaClient.rawRequest", () => { }); it("should return $response with the full Axios response", async () => { + const fgaClient = new OpenFgaClient(testConfig); const responseData = { stores: [] }; nock(testConfig.apiUrl!) @@ -78,6 +76,7 @@ describe("OpenFgaClient.rawRequest", () => { describe("POST requests", () => { it("should make POST requests with body", async () => { + const fgaClient = new OpenFgaClient(testConfig); const requestBody = { name: "New Store" }; const responseData = { id: "new-store-id", name: "New Store" }; @@ -95,6 +94,7 @@ describe("OpenFgaClient.rawRequest", () => { }); it("should set Content-Type header for POST requests with body", async () => { + const fgaClient = new OpenFgaClient(testConfig); const requestBody = { foo: "bar" }; nock(testConfig.apiUrl!, { @@ -118,6 +118,7 @@ describe("OpenFgaClient.rawRequest", () => { describe("PUT requests", () => { it("should make PUT requests with body", async () => { + const fgaClient = new OpenFgaClient(testConfig); const requestBody = { name: "Updated Store" }; nock(testConfig.apiUrl!) @@ -136,6 +137,8 @@ describe("OpenFgaClient.rawRequest", () => { describe("DELETE requests", () => { it("should make DELETE requests", async () => { + const fgaClient = new OpenFgaClient(testConfig); + nock(testConfig.apiUrl!) .delete(`/stores/${OPENFGA_STORE_ID}`) .reply(204, {}); @@ -151,6 +154,7 @@ describe("OpenFgaClient.rawRequest", () => { describe("PATCH requests", () => { it("should make PATCH requests with body", async () => { + const fgaClient = new OpenFgaClient(testConfig); const requestBody = { name: "Patched Store" }; nock(testConfig.apiUrl!) @@ -169,6 +173,8 @@ describe("OpenFgaClient.rawRequest", () => { describe("custom headers", () => { it("should include custom headers in requests", async () => { + const fgaClient = new OpenFgaClient(testConfig); + nock(testConfig.apiUrl!, { reqheaders: { "X-Custom-Header": "custom-value", @@ -194,6 +200,8 @@ describe("OpenFgaClient.rawRequest", () => { describe("error handling", () => { it("should throw FgaApiNotFoundError for 404 responses", async () => { + const fgaClient = new OpenFgaClient(testConfig); + nock(testConfig.apiUrl!) .get("/nonexistent-endpoint") .reply(404, { @@ -210,6 +218,8 @@ describe("OpenFgaClient.rawRequest", () => { }); it("should throw FgaApiValidationError for 400 responses", async () => { + const fgaClient = new OpenFgaClient(testConfig); + nock(testConfig.apiUrl!) .post("/stores") .reply(400, { @@ -235,11 +245,6 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { ...baseConfig, credentials: { method: CredentialsMethod.None } }; - let fgaClient: OpenFgaClient; - - beforeAll(() => { - fgaClient = new OpenFgaClient(testConfig); - }); afterEach(() => { nock.cleanAll(); @@ -247,6 +252,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { describe("path parameter replacement", () => { it("should replace path parameters with values (single_parameter)", async () => { + const fgaClient = new OpenFgaClient(testConfig); const storeId = "01ARZ3NDEKTSV4RRFFQ69G5FAV"; const responseData = { id: storeId, name: "Test Store" }; @@ -264,6 +270,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { }); it("should replace multiple path parameters (multiple_parameters)", async () => { + const fgaClient = new OpenFgaClient(testConfig); const storeId = "store-123"; const modelId = "model-456"; const responseData = { id: modelId }; @@ -282,6 +289,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { }); it("should URL-encode path parameter values with spaces (parameter_with_special_characters)", async () => { + const fgaClient = new OpenFgaClient(testConfig); const storeId = "store id with spaces"; const encodedStoreId = "store%20id%20with%20spaces"; @@ -299,6 +307,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { }); it("should URL-encode path parameter values with URL-unsafe characters (parameter_with_url_unsafe_characters)", async () => { + const fgaClient = new OpenFgaClient(testConfig); const id = "test/with?special&chars"; const encodedId = encodeURIComponent(id); @@ -317,6 +326,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { }); it("should URL-encode unicode characters in path parameters (parameter_with_unicode)", async () => { + const fgaClient = new OpenFgaClient(testConfig); const name = "用户"; const encodedName = encodeURIComponent(name); // %E7%94%A8%E6%88%B7 @@ -335,6 +345,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { }); it("should ignore unused path parameters (unused_parameters_ignored)", async () => { + const fgaClient = new OpenFgaClient(testConfig); const storeId = "123"; nock(testConfig.apiUrl!) @@ -354,6 +365,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { }); it("should replace parameter appearing multiple times in path (parameter_appears_multiple_times)", async () => { + const fgaClient = new OpenFgaClient(testConfig); const id = "abc"; nock(testConfig.apiUrl!) @@ -370,6 +382,8 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { }); it("should allow empty parameter value (empty_parameter_value)", async () => { + const fgaClient = new OpenFgaClient(testConfig); + nock(testConfig.apiUrl!) .get("/stores/") .reply(200, { id: "" }); @@ -384,6 +398,8 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { }); it("should work without pathParams for paths with no template (no_parameters)", async () => { + const fgaClient = new OpenFgaClient(testConfig); + nock(testConfig.apiUrl!) .get("/stores") .reply(200, { stores: [] }); @@ -397,6 +413,8 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { }); it("should throw error for unresolved path parameters", async () => { + const fgaClient = new OpenFgaClient(testConfig); + await expect( fgaClient.rawRequest({ method: "GET", @@ -407,6 +425,8 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { }); it("should throw error when some path parameters are missing", async () => { + const fgaClient = new OpenFgaClient(testConfig); + await expect( fgaClient.rawRequest({ method: "GET", @@ -419,6 +439,8 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { describe("operationName for telemetry", () => { it("should accept operationName parameter", async () => { + const fgaClient = new OpenFgaClient(testConfig); + nock(testConfig.apiUrl!) .get("/stores") .reply(200, { stores: [] }); @@ -434,6 +456,8 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { }); it("should work without operationName (backward compatible)", async () => { + const fgaClient = new OpenFgaClient(testConfig); + nock(testConfig.apiUrl!) .get("/stores") .reply(200, { stores: [] }); From 598de4bce0c816913445d93f100818219d94c357 Mon Sep 17 00:00:00 2001 From: detailswes Date: Fri, 16 Jan 2026 16:13:05 +0530 Subject: [PATCH 37/44] Update rawRequests --- tests/rawRequest.test.ts | 56 +++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/tests/rawRequest.test.ts b/tests/rawRequest.test.ts index 4738e844..08910704 100644 --- a/tests/rawRequest.test.ts +++ b/tests/rawRequest.test.ts @@ -7,16 +7,21 @@ import { FgaApiValidationError, } from "../index"; import { CredentialsMethod } from "../credentials"; -import { baseConfig, OPENFGA_STORE_ID } from "./helpers/default-config"; +import { baseConfig, defaultConfiguration, OPENFGA_STORE_ID } from "./helpers/default-config"; nock.disableNetConnect(); describe("OpenFgaClient.rawRequest", () => { + const basePath = defaultConfiguration.getBasePath(); const testConfig: UserClientConfigurationParams = { ...baseConfig, credentials: { method: CredentialsMethod.None } }; + beforeAll(() => { + nock.cleanAll(); + }); + afterEach(() => { nock.cleanAll(); }); @@ -26,7 +31,7 @@ describe("OpenFgaClient.rawRequest", () => { const fgaClient = new OpenFgaClient(testConfig); const responseData = { stores: [{ id: "store-1", name: "Test Store" }] }; - nock(testConfig.apiUrl!) + nock(basePath) .get("/stores") .reply(200, responseData); @@ -42,7 +47,7 @@ describe("OpenFgaClient.rawRequest", () => { const fgaClient = new OpenFgaClient(testConfig); const responseData = { stores: [] }; - nock(testConfig.apiUrl!) + nock(basePath) .get("/stores") .query({ page_size: 10, name: "test" }) .reply(200, responseData); @@ -60,7 +65,7 @@ describe("OpenFgaClient.rawRequest", () => { const fgaClient = new OpenFgaClient(testConfig); const responseData = { stores: [] }; - nock(testConfig.apiUrl!) + nock(basePath) .get("/stores") .reply(200, responseData); @@ -80,7 +85,7 @@ describe("OpenFgaClient.rawRequest", () => { const requestBody = { name: "New Store" }; const responseData = { id: "new-store-id", name: "New Store" }; - nock(testConfig.apiUrl!) + nock(basePath) .post("/stores", requestBody) .reply(201, responseData); @@ -97,7 +102,7 @@ describe("OpenFgaClient.rawRequest", () => { const fgaClient = new OpenFgaClient(testConfig); const requestBody = { foo: "bar" }; - nock(testConfig.apiUrl!, { + nock(basePath, { reqheaders: { "Content-Type": "application/json", }, @@ -121,7 +126,7 @@ describe("OpenFgaClient.rawRequest", () => { const fgaClient = new OpenFgaClient(testConfig); const requestBody = { name: "Updated Store" }; - nock(testConfig.apiUrl!) + nock(basePath) .put(`/stores/${OPENFGA_STORE_ID}`, requestBody) .reply(200, { success: true }); @@ -139,7 +144,7 @@ describe("OpenFgaClient.rawRequest", () => { it("should make DELETE requests", async () => { const fgaClient = new OpenFgaClient(testConfig); - nock(testConfig.apiUrl!) + nock(basePath) .delete(`/stores/${OPENFGA_STORE_ID}`) .reply(204, {}); @@ -157,7 +162,7 @@ describe("OpenFgaClient.rawRequest", () => { const fgaClient = new OpenFgaClient(testConfig); const requestBody = { name: "Patched Store" }; - nock(testConfig.apiUrl!) + nock(basePath) .patch(`/stores/${OPENFGA_STORE_ID}`, requestBody) .reply(200, { success: true }); @@ -175,7 +180,7 @@ describe("OpenFgaClient.rawRequest", () => { it("should include custom headers in requests", async () => { const fgaClient = new OpenFgaClient(testConfig); - nock(testConfig.apiUrl!, { + nock(basePath, { reqheaders: { "X-Custom-Header": "custom-value", }, @@ -202,7 +207,7 @@ describe("OpenFgaClient.rawRequest", () => { it("should throw FgaApiNotFoundError for 404 responses", async () => { const fgaClient = new OpenFgaClient(testConfig); - nock(testConfig.apiUrl!) + nock(basePath) .get("/nonexistent-endpoint") .reply(404, { code: "undefined_endpoint", @@ -220,7 +225,7 @@ describe("OpenFgaClient.rawRequest", () => { it("should throw FgaApiValidationError for 400 responses", async () => { const fgaClient = new OpenFgaClient(testConfig); - nock(testConfig.apiUrl!) + nock(basePath) .post("/stores") .reply(400, { code: "validation_error", @@ -241,11 +246,16 @@ describe("OpenFgaClient.rawRequest", () => { }); describe("OpenFgaClient.rawRequest - path parameters", () => { + const basePath = defaultConfiguration.getBasePath(); const testConfig: UserClientConfigurationParams = { ...baseConfig, credentials: { method: CredentialsMethod.None } }; + beforeAll(() => { + nock.cleanAll(); + }); + afterEach(() => { nock.cleanAll(); }); @@ -256,7 +266,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { const storeId = "01ARZ3NDEKTSV4RRFFQ69G5FAV"; const responseData = { id: storeId, name: "Test Store" }; - nock(testConfig.apiUrl!) + nock(basePath) .get(`/stores/${storeId}`) .reply(200, responseData); @@ -275,7 +285,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { const modelId = "model-456"; const responseData = { id: modelId }; - nock(testConfig.apiUrl!) + nock(basePath) .get(`/stores/${storeId}/authorization-models/${modelId}`) .reply(200, responseData); @@ -293,7 +303,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { const storeId = "store id with spaces"; const encodedStoreId = "store%20id%20with%20spaces"; - nock(testConfig.apiUrl!) + nock(basePath) .get(`/stores/${encodedStoreId}`) .reply(200, { id: storeId }); @@ -312,7 +322,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { const encodedId = encodeURIComponent(id); // Use regex matching to handle URL-encoded special characters properly - nock(testConfig.apiUrl!) + nock(basePath) .get(new RegExp(`/items/${encodedId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`)) .reply(200, { id: id }); @@ -331,7 +341,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { const encodedName = encodeURIComponent(name); // %E7%94%A8%E6%88%B7 // Use regex matching to handle URL-encoded unicode characters properly - nock(testConfig.apiUrl!) + nock(basePath) .get(new RegExp(`/users/${encodedName}`)) .reply(200, { name: name }); @@ -348,7 +358,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { const fgaClient = new OpenFgaClient(testConfig); const storeId = "123"; - nock(testConfig.apiUrl!) + nock(basePath) .get(`/stores/${storeId}`) .reply(200, { id: storeId }); @@ -368,7 +378,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { const fgaClient = new OpenFgaClient(testConfig); const id = "abc"; - nock(testConfig.apiUrl!) + nock(basePath) .get(`/stores/${id}/check/${id}`) .reply(200, { id: id }); @@ -384,7 +394,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { it("should allow empty parameter value (empty_parameter_value)", async () => { const fgaClient = new OpenFgaClient(testConfig); - nock(testConfig.apiUrl!) + nock(basePath) .get("/stores/") .reply(200, { id: "" }); @@ -400,7 +410,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { it("should work without pathParams for paths with no template (no_parameters)", async () => { const fgaClient = new OpenFgaClient(testConfig); - nock(testConfig.apiUrl!) + nock(basePath) .get("/stores") .reply(200, { stores: [] }); @@ -441,7 +451,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { it("should accept operationName parameter", async () => { const fgaClient = new OpenFgaClient(testConfig); - nock(testConfig.apiUrl!) + nock(basePath) .get("/stores") .reply(200, { stores: [] }); @@ -458,7 +468,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { it("should work without operationName (backward compatible)", async () => { const fgaClient = new OpenFgaClient(testConfig); - nock(testConfig.apiUrl!) + nock(basePath) .get("/stores") .reply(200, { stores: [] }); From 200ad3e62a084fca3dae43a108195240267970b1 Mon Sep 17 00:00:00 2001 From: detailswes Date: Fri, 16 Jan 2026 16:20:09 +0530 Subject: [PATCH 38/44] update rawRequests.ts --- tests/rawRequest.test.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/rawRequest.test.ts b/tests/rawRequest.test.ts index 08910704..a15e5efe 100644 --- a/tests/rawRequest.test.ts +++ b/tests/rawRequest.test.ts @@ -9,8 +9,6 @@ import { import { CredentialsMethod } from "../credentials"; import { baseConfig, defaultConfiguration, OPENFGA_STORE_ID } from "./helpers/default-config"; -nock.disableNetConnect(); - describe("OpenFgaClient.rawRequest", () => { const basePath = defaultConfiguration.getBasePath(); const testConfig: UserClientConfigurationParams = { @@ -19,13 +17,20 @@ describe("OpenFgaClient.rawRequest", () => { }; beforeAll(() => { + nock.restore(); nock.cleanAll(); + nock.activate(); + nock.disableNetConnect(); }); afterEach(() => { nock.cleanAll(); }); + afterAll(() => { + nock.restore(); + }); + describe("GET requests", () => { it("should make GET requests successfully", async () => { const fgaClient = new OpenFgaClient(testConfig); @@ -253,13 +258,20 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { }; beforeAll(() => { + nock.restore(); nock.cleanAll(); + nock.activate(); + nock.disableNetConnect(); }); afterEach(() => { nock.cleanAll(); }); + afterAll(() => { + nock.restore(); + }); + describe("path parameter replacement", () => { it("should replace path parameters with values (single_parameter)", async () => { const fgaClient = new OpenFgaClient(testConfig); From 37ad6f59244aaef1c0a99886e04974a6be5ead86 Mon Sep 17 00:00:00 2001 From: detailswes <97790157+Abishek-Newar@users.noreply.github.com> Date: Tue, 27 Jan 2026 13:34:29 +0530 Subject: [PATCH 39/44] fix: changed the naming conventions --- README.md | 8 +-- api.ts | 46 ++++++++-------- client.ts | 22 ++++---- ...rawRequest.test.ts => apiExecutor.test.ts} | 52 +++++++++---------- 4 files changed, 64 insertions(+), 64 deletions(-) rename tests/{rawRequest.test.ts => apiExecutor.test.ts} (91%) diff --git a/README.md b/README.md index 97be835a..0cb0a37f 100644 --- a/README.md +++ b/README.md @@ -856,7 +856,7 @@ const response = await fgaClient.writeAssertions([{ ### 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 `rawRequest` method available on the `OpenFgaClient`. The `rawRequest` 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). +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 @@ -874,7 +874,7 @@ const fgaClient = new OpenFgaClient({ }); // Call a custom endpoint using path parameters -const response = await fgaClient.rawRequest({ +const response = await fgaClient.apiExecutor({ operationName: 'CustomEndpoint', // For telemetry/logging method: 'POST', path: '/stores/{store_id}/custom-endpoint', @@ -896,7 +896,7 @@ console.log('Response:', response); ```javascript // Get a list of stores with query parameters -const stores = await fgaClient.rawRequest({ +const stores = await fgaClient.apiExecutor({ method: 'GET', path: '/stores', queryParams: { @@ -913,7 +913,7 @@ console.log('Stores:', stores); 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.rawRequest({ +const response = await fgaClient.apiExecutor({ method: 'GET', path: '/stores/{store_id}/authorization-models/{model_id}', pathParams: { diff --git a/api.ts b/api.ts index 0a534e93..75bd1e57 100644 --- a/api.ts +++ b/api.ts @@ -797,19 +797,19 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio }; }, /** - * 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} [queryParams] - Optional query parameters - * @param {Record} [pathParams] - Optional path parameters to replace template variables - * @param {*} [options] Override http request option. - * @throws { FgaError } - */ - rawRequest: (method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", path: string, body?: any, queryParams?: Record, pathParams?: Record, options: any = {}): RequestArgs => { + * 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} [queryParams] - Optional query parameters + * @param {Record} [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, pathParams?: Record, options: any = {}): RequestArgs => { // Build path by replacing template parameters with URL-encoded values let localVarPath = path; if (pathParams) { @@ -1150,14 +1150,14 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials: * @param {any} [body] - Optional request body * @param {Record} [queryParams] - Optional query parameters * @param {Record} [pathParams] - Optional path parameters to replace template variables - * @param {string} [operationName] - Optional operation name for telemetry (defaults to "RawRequest") + * @param {string} [operationName] - Optional operation name for telemetry (defaults to "ApiExecutor") * @param {*} [options] Override http request option. * @throws { FgaError } */ - async rawRequest(method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", path: string, body?: any, queryParams?: Record, pathParams?: Record, operationName?: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { - const localVarAxiosArgs = localVarAxiosParamCreator.rawRequest(method, path, body, queryParams, pathParams, options); + async apiExecutor(method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", path: string, body?: any, queryParams?: Record, pathParams?: Record, operationName?: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { + const localVarAxiosArgs = localVarAxiosParamCreator.apiExecutor(method, path, body, queryParams, pathParams, options); return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, { - [TelemetryAttribute.FgaClientRequestMethod]: operationName || "RawRequest", + [TelemetryAttribute.FgaClientRequestMethod]: operationName || "ApiExecutor", }); }, }; @@ -1370,12 +1370,12 @@ export const OpenFgaApiFactory = function (configuration: Configuration, credent * @param {any} [body] - Optional request body * @param {Record} [queryParams] - Optional query parameters * @param {Record} [pathParams] - Optional path parameters to replace template variables - * @param {string} [operationName] - Optional operation name for telemetry (defaults to "RawRequest") + * @param {string} [operationName] - Optional operation name for telemetry (defaults to "ApiExecutor") * @param {*} [options] Override http request option. * @throws { FgaError } */ - rawRequest(method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", path: string, body?: any, queryParams?: Record, pathParams?: Record, operationName?: string, options?: any): PromiseResult { - return localVarFp.rawRequest(method, path, body, queryParams, pathParams, operationName, options).then((request) => request(axios)); + apiExecutor(method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", path: string, body?: any, queryParams?: Record, pathParams?: Record, operationName?: string, options?: any): PromiseResult { + return localVarFp.apiExecutor(method, path, body, queryParams, pathParams, operationName, options).then((request) => request(axios)); }, }; }; @@ -1621,13 +1621,13 @@ export class OpenFgaApi extends BaseAPI { * @param {any} [body] - Optional request body * @param {Record} [queryParams] - Optional query parameters * @param {Record} [pathParams] - Optional path parameters to replace template variables - * @param {string} [operationName] - Optional operation name for telemetry (defaults to "RawRequest") + * @param {string} [operationName] - Optional operation name for telemetry (defaults to "ApiExecutor") * @param {*} [options] Override http request option. * @throws { FgaError } * @memberof OpenFgaApi */ - public rawRequest(method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", path: string, body?: any, queryParams?: Record, pathParams?: Record, operationName?: string, options?: any): Promise> { - return OpenFgaApiFp(this.configuration, this.credentials).rawRequest(method, path, body, queryParams, pathParams, operationName, options).then((request) => request(this.axios)); + public apiExecutor(method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", path: string, body?: any, queryParams?: Record, pathParams?: Record, operationName?: string, options?: any): Promise> { + return OpenFgaApiFp(this.configuration, this.credentials).apiExecutor(method, path, body, queryParams, pathParams, operationName, options).then((request) => request(this.axios)); } } diff --git a/client.ts b/client.ts index 828740a0..6191be5c 100644 --- a/client.ts +++ b/client.ts @@ -259,18 +259,18 @@ export type ClientListRelationsRequest = Omit)[]; /** - * HTTP methods supported by rawRequest + * HTTP methods supported by apiExecutor */ export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; /** - * Request parameters for rawRequest method + * Request parameters for apiExecutor method */ -export interface ClientRawRequestParams { +export interface ClientApiExecutorParams { /** * Operation name for telemetry and logging (e.g., "CustomCheck", "CustomEndpoint"). * Used for observability when calling new or experimental endpoints. - * Defaults to "RawRequest" if not provided. + * Defaults to "ApiExecutor" if not provided. */ operationName?: string; /** HTTP method */ @@ -984,11 +984,11 @@ export class OpenFgaClient extends BaseAPI { /** - * RawRequest lets you send any HTTP request directly to an OpenFGA API endpoint. + * 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 {ClientRawRequestParams} request - The request parameters + * @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 @@ -1002,7 +1002,7 @@ export class OpenFgaClient extends BaseAPI { * * @example * // Call a new endpoint using path parameters (recommended) - * const response = await client.rawRequest({ + * const response = await client.apiExecutor({ * operationName: 'CustomCheck', * method: 'POST', * path: '/stores/{store_id}/custom-endpoint', @@ -1012,17 +1012,17 @@ export class OpenFgaClient extends BaseAPI { * * @example * // Call an existing endpoint with query parameters - * const stores = await client.rawRequest({ + * const stores = await client.apiExecutor({ * method: 'GET', * path: '/stores', * queryParams: { page_size: 10 }, * }); */ - async rawRequest( - request: ClientRawRequestParams, + async apiExecutor( + request: ClientApiExecutorParams, options: ClientRequestOpts = {} ): PromiseResult { - return this.api.rawRequest( + return this.api.apiExecutor( request.method, request.path, request.body, diff --git a/tests/rawRequest.test.ts b/tests/apiExecutor.test.ts similarity index 91% rename from tests/rawRequest.test.ts rename to tests/apiExecutor.test.ts index a15e5efe..0418f6a3 100644 --- a/tests/rawRequest.test.ts +++ b/tests/apiExecutor.test.ts @@ -9,7 +9,7 @@ import { import { CredentialsMethod } from "../credentials"; import { baseConfig, defaultConfiguration, OPENFGA_STORE_ID } from "./helpers/default-config"; -describe("OpenFgaClient.rawRequest", () => { +describe("OpenFgaClient.apiExecutor", () => { const basePath = defaultConfiguration.getBasePath(); const testConfig: UserClientConfigurationParams = { ...baseConfig, @@ -40,7 +40,7 @@ describe("OpenFgaClient.rawRequest", () => { .get("/stores") .reply(200, responseData); - const result = await fgaClient.rawRequest({ + const result = await fgaClient.apiExecutor({ method: "GET", path: "/stores", }); @@ -57,7 +57,7 @@ describe("OpenFgaClient.rawRequest", () => { .query({ page_size: 10, name: "test" }) .reply(200, responseData); - const result = await fgaClient.rawRequest({ + const result = await fgaClient.apiExecutor({ method: "GET", path: "/stores", queryParams: { page_size: 10, name: "test" }, @@ -74,7 +74,7 @@ describe("OpenFgaClient.rawRequest", () => { .get("/stores") .reply(200, responseData); - const result = await fgaClient.rawRequest({ + const result = await fgaClient.apiExecutor({ method: "GET", path: "/stores", }); @@ -94,7 +94,7 @@ describe("OpenFgaClient.rawRequest", () => { .post("/stores", requestBody) .reply(201, responseData); - const result = await fgaClient.rawRequest({ + const result = await fgaClient.apiExecutor({ method: "POST", path: "/stores", body: requestBody, @@ -115,7 +115,7 @@ describe("OpenFgaClient.rawRequest", () => { .post("/stores", requestBody) .reply(200, {}); - await fgaClient.rawRequest({ + await fgaClient.apiExecutor({ method: "POST", path: "/stores", body: requestBody, @@ -135,7 +135,7 @@ describe("OpenFgaClient.rawRequest", () => { .put(`/stores/${OPENFGA_STORE_ID}`, requestBody) .reply(200, { success: true }); - const result = await fgaClient.rawRequest({ + const result = await fgaClient.apiExecutor({ method: "PUT", path: `/stores/${OPENFGA_STORE_ID}`, body: requestBody, @@ -153,7 +153,7 @@ describe("OpenFgaClient.rawRequest", () => { .delete(`/stores/${OPENFGA_STORE_ID}`) .reply(204, {}); - const result = await fgaClient.rawRequest({ + const result = await fgaClient.apiExecutor({ method: "DELETE", path: `/stores/${OPENFGA_STORE_ID}`, }); @@ -171,7 +171,7 @@ describe("OpenFgaClient.rawRequest", () => { .patch(`/stores/${OPENFGA_STORE_ID}`, requestBody) .reply(200, { success: true }); - const result = await fgaClient.rawRequest({ + const result = await fgaClient.apiExecutor({ method: "PATCH", path: `/stores/${OPENFGA_STORE_ID}`, body: requestBody, @@ -193,7 +193,7 @@ describe("OpenFgaClient.rawRequest", () => { .get("/stores") .reply(200, {}); - await fgaClient.rawRequest( + await fgaClient.apiExecutor( { method: "GET", path: "/stores", @@ -220,7 +220,7 @@ describe("OpenFgaClient.rawRequest", () => { }); await expect( - fgaClient.rawRequest({ + fgaClient.apiExecutor({ method: "GET", path: "/nonexistent-endpoint", }) @@ -238,7 +238,7 @@ describe("OpenFgaClient.rawRequest", () => { }); await expect( - fgaClient.rawRequest({ + fgaClient.apiExecutor({ method: "POST", path: "/stores", body: { invalid: "data" }, @@ -250,7 +250,7 @@ describe("OpenFgaClient.rawRequest", () => { }); -describe("OpenFgaClient.rawRequest - path parameters", () => { +describe("OpenFgaClient.apiExecutor - path parameters", () => { const basePath = defaultConfiguration.getBasePath(); const testConfig: UserClientConfigurationParams = { ...baseConfig, @@ -282,7 +282,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { .get(`/stores/${storeId}`) .reply(200, responseData); - const result = await fgaClient.rawRequest({ + const result = await fgaClient.apiExecutor({ method: "GET", path: "/stores/{store_id}", pathParams: { store_id: storeId }, @@ -301,7 +301,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { .get(`/stores/${storeId}/authorization-models/${modelId}`) .reply(200, responseData); - const result = await fgaClient.rawRequest({ + const result = await fgaClient.apiExecutor({ method: "GET", path: "/stores/{store_id}/authorization-models/{model_id}", pathParams: { store_id: storeId, model_id: modelId }, @@ -319,7 +319,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { .get(`/stores/${encodedStoreId}`) .reply(200, { id: storeId }); - const result = await fgaClient.rawRequest({ + const result = await fgaClient.apiExecutor({ method: "GET", path: "/stores/{store_id}", pathParams: { store_id: storeId }, @@ -338,7 +338,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { .get(new RegExp(`/items/${encodedId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`)) .reply(200, { id: id }); - const result = await fgaClient.rawRequest({ + const result = await fgaClient.apiExecutor({ method: "GET", path: "/items/{id}", pathParams: { id: id }, @@ -357,7 +357,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { .get(new RegExp(`/users/${encodedName}`)) .reply(200, { name: name }); - const result = await fgaClient.rawRequest({ + const result = await fgaClient.apiExecutor({ method: "GET", path: "/users/{name}", pathParams: { name: name }, @@ -374,7 +374,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { .get(`/stores/${storeId}`) .reply(200, { id: storeId }); - const result = await fgaClient.rawRequest({ + const result = await fgaClient.apiExecutor({ method: "GET", path: "/stores/{store_id}", pathParams: { @@ -394,7 +394,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { .get(`/stores/${id}/check/${id}`) .reply(200, { id: id }); - const result = await fgaClient.rawRequest({ + const result = await fgaClient.apiExecutor({ method: "GET", path: "/stores/{id}/check/{id}", pathParams: { id: id }, @@ -410,7 +410,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { .get("/stores/") .reply(200, { id: "" }); - const result = await fgaClient.rawRequest({ + const result = await fgaClient.apiExecutor({ method: "GET", path: "/stores/{store_id}", pathParams: { store_id: "" }, @@ -426,7 +426,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { .get("/stores") .reply(200, { stores: [] }); - const result = await fgaClient.rawRequest({ + const result = await fgaClient.apiExecutor({ method: "GET", path: "/stores", }); @@ -438,7 +438,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { const fgaClient = new OpenFgaClient(testConfig); await expect( - fgaClient.rawRequest({ + fgaClient.apiExecutor({ method: "GET", path: "/stores/{store_id}/check", // pathParams intentionally omitted @@ -450,7 +450,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { const fgaClient = new OpenFgaClient(testConfig); await expect( - fgaClient.rawRequest({ + fgaClient.apiExecutor({ method: "GET", path: "/stores/{store_id}/authorization-models/{model_id}", pathParams: { store_id: "abc" }, // model_id is missing @@ -468,7 +468,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { .reply(200, { stores: [] }); // Should complete without error when operationName is provided - const result = await fgaClient.rawRequest({ + const result = await fgaClient.apiExecutor({ operationName: "CustomListStores", method: "GET", path: "/stores", @@ -484,7 +484,7 @@ describe("OpenFgaClient.rawRequest - path parameters", () => { .get("/stores") .reply(200, { stores: [] }); - const result = await fgaClient.rawRequest({ + const result = await fgaClient.apiExecutor({ method: "GET", path: "/stores", }); From 38a3a328d8f13e2022588488335c9bc1242e43ce Mon Sep 17 00:00:00 2001 From: Abishek Newar <97790157+Abishek-Newar@users.noreply.github.com> Date: Wed, 11 Feb 2026 08:23:26 +0530 Subject: [PATCH 40/44] undo the chanegs in nocks and chages in index.ts --- tests/helpers/index.ts | 2 +- tests/helpers/nocks.ts | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/helpers/index.ts b/tests/helpers/index.ts index 9eeba356..3a37f229 100644 --- a/tests/helpers/index.ts +++ b/tests/helpers/index.ts @@ -1,2 +1,2 @@ export { getNocks } from "./nocks"; -export { baseConfig, defaultConfiguration, OPENFGA_STORE_ID } from "./default-config"; +export { baseConfig, defaultConfiguration } from "./default-config"; diff --git a/tests/helpers/nocks.ts b/tests/helpers/nocks.ts index f75245b1..11c4e986 100644 --- a/tests/helpers/nocks.ts +++ b/tests/helpers/nocks.ts @@ -37,7 +37,7 @@ export const getNocks = ((nock: typeof Nock) => ({ statusCode = 200, headers = {}, ) => { - return nock(`https://${apiTokenIssuer}`, { reqheaders: { "Content-Type": "application/x-www-form-urlencoded"} }) + return nock(`https://${apiTokenIssuer}`, { reqheaders: { "Content-Type": "application/x-www-form-urlencoded" } }) .post("/oauth/token") .reply(statusCode, { access_token: accessToken, @@ -144,8 +144,8 @@ export const getNocks = ((nock: typeof Nock) => ({ .query({ page_size: pageSize, continuation_token: contToken, - ...(type ? { type } : { }), - ...(startTime ? {start_time: startTime } :{}) + ...(type ? { type } : {}), + ...(startTime ? { start_time: startTime } : {}) }) .reply(200, { changes: [{ @@ -164,10 +164,10 @@ export const getNocks = ((nock: typeof Nock) => ({ storeId: string, tuple: TupleKey, basePath = defaultConfiguration.getBasePath(), - consistency: ConsistencyPreference|undefined = undefined, + consistency: ConsistencyPreference | undefined = undefined, ) => { return nock(basePath) - .post(`/stores/${storeId}/read`, (body: ReadRequest) => + .post(`/stores/${storeId}/read`, (body: ReadRequest) => body.consistency === consistency ) .reply(200, { tuples: [], continuation_token: "" } as ReadResponse); @@ -197,7 +197,7 @@ export const getNocks = ((nock: typeof Nock) => ({ basePath = defaultConfiguration.getBasePath(), response: { allowed: boolean } | { code: string, message: string } = { allowed: true }, statusCode = 200, - consistency: ConsistencyPreference|undefined = undefined, + consistency: ConsistencyPreference | undefined = undefined, ) => { return nock(basePath) .post(`/stores/${storeId}/check`, (body: CheckRequest) => @@ -212,7 +212,7 @@ export const getNocks = ((nock: typeof Nock) => ({ storeId: string, responseBody: BatchCheckResponse, basePath = defaultConfiguration.getBasePath(), - consistency: ConsistencyPreference|undefined | undefined, + consistency: ConsistencyPreference | undefined | undefined, authorizationModelId = "auth-model-id", ) => { return nock(basePath) @@ -226,10 +226,10 @@ export const getNocks = ((nock: typeof Nock) => ({ storeId: string, tuple: TupleKey, basePath = defaultConfiguration.getBasePath(), - consistency: ConsistencyPreference|undefined = undefined, + consistency: ConsistencyPreference | undefined = undefined, ) => { return nock(basePath) - .post(`/stores/${storeId}/expand`, (body: ExpandRequest) => + .post(`/stores/${storeId}/expand`, (body: ExpandRequest) => body.consistency === consistency ) .reply(200, { tree: {} } as ExpandResponse); @@ -238,7 +238,7 @@ export const getNocks = ((nock: typeof Nock) => ({ storeId: string, responseBody: ListObjectsResponse, basePath = defaultConfiguration.getBasePath(), - consistency: ConsistencyPreference|undefined = undefined, + consistency: ConsistencyPreference | undefined = undefined, ) => { return nock(basePath) .post(`/stores/${storeId}/list-objects`, (body: ListUsersRequest) => @@ -266,10 +266,10 @@ export const getNocks = ((nock: typeof Nock) => ({ storeId: string, responseBody: ListUsersResponse, basePath = defaultConfiguration.getBasePath(), - consistency: ConsistencyPreference|undefined = undefined + consistency: ConsistencyPreference | undefined = undefined ) => { return nock(basePath) - .post(`/stores/${storeId}/list-users`, (body: ListUsersRequest) => + .post(`/stores/${storeId}/list-users`, (body: ListUsersRequest) => body.consistency === consistency ) .reply(200, responseBody); From 659e88a0c46368113fc79fc6608be7c2877f999b Mon Sep 17 00:00:00 2001 From: Abishek Newar <97790157+Abishek-Newar@users.noreply.github.com> Date: Wed, 11 Feb 2026 18:59:40 +0530 Subject: [PATCH 41/44] removed white spaces from nocks --- tests/helpers/nocks.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/helpers/nocks.ts b/tests/helpers/nocks.ts index 11c4e986..0a6d0444 100644 --- a/tests/helpers/nocks.ts +++ b/tests/helpers/nocks.ts @@ -37,7 +37,7 @@ export const getNocks = ((nock: typeof Nock) => ({ statusCode = 200, headers = {}, ) => { - return nock(`https://${apiTokenIssuer}`, { reqheaders: { "Content-Type": "application/x-www-form-urlencoded" } }) + return nock(`https://${apiTokenIssuer}`, { reqheaders: { "Content-Type": "application/x-www-form-urlencoded"} }) .post("/oauth/token") .reply(statusCode, { access_token: accessToken, @@ -144,8 +144,8 @@ export const getNocks = ((nock: typeof Nock) => ({ .query({ page_size: pageSize, continuation_token: contToken, - ...(type ? { type } : {}), - ...(startTime ? { start_time: startTime } : {}) + ...(type ? { type } : { }), + ...(startTime ? { start_time: startTime } :{}) }) .reply(200, { changes: [{ @@ -164,10 +164,10 @@ export const getNocks = ((nock: typeof Nock) => ({ storeId: string, tuple: TupleKey, basePath = defaultConfiguration.getBasePath(), - consistency: ConsistencyPreference | undefined = undefined, + consistency: ConsistencyPreference|undefined = undefined, ) => { return nock(basePath) - .post(`/stores/${storeId}/read`, (body: ReadRequest) => + .post(`/stores/${storeId}/read`, (body: ReadRequest) => body.consistency === consistency ) .reply(200, { tuples: [], continuation_token: "" } as ReadResponse); @@ -197,7 +197,7 @@ export const getNocks = ((nock: typeof Nock) => ({ basePath = defaultConfiguration.getBasePath(), response: { allowed: boolean } | { code: string, message: string } = { allowed: true }, statusCode = 200, - consistency: ConsistencyPreference | undefined = undefined, + consistency: ConsistencyPreference|undefined = undefined, ) => { return nock(basePath) .post(`/stores/${storeId}/check`, (body: CheckRequest) => @@ -212,7 +212,7 @@ export const getNocks = ((nock: typeof Nock) => ({ storeId: string, responseBody: BatchCheckResponse, basePath = defaultConfiguration.getBasePath(), - consistency: ConsistencyPreference | undefined | undefined, + consistency: ConsistencyPreference|undefined | undefined, authorizationModelId = "auth-model-id", ) => { return nock(basePath) @@ -226,10 +226,10 @@ export const getNocks = ((nock: typeof Nock) => ({ storeId: string, tuple: TupleKey, basePath = defaultConfiguration.getBasePath(), - consistency: ConsistencyPreference | undefined = undefined, + consistency: ConsistencyPreference|undefined = undefined, ) => { return nock(basePath) - .post(`/stores/${storeId}/expand`, (body: ExpandRequest) => + .post(`/stores/${storeId}/expand`, (body: ExpandRequest) => body.consistency === consistency ) .reply(200, { tree: {} } as ExpandResponse); @@ -238,7 +238,7 @@ export const getNocks = ((nock: typeof Nock) => ({ storeId: string, responseBody: ListObjectsResponse, basePath = defaultConfiguration.getBasePath(), - consistency: ConsistencyPreference | undefined = undefined, + consistency: ConsistencyPreference|undefined = undefined, ) => { return nock(basePath) .post(`/stores/${storeId}/list-objects`, (body: ListUsersRequest) => @@ -266,10 +266,10 @@ export const getNocks = ((nock: typeof Nock) => ({ storeId: string, responseBody: ListUsersResponse, basePath = defaultConfiguration.getBasePath(), - consistency: ConsistencyPreference | undefined = undefined + consistency: ConsistencyPreference|undefined = undefined ) => { return nock(basePath) - .post(`/stores/${storeId}/list-users`, (body: ListUsersRequest) => + .post(`/stores/${storeId}/list-users`, (body: ListUsersRequest) => body.consistency === consistency ) .reply(200, responseBody); @@ -287,4 +287,4 @@ export const getNocks = ((nock: typeof Nock) => ({ .put(`/stores/${storeId}/assertions/${modelId}`) .reply(responseStatus); }, -})); \ No newline at end of file + })); \ No newline at end of file From 29ec04a8c064f710963c28f6c7907f3b2a6dd19a Mon Sep 17 00:00:00 2001 From: Abishek Newar Date: Wed, 11 Feb 2026 23:11:42 +0530 Subject: [PATCH 42/44] updated teh api.ts --- api.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api.ts b/api.ts index 47807711..a138f8b2 100644 --- a/api.ts +++ b/api.ts @@ -28,6 +28,7 @@ import { import { Configuration } from "./configuration"; import { Credentials } from "./credentials"; import { assertParamExists } from "./validation"; +import { FgaValidationError } from "./errors"; import { AbortedMessageResponse, @@ -863,7 +864,7 @@ export const OpenFgaApiAxiosParamCreator = function (configuration: Configuratio if (localVarPath.includes("{") && localVarPath.includes("}")) { const unresolvedMatch = localVarPath.match(/\{([^}]+)\}/); if (unresolvedMatch) { - throw new Error(`Path parameter '${unresolvedMatch[1]}' was not provided for path: ${path}`); + throw new FgaValidationError(unresolvedMatch[1], `Path parameter '${unresolvedMatch[1]}' was not provided for path: ${path}`); } } From 2e18269a0c1fd6e8178b69f01b9e38592434239e Mon Sep 17 00:00:00 2001 From: Abishek Newar Date: Wed, 11 Feb 2026 23:14:45 +0530 Subject: [PATCH 43/44] reverted nocks.ts --- tests/helpers/nocks.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/helpers/nocks.ts b/tests/helpers/nocks.ts index 0a6d0444..f75245b1 100644 --- a/tests/helpers/nocks.ts +++ b/tests/helpers/nocks.ts @@ -145,7 +145,7 @@ export const getNocks = ((nock: typeof Nock) => ({ page_size: pageSize, continuation_token: contToken, ...(type ? { type } : { }), - ...(startTime ? { start_time: startTime } :{}) + ...(startTime ? {start_time: startTime } :{}) }) .reply(200, { changes: [{ @@ -287,4 +287,4 @@ export const getNocks = ((nock: typeof Nock) => ({ .put(`/stores/${storeId}/assertions/${modelId}`) .reply(responseStatus); }, - })); \ No newline at end of file +})); \ No newline at end of file From 6210bcc8515784ec1939c15864e5a541a8ad0bc3 Mon Sep 17 00:00:00 2001 From: Anurag Bandyopadhyay Date: Tue, 24 Feb 2026 08:15:43 +0530 Subject: [PATCH 44/44] fix: whitespace change --- tests/helpers/nocks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/helpers/nocks.ts b/tests/helpers/nocks.ts index f75245b1..11e73a15 100644 --- a/tests/helpers/nocks.ts +++ b/tests/helpers/nocks.ts @@ -287,4 +287,4 @@ export const getNocks = ((nock: typeof Nock) => ({ .put(`/stores/${storeId}/assertions/${modelId}`) .reply(responseStatus); }, -})); \ No newline at end of file +}));