diff --git a/README.md b/README.md index 17025cdc..c51d5a87 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,16 @@ yarn add @openfga/sdk ### Supported Runtimes +The SDK ships with both CommonJS and ES Module builds, and should work out of the box in Node.js, Deno, Cloudflare Workers, and modern bundlers. + +```javascript +// CommonJS +const { OpenFgaClient } = require("@openfga/sdk"); + +// ES Modules +import { OpenFgaClient } from "@openfga/sdk"; +``` + For details on the supported Node.js versions and our support policy, see [SUPPORTED_RUNTIMES.md](./SUPPORTED_RUNTIMES.md). ## Getting Started @@ -102,7 +112,7 @@ We strongly recommend you initialize the `OpenFgaClient` only once and then re-u #### No Credentials ```javascript -const { OpenFgaClient } = require('@openfga/sdk'); // OR import { OpenFgaClient } from '@openfga/sdk'; +import { OpenFgaClient } from '@openfga/sdk'; const fgaClient = new OpenFgaClient({ apiUrl: process.env.FGA_API_URL, // required @@ -114,7 +124,7 @@ const fgaClient = new OpenFgaClient({ #### API Token ```javascript -const { OpenFgaClient, CredentialsMethod } = require('@openfga/sdk'); // OR import { OpenFgaClient, CredentialsMethod } from '@openfga/sdk'; +import { OpenFgaClient, CredentialsMethod } from '@openfga/sdk'; const fgaClient = new OpenFgaClient({ apiUrl: process.env.FGA_API_URL, // required @@ -132,7 +142,7 @@ const fgaClient = new OpenFgaClient({ #### Client Credentials ```javascript -const { OpenFgaClient, CredentialsMethod } = require('@openfga/sdk'); // OR import { OpenFgaClient, CredentialsMethod } from '@openfga/sdk'; +import { OpenFgaClient, CredentialsMethod } from '@openfga/sdk'; const fgaClient = new OpenFgaClient({ apiUrl: process.env.FGA_API_URL, // required @@ -157,7 +167,7 @@ const fgaClient = new OpenFgaClient({ You can set default headers that will be sent with every request during client initialization: ```javascript -const { OpenFgaClient } = require('@openfga/sdk'); // OR import { OpenFgaClient } from '@openfga/sdk'; +import { OpenFgaClient } from '@openfga/sdk'; const fgaClient = new OpenFgaClient({ apiUrl: process.env.FGA_API_URL, @@ -195,7 +205,7 @@ const result = await fgaClient.check({ The SDK uses the native `fetch` API by default. You can provide a custom `HttpClient` to control the underlying HTTP behavior. ```javascript -const { OpenFgaClient } = require('@openfga/sdk'); // OR import { OpenFgaClient } from '@openfga/sdk'; +import { OpenFgaClient } from '@openfga/sdk'; const fgaClient = new OpenFgaClient( { @@ -954,7 +964,7 @@ This is useful when: #### Example: Calling a Custom Endpoint with POST ```javascript -const { OpenFgaClient } = require('@openfga/sdk'); +import { OpenFgaClient } from "@openfga/sdk"; const fgaClient = new OpenFgaClient({ apiUrl: process.env.FGA_API_URL, @@ -1056,7 +1066,7 @@ To customize this behavior, create an object with `maxRetry` and `minWaitInMs` p Apply your custom retry values by setting to `retryParams` on the configuration object passed to the `OpenFgaClient` call. ```javascript -const { OpenFgaClient } = require('@openfga/sdk'); // OR import { OpenFgaClient } from '@openfga/sdk'; +import { OpenFgaClient } from '@openfga/sdk'; const fgaClient = new OpenFgaClient({ apiUrl: process.env.FGA_API_URL, // required diff --git a/api.ts b/api.ts index 85bc26ea..8d0abc96 100644 --- a/api.ts +++ b/api.ts @@ -11,7 +11,7 @@ */ -import { BaseAPI } from "./base"; +import { BaseAPI } from "./base.js"; import { DUMMY_BASE_URL, setSearchParams, @@ -28,12 +28,12 @@ import { RequestBuilderParams, RequestBuilderOptions, RequestBuilder, -} from "./common"; +} from "./common.js"; export type { HttpMethod, RequestBuilderParams, RequestBuilderOptions }; -import { Configuration } from "./configuration"; -import { Credentials } from "./credentials"; -import { assertParamExists } from "./validation"; +import { Configuration } from "./configuration.js"; +import { Credentials } from "./credentials/index.js"; +import { assertParamExists } from "./validation.js"; import { AbortedMessageResponse, @@ -122,8 +122,8 @@ import { WriteRequest, WriteRequestDeletes, WriteRequestWrites, -} from "./apiModel"; -import { TelemetryAttribute, TelemetryAttributes } from "./telemetry/attributes"; +} from "./apiModel.js"; +import { TelemetryAttribute, TelemetryAttributes } from "./telemetry/attributes.js"; /** diff --git a/base.ts b/base.ts index ed6525d6..25ec5a5a 100644 --- a/base.ts +++ b/base.ts @@ -1,6 +1,6 @@ -import { Configuration, UserConfigurationParams } from "./configuration"; -import { HttpClient } from "./common"; -import { Credentials } from "./credentials"; +import { Configuration, UserConfigurationParams } from "./configuration.js"; +import { HttpClient } from "./common.js"; +import { Credentials } from "./credentials/index.js"; const DEFAULT_CONNECTION_TIMEOUT_IN_MS = 10000; diff --git a/client.ts b/client.ts index 1629126b..3d1e87bf 100644 --- a/client.ts +++ b/client.ts @@ -1,7 +1,7 @@ -import asyncPool = require("tiny-async-pool"); +import { asyncPool } from "./utils/utils-lite/async-pool.js"; -import { OpenFgaApi, HttpMethod, RequestBuilderParams, RequestBuilderOptions } from "./api"; -import type { FgaResponse, HttpClient } from "./common"; +import { OpenFgaApi, HttpMethod, RequestBuilderParams, RequestBuilderOptions } from "./api.js"; +import type { FgaResponse, HttpClient } from "./common.js"; export type { HttpMethod, RequestBuilderParams, RequestBuilderOptions }; import { Assertion, @@ -40,19 +40,19 @@ import { WriteRequest, WriteRequestWritesOnDuplicate, WriteRequestDeletesOnMissing, -} from "./apiModel"; -import { BaseAPI } from "./base"; -import { CallResult, PromiseResult } from "./common"; -import { Configuration, RetryParams, UserConfigurationParams } from "./configuration"; -import { FgaApiAuthenticationError, FgaRequiredParamError, FgaValidationError } from "./errors"; +} from "./apiModel.js"; +import { BaseAPI } from "./base.js"; +import { CallResult, PromiseResult } from "./common.js"; +import { Configuration, RetryParams, UserConfigurationParams } from "./configuration.js"; +import { FgaApiAuthenticationError, FgaRequiredParamError, FgaValidationError } from "./errors.js"; import { chunkArray, generateRandomIdWithNonUniqueFallback, setHeaderIfNotSet, -} from "./utils"; -import { isWellFormedUlidString } from "./validation"; -import SdkConstants from "./constants"; -import { parseNDJSONStream } from "./streaming"; +} from "./utils/index.js"; +import { isWellFormedUlidString } from "./validation.js"; +import SdkConstants from "./constants/index.js"; +import { parseNDJSONStream } from "./streaming.js"; export type UserClientConfigurationParams = UserConfigurationParams & { storeId?: string; diff --git a/common.ts b/common.ts index bed8217c..5cbb6c31 100644 --- a/common.ts +++ b/common.ts @@ -1,6 +1,6 @@ -import { Configuration, RetryParams } from "./configuration"; -import SdkConstants from "./constants"; -import type { Credentials } from "./credentials"; +import { Configuration, RetryParams } from "./configuration.js"; +import SdkConstants from "./constants/index.js"; +import type { Credentials } from "./credentials/index.js"; import { FgaApiError, FgaApiInternalError, @@ -11,11 +11,11 @@ import { FgaError, FgaValidationError, HttpErrorContext, -} from "./errors"; -import { setNotEnumerableProperty } from "./utils"; -import { TelemetryAttribute, TelemetryAttributes } from "./telemetry/attributes"; -import { TelemetryConfiguration } from "./telemetry/configuration"; -import { TelemetryHistograms } from "./telemetry/histograms"; +} from "./errors.js"; +import { setNotEnumerableProperty } from "./utils/index.js"; +import { TelemetryAttribute, TelemetryAttributes } from "./telemetry/attributes.js"; +import { TelemetryConfiguration } from "./telemetry/configuration.js"; +import { TelemetryHistograms } from "./telemetry/histograms.js"; /** * diff --git a/configuration.ts b/configuration.ts index 9e61a8e4..6b9d9295 100644 --- a/configuration.ts +++ b/configuration.ts @@ -1,8 +1,8 @@ -import { ApiTokenConfig, AuthCredentialsConfig, ClientCredentialsConfig, CredentialsMethod } from "./credentials/types"; -import { FgaValidationError, } from "./errors"; -import { assertParamExists, isWellFormedUriString } from "./validation"; -import { TelemetryConfig, TelemetryConfiguration } from "./telemetry/configuration"; -import SdkConstants from "./constants"; +import { ApiTokenConfig, AuthCredentialsConfig, ClientCredentialsConfig, CredentialsMethod } from "./credentials/types.js"; +import { FgaValidationError, } from "./errors.js"; +import { assertParamExists, isWellFormedUriString } from "./validation.js"; +import { TelemetryConfig, TelemetryConfiguration } from "./telemetry/configuration.js"; +import SdkConstants from "./constants/index.js"; // default maximum number of retry const DEFAULT_MAX_RETRY = SdkConstants.DefaultMaxRetry; diff --git a/credentials/credentials.ts b/credentials/credentials.ts index 1d2ef250..3bd15d06 100644 --- a/credentials/credentials.ts +++ b/credentials/credentials.ts @@ -13,15 +13,15 @@ import * as jose from "jose"; -import { assertParamExists, isWellFormedUriString } from "../validation"; -import { FgaApiAuthenticationError, FgaApiError, FgaValidationError } from "../errors"; -import { attemptHttpRequest, HttpClient } from "../common"; -import { AuthCredentialsConfig, PrivateKeyJWTConfig, ClientCredentialsConfig, ClientSecretConfig, CredentialsMethod } from "./types"; -import { TelemetryAttributes } from "../telemetry/attributes"; -import { TelemetryCounters } from "../telemetry/counters"; -import { TelemetryConfiguration } from "../telemetry/configuration"; +import { assertParamExists, isWellFormedUriString } from "../validation.js"; +import { FgaApiAuthenticationError, FgaApiError, FgaValidationError } from "../errors.js"; +import { attemptHttpRequest, HttpClient } from "../common.js"; +import { AuthCredentialsConfig, PrivateKeyJWTConfig, ClientCredentialsConfig, ClientSecretConfig, CredentialsMethod } from "./types.js"; +import { TelemetryAttributes } from "../telemetry/attributes.js"; +import { TelemetryCounters } from "../telemetry/counters.js"; +import { TelemetryConfiguration } from "../telemetry/configuration.js"; import { randomUUID } from "crypto"; -import SdkConstants from "../constants"; +import SdkConstants from "../constants/index.js"; interface ClientSecretRequest { client_id: string; diff --git a/credentials/index.ts b/credentials/index.ts index b3fce532..af6c912c 100644 --- a/credentials/index.ts +++ b/credentials/index.ts @@ -11,5 +11,5 @@ */ -export * from "./credentials"; -export * from "./types"; +export * from "./credentials.js"; +export * from "./types.js"; diff --git a/errors.ts b/errors.ts index 1951fb89..78f315d2 100644 --- a/errors.ts +++ b/errors.ts @@ -2,7 +2,21 @@ import { ErrorCode, InternalErrorCode, NotFoundErrorCode, -} from "./apiModel"; +} from "./apiModel.js"; + +/** + * Context extracted from a failed HTTP request/response, + * used to construct SDK error classes without coupling to any HTTP library. + */ +export interface HttpErrorContext { + status?: number; + statusText?: string; + headers?: Record; + data?: any; + requestUrl?: string; + requestMethod?: string; + requestData?: any; +} /** * Context extracted from a failed HTTP request/response, diff --git a/index.ts b/index.ts index aa9eee6f..991963f0 100644 --- a/index.ts +++ b/index.ts @@ -11,17 +11,17 @@ */ -export * from "./api"; -export * from "./client"; -export * from "./apiModel"; -export { Configuration, UserConfigurationParams, GetDefaultRetryParams } from "./configuration"; -export { Credentials, CredentialsMethod } from "./credentials"; -export * from "./telemetry/attributes"; -export * from "./telemetry/configuration"; -export * from "./telemetry/counters"; -export * from "./telemetry/histograms"; -export * from "./telemetry/metrics"; -export * from "./errors"; -export { FgaResponse, HttpClient, CallResult, PromiseResult } from "./common"; -export { parseNDJSONStream } from "./streaming"; +export * from "./api.js"; +export * from "./client.js"; +export * from "./apiModel.js"; +export { Configuration, UserConfigurationParams, GetDefaultRetryParams } from "./configuration.js"; +export { Credentials, CredentialsMethod } from "./credentials/index.js"; +export * from "./telemetry/attributes.js"; +export * from "./telemetry/configuration.js"; +export * from "./telemetry/counters.js"; +export * from "./telemetry/histograms.js"; +export * from "./telemetry/metrics.js"; +export * from "./errors.js"; +export { FgaResponse, HttpClient, CallResult, PromiseResult } from "./common.js"; +export { parseNDJSONStream } from "./streaming.js"; diff --git a/package-lock.json b/package-lock.json index f370ba93..8011cd13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,13 +10,11 @@ "license": "Apache-2.0", "dependencies": { "@opentelemetry/api": "^1.9.1", - "jose": "5.10.0", - "tiny-async-pool": "^2.1.0" + "jose": "5.10.0" }, "devDependencies": { "@types/jest": "30.0.0", "@types/node": "^25.6.0", - "@types/tiny-async-pool": "^2.0.3", "@typescript-eslint/eslint-plugin": "^8.59.2", "@typescript-eslint/parser": "^8.59.2", "eslint": "^10.3.0", @@ -1479,13 +1477,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/tiny-async-pool": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/tiny-async-pool/-/tiny-async-pool-2.0.3.tgz", - "integrity": "sha512-n3l1s538tKo9RBoHs4I3DG/VmD3VYhF5mHcgu1sU4Lq7JCNBtxnpBy3OkWSbZsp5r5QOuplh2UkXXXwufoAuNQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/yargs": { "version": "17.0.35", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", @@ -2228,9 +2219,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.10.40", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.40.tgz", - "integrity": "sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.4.tgz", + "integrity": "sha512-s4+sLr9mZ/CyqeRritFeYV/Zx73OAtmaHn6kkBS1XRoJn1hrg3xIDUcpicAEX68tkcIN0iBCgti31C8zxtkhsQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2241,22 +2232,22 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", + "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/browserslist": { - "version": "4.28.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", - "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==", + "version": "4.28.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", + "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==", "dev": true, "funding": [ { @@ -2274,10 +2265,10 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.38", - "caniuse-lite": "^1.0.30001799", - "electron-to-chromium": "^1.5.376", - "node-releases": "^2.0.48", + "baseline-browser-mapping": "^2.10.44", + "caniuse-lite": "^1.0.30001806", + "electron-to-chromium": "^1.5.393", + "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, "bin": { @@ -2338,9 +2329,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001799", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", - "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", "dev": true, "funding": [ { @@ -2524,13 +2515,6 @@ "dev": true, "license": "MIT" }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -2621,9 +2605,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.380", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.380.tgz", - "integrity": "sha512-W6d5AbuEoRayO447cqrg6lKJIlscgRnnxOZl/08kfV71BQDoEBC7Wwis68z87LjyK6f4kWyTaubuDbhHKrZkbA==", + "version": "1.5.396", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.396.tgz", + "integrity": "sha512-yHiw2Y3C3H9U6TMbOfoWK/BPreiOPXRfTWPBwQBoZG6/8TB6eOPnsy5oaRYuatR7Fw2SJ4kKforgufeo7fq0EQ==", "dev": true, "license": "ISC" }, @@ -3126,23 +3110,6 @@ "node": ">=10.13.0" } }, - "node_modules/glob/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/glob/node_modules/minimatch": { "version": "9.0.9", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", @@ -4389,9 +4356,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.50", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz", - "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==", + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", "dev": true, "license": "MIT", "engines": { @@ -5136,24 +5103,6 @@ "node": ">=8" } }, - "node_modules/test-exclude/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", - "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/test-exclude/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -5189,12 +5138,6 @@ "node": "*" } }, - "node_modules/tiny-async-pool": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tiny-async-pool/-/tiny-async-pool-2.1.0.tgz", - "integrity": "sha512-ltAHPh/9k0STRQqaoUX52NH4ZQYAJz24ZAEwf1Zm+HYg3l9OXTWeqWKyYsHu40wF/F0rxd2N2bk5sLvX2qlSvg==", - "license": "MIT" - }, "node_modules/tinyglobby": { "version": "0.2.16", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", diff --git a/package.json b/package.json index 1b432792..da91b47e 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,22 @@ "zanzibar" ], "license": "Apache-2.0", - "main": "./dist/index.js", + "main": "./dist/cjs/index.js", + "module": "./dist/esm/index.js", "typings": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/esm/index.js", + "require": "./dist/cjs/index.js", + "default": "./dist/cjs/index.js" + } + }, "scripts": { - "build": "tsc --outDir dist/", + "build": "npm run build:cjs && npm run build:esm && npm run build:post", + "build:cjs": "tsc --project tsconfig.json", + "build:esm": "tsc --project tsconfig.esm.json", + "build:post": "echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json && echo '{\"type\":\"module\"}' > dist/esm/package.json", "prepublishOnly": "rm -rf dist/ && npm run build", "test": "jest --config ./tests/jest.config.js", "lint": "eslint .", @@ -23,13 +35,11 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.1", - "jose": "5.10.0", - "tiny-async-pool": "^2.1.0" + "jose": "5.10.0" }, "devDependencies": { "@types/jest": "30.0.0", "@types/node": "^25.6.0", - "@types/tiny-async-pool": "^2.0.3", "@typescript-eslint/eslint-plugin": "^8.59.2", "@typescript-eslint/parser": "^8.59.2", "eslint": "^10.3.0", @@ -40,7 +50,9 @@ "typescript": "^6.0.3" }, "overrides": { - "js-yaml": "^4.2.0" + "js-yaml": "^4.2.0", + "brace-expansion": "^5.0.8", + "@babel/core": "^7.29.7" }, "files": [ "CHANGELOG.md", diff --git a/telemetry/configuration.ts b/telemetry/configuration.ts index ba9e770d..5839b4d6 100644 --- a/telemetry/configuration.ts +++ b/telemetry/configuration.ts @@ -11,9 +11,9 @@ */ -import { FgaValidationError } from "../errors"; -import { TelemetryAttribute } from "./attributes"; -import { TelemetryMetric, MetricRecorder } from "./metrics"; +import { FgaValidationError } from "../errors.js"; +import { TelemetryAttribute } from "./attributes.js"; +import { TelemetryMetric, MetricRecorder } from "./metrics.js"; /** * Configuration for a telemetry metric. diff --git a/telemetry/metrics.ts b/telemetry/metrics.ts index 44e6303e..635ce75f 100644 --- a/telemetry/metrics.ts +++ b/telemetry/metrics.ts @@ -12,10 +12,10 @@ import { Counter, Histogram, Meter } from "@opentelemetry/api"; -import { TelemetryCounter } from "./counters"; -import { TelemetryHistogram } from "./histograms"; +import { TelemetryCounter } from "./counters.js"; +import { TelemetryHistogram } from "./histograms.js"; import { metrics } from "@opentelemetry/api"; -import SdkConstants from "../constants"; +import SdkConstants from "../constants/index.js"; export enum TelemetryMetric { CounterCredentialsRequest = "counterCredentialsRequest", diff --git a/tests/apiExecutor.test.ts b/tests/apiExecutor.test.ts index ee175c2b..592f711f 100644 --- a/tests/apiExecutor.test.ts +++ b/tests/apiExecutor.test.ts @@ -5,9 +5,9 @@ import { UserClientConfigurationParams, FgaApiNotFoundError, FgaApiValidationError, -} from "../index"; -import { CredentialsMethod } from "../credentials"; -import { baseConfig, defaultConfiguration, OPENFGA_STORE_ID } from "./helpers/default-config"; +} from "../index.js"; +import { CredentialsMethod } from "../credentials/index.js"; +import { baseConfig, defaultConfiguration, OPENFGA_STORE_ID } from "./helpers/default-config.js"; describe("OpenFgaClient.executeApiRequest", () => { const basePath = defaultConfiguration.getBasePath(); diff --git a/tests/client.test.ts b/tests/client.test.ts index a03b3dd1..66205e9a 100644 --- a/tests/client.test.ts +++ b/tests/client.test.ts @@ -13,8 +13,8 @@ import { ErrorCode, ClientWriteRequestOnDuplicateWrites, ClientWriteRequestOnMissingDeletes, -} from "../index"; -import { baseConfig, defaultConfiguration, getNocks } from "./helpers"; +} from "../index.js"; +import { baseConfig, defaultConfiguration, getNocks } from "./helpers/index.js"; const nocks = getNocks(nock); diff --git a/tests/credentials.test.ts b/tests/credentials.test.ts index fcc2beff..4c880bd3 100644 --- a/tests/credentials.test.ts +++ b/tests/credentials.test.ts @@ -1,16 +1,16 @@ import nock from "nock"; import * as jose from "jose"; -import { Credentials, CredentialsMethod, DEFAULT_TOKEN_ENDPOINT_PATH } from "../credentials"; -import { AuthCredentialsConfig } from "../credentials/types"; -import { TelemetryConfiguration } from "../telemetry/configuration"; -import SdkConstants from "../constants"; +import { Credentials, CredentialsMethod, DEFAULT_TOKEN_ENDPOINT_PATH } from "../credentials/index.js"; +import { AuthCredentialsConfig } from "../credentials/types.js"; +import { TelemetryConfiguration } from "../telemetry/configuration.js"; +import SdkConstants from "../constants/index.js"; import { OPENFGA_API_AUDIENCE, OPENFGA_CLIENT_ASSERTION_SIGNING_KEY, OPENFGA_CLIENT_ID, OPENFGA_CLIENT_SECRET, -} from "./helpers/default-config"; -import { FgaApiAuthenticationError, FgaValidationError } from "../errors"; +} from "./helpers/default-config.js"; +import { FgaApiAuthenticationError, FgaValidationError } from "../errors.js"; describe("Credentials", () => { const mockTelemetryConfig: TelemetryConfiguration = new TelemetryConfiguration({}); diff --git a/tests/errors-authentication.test.ts b/tests/errors-authentication.test.ts index 22243fb7..ea042ff5 100644 --- a/tests/errors-authentication.test.ts +++ b/tests/errors-authentication.test.ts @@ -1,4 +1,4 @@ -import { FgaApiAuthenticationError, FgaApiError, HttpErrorContext } from "../errors"; +import { FgaApiAuthenticationError, FgaApiError, HttpErrorContext } from "../errors.js"; describe("errors.ts", () => { describe("FgaApiAuthenticationError", () => { diff --git a/tests/errors.test.ts b/tests/errors.test.ts index 2285a99d..364d2b3b 100644 --- a/tests/errors.test.ts +++ b/tests/errors.test.ts @@ -1,5 +1,5 @@ -import { FgaError, FgaApiError, HttpErrorContext } from "../errors"; -import SdkConstants from "../constants"; +import { FgaError, FgaApiError, HttpErrorContext } from "../errors.js"; +import SdkConstants from "../constants/index.js"; describe("errors.ts", () => { describe("FgaError", () => { diff --git a/tests/fetch-http-client.test.ts b/tests/fetch-http-client.test.ts index ea41d88e..312dbc29 100644 --- a/tests/fetch-http-client.test.ts +++ b/tests/fetch-http-client.test.ts @@ -1,5 +1,5 @@ -import { attemptHttpRequest, HttpClient, FgaResponse } from "../common"; -import SdkConstants from "../constants"; +import { attemptHttpRequest, HttpClient, FgaResponse } from "../common.js"; +import SdkConstants from "../constants/index.js"; import { FgaError, FgaApiValidationError, @@ -8,8 +8,8 @@ import { FgaApiRateLimitExceededError, FgaApiInternalError, FgaApiError, -} from "../errors"; -import { OpenFgaClient } from "../client"; +} from "../errors.js"; +import { OpenFgaClient } from "../client.js"; function mockHttpClient( fetchImpl: (url: string | URL | Request, init?: RequestInit) => Promise, diff --git a/tests/headers.test.ts b/tests/headers.test.ts index 95dae203..6715d215 100644 --- a/tests/headers.test.ts +++ b/tests/headers.test.ts @@ -1,7 +1,7 @@ import nock from "nock"; -import { OpenFgaClient, UserClientConfigurationParams } from "../index"; -import { baseConfig } from "./helpers/default-config"; -import { CredentialsMethod } from "../credentials"; +import { OpenFgaClient, UserClientConfigurationParams } from "../index.js"; +import { baseConfig } from "./helpers/default-config.js"; +import { CredentialsMethod } from "../credentials/index.js"; nock.disableNetConnect(); diff --git a/tests/helpers/default-config.ts b/tests/helpers/default-config.ts index 1dd2bebd..b10853ed 100644 --- a/tests/helpers/default-config.ts +++ b/tests/helpers/default-config.ts @@ -1,5 +1,5 @@ -import { ClientConfiguration, UserClientConfigurationParams } from "../../client"; -import { CredentialsMethod } from "../../credentials"; +import { ClientConfiguration, UserClientConfigurationParams } from "../../client.js"; +import { CredentialsMethod } from "../../credentials/index.js"; export const OPENFGA_STORE_ID = "01H0H015178Y2V4CX10C2KGHF4"; export const OPENFGA_MODEL_ID = "01HWBBMZTT7F1M97DVXQK4Z7J3"; diff --git a/tests/helpers/index.ts b/tests/helpers/index.ts index 3a37f229..9efdae12 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 { getNocks } from "./nocks.js"; +export { baseConfig, defaultConfiguration } from "./default-config.js"; diff --git a/tests/helpers/nocks.ts b/tests/helpers/nocks.ts index 3da81711..e60d9ef0 100644 --- a/tests/helpers/nocks.ts +++ b/tests/helpers/nocks.ts @@ -27,8 +27,8 @@ import { TupleKey, TupleOperation, WriteAuthorizationModelRequest, -} from "../../index"; -import { defaultConfiguration } from "./default-config"; +} from "../../index.js"; +import { defaultConfiguration } from "./default-config.js"; export const getNocks = ((nock: Nock) => ({ tokenExchange: ( diff --git a/tests/index.test.ts b/tests/index.test.ts index b242a048..569ebfec 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -14,17 +14,17 @@ import { FgaError, OpenFgaApi, TelemetryAttribute, -} from "../index"; -import { CallResult } from "../common"; -import { GetDefaultRetryParams } from "../configuration"; -import { AuthCredentialsConfig } from "../credentials"; +} from "../index.js"; +import { CallResult } from "../common.js"; +import { GetDefaultRetryParams } from "../configuration.js"; +import { AuthCredentialsConfig } from "../credentials/index.js"; import { baseConfig, defaultConfiguration, OPENFGA_API_TOKEN_ISSUER, OPENFGA_CLIENT_ASSERTION_SIGNING_KEY, -} from "./helpers/default-config"; -import { getNocks } from "./helpers/nocks"; +} from "./helpers/default-config.js"; +import { getNocks } from "./helpers/nocks.js"; const nocks = getNocks(nock); nock.disableNetConnect(); diff --git a/tests/jest.config.js b/tests/jest.config.js index a6f6aa57..f4e3c126 100644 --- a/tests/jest.config.js +++ b/tests/jest.config.js @@ -6,6 +6,9 @@ module.exports = { "^.+\\.ts$": ["ts-jest", { tsconfig: "tests/tsconfig.spec.json" }], }, moduleFileExtensions: ["js", "d.ts", "ts", "json"], + moduleNameMapper: { + "^(\\.{1,2}/.*)\\.js$": "$1", + }, setupFilesAfterEnv: ["./tests/setup.ts"], collectCoverage: true, coverageReporters: ["text", "cobertura", "lcov"], diff --git a/tests/streaming.test.ts b/tests/streaming.test.ts index aaa39092..fc4b62f0 100644 --- a/tests/streaming.test.ts +++ b/tests/streaming.test.ts @@ -13,7 +13,7 @@ import { Readable } from "node:stream"; import { EventEmitter } from "node:events"; -import { parseNDJSONStream } from "../streaming"; +import { parseNDJSONStream } from "../streaming.js"; describe("Streaming Utilities", () => { describe("parseNDJSONStream (Node.js)", () => { diff --git a/tests/telemetry/attributes.test.ts b/tests/telemetry/attributes.test.ts index b0df321f..2e10828c 100644 --- a/tests/telemetry/attributes.test.ts +++ b/tests/telemetry/attributes.test.ts @@ -1,4 +1,4 @@ -import { TelemetryAttribute, TelemetryAttributes } from "../../telemetry/attributes"; +import { TelemetryAttribute, TelemetryAttributes } from "../../telemetry/attributes.js"; describe("TelemetryAttributes", () => { diff --git a/tests/telemetry/configuration.test.ts b/tests/telemetry/configuration.test.ts index 4beccb8c..b16a996d 100644 --- a/tests/telemetry/configuration.test.ts +++ b/tests/telemetry/configuration.test.ts @@ -1,6 +1,6 @@ -import { TelemetryConfiguration, TelemetryMetricConfig } from "../../telemetry/configuration"; -import { TelemetryAttribute } from "../../telemetry/attributes"; -import { TelemetryMetric } from "../../telemetry/metrics"; +import { TelemetryConfiguration, TelemetryMetricConfig } from "../../telemetry/configuration.js"; +import { TelemetryAttribute } from "../../telemetry/attributes.js"; +import { TelemetryMetric } from "../../telemetry/metrics.js"; describe("TelemetryConfiguration", () => { test("should use defaults if not all metrics defined", () => { diff --git a/tests/telemetry/counters.test.ts b/tests/telemetry/counters.test.ts index 64ddb575..bf4cbbe0 100644 --- a/tests/telemetry/counters.test.ts +++ b/tests/telemetry/counters.test.ts @@ -1,4 +1,4 @@ -import { TelemetryCounters } from "../../telemetry/counters"; +import { TelemetryCounters } from "../../telemetry/counters.js"; describe("TelemetryCounters", () => { test("should have correct counter details", () => { diff --git a/tests/telemetry/histograms.test.ts b/tests/telemetry/histograms.test.ts index 390ecf06..697d539a 100644 --- a/tests/telemetry/histograms.test.ts +++ b/tests/telemetry/histograms.test.ts @@ -1,4 +1,4 @@ -import { TelemetryHistograms } from "../../telemetry/histograms"; +import { TelemetryHistograms } from "../../telemetry/histograms.js"; describe("TelemetryHistograms", () => { test("should have correct histogram details for request duration", () => { diff --git a/tests/telemetry/metrics.test.ts b/tests/telemetry/metrics.test.ts index 5fca7dc9..ade3ae0a 100644 --- a/tests/telemetry/metrics.test.ts +++ b/tests/telemetry/metrics.test.ts @@ -1,8 +1,8 @@ -import { MetricRecorder } from "../../telemetry/metrics"; -import { TelemetryCounters } from "../../telemetry/counters"; -import { TelemetryHistograms } from "../../telemetry/histograms"; -import { TelemetryAttributes } from "../../telemetry/attributes"; -import SdkConstants from "../../constants"; +import { MetricRecorder } from "../../telemetry/metrics.js"; +import { TelemetryCounters } from "../../telemetry/counters.js"; +import { TelemetryHistograms } from "../../telemetry/histograms.js"; +import { TelemetryAttributes } from "../../telemetry/attributes.js"; +import SdkConstants from "../../constants/index.js"; import { metrics } from "@opentelemetry/api"; jest.mock("@opentelemetry/api", () => ({ diff --git a/tests/validation.test.ts b/tests/validation.test.ts index 96e3ddc2..6ac149a0 100644 --- a/tests/validation.test.ts +++ b/tests/validation.test.ts @@ -1,4 +1,4 @@ -import { isWellFormedUlidString } from "../validation"; +import { isWellFormedUlidString } from "../validation.js"; describe("validation.ts", () => { describe("isWellFormedUlidString", () => { diff --git a/tsconfig.esm.json b/tsconfig.esm.json new file mode 100644 index 00000000..ea3840e4 --- /dev/null +++ b/tsconfig.esm.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "es2020", + "moduleResolution": "bundler", + "outDir": "dist/esm", + "declaration": false, + "declarationDir": null + } +} diff --git a/tsconfig.json b/tsconfig.json index 193774f6..e41593e6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,8 @@ "module": "commonjs", "noImplicitAny": true, "strict": true, - "outDir": "dist", + "declarationDir": "dist", + "outDir": "dist/cjs", "rootDir": ".", "typeRoots": [ "node_modules/@types" diff --git a/utils/generate-random-id.ts b/utils/generate-random-id.ts index ba88ec0a..64e37d51 100644 --- a/utils/generate-random-id.ts +++ b/utils/generate-random-id.ts @@ -3,9 +3,10 @@ import { randomUUID, randomBytes } from "crypto"; /** * Generates a random ID * - * Note: May not return a valid value on older browsers - we're fine with this for now + * Note: May not return a secure random value. + * We're fine with this, as this is just used to identify requests. */ -export function generateRandomId(): string | undefined { +export function generateRandomId(): string { if (typeof randomUUID === "function") { return randomUUID(); } @@ -15,10 +16,15 @@ export function generateRandomId(): string | undefined { return randomBytes(20).toString("hex"); } - // For older browsers - return; + // Fallback for older browsers and runtimes without crypto support + const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let result = ""; + for (let i = 0; i < 20; i++) { + result += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return result; } export function generateRandomIdWithNonUniqueFallback(): string { - return generateRandomId() || "00000000-0000-0000-0000-000000000000"; + return generateRandomId(); } diff --git a/utils/index.ts b/utils/index.ts index 441a9a1a..df039048 100644 --- a/utils/index.ts +++ b/utils/index.ts @@ -1,5 +1,2 @@ -export * from "./assert-never"; -export * from "./chunk-array"; -export * from "./generate-random-id"; -export * from "./set-header-if-not-set"; -export * from "./set-not-enumerable-property"; +export * from "./utils-lite/index.js"; +export * from "./generate-random-id.js"; diff --git a/utils/assert-never.ts b/utils/utils-lite/assert-never.ts similarity index 100% rename from utils/assert-never.ts rename to utils/utils-lite/assert-never.ts diff --git a/utils/utils-lite/async-pool.ts b/utils/utils-lite/async-pool.ts new file mode 100644 index 00000000..df28ab35 --- /dev/null +++ b/utils/utils-lite/async-pool.ts @@ -0,0 +1,69 @@ +/** + * Async concurrency pool. + * + * Based on tiny-async-pool by Rafael Xavier de Souza, licensed under MIT. + * https://github.com/rxaviers/async-pool + * + * Original license from https://github.com/rxaviers/async-pool/blob/master/LICENSE-MIT: + * + * Copyright (c) 2017 Rafael Xavier de Souza http://rafael.xavier.blog.br + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * Runs async functions in a limited concurrency pool. + * Yields results as they complete, in order of input. + * Rejects immediately if any promise rejects. + * + * @param concurrency - Maximum number of concurrent executions (>= 1) + * @param iterable - Input items to process + * @param iteratorFn - Async function to apply to each item + */ +export async function* asyncPool( + concurrency: number, + iterable: Iterable, + iteratorFn: (item: IN) => Promise, +): AsyncGenerator { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const executing = new Set>(); + + async function consume(): Promise { + const [promise, value] = await Promise.race(executing); + executing.delete(promise); + return value; + } + + for (const item of iterable) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let promise: Promise; + promise = (async () => iteratorFn(item))() + .then((value): [Promise, OUT] => [promise, value]); + executing.add(promise); + if (executing.size >= concurrency) { + yield await consume(); + } + } + while (executing.size) { + yield await consume(); + } +} diff --git a/utils/chunk-array.ts b/utils/utils-lite/chunk-array.ts similarity index 100% rename from utils/chunk-array.ts rename to utils/utils-lite/chunk-array.ts diff --git a/utils/utils-lite/index.ts b/utils/utils-lite/index.ts new file mode 100644 index 00000000..668a40d9 --- /dev/null +++ b/utils/utils-lite/index.ts @@ -0,0 +1,5 @@ +export * from "./assert-never.js"; +export * from "./async-pool.js"; +export * from "./chunk-array.js"; +export * from "./set-header-if-not-set.js"; +export * from "./set-not-enumerable-property.js"; diff --git a/utils/set-header-if-not-set.ts b/utils/utils-lite/set-header-if-not-set.ts similarity index 100% rename from utils/set-header-if-not-set.ts rename to utils/utils-lite/set-header-if-not-set.ts diff --git a/utils/set-not-enumerable-property.ts b/utils/utils-lite/set-not-enumerable-property.ts similarity index 100% rename from utils/set-not-enumerable-property.ts rename to utils/utils-lite/set-not-enumerable-property.ts diff --git a/validation.ts b/validation.ts index 33d75b21..91ca4535 100644 --- a/validation.ts +++ b/validation.ts @@ -1,4 +1,4 @@ -import { FgaRequiredParamError } from "./errors"; +import { FgaRequiredParamError } from "./errors.js"; /** *