@abapify/speci

中文文档
0.4.34发布于 2026/11/03MITTypeScriptES Module零依赖构建来源已验证近 30 天下载 5950

TypeScript 最小箭头功能合同规格系统

中文使用文档
文档目录24 节
  1. 哲学
  2. 以 ts-xsd 开发
  3. 模块结构
  4. 安装
  5. 快速启动
  6. 1. 确定合同
  7. 2. 生成一个输入客户端
  8. 核心概念
  9. 结束点描述符
  10. HTTP 帮助对象
  11. 司马支持
  12. 全球错误反应
  13. 路径参数
  14. 处理错误
  15. 自定义 HTTP 适配器
  16. 截取器
  17. Speci 能够生成的
  18. 为什么是箭头函数?
  19. 从Ts -rest来?
  20. 与 ts-rest 比较
  21. 与其他工具的比较
  22. 文档
  23. 许可证
  24. 捐款

光谱

version

TypeScript 最小箭头功能合同规格系统

零取装饰. 零号DSL. 零从属. 仅仅是 TypeScript 箭头函数 .

部分 ADT 工具包 基础 - 见 主读取器 建筑概况.

哲学

端点 = 箭头函数,其参数定义了合同,而返回值则定义了操作.

import { http } from '@abapify/speci/rest';

// Shortcut syntax - super clean!
const updateUser = (id: string, user: UserInput) =>
  http.put<User>(`/users/${id}`, user);

这在保持最低水平和TypeScript-native的同时,表现极强. 在快捷键语法中选择简单或完整的语法来控制.

以 ts-xsd 开发

斯佩克是紧密结合 t-xsd 支助 基于XML的合同虽然大多数REST图书馆都假设JSON, Speci是 Serializable<T> 接口可以与 XML 计划无缝地融合 :

import { parse, build, type XsdSchema, type InferXsd } from 'ts-xsd';
import { http } from '@abapify/speci/rest';

// ts-xsd schema with parse/build
const TransportSchema = {
  /* ... */
} as const satisfies XsdSchema;
type Transport = InferXsd<typeof TransportSchema>;

const transportApi = {
  // Speci infers types from ts-xsd's Serializable interface
  create: (transport: Transport) =>
    http.post<Transport>('/transports', transport),
};

这使得Speci对于使用XML(SAP APT,SOAP服务等)的企业API来说是理想的,同时保持与Zod等基于JSON的策略完全兼容.

模块结构

Speci被组织成特定协议模块:

  • speci - 核心类型和公用事业(议定书不可知性)
  • speci/rest - REST API(帮助者、类型、客户端生成)
  • speci/openapi - OpenAPI生成(计划)
  • speci/cli - 生产(计划)CLI
  • speci/graphql - 图QL(计划)
  • speci/grpc - gRPC(计划)

每个协议模块都是自成一体的,有自己的类型,帮助者,以及客户端生成.

安装

npm install speci
# or
bun add speci

快速启动

1. 确定合同

import { http, type RestContract } from '@abapify/speci/rest';

// Define schemas (use any schema library: Zod, JSON Schema, etc.)
interface User {
  id: string;
  name: string;
  email: string;
}

interface CreateUserInput {
  name: string;
  email: string;
}

interface ErrorResponse {
  error: string;
  message: string;
}

// Define your API contract - choose your style!
export const api = {
  users: {
    // Shortcut syntax - just specify the success type
    list: () => http.get<User[]>('/users'),

    // Full syntax - explicit control over all responses
    get: (id: string) =>
      http.get(`/users/${id}`, {
        responses: {
          200: undefined as unknown as User,
          404: undefined as unknown as ErrorResponse,
        },
      }),

    // Shortcut - pass body directly
    create: (user: CreateUserInput) => http.post<User>('/users', user),

    // Full syntax with body and responses
    update: (id: string, user: Partial<User>) =>
      http.put(`/users/${id}`, {
        body: user,
        responses: {
          200: undefined as unknown as User,
          404: undefined as unknown as ErrorResponse,
        },
      }),

    // Shortcut - defaults to 204 response
    delete: (id: string) => http.delete(`/users/${id}`),
  },
} satisfies RestContract;

2. 生成一个输入客户端

import {
  createClient,
  createFetchAdapter,
  HttpError,
} from '@abapify/speci/rest';

const client = createClient(api, {
  baseUrl: 'https://api.example.com',
  adapter: createFetchAdapter(),
});

// Use it with full type safety - returns only success types
const users = await client.users.list(); // Type: User[]
const user = await client.users.get('123'); // Type: User

// Errors are thrown as HttpError with typed payloads
try {
  const user = await client.users.get('999');
} catch (error) {
  if (client.users.get.isError(error)) {
    // error.payload is typed as ErrorResponse!
    console.error(error.status, error.payload.error);
  }
}

核心概念

结束点描述符

每个端点由返回描述符的箭头函数定义:

const endpoint = (...params) => ({
  method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS',
  path: '/path/with/${params}',
  body: any, // Request body schema
  query: any, // Query parameters schema
  headers: any, // Headers schema
  responses: {
    // Response schemas by status code
    200: SuccessSchema,
    400: ErrorSchema,
  },
  metadata: {
    // Optional metadata
    description: 'Endpoint description',
    tags: ['user', 'admin'],
    deprecated: false,
  },
});

HTTP 帮助对象

这个 @abapify/speci/rest 模块提供 http 所有 HTTP 方法的对象 :

import { http } from '@abapify/speci/rest';

// Shortcut syntax - super clean!
http.get<SuccessType>(path); // GET with 200 response
http.post<SuccessType>(path, body); // POST with 201 response
http.put<SuccessType>(path, body); // PUT with 200 response
http.patch<SuccessType>(path, body); // PATCH with 200 response
http.delete(path); // DELETE with 204 response

// Full syntax - explicit control
http.get(path, { responses: { 200: Type, 404: Error } });
http.post(path, { body, responses: { 201: Type, 400: Error } });

// Examples
const getUser = (id: string) => http.get<User>(`/users/${id}`);
const createUser = (user: CreateUserInput) => http.post<User>('/users', user);

为什么 http 对象? 这个 delete 关键词在 JavaScript 中保留, 因此我们使用 http.delete 而不是独立的 delete 函数.

司马支持

Speci 支持 任何计划库 - 佐德,JSON斯克马,定制计划等等。 使用 schema() 干净语法的助手 :

import { z } from 'zod';
import { schema } from '@abapify/speci/rest';

// Zod schemas
const UserSchema = z.object({
  id: z.string(),
  name: z.string(),
  email: z.string().email(),
});

type User = z.infer<typeof UserSchema>;

const api = {
  getUser: (id: string) =>
    http.get(`/users/${id}`, {
      responses: { 200: schema(UserSchema, {} as User) }, // ✅ Schema + type
    }),
};

// Custom schemas (e.g., XML schemas for ADT)
const ClassSchema = {
  element: 'class',
  attributes: ['name', 'type'],
  // ... your schema definition
};

interface ClassXml {
  name: string;
  type: string;
  // ... type definition
}

const adtApi = {
  getClass: (name: string) =>
    http.get(`/classes/${name}`, {
      responses: { 200: schema(ClassSchema, {} as ClassXml) }, // ✅ Schema + type
    }),
};

如何运作:

  • schema(schemaObject, {} as Type) 提供运行时间计划和编译时间类型
  •  适配器 查看 schema 对象,并将其用于分析/验证
  • TypeScript 为全类型安全查看类型
  • 只类型断言仍然有效 : undefined as unknown as Type

这让斯佩克 计划不可知 - 用你喜欢的验证库!

全球错误反应

避免在所有端点重复错误类型 :

import { createHttp } from '@abapify/speci/rest';

// 1. Define global error responses
const globalErrors = {
  400: undefined as unknown as ApiError,
  401: undefined as unknown as ApiError,
  403: undefined as unknown as ApiError,
  404: undefined as unknown as ApiError,
  500: undefined as unknown as ApiError,
} as const;

// 2. Create http instance with global errors
const api = createHttp(globalErrors);

// 3. Now only specify success responses!
const userApi = {
  list: () => api.get<User[]>('/users'),
  // 400, 401, 403, 404, 500 automatically added!

  get: (id: string) => api.get<User>(`/users/${id}`),
  // Global errors merged automatically

  create: (user: CreateUserInput) => api.post<User>('/users', user),
  // All endpoints get global errors
};

路径参数

路径参数自动从模板文字中提取 :

const getPost = (userId: string, postId: string) =>
  http.get<Post>(`/users/${userId}/posts/${postId}`);

// Speci automatically maps:
// - First param (userId) → ${userId} in path
// - Second param (postId) → ${postId} in path

处理错误

错误显示为 HttpError 带有打出的有效载荷:

import { HttpError } from '@abapify/speci/rest';

try {
  const user = await client.users.get('123');
  // user is typed as User (not User | ErrorResponse)
} catch (error) {
  // Option 1: Use endpoint-specific type guard
  if (client.users.get.isError(error)) {
    // error.payload is typed as ErrorResponse
    console.error(`HTTP ${error.status}:`, error.payload.error);
  }

  // Option 2: Generic HttpError check
  else if (error instanceof HttpError) {
    console.error(`HTTP ${error.status}:`, error.payload);
  }

  // Option 3: Network or other errors
  else {
    console.error('Network error:', error);
  }
}

自定义 HTTP 适配器

斯佩克是适应者-不可知论者. 带上您的 HTTP 客户端 :

import type { HttpAdapter } from '@abapify/speci/rest';

const myAdapter: HttpAdapter = {
  async request({ method, url, body, query, headers }) {
    // Use any HTTP client: axios, got, ky, wretch, etc.
    return await yourHttpClient.request({ method, url, body, query, headers });
  },
};

const client = createClient(api, {
  baseUrl: 'https://api.example.com',
  adapter: myAdapter,
});

截取器

添加请求/答复/截取器:

const client = createClient(api, {
  baseUrl: 'https://api.example.com',
  adapter: createFetchAdapter(),

  // Add auth token to all requests
  onRequest: async (options) => ({
    ...options,
    headers: {
      ...options.headers,
      Authorization: `Bearer ${getToken()}`,
    },
  }),

  // Transform responses
  onResponse: async (response) => {
    console.log('Response:', response);
    return response;
  },

  // Handle errors
  onError: async (error) => {
    console.error('Error:', error);
    throw error;
  },
});

Speci 能够生成的

从你的箭形功能合同中,斯佩克可以生成:

  • ✅ 类型客户端 (已执行)
  • 🚧 服务器路由 (计划)
  • 🚧 OpenAPI 规格 (计划)
  • 🚧 模拟服务器 (计划)
  • 🚧 测试固定装置 (计划)
  • 🚧 CLI 工具 (计划)
  • 🚧 图QL 计划 (计划)
  • 🚧 gRPC 定义 (计划)

为什么是箭头函数?

✅ 没有类型脚本 AST 解析 - TS 给您本地函数类型
✅ 无装饰器 - 他们是可选的合成糖
✅ 没有模板升解析 - 简单的可变提取
✅ 完全可读 - 看起来像普通域号
✅ 完全可见 - 所有REST/HATEOAS/GRPC概念都可以包起来
✅ 零框架组合 - 纯类型脚本

从Ts -rest来?

Speci使用相同的合同第一哲学,但带有箭头功能:

// ts-rest
const contract = c.router({
  getUser: {
    method: 'GET',
    path: '/users/:id',
    responses: { 200: UserSchema },
  },
});

// Speci
const contract = {
  getUser: (id: string) => http.get<User>(`/users/${id}`),
};

同样的安全性, 少了锅炉板。 选择 ts-rest 表示成熟度, Speci 表示简单和 XML 支持 .

与 ts-rest 比较

ts-resst (英语) 是一个优秀的,成熟的图书馆 用于类型的安全 REST 合同 我们推荐它 用于大多数使用案例。 斯佩克涵盖您需要的特定情景: 动态路由作为函数 (终点定义为有参数的箭头函数), 方案的灵活性 (通过适配器,而不仅仅是Zod)和a 灵活插件系统 用于自定义扩展。 如果Ts -rest的第一合同方法对你有用,就使用它——它经过了战斗测试和生产准备.

与其他工具的比较

特性标本ts-resst (英语)tRPC 主题打开API
语法箭头函数构建器 API程序也门/日本
类型安全满分满分满分编码源
依赖关系0 个少数许多许多
学习曲线最小值低级中型高级
客户端对对对对
服务器计划数对对* 部分内容
验证计划数祖德 苏德祖德 苏德变相

文档

许可证

麻省理工学院

捐款

欢迎捐款! 这是v0.1 - 最小可行的核心。 未来版本将增加:

  • 服务器适配器(Express, Fastify, Hono等)
  • OpenAPI 生成
  • 模拟服务器生成
  • Schema验证(佐德,JSON Schema等)
  • 还有更多!

版本与发布记录

@abapify/speci 共发布 31 个版本,首次发布于 2026/04/21;过去 12 个月发布了 31 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.4.34latest2026/11/033 天前
0.4.332026/11/023 天前
0.4.322026/11/023 天前
0.4.302026/10/316 天前
0.4.292026/10/306 天前
0.4.272026/10/307 天前

关于 @abapify/speci 的常见问题

@abapify/speci 是什么?

TypeScript 最小箭头功能合同规格系统

如何安装 @abapify/speci?

在项目目录运行 npm install @abapify/speci,或使用 pnpm add / yarn add / bun add。

@abapify/speci 支持 TypeScript 吗?

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

@abapify/speci 支持 ESM 还是 CommonJS?

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

@abapify/speci 可以商用吗?

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

@abapify/speci 的下载量有多少?

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

@abapify/speci 还在维护吗?

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