@frontegg/e10s-client

中文文档
4.0.0发布于 2026/11/05ISCTypeScriptCommonJS近 30 天下载 1.2万

原始使用文档
文档目录16 节
  1. Entitlements NodeJS Client
  2. Table of Contents
  3. Installation
  4. Prerequisite
  5. Usage
  6. Initialize the client
  7. Configuration Options
  8. Multiple Frontegg instances
  9. Setting up the Subject Context
  10. Query
  11. Examples
  12. Lookup Operations
  13. Lookup Entitlements
  14. Lookup Target Entities
  15. Lookup Entities
  16. Monitoring

Frontegg Logo

Entitlements NodeJS Client

Table of Contents

Installation

To install the package using npm, run the following

$ npm install @frontegg/e10s-client

Prerequisite

The Entitlements Client interacts with Frontegg’s ReBAC authorization engine to evaluate permissions and query access relationships.

Look for instructions here

Usage

Initialize the client

import { EntitlementsClientFactory, RequestContextType } from '@frontegg/e10s-client';

const e10sClient = EntitlementsClientFactory.create({
    engineEndpoint: 'localhost:50051',
    engineToken: 'your-engine-token'
});

Configuration Options

import { EntitlementsClientFactory } from '@frontegg/e10s-client';

const e10sClient = EntitlementsClientFactory.create({
    engineEndpoint: 'localhost:50051',
    engineToken: 'your-engine-token',
    logging: {
        client: customLoggingClient, // Optional: custom logging client
        logResults: true // Optional: log all query results
    },
    fallbackConfiguration: {
        // Optional: fallback behavior on errors
        defaultFallback: false
    }
});

Multiple Frontegg instances

One SpiceDB can serve several Frontegg instances. Each instance is a vendor, and the SDK namespaces every read to that instance's schema prefix, derived from its vendorId (v_ + the vendorId with - replaced by _). Configure the instances once and pick one per call with instanceId:

const e10sClient = EntitlementsClientFactory.create({
    engineEndpoint: 'localhost:50051',
    engineToken: 'your-engine-token',
    instances: [
        { instanceId: 'eu', vendorId: '2f9c1a44-7b0e-4a1e-9f8a-1c2d3e4f5a6b' },
        { instanceId: 'us', vendorId: '8b1d0e77-3c5a-4f2b-9d6e-7a8b9c0d1e2f' }
    ],
    defaultInstanceId: 'eu'
});

await e10sClient.isEntitledTo(subjectContext, requestContext, { instanceId: 'us' });
  • instanceId is optional on every call. With one instance configured, or with defaultInstanceId set, it can be omitted; otherwise the call throws InstanceIdRequiredException. An unknown id throws UnknownInstanceException. Both extend InstanceResolutionException and are thrown rather than answered with the fallback.
  • The prefix always comes from the vendorId and cannot be set directly. A vendorId may contain only a-z, 0-9 and -, be at most 61 characters, and not end with -; anything else, including uppercase letters and _, throws ConfigurationInputIsInvalidException when the client is created. An instanceId must be 1 to 63 characters of a-z, 0-9 and -, starting with a letter or digit, and legacy is reserved; anything else throws ConfigurationInputIsInvalidException when the client is created.
  • Each instance may carry its own fallbackConfiguration; the client-wide one applies otherwise.
  • With instances configured, object types passed to the SDK must not contain /, because the SDK applies the prefix itself. Such a type throws InvalidObjectTypeException from isEntitledTo, lookupTargetEntities and lookupEntities instead of being answered with the fallback. In isEntitledToMany only that item fails: it comes back as { result: false, error: '<reason>' } while the other items are still answered, so treat any result !== true as denied.
  • With no instances configured, every read is unprefixed and valid object types are passed through unchanged, so a type such as acme/document keeps working, and so does a plain name of your own such as v_user. This is a breaking change for existing callers in two cases, both of which now throw InvalidObjectTypeException instead of being answered with the fallback: an object type that is not a valid SpiceDB name (see the next point), which used to reach SpiceDB and come back as the configured fallback, and a namespaced type whose prefix starts with the reserved vendor prefix v_, such as v_acme/document, so a legacy client can never address a vendor's namespace on a shared SpiceDB.
  • In both modes an object type must be a valid SpiceDB name: 3 to 64 characters, starting with a-z, containing only a-z, 0-9 and _, and ending with a-z or 0-9. In legacy mode each prefix before a / follows the same rules but is at most 63 characters. Anything else, such as Document, my-entity or a, throws InvalidObjectTypeException instead of reaching SpiceDB as a parse error.
  • Log payloads carry the resolved instanceId (legacy when no instances are configured). LoggingClient.log and LoggingClient.error receive it as an optional trailing { instanceId } argument, so existing implementations keep working.

Reading an instance schema

const schema = await e10sClient.readSchemaFor({ instanceId: 'us' });

readSchemaFor returns only that instance's definition and caveat blocks, with its schema prefix stripped from their names and type references. It is a read-only view: top-level directives are dropped, so it cannot be written back. If the schema cannot be split into blocks safely it throws SchemaParseException rather than risk returning another instance's definitions. With no instances configured it returns only the unprefixed definition and caveat blocks, unchanged, and never another vendor's prefixed blocks.

SpiceDB has no partial schema read, so every call reads the whole cluster schema and filters it here. Treat readSchemaFor as an administrative call and keep it off the request path.

Setting up the Subject Context

Subject context describes the user which performs the action, these can be taken from Frontegg JWT if authenticating with Frontegg

const subjectContext: SubjectContext = {
    tenantId: 'my-tenant-id',
    userId: 'my-user-id', // Optional
    permissions: ['read', 'write'], // Optional
    attributes: { 'my-custom-attribute': 'some-value' } // Optional
};

Query

The Entitlements client allows you to query for a feature, permission or a route entitlement, each requires different context information.

Query for Feature

const e10sResult = await e10sClient.isEntitledTo(subjectContext, {
    type: RequestContextType.Feature,
    featureKey: 'my-cool-feature'
});

if (!e10sResult.result) {
    console.log(`User is not entitled to "my-cool-feature" feature`);
}

Query for Permission

const e10sResult = await e10sClient.isEntitledTo(subjectContext, {
    type: RequestContextType.Permission,
    permissionKey: 'read'
});

if (!e10sResult.result) {
    console.log(`User is not entitled to "read" permission`);
}

Query for Route

const e10sResult = await e10sClient.isEntitledTo(subjectContext, {
    type: RequestContextType.Route,
    method: 'GET',
    path: '/users'
});

if (!e10sResult.result) {
    console.log(`User is not entitled to "GET /users" route`);
}

Query for FGA (Fine-Grained Authorization)

const e10sResult = await e10sClient.isEntitledTo(
    {
        entityType: 'user',
        key: '[email protected]'
    },
    {
        type: RequestContextType.Entity,
        entityType: 'document',
        key: 'README.md',
        action: 'read'
    }
);

if (!e10sResult.result) {
    console.log(`User is not allowed to read document`);
}

Query for FGA with Time-Based Access (active_at caveat)

For relationships that use the active_at caveat to control time-based access, you can specify the at parameter to evaluate access at a specific point in time.

const e10sResult = await e10sClient.isEntitledTo(
    {
        entityType: 'user',
        key: '[email protected]'
    },
    {
        type: RequestContextType.Entity,
        entityType: 'document',
        key: 'README.md',
        action: 'read',
        at: '2026-01-15T12:00:00Z'
    }
);

if (!e10sResult.result) {
    console.log(`User is not allowed to read document at the specified time`);
}

The at parameter accepts ISO 8601 format strings:

  • UTC format: 2025-12-31T23:59:59Z
  • Timezone offset: 2025-12-31T23:59:59+02:00

If at not provided, it defaults to the current UTC time.

Note: The at parameter is also supported in Lookup Operations with the same format and behavior.

Examples

Runnable demos live in examples/. They use a separate package.json and are not installed when you run yarn at the repo root.

cd examples
yarn install   # pulls @frontegg/e10s-client@latest from npm

See examples/Readme.md for Docker/SpiceDB setup and demo scripts.

Lookup Operations

The client provides lookup operations that query the ReBAC authorization model to discover access relationships between entities.

FGA lookup operations support the optional at parameter for time-based access control (see Time-Based Access).

Lookup Entitlements

Find all entitlement feature keys granted to a tenant or user.

This lookup returns effective feature entitlements for the requested subject:

  • When only tenantId is provided, the response contains feature grants available to that tenant.
  • When both tenantId and userId are provided, the response contains the user's effective feature grants: tenant-inherited grants plus user-direct grants.
  • If userId is provided but the user is not a member of the tenant, the response is empty.
  • If the same feature key is granted through both the tenant and the user, it is returned once in the page response.

Use a tenant-only subject when you need tenant-level entitlements. Use a subject with userId when you need the feature set that should apply to a specific user.

Pagination note: For user lookups, tenant and user grants are looked up and paginated independently. A feature key that is reachable through both streams may appear on different pages, so callers that aggregate multiple pages should deduplicate feature keys across pages if needed.

const response = await e10sClient.lookupEntitlements({
    subject: {
        tenantId: 'tenant-123',
        userId: 'user-456', // Optional: include for effective user entitlements
        attributes: { plan: 'pro' } // Optional: evaluated by targeting rules
    },
    criteria: {
        type: RequestContextType.Feature
    },
    limit: 100, // Optional: default 50
    cursor: undefined // Optional: pagination cursor
});

console.log(`Found ${response.totalReturned} entitlement features`);

response.entitlements.forEach((entitlement) => {
    console.log(`${entitlement.type}:${entitlement.key}`);
    // entitlement.permissionship: 'HAS_PERMISSION' | 'CONDITIONAL_PERMISSION' | 'NO_PERMISSION'
});

if (response.cursor) {
    const nextPage = await e10sClient.lookupEntitlements({
        // ... same params
        cursor: response.cursor
    });
}

Lookup Target Entities

Find all TargetEntity instances (i.e. documents) of a given type that an entity (i.e user) is entitled to perform a specific action on.

const response = await e10sClient.lookupTargetEntities({
    entityType: 'user',
    entityId: 'user-123',
    TargetEntityType: 'document',
    action: 'read',
    limit: 100, // Optional: limit number of results (default: 50, max: 1000)
    cursor: undefined, // Optional: pagination cursor
    at: '2026-01-15T12:00:00Z' // Optional: ISO 8601 timestamp for active_at caveat
});

console.log(`Found ${response.totalReturned} Target Entities`);

response.targets.forEach((target) => {
    console.log(`${target.TargetEntityType}:${target.TargetEntityId}`);
    // target.permissionship: 'HAS_PERMISSION' | 'CONDITIONAL_PERMISSION' | 'NO_PERMISSION'
});

// For pagination, use the returned cursor
if (response.cursor) {
    const nextPage = await e10sClient.lookupTargetEntities({
        // ... same params
        cursor: response.cursor
    });
}

Lookup Entities

Find all entities (i.e. users) of a given type that are entitled to perform a specific action on a given entity instance (i.e. documents)

const response = await e10sClient.lookupEntities({
    TargetEntityType: 'document',
    TargetEntityId: 'doc-456',
    entityType: 'user',
    action: 'read',
    at: '2026-01-15T12:00:00Z'
});

console.log(`Found ${response.totalReturned} entities`);

response.entities.forEach((entity) => {
    console.log(`${entity.entityType}:${entity.entityId}`);
    // entity.permissionship: 'HAS_PERMISSION' | 'CONDITIONAL_PERMISSION' | 'NO_PERMISSION'
});

Monitoring

In case monitoring mode is enabled, the real results will only be logged, and the following payload will always return

{
    "result": true,
    "monitoring": true
}

版本与发布记录

@frontegg/e10s-client 共发布 39 个版本,首次发布于 2024/05/12;过去 12 个月发布了 11 个版本。

版本发布日期距今
4.0.0latest2026/11/0510 天前
3.0.12026/05/315 个月前
3.0.1-alpha.1next2026/05/315 个月前
3.0.02026/01/1410 个月前
3.0.0-alpha.172026/01/1410 个月前
3.0.0-alpha.162026/01/0510 个月前

关于 @frontegg/e10s-client 的常见问题

如何安装 @frontegg/e10s-client?

在项目目录运行 npm install @frontegg/e10s-client,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:axios(^1)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@frontegg/e10s-client 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@frontegg/e10s-client 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@frontegg/e10s-client 可以商用吗?

@frontegg/e10s-client 使用 ISC 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@frontegg/e10s-client 的下载量有多少?

根据 npm 官方下载统计,@frontegg/e10s-client 在 2026/10/16 – 2026/11/14 这 30 天内被下载 12,296 次,日均约 410 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@frontegg/e10s-client 还在维护吗?

最新版本 4.0.0 发布于 2026/11/05(10 天前);至今共发布 39 个版本,其中过去 12 个月发布了 11 个;首次发布于 2024/05/12。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。