npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dofe/sso-contracts

v0.1.50

Published

SSO API contracts and schemas for DofeAI platform

Readme

@repo/contracts - API 契约

使用 ts-rest 实现的前后端类型安全 API 契约。

安装

{
  "dependencies": {
    "@repo/contracts": "workspace:*"
  }
}

使用

前端使用

import { teamContract } from '@repo/contracts/api';
import { initClient } from '@ts-rest/core';

const client = initClient(teamContract, {
  baseUrl: '/api',
});

const { data } = await client.getInfo({ params: { teamId: '123' } });

后端使用

import { teamContract } from '@repo/contracts/api';
import { TsRestHandler, tsRestHandler } from '@ts-rest/nest';
import { success } from '@/common/ts-rest';

@Controller()
export class TeamController {
  @TsRestHandler(teamContract.getInfo)
  async getInfo() {
    return tsRestHandler(teamContract.getInfo, async ({ params }) => {
      const team = await this.teamService.getInfo(params.teamId);
      return success(team);
    });
  }
}

错误码系统

错误码按业务域组织,从 @repo/contracts/errors 导出。

错误码域

| 域 | 前缀 | 导入 | | ------- | ------- | ------------------ | | Team | 1xx | TeamErrorCode | | User | 2xx | UserErrorCode | | Space | 3xx | SpaceErrorCode | | Folder | 4xx | FolderErrorCode | | File | 5xx | FileErrorCode | | Comment | 56x-57x | CommentErrorCode | | Payment | 7xx | PaymentErrorCode | | Common | 9xx | CommonErrorCode |

后端错误处理

import { TeamErrorCode, apiError } from '@repo/contracts/errors';
import { ApiExceptionV2 } from '@/filter/exception/api-exception-v2';

// 简单错误
throw apiError(TeamErrorCode.TeamNotFound);

// 带数据的错误
throw apiError(TeamErrorCode.TeamNotFound, { teamId: '123' });

// 直接使用 ApiExceptionV2
throw ApiExceptionV2.fromCode(TeamErrorCode.TeamNotFound);

前端错误处理

import { TeamErrorCode, handleApiError, createErrorHandler } from '@repo/contracts/errors';

// 创建类型安全的错误处理器
const teamErrorHandler = createErrorHandler({
  [TeamErrorCode.TeamNotFound]: {
    message: '团队不存在',
    action: () => router.push('/teams'),
  },
  [TeamErrorCode.TeamOpNoPermission]: {
    message: '您没有权限执行此操作',
  },
});

// 处理 API 响应
const handleTeamError = (errorCode: number) => {
  const result = teamErrorHandler(errorCode);
  if (result) {
    toast.error(result.message);
    result.action?.();
  }
};

契约中的类型化错误响应

import { TeamErrorCode } from '../errors/domains/team.errors';
import { createTypedErrorResponse } from '../errors/error-response';

export const teamContract = c.router({
  getInfo: {
    method: 'GET',
    path: '/:teamId',
    responses: {
      200: createApiResponse(TeamInfoSchema),
      400: createTypedErrorResponse([
        TeamErrorCode.TeamNotFound,
        TeamErrorCode.TeamMemberViewNoPermission,
      ] as const),
    },
  },
});

验证 Schema

import { TeamNameSchema, PaginationQuerySchema } from '@repo/contracts/schemas';

// 在表单中使用
const teamForm = useForm({
  resolver: zodResolver(
    z.object({
      name: TeamNameSchema,
    }),
  ),
});

// 在 API 查询中使用
const query = PaginationQuerySchema.parse(searchParams);

目录结构

src/
├── api/                    # API 契约
│   ├── team.contract.ts
│   ├── user.contract.ts
│   ├── space.contract.ts
│   └── index.ts
├── errors/                 # 错误码系统
│   ├── domains/            # 域错误定义
│   │   ├── team.errors.ts
│   │   ├── user.errors.ts
│   │   └── ...
│   ├── codes.ts            # 统一错误码
│   ├── error-response.ts   # 类型化错误响应辅助函数
│   └── index.ts
├── schemas/                # Zod Schema
│   ├── team.schema.ts
│   ├── user.schema.ts
│   └── ...
├── base.ts                 # 基础 Schema (createApiResponse 等)
└── index.ts                # 主导出

构建

pnpm --filter @repo/contracts build

迁移

从旧错误系统迁移请参阅:

错误码系统状态

已完成

  • 所有错误码已迁移到 @packages/contracts/src/errors
  • 错误码枚举已改为字符串格式(避免打包类型问题)
  • i18n 错误消息已迁移到 errors.json
  • 过期文件已清理

已完成

  • 所有文件已迁移到新系统或桥接文件
  • code.enum.ts 已无任何直接引用(可安全删除)

📊 实施进度100%


NPM 发布流程

发布说明

本包在 workspace 内使用名称 @repo/contracts,发布到 npmjs 时使用名称 @dofe/sso-contracts

发布脚本会自动处理名称转换:

  • prepack - 发布前将名称改为 @dofe/sso-contracts
  • postpack - 发布后恢复为 @repo/contracts

发布命令

从根目录发布(推荐)

# 在 sso.dofe.ai 根目录执行

# 发布单个包
pnpm release:contracts          # 发布 @dofe/sso-contracts
pnpm release:contracts:otp      # 带 OTP 发布

# 批量发布所有包
pnpm release:all                # 发布 contracts、hooks、ui
pnpm release:all:otp            # 带 OTP 批量发布

从包目录发布

# 进入 contracts 目录
cd sso.dofe.ai/packages/contracts

# 手动发布流程
pnpm build
node scripts/prepack.mjs
npm publish --access public --ignore-scripts --otp=<你的OTP码>
node scripts/postpack.mjs

从根目录发布流程详解

pnpm release:contracts:otp

执行步骤:

  1. 进入 packages/contracts 目录
  2. 运行 prepack 修改 package.json(名称改为 @dofe/sso-contracts
  3. 执行 npm publish --access public --ignore-scripts --otp
  4. 运行 postpack 恢复 package.json 原始状态

前置条件

  1. npm 账户登录:

    npm login
  2. 验证登录状态:

    npm whoami

其他项目引用

发布后,agents.dofe.ai、models.dofe.ai 等项目可通过以下方式引用:

{
  "dependencies": {
    "@dofe/sso-contracts": "^0.1.0"
  }
}
import { InternalTenantSchema, TeamMemberRoleSchema } from '@dofe/sso-contracts';
import { internalContract } from '@dofe/sso-contracts/api';

可用导出

| 导出路径 | 内容 | | -------------------------------------- | ---------------------------------- | | @dofe/sso-contracts | 所有 schemas、contracts、errors | | @dofe/sso-contracts/api | API contracts | | @dofe/sso-contracts/schemas | Zod schemas | | @dofe/sso-contracts/schemas/internal | Internal API schemas (Tenant/Team) | | @dofe/sso-contracts/errors | Error codes | | @dofe/sso-contracts/base | Base utilities (createApiResponse) |

常见问题

Q: 发布失败 "You do not have permission"

A: 确保 npm 账户有权限发布 @dofe scope 包。需要先在 npmjs 创建或加入 @dofe organization。

Q: 发布失败 "Package already exists"

A: 版本号已存在,需要升级版本:

pnpm release:patch

Q: 本地安装失败 "Cannot resolve @repo/contracts"

A: 这是因为 workspace 依赖问题。确保在 sso.dofe.ai 项目根目录运行 pnpm install