# @alxia/janus 中文文档

规范地址：https://npmcn.com/package/%40alxia/janus/

版本：0\.4\.2
npm 元数据更新时间：2026-11-06T19:29:56.858Z
中文译文更新时间：2026-11-08T01:45:47.161Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40alxia/janus
原文对照：https://npmcn.com/package/%40alxia/janus/?lang=original
源码仓库：https://github.com/softistx/alxia

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：165 kB，32 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- 运行依赖：0 个
- peer 依赖：typescript ^6\.0\.3 \|\| ^7\.0\.0，@alxia/core ^0\.19\.0，@softistx/janus \>=0\.38\.1 \<0\.42\.0
- 首次发布：2026-10-03T17:20:07.610Z
- 最新版本发布：2026-11-06T19:29:56.697Z
- 近 30 天下载量：1932（2026-10-06 至 2026-11-04，npm 官方下载统计）
- 已发布版本数：26（近 12 个月 26 个）

## 中文简介

在 @softistx/ janus 上的 alxia 身份和会话: 上下文输入的用户, 会话 cookie 保存并更新, 输入了 401

## 安装

```sh
npm install @alxia/janus
```

也可以使用：`pnpm add @alxia/janus`、`yarn add @alxia/janus`、`bun add @alxia/janus`

## README

# @alxia/ janus (英语)

身份、会话和权限 [阿尔克夏](https://www.npmjs.com/package/@alxia/core), (中文(简体) ), 打开 [`@softistx/janus`](https://github.com/orgs/softistx/packages/npm/package/janus): 你的过程, 你的数据库。 在上下文中键入的用户, 会话 cookie 保存和 雅努斯的拒绝以他们的身份 和一个安全的身体来回应 没有 依赖性.

```sh
bun add @alxia/janus @softistx/janus @alxia/core
bun add -d typescript
```

`@softistx/janus` 是私人的,在 GitHub 包上。 在您的地图中绘制其范围 `bunfig.toml` 并导出一个读取软件包的符号(`read:packages`:

```toml
[install.scopes]
"@softistx" = { url = "https://npm.pkg.github.com/", token = "$GITHUB_TOKEN" }
```

镜子 [`@softistx/janus-hono`](https://github.com/orgs/softistx/packages/npm/package/janus-hono)数字 : 同样的饼干,同样的规则,同样的身体.

一个叫 janus API 而不是运行的 BFF `@softistx/janus` 自己,想要 [`@alxia/react-router/session`](https://www.npmjs.com/package/@alxia/react-router)。 。 。 .

## 使用量

```ts
import { alxia, validate } from '@alxia/core';
import { janusErrors, session } from '@alxia/janus';
import { createMemoryStores, janus, scryptHasher } from '@softistx/janus';
import { z } from 'zod';

const accounts = janus({
    user: z.object({ email: z.email(), name: z.string() }),
    password: { login: 'email' },
    store: createMemoryStores(),       // your database's adapter in production
    hasher: scryptHasher(),
});

const SignIn = z.object({ email: z.string(), password: z.string() });

const app = alxia()
    .use(janusErrors(), session(accounts))                // errors first, then the session: anonymous may sign in
    .post('/signin', validate({ body: SignIn }), async ({ body, auth, reply }) => {
        const signedIn = await accounts.signIn(body);
        return reply.ok({ id: auth.send(signedIn).id });  // the token in the cookie
    })
    .post('/signout', async ({ auth, reply }) => reply.ok(await auth.signOut()))
    .use(session(accounts, { required: true }))           // the routes after it, and any unmatched request
    .get('/me', ({ user, reply }) => reply.ok(user));     // user typed by the schema
```

`janusErrors()` 财务报告和审定财务报表 `session()` 是中间的软件. `janusErrors()` 答题 抛出的东西,你把它交给 `use` **在此之前** `session()`。 。 。 .

## `session(accounts, options?)`

读完之后的路线 `user`, (中文(简体) ), `session` 财务报告和审定财务报表 `auth`与 `required: true`,匿名请求是401 `{ error: 'unauthenticated' }` 财务报告和审定财务报表 `user` 永远 `null`在应用程序上,a 需要的会话也会运行在请求上 没有路由匹配:匿名 请求去向缺失路径获得401,而不是404. 把它放进一个 `group` 改为 只保护一些路线,没有,它就是 `null` 对于匿名请求。 A 类 `boolean` 仅在运行时类型中知道 两者:401和a `user` 这可能是 `null`。 。 。 . `type` 缩小到一个用户 类型 `janus()`· ; `device` 命名设备饼干.

会议内容如下: `Authorization: Bearer`, (中文(简体) ), `X-Session-Token`,或 饼干 一个经过更新的路段 被再次作为饼干 之后的路线 - 只针对一个将它作为一个, 从来没有超过一个会议的请求 曲奇路线设定本身. **断电不是匿名的**: 一个商店 无法回答 : `STORE_FAILED`,回答503 由 `janusErrors()`。 。 。 .

## `ctx.auth`: 签名进出

签到就留在你身上 `janus()` 实例 `accounts.signIn`, (中文(简体) ), `accounts.signUp`, (中文(简体) ), `accounts.patient.signIn`,代码,链接——和 `ctx.auth` 请求是否 部分,受约束:

| `ctx.auth.` |  |
| --- | --- |
| `send(signedIn)` | 设置会话 cookie(和设备 cookie,带有 `deviceToken`)并返回用户: 符号从不在体内 |
| `signOut()` | 取消请求的会话并清除其饼干,不管答案是什么 |
| `device` | 设备饼干的值,或 `null`,用于 `signIn(…, { device: ctx.auth.device })` 与 `janus({ devices })` |

把签入路线放在后面 `session(accounts)` 这是 **没有** 要求: 后面 `required: true`匿名请求——任何签字者——是 401. 路线之外(工作、测试), `sendSession(ctx, accounts, signedIn)`, (中文(简体) ), `signOut(ctx, accounts)` 财务报告和审定财务报表 `deviceOf(ctx)` 是相同的功能,没有约束.

## `janusErrors(options?)`

尝试/捕获中件: 每件 `JanusError` 被抛在后面——通过 `session()`, (中文(简体) ), `permission()` 或一条路线——以雅努斯的地位回答 以及一个拥有 `code` 只有客户可以采取行动 问题,最小长度,尝试左起,等待秒(与 `Retry-After`) (中文(简体) ). 永不入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出入出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出. `report(error, ctx)` 实值 5xx的电话。 任何其它的事情,扔到中间的器皿上 这个 在应用程序上 `alxia({ errors: 'problem' })`它回答一个RFC 9457 相反,问题 `code` (a) 延长任期 所需经费 `session()`401岁和 `permission()`401、404和403跟着 该应用程序的格式也。 于 `session()`,它不会看到会议 `STORE_FAILED`:它首先进行,然后是观察员(`logger()`, (中文(简体) ), `secureHeaders()`以便他们看见它的答复。 观察员解决 `next()` 没有吞下错误,所以 `janusErrors()` 之前,或 在此之前 `createI18n()`继续回答.

| 状态 | 代码 |
| --- | --- |
| 400个 | `USER_INVALID`, (中文(简体) ), `PASSWORD_TOO_SHORT`, (中文(简体) ), `TOKEN_*`. |
| 401 (中文(简体) ) | `CREDENTIALS_INVALID`, (中文(简体) ), `CODE_INVALID` |
| 403 (韩语) | `USER_INACTIVE`, (中文(简体) ), `STEP_UP_REQUIRED` |
| 404 (韩语) | `NOT_FOUND` |
| 409个 | `LOGIN_TAKEN`, (中文(简体) ), `VERSION_CONFLICT`, (中文(简体) ), `SECOND_FACTOR_*` |
| 429个 | `MAIL_THROTTLED` (单位:千美元)`@softistx/janus` 页:1 `retryAfter` 财务报告和审定财务报表 `Retry-After` |
| 500个 | `PERMISSION_DEPTH` |
| 501 (简体中文) | `UNSUPPORTED` |
| 第503条 | `STORE_FAILED` |

## `permission(access, permission, type, load, options?)`

一个警卫 `@softistx/janus/permissions`: 运行后的路线,仅在 会议主题 `user`,或 `options.subject(ctx)` - 坚守 `permission` 在对象上 `load` 找到他们读作 `object`。 。 。 . 匿名是401,没有装入404,拒绝403,每个输入。 范围 带它 `group`数字 :

```ts
import { alxia } from '@alxia/core';
import { byParam, janusErrors, permission, session } from '@alxia/janus';
import { createMemoryStores, janus, scryptHasher } from '@softistx/janus';
import { createMemoryRelations, defineModel, permissions } from '@softistx/janus/permissions';
import { z } from 'zod';

const relations = createMemoryRelations();
const accounts = janus({
    user: z.object({ email: z.email() }),
    password: { login: 'email' },
    store: createMemoryStores(),
    relations,
    hasher: scryptHasher(),
});
const access = permissions({
    model: defineModel({
        subjects: accounts.types,
        types: { record: { related: { owners: ['user'] }, permits: { view: ['owners'] } } },
    }),
    store: relations,
});

const records = new Map([['r1', { id: 'r1', title: 'Blood test' }]]);
const findRecord = (id: string) => records.get(id) ?? null;

const app = alxia()
    .use(janusErrors())
    .use(session(accounts))
    .group('/records/:id', (record) =>
        record
            .use(permission(access, 'view', 'record', byParam('id', findRecord)))
            .get('/', ({ object, reply }) => reply(200, object)), // object: what findRecord found
    );
```

`byParam(name, find)` 按路径参数加载。 允许 条件需要上下文 `ctx: (ctx, object) => …`时 准确的排序.

### 阅读应用程序的上下文

注释 `load`, (中文(简体) ), `subject` 或者说 `ctx`读取之前的参数 中间软件添加。 警卫需要它:一个不给它的应用程序 在警卫不能使用之前.

```ts
import type { BaseContext } from '@alxia/core';

const byTenant = permission(
    access,
    'view',
    'record',
    ({ tenant, pathParams }: BaseContext & { tenant: { records: Map<string, { id: string; title: string }> } }) =>
        tenant.records.get(pathParams['id'] ?? '') ?? null,
);

alxia().plugin(tenancy).use(session(accounts)).use(byTenant); // tenancy derives tenant
alxia().use(session(accounts)).use(byTenant); // a compile error: this app gives no `tenant`
```

附加说明的召回 `any` 不需要什么 所以警卫被拒 在每一个应用程序上:注释其内容,或者不附加说明.

## API 密码

| 导出 |  |
| --- | --- |
| `session(accounts, options?)` | 中间软件 : `user`, (中文(简体) ), `session`, (中文(简体) ), `auth` |
| `SessionOptions` | 其选项: `type`, (中文(简体) ), `required`, (中文(简体) ), `device` |
| `RequestAuth`, (中文(简体) ), `SessionOpened` | 类型 `ctx.auth`数字 : `send`, (中文(简体) ), `signOut`, (中文(简体) ), `device`;和什么 `send` 选择: `token`, (中文(简体) ), `session` 财务报告和审定财务报表 `user` 从 `@softistx/janus`是因为 `SignedIn`,以及 `deviceToken?` |
| `sendSession`, (中文(简体) ), `signOut`, (中文(简体) ), `deviceOf`, (中文(简体) ), `sendDevice` | 饼干,在路外 |
| `DEVICE_COOKIE` | 设备饼干的名字, `janus-device`: 一个 `@softistx/janus-hono` 使用,所以一个设备记得另一个设备 |
| `SendSessionOptions`, (中文(简体) ), `DeviceCookieOptions` | 其选项: `device`· ; `name`, (中文(简体) ), `domain`, (中文(简体) ), `path`, (中文(简体) ), `sameSite`, (中文(简体) ), `secure`, (中文(简体) ), `maxAge` |
| `janusErrors(options?)` | 中间器皿:詹努斯的拒绝得到了回答; `use` 曾经 `session()` |
| `JanusErrors`, (中文(简体) ), `SessionMiddleware` | 类型 `janusErrors()` 财务报告和审定财务报表 `session()` 生产 |
| `JanusErrorsOptions` | 其选项: `report`,每个错误都拨打,回答 5xx |
| `permission(…)`, (中文(简体) ), `byParam(…)` | 警卫 |
| `PermissionOptions`, (中文(简体) ), `OptionsArgs` | 其选项: `subject`, (中文(简体) ), `ctx`;和它的其他论点,在许可有条件时,选择权需要准确的;两者都取什么 `subject` 财务报告和审定财务报表 `ctx` 读取 `BaseContext` 作为最后两个默认参数 |
| `bodyOf`, (中文(简体) ), `statusOf` | 拒绝的主体和地位 |
| `UnauthenticatedBody`, (中文(简体) ), `JanusErrorBody`, (中文(简体) ), `PermissionRefusedBody` | 类型 |
| `UnauthenticatedProblem`, (中文(简体) ), `JanusErrorProblem`, (中文(简体) ), `PermissionRefusedProblem` | 同样的答案 `@alxia/core`是因为 `alxia({ errors: 'problem' })`: RFC 9457问题, `janusErrors()`与它的 `code` 作为扩展的正文 |
| `Auth`, (中文(简体) ), `UserOfAuth`, (中文(简体) ), `ObjectData`, (中文(简体) ), `Awaitable` | 页:1 `janus()` 此软件包的调用、 它所知道的用户、 应用程序加载时的对象、 值或承诺 |

## 文档

-   [指南](https://github.com/softistx/alxia/tree/develop/packages/janus/docs):每区一页——会话,签入和签出 `ctx.auth` 以及设备饼干,詹纳斯的错误及其状态, 和许可守卫,读取一个早期的中间软件添加的内容.
-   [解决问题](https://github.com/softistx/alxia/blob/develop/packages/janus/docs/troubleshooting.md):错误消息,或匿名请求,拒绝,或当不应该是404时,如何处理.
-   [路线图](https://github.com/softistx/alxia/blob/develop/packages/janus/docs/roadmap.md):即将到来的,以及没有计划的.
-   [食谱](https://github.com/softistx/alxia/blob/develop/docs/recipes/README.md)数字 : [认证请求](https://github.com/softistx/alxia/blob/develop/docs/recipes/authentication.md), (中文(简体) ), [持续回答错误](https://github.com/softistx/alxia/blob/develop/docs/recipes/errors.md)。 。 。 .

## 常见问题

### @alxia/janus 是什么？

在 @softistx/ janus 上的 alxia 身份和会话: 上下文输入的用户, 会话 cookie 保存并更新, 输入了 401

### 如何安装 @alxia/janus？

在项目目录运行 npm install @alxia/janus，或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖：typescript（^6\.0\.3 \|\| ^7\.0\.0）、@alxia/core（^0\.19\.0）、@softistx/janus（\>=0\.38\.1 \<0\.42\.0）。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖，Yarn 需要手动安装。

### @alxia/janus 支持 TypeScript 吗？

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

### @alxia/janus 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### @alxia/janus 可以商用吗？

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

### @alxia/janus 的下载量有多少？

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

### @alxia/janus 还在维护吗？

最新版本 0\.4\.2 发布于 2026/11/07（1 天前）；至今共发布 26 个版本，其中过去 12 个月发布了 26 个；首次发布于 2026/10/04。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

