@alxia/janus

中文文档
0.4.2发布于 2026/11/07MITTypeScriptES Module零依赖近 30 天下载 1932

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

中文使用文档
文档目录8 节
  1. 使用量
  2. session(accounts, options?)
  3. ctx.auth : 签名进出
  4. janusErrors(options?)
  5. permission(access, permission, type, load, options?)
  6. 阅读应用程序的上下文
  7. API 密码
  8. 文档

@alxia/ janus (英语)

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

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

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

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

镜子 @softistx/janus-hono数字 : 同样的饼干,同样的规则,同样的身体.

一个叫 janus API 而不是运行的 BFF @softistx/janus 自己,想要 @alxia/react-router/session。 。 。 .

使用量

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数字 :

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读取之前的参数 中间软件添加。 警卫需要它:一个不给它的应用程序 在警卫不能使用之前.

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() 此软件包的调用、 它所知道的用户、 应用程序加载时的对象、 值或承诺

文档

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

版本与发布记录

@alxia/janus 共发布 26 个版本,首次发布于 2026/10/04;过去 12 个月发布了 26 个版本。

版本发布日期距今
0.4.2latest2026/11/071 天前
0.4.12026/11/071 天前
0.4.02026/11/062 天前
0.3.182026/11/025 天前
0.3.172026/11/017 天前
0.3.162026/10/2910 天前

关于 @alxia/janus 的常见问题

@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 和提交记录判断。