# @acms-subir/client 中文文档

规范地址：https://npmcn.com/package/%40acms-subir/client/

版本：0\.1\.37
npm 元数据更新时间：2026-11-04T22:30:17.021Z
中文译文更新时间：2026-11-06T13:38:06.305Z
软件包许可：MIT

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

原始 npm 页面：https://www.npmjs.com/package/%40acms-subir/client
原文对照：https://npmcn.com/package/%40acms-subir/client/?lang=original
项目主页：https://acms.subir.dev/
源码仓库：https://onedev.subir.dev/acms-subir

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

- 包体积（解压后）：214 kB，10 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- Node.js 要求：\>=18
- 运行依赖：0 个
- 首次发布：2026-10-01T01:03:25.181Z
- 最新版本发布：2026-11-04T22:30:16.846Z
- 近 30 天下载量：3105（2026-10-05 至 2026-11-03，npm 官方下载统计）
- 已发布版本数：38（近 12 个月 38 个）

## 中文简介

为ACMS公共移动API打入,无依赖客户端.

## 安装

```sh
npm install @acms-subir/client
```

也可以使用：`pnpm add @acms-subir/client`、`yarn add @acms-subir/client`、`bun add @acms-subir/client`

## README

# @acms-subir/客户端

ACMS 公共 API v1. 支持 Expo/ React 的无依赖性ESM TypeScript客户端 本地、浏览器和节点 18+ 使用全局 `fetch`, (中文(简体) ), `URL`, (中文(简体) ), `Headers` 财务报告和审定财务报表 `AbortController`供应 `fetch` 当环境需要适配器时.

```sh
npm install @acms-subir/client
```

```ts
import { createClient, isAcmsError } from '@acms-subir/client';

const client = createClient({ baseUrl: 'https://example.com', locale: 'es' });
const site = await client.site();
const home = await client.home();
const page = await client.page('about/team');
for await (const post of client.paginate.posts({ collection: 'blog', limit: 50 })) {
  console.log(post.title);
}
const english = client.withLocale('en');
```

`baseUrl` 接受一个站点来源或完整 `/api/v1` URL,带有可选选项 主机前缀 。 返回所有方法 **未包装 `data`**· ; `openapi()` 返回时 原始文档。 每个方法都接受 `locale` 财务报告和审定财务报表 `signal` 选项对象. 语言只发送到支持翻译的路径. `withLocale` 份额 与原始客户端的缓存和运输配置. `site()` 财务报告和审定财务报表 `pushTopics()` 从核心0.3.32开始使用一种语言; 旧网站拒绝它,所以 客户端再问一次没有它,并获得主语言. `book()` 发送客户端的 `locale` 因此,预定的电子邮件(和链接) 使用应用软件的语言; 在核心 0.3.38 拒绝该字段之前, 因此,客户的书 再次没有它, 用同样的钥匙, 在网站的主 语言。 更新网站后, 才能使用更新客户端发送应用程序构建 .

| 方法 | 选项/ 行为 |
| --- | --- |
| `site()`, (中文(简体) ), `theme()`, (中文(简体) ), `home()` | 身份(包括 `legal.privacy` 编号 : `legal.terms` 要在应用程序中链接的 URL 、 符號、 已解析的主页 |
| `page(slug, opts)` | 已解析的页面, 包括段落; 支持嵌入式 satters |
| `content(opts)` | `kind`, (中文(简体) ), `collection`, (中文(简体) ), `q`, (中文(简体) ), `page`, (中文(简体) ), `limit`, (中文(简体) ), `cursor` |
| `contentById(id, opts)` | 可选 `kind` 拆分导入的ID |
| `posts(opts)` | 内容过滤到员额; `collection`, (中文(简体) ), `tag`, (中文(简体) ), `q`标记 |
| `post(slug, opts)` | 扫描邮政摘要,然后用ID加载细节;通过 `collection` 对于模糊的子弹 |
| `pages(opts)` | 页次 **总结**,带有页形;使用 `page()` 用于款次 |
| `search(text, opts)` | 顶级匹配 `posts`, (中文(简体) ), `pages` 财务报告和审定财务报表 `events`附加时 `more` 每组; `types`, (中文(简体) ), `collection`, (中文(简体) ), `limit` (1–20,默认5). 空文本解决无请求 |
| `events(opts)` | 发生情况 `from`, (中文(简体) ), `to`, (中文(简体) ), `calendar`, (中文(简体) ), `tag`, (中文(简体) ), `excludeTag`, (中文(简体) ), `room`, (中文(简体) ), `q`帕吉纳克 过滤器使用一个值或数组( 其中任何一个匹配) |
| `event(slug, opts)` | 不论日期如何,用一发一发子弹`GET /events/{slug}`; 在超过核心0.3.40的站点上, 请求窗口中的第一个匹配) |
| `eventDetail(slug, { on? })` | 事件及其下一场,或者说 `on` (单位:千美元)`YYYY-MM-DD`(中文(简体) ) |
| `calendars()` | 公共日历 |
| `resources(opts)`, (中文(简体) ), `resource(slug, opts)` | 公共资源; 搜索搜索列表页面 |
| `availability(opts)` | 需求 `resource`, (中文(简体) ), `link`; 可选 `from`, (中文(简体) ), `days`, (中文(简体) ), `minutes` |
| `roomFree({ room, start, end })` | 自由或繁忙的一间房间,网站让人们检查,时间繁忙,没有名字;从未缓存(核心0.3.45+) |
| `rsvpStatus(slug, { start })` | 一个事件日期的RSVP状态: `open` 或者说 `reason` (单位:千美元)`disabled`, (中文(简体) ), `ended`, (中文(简体) ), `closed` 过了赛事的最后期限, `cancelled`, (中文(简体) ), `full`, (中文(简体) ), `closesAt` (当RSVPs到日期关闭时,或 `null`: 打开直到结束),活 `going`编号 :`left` (持有的等待列表点数已取出), `maxSeats` 现在有一个RSVP, `autoConfirm`, (中文(简体) ), `waitlist` (单位:千美元)`enabled`:全日期按顺序排列; `waiting` 人为 `seats` 席位; `maxSeats` 以一行为单位) `form` (问 题 `version`,或 `null`) (中文(简体) ). `start` 事件发生的起始时间与 `events()` 返回( UTC ISO 字符串, 如 `2026-10-05T14:00:00.000Z`) (中文(简体) ). 从未缓存 |
| `book(opts)` | 资源预订或RSVP输入(`RsvpInput` 用于RSVP; `waitlist: true` 加入完整日期的等待列表, 状态 `waitlisted`),可选 `idempotencyKey`; 发送客户端的 `locale` (或 `opts.locale`用于电子邮件 |
| `openapi()` | Raw OpenAPI 文档 |
| `registerDevice({ token, platform, locale?, topics?, channels? })` | 注册或刷新博览会推算符(每次启动应用程序都要呼叫); `{ created, device }`。 。 。 . `channels`: Android 频道 所创建的应用程序(例如. `['urgent']`(中文(简体) ) |
| `updateDevice(token, { topics?, locale?, channels? })` | 更改设备的主题、语言或频道 |
| `unregisterDevice(token)` | 退出(专有);也解除工作人员测试电话的链接 |
| `pushTopics()` | 以客户语言(内置和网站本身)命名的作为通知切换的题目, `kind: 'site'`(中文(简体) ) |
| `bibles()`, (中文(简体) ), `biblePassage(ref, { bible })`, (中文(简体) ), `bibleChapter(book, chapter, { bible })` | 在帖子(Settings → Bible, core 0.3.84+)中为圣经链接所写的圣经诗句: 站点的圣经,一个段落(一个链接) `?ref=`),一个有标题和相邻的章节; 404 在关闭时 |
| `linkStaffPhone(token, code)` | 仅工作人员:将这个已注册设备与管理员测试手机的6位数代码相接,从而获得此人的测试通知; `{ linked, staff: { name } }`。错误的、使用过的或过期的代码是 400,太多尝试了 429; 从未重复过 |
| `unlinkStaffPhone(token)` | 脱钩(专有); `{ linked: false, staff: null }` |
| `links()` | 链接页:简介、社交和可见块(`link`, (中文(简体) ), `email`, (中文(简体) ), `phone`, (中文(简体) ), `contact`, (中文(简体) ), `heading`, (中文(简体) ), `text`, (中文(简体) ), `spacer`, (中文(简体) ), `live`, (中文(简体) ), `embed`(中文(简体) ) |
| `live()` | 现场视频:当前广播(`snapshot` 或者说 `null`, (中文(简体) ), `serverNow` 和拥有者 `display` 选择。 从未缓存; 404 时 `site.features.live` 错误 |
| `announcements({ kind?, pinned?, following?, scope?, page?, limit? })`, (中文(简体) ), `announcement(id)`, (中文(简体) ), `paginate.announcements()` | 以客户语言发布最新消息`pinned: true`:旗帜,紧急第一;404 `site.features.announcements` 错误 |
| `form(id)`, (中文(简体) ), `submitForm(id, { answers, version?, idempotencyKey? })` | 已公布的窗体问题; 发送答案( 复选框组为数组) 。 每次呼叫都会发出一个键; 重复重用它 |
| `requestOptions()`, (中文(简体) ), `submitRequest(input, { idempotencyKey? })` | 没有说明的事件请求:表格提供的内容`open`, (中文(简体) ), `types`, (中文(简体) ), `rooms`, (中文(简体) ), `replyTime`, (中文(简体) ), `notice`, (中文(简体) ), `phone`, (中文(简体) ), `timezone`; 从未缓存),并请求新事件或更改(`{ id }`. 404 同时 `site.features.requests` 是虚假的。 见下文事件要求 |
| `manageBooking(token)`, (中文(简体) ), `cancelBooking(token)` | 书商自己订的 `manageToken`,并取消它(或留下一个等待者,或释放一个被扣留的地方); `can` 说什么是允许的。 一个等待名单的RSVP有 `booking.position` 以物配主 `booking.holdUntil` |
| `confirmBooking(token)` | 检查一下报到者`can.confirm`: `confirmed`,或 `pending` 当RSVP需要批准时. 410次 一次 `holdUntil` 通过了。 核心资源0.3.40或以后 |
| `rescheduleAvailability(token, { from?, days?, minutes? })`, (中文(简体) ), `rescheduleBooking(token, { start, minutes? })` | 移动它的开放时间(它自己的时间算作自由),然后移动 |
| `bookingIcs(token)` | 预订为 iCalendar 文件( 字符串) |
| `requestGuestCode(email)`, (中文(简体) ), `guestSignIn({ email, code, deviceName? })`, (中文(简体) ), `guestBookings(guestToken, { when? })`, (中文(简体) ), `guestSignOut(guestToken)` | "我的预订"的来宾报名 |

服务器没有后断层,事件细节或资源细节端点. 这些 便利方法可提出多项请求. `event()` 只发现事件 在其窗口中出现; 未列出 需要日历 `calendar`它不会引起任意的历史事件. 如果弹出后弹出相撞而未提供收藏,则第一个列出的比赛获胜.

搜索需要 `site.features.search` (除非网站设置) `acms({ search: { enabled: false } })`) (中文(简体) ). 它使用网站的搜索索引和 排名:标题数最多,再取出节选,标记和正文. 每个字 必须匹配,而最后一个匹配为前缀(`birth` 发现"出生日". `q` 打开 `content()`编号 :`posts()` 返回时 分级摘要、页前帖子和通过页数,每页最多200个 有点. `search()` 用于合并结果显示屏:每一次命中都带有简单文字 `snippet` 与 `highlights` (单位:千美元)`{ start, end }` 区域)改为粗体,事件加 他们的下一个 `occurrence`启用 `more.posts` 提供"见",然后 `posts({ q, collection })`。匹配使用主语文本,所以 已翻译标题已显示,但没有搜索.

增殖用途 `page`编号 :`nextPage`,而不是不透明的光标. `cursor` 是数字 别名用于 `page`; 明示 `page` 赢了. `tag` 过滤器 **每个获取的页面 当地**,这样一页就可以空出一个非无 `nextPage`。继续 通过这些页面. `paginate.content/posts/pages/events/resources` 产量 单个项目(活动产生事件)。 他们不重复改变 服务器结果或提供快照一致性; 在您的应用程序中复制ID.

## 预订、错误和注销

```ts
import type { RsvpInput } from '@acms-subir/client';

// Persist a UUID v4 with this booking intent before submitting; reuse it until
// the outcome is known. Use your platform's secure UUID/storage facilities.
const idempotencyKey = crypto.randomUUID();
try {
  const result = await client.book({
    resource: 'hall', link: 'meeting', start: '2026-10-05T14:00:00Z', minutes: 60,
    name: 'Example Guest', email: 'guest@example.com', idempotencyKey,
  });
  // Store result.manageToken securely; it grants booking-management access.
} catch (error) {
  if (isAcmsError(error)) {
    console.log(error.status, error.code, error.message, error.retryAfter);
    // error.idempotencyKey preserves even an automatically generated key.
  }
}

// RSVP to one date of an event (needs only the calendar: site.features.rsvp)
const { occurrences } = await client.events({ q: 'supper' });
const date = occurrences[0];
const status = await client.rsvpStatus(date.event.slug, { start: date.start });
if (status.open) {
  const rsvp: RsvpInput = {
    event: date.event.slug, start: date.start, seats: 2, name: 'Example Guest', email: 'guest@example.com',
    // field id → answer; checkbox groups send a list
    answers: { meal: 'Vegetarian', help: ['Setup', 'Music'] },
    // required when the event asks questions: a changed form answers 409, so read the status again
    ...(status.form ? { formVersion: status.form.version } : {}),
  };
  // an email that already has an RSVP for this date: AcmsError 409 with code 'duplicate' (its link is emailed again)
  const result = await client.book({ ...rsvp, idempotencyKey: crypto.randomUUID() });
  // pending when the organiser approves RSVPs; status.form?.confirmation is the thank-you text.
  // result.manageToken changes or cancels it later (manageBooking, cancelBooking), like the email's link:
  // keep it in secure storage only.
  console.log(result.booking.status);
} else if (status.reason === 'full' && status.waitlist.enabled) {
  // a place in line (status 'waitlisted', no seats); when a spot opens the site emails them and holds it
  // for 24 hours: then manageBooking shows status 'offered' and holdUntil, and confirmBooking(token) takes it
  const result = await client.book({
    event: date.event.slug, start: date.start, seats: 2, name: 'Example Guest', email: 'guest@example.com', waitlist: true,
    ...(status.form ? { formVersion: status.form.version } : {}), idempotencyKey: crypto.randomUUID(),
  });
  console.log(result.booking.status);
}

const controller = new AbortController();
const pending = client.home({ signal: controller.signal });
controller.abort();
await pending.catch(() => {});
```

安全重复的请求(GET、PUT、DELETE、设备注册和 带有Idepotency-Key的 POST:预订、表格回复和事件请求)再试网络 失败,5xx和429答复最多两次(总共三次尝试) 250/500ms 备份和至少服务器的 `Retry-After` 延迟( 秒或 HTTP) (日期)。 等待会超过时间 `timeoutMs` 将429直接丢出 `retryAfter`。一发写出(请求和签名、取消和 移动预订、确认被扣位置、客人签出)永远不会自动重复: 网络出错, 请在再次尝试前检查状态 。 总是重复预订 重复使用同一序列体和UUID. 默认每个 `book()` 调用创建 使用键 `crypto.randomUUID`,返回到 Math.random UUID v4 中 无. 用于恢复呼叫/应用程序重新启动、供应和坚持 安全UUUID。 永远不要仅仅因为反应超时而产生新的意图.

`timeoutMs` 默认为 三万,并覆盖整个请求,包括复试, 缓存访问和身体读取。 较长的费率限制延迟可能使 预算。 明确中止和超时不自动重审;不确定 预订必须用它现有的钥匙重新测试. `AcmsError` 曝光情况 `status`, (中文(简体) ), `code`, (中文(简体) ), `message`, (中文(简体) ), `retryAfter` (秒),可选验证 `issues` 财务报告和审定财务报表 `idempotencyKey`本地失败使用状态 0 和 `network_error`, (中文(简体) ), `timeout`, (中文(简体) ), `aborted`,或 `invalid_response`; 非 JSON HTTP 错误使用 `http_error`。 。 。 . 方便查询对每一项基本请求分别适用超时.

## 事件请求

一个站点可以接收人们的事件请求 无账户( a)`acms({ requests: true })`这样 `site.features.requests` (b) 工作人员在日历中回答请求。 有人问 新的东西,或者换成一个事件, 并给出一个名字,电子邮件和 可选择的电话。 在工作人员批准之前,日历上没有任何内容.

```ts
import type { RequestInput } from '@acms-subir/client';

const options = await client.requestOptions();
// { open, types: [{ id, label }], rooms: [{ slug, name, color }], replyTime: '3 days', notice, phone, timezone }
if (!options.open) {
  // say "We aren't taking requests here right now." and show options.phone; keep the screen itself reachable
}
const ask: RequestInput = {
  kind: 'new', type: options.types[0]?.id, title: 'Guitar workshop',
  // the site's wall clock (options.timezone), in the future; the end may be just HH:mm on the same day
  start: '2026-11-07T18:00', end: '20:00',
  repeat: 'weekly',                       // 'once' (default), 'weekly', or 'other' with repeatText
  rooms: ['fellowship-hall'],             // slugs from options.rooms, or roomsUnsure: true
  people: 12, message: 'We bring our own chairs.',
  name: 'Example Guest', email: 'guest@example.com', phone: '555 0100',
};
const { id } = await client.submitRequest(ask, { idempotencyKey: crypto.randomUUID() });

// a change to a public event: one date of it (YYYY-MM-DD, the site's day), or leave date out for every date
await client.submitRequest({ kind: 'change', event: 'fall-fair', date: '2026-11-08', message: 'Could it start at 10?', name: 'Example Guest', email: 'guest@example.com' });
```

-   请检查access-date=中的日期值 (帮助) `roomFree({ room, start, end })` (需要在该网站上预订; 时间需要一个区域,所以转换 时钟数 `options.timezone`) (中文(简体) ).
-   每个请求保留一个“同上不便键”并重新用于重试:重试 同样的回答 `{ id }`,与其他细节相同的密钥是 409 `changed`。每通电话没有 `idempotencyKey` 做了一个新的.
-   客户的 `locale` (或 `input.locale`进入身体, 所以电子邮件 关于请求 说应用程序的语言.
-   拒绝是 `AcmsError` 谁 `code` 说为什么: `closed` (409起请求) ),一个字段(400: `title`, (中文(简体) ), `type`, (中文(简体) ), `start`, (中文(简体) ), `end`, (中文(简体) ), `past`, (中文(简体) ), `far`, (中文(简体) ), `repeat`, (中文(简体) ), `room`, (中文(简体) ), `people`, (中文(简体) ), `event`, (中文(简体) ), `date`, (中文(简体) ), `what` (变化的 电文), `name`, (中文(简体) ), `email`, (中文(简体) ), `phone`, (中文(简体) ), `changed` (409个)或 `busy` 编号 : `full` (429个 `retryAfter`10个请求,每个地址一分钟,加上 站点日封顶.

## 推动通知

当网站允许推时可用(`acms({ mobile: { push: true } })`(三); 否则,这些返回一个404 `AcmsError`。托肯斯来自 `expo-notifications` (单位:千美元)`getExpoPushTokenAsync`) (中文(简体) ). 网站存储的纪念品, 平台、语言和专题.

```ts
const { topics } = await client.pushTopics();          // [{ id: 'news', label: 'News', kind: 'general' }, …]
await client.registerDevice({ token, platform: 'ios', locale: 'es', topics: ['news', 'events'] });
await client.updateDevice(token, { topics: ['events'] });
await client.unregisterDevice(token);
```

在一次活动之后: `event.follow.topic` (单位:千美元)`event:<id>`当它不能是 随行)像其它任何设备的主题一样进入设备主题;站点放弃了它 一旦事件没有日期了。 活动列表 a 电话:

```ts
const topics = [...device.topics, event.follow.topic!];
await client.updateDevice(token, { topics });
const following = followedEvents(topics);                          // event ids
await client.announcements({ following });                          // + their messages and notices (kind 'message')
await client.announcements({ following, scope: 'following' });      // only those ("My classes")
```

追随者也可以在每次约会之前,在 `event.follow.reminders` (单位:千美元)`[{ id: '1h' | 'evening', topic, at }]`, (中文(简体) ), `at` 页:1 晚上时间 HH:mm现场时间; `[]` 当历法没有规定的时候 `topic` (单位:千美元)`event:<id>:remind:1h`在活动的主题之外,一个 一次; `event.follow.reminderStart` 新的跟踪者开始于 (单位:千美元)`'none'` =没有提醒,直到他们选择。 网站只保留一个提醒 它的话题, 当日历停止提供它时, 放弃它.

```ts
import { reminderOf, withReminder } from '@acms-subir/client';
reminderOf(device.topics, event.id);                                             // '1h' | 'evening' | null
await client.updateDevice(token, { topics: withReminder(device.topics, event.id, 'evening') }); // "The evening before, at 7:00 PM"
await client.updateDevice(token, { topics: withReminder(device.topics, event.id, null) });      // no reminder
```

`event.hosts` 当其日历显示时, 列出其主机; `event.hostsLabel` 日历用请求的语言(“Maestro/a”,或 `''` 当它没有设定的时候.

工作人员可以把自己的电话连接起来进行测试(“发送测试与我 在管理员中:管理员的测试手机面板显示一次性6位数 代码(10分,单用),应用程序随这款手机的代号发送. `device.staff` (从) `registerDevice` 财务报告和审定财务报表 `updateDevice`和链接 反应名称与谁有关(仅与谁有关),或 `null`。测试推 携带 `data.test: true`。 。 。 .

```ts
const { staff } = await client.linkStaffPhone(token, '123456');  // { name: 'Ana López' }; 400 for a wrong, used or expired code
await client.unlinkStaffPhone(token);                             // turning notifications off unlinks it too
```

## 实况录像

`site.features.live` 网站现场视频(整合 ) 现场视频 视频)没有关闭. `live()` 返回网站显示的相同快照 : `status` (单位:千美元)`live`, (中文(简体) ), `upcoming` 或者说 `offline` 为"注意我们的频道"倒下, `title`, (中文(简体) ), `watchUrl`,绝对值 `thumbUrl` (空地当无), `startedAt`, (中文(简体) ), `expiresAt` 和可选 `channel` 财务报告和审定财务报表 `viewers`。时间是 Unix 数以毫秒计 **服务器** 时钟: 保留 `serverNow - Date.now()` 作为 并停止一次将快照作为电流处理 `expiresAt` (非零时) 即使后来的请求失败了 也要按那个时钟来. `display` 携带 拥有者的选择(`style`数字 : `poster`, (中文(简体) ), `split` 或者说 `row`· ; `bar`, (中文(简体) ), `header`, (中文(简体) ), `dock`(中文(简体) ) 因此,一个应用程序可以跟踪网站。 大约每30秒播放一次,而应用程序是 在前方; `@acms-subir/native`是因为 `useLive()` 做这一切。 这个 "我们现场直播"的通知是推题 `live`。 。 。 .

```ts
const { snapshot, serverNow } = await client.live();
const offset = serverNow - Date.now();
const current = snapshot && (!snapshot.expiresAt || snapshot.expiresAt > Date.now() + offset) ? snapshot : null;
if (current?.status === 'live') console.log(`Live: ${current.title} → ${current.watchUrl}`);
```

## 缓存和信头

ETag 重新验证默认为内存的 URL 键缓存 。 每个读取联系人 服务器 `If-None-Match`; 一个 304 重用保存的数据。 这不是一个 离线后退 将返回的缓存对象视为不可改变 。 使用 `cache: false` 来禁用它。 可用性,活状态,RSVP状态,预订结果和出错永远不会被缓存. 无ETag或与 `no-store` 取消旧条目.

```ts
import type { ClientCache } from '@acms-subir/client';
const cache: ClientCache = {
  async get(url) { /* retrieve { etag, body } from app storage */ return null; },
  async set(url, entry) { /* persist entry; null means delete */ },
};
const persistent = createClient({ baseUrl: 'https://example.com/api/v1', cache });
```

适配器可以是同步的或同步的. 将持续缓存限制为 一个站点的公开API 并账户存储限制。 缓存适配器失败 向打电话者宣传。 自定义 `headers` 支持; 不嵌入特权 证书。 请求省略浏览器权限 。 有条件的标题得到管理 由客户端; 逐个拨号的密钥会覆盖已配置的头键.

## 发展

```sh
npm run build -w packages/client
npm run test -w packages/client
npm run check:types -w packages/client
npm run generate:types -w packages/client  # after reviewing core schema changes
```

合同测试使用 Core 虚拟导入实际核心 OpenAPI 生成器 模块测试根,将所有 GET/POST 路径与客户端方法注册进行对比, 和检查生成的请求/答复类型字节。 总部和备选方案 是运输业务,没有单独的数据返回方法。 无核心 代码,Zod,或节点内置的飞船 在这个包的运行时间.

## 管理预订和"我的预订"

```ts
// a booking the app made (result.manageToken from book()), or one from guestBookings()
const view = await client.manageBooking(manageToken);
if (view.can.reschedule) {
  const open = await client.rescheduleAvailability(manageToken, { days: 7 });
  await client.rescheduleBooking(manageToken, { start: open.days[0].slots[0].start });
}
if (view.can.cancel) await client.cancelBooking(manageToken);

// guest sign-in (check site.features.guestSignIn first)
await client.requestGuestCode('ana@example.com');
const { accessToken } = await client.guestSignIn({ email: 'ana@example.com', code: '481203' });
const mine = await client.guestBookings(accessToken, { when: 'upcoming' });
```

预订和来宾信使以信封形式旅行(`Booking-Token`, (中文(简体) ), `Authorization: Bearer`),从不在URL中,他们的回复也从不被缓存. 将两者安全储存(`expo-secure-store`) (中文(简体) ). `can.closed` 表示 预订类型更改截断已过:显示“联系我们”而不是“联系我们” 按钮。 一个来宾标志在未使用90天后结束;401表示再次签入.

## 编辑应用程序(工作人员登录)

用于网站 `acms({ mobile: { editor: { redirectUris } } })`工作人员在 在系统浏览器(OAuth 2.0 + PKCE);应用程序从未看到密码.

```ts
import { createEditorAuth, createEditorSession, createEditorClient } from '@acms-subir/client';

const auth = createEditorAuth({ baseUrl: 'https://example.com', clientId: 'org.example.staff', redirectUri: 'org.example.staff://auth' });
const session = createEditorSession({ auth, storage /* get/set backed by expo-secure-store */, onSignedOut });
const api = createEditorClient({ baseUrl: 'https://example.com', session });

const { url, verifier, state } = await auth.start({ deviceName: 'Ana’s iPhone' });
// open `url` with expo-web-browser openAuthSessionAsync, then:
const { code } = auth.parseRedirect(returnedUrl, state);
await session.signIn(await auth.exchangeCode({ code, codeVerifier: verifier }));

const doc = await api.getDocument('post', id);
await api.saveDocument('post', id, { revision: doc.revision, fields: { title: 'New title' } });
```

在反应土著通行证 `crypto: { randomBytes, sha256 }` 从前催眠到 `createEditorAuth` (Web Crypto是默认). 刷新为单飞; 401在刷新后再试一次 然后 `onAuthError` 运行。 写作携带 页:1 `revision` 你上膛了(第409次有人抢救).

时间安排和设置 : `schedule({ from, to, resource? })` (至多63天) 预订、被封锁的时间、事件和资料), `createBooking()` (关于某人的) 代表, `rescheduleBooking(id, …)`, (中文(简体) ), `listResources()` 编号 : `getResource()` 编号 : `createResource()` 编号 : `updateResource()` 编号 : `deleteResource()`, (中文(简体) ), `createBookingLink()` 编号 : `updateBookingLink()` 编号 : `deleteBookingLink()`, (中文(简体) ), `blockTime()` 编号 : `unblockTime()`, (中文(简体) ), `listCalendars()` 编号 : `createCalendar()` 编号 : `updateCalendar()` 编号 : `deleteCalendar()`, (中文(简体) ), `listForms()` 编号 : `listFormResponses()`, (中文(简体) ), `getLinksPage()` 编号 : `saveLinksPage()`。 。 。 . `updateBookingLink(resourceId, linkId, { order: 0 })` 先放一个预订类型 在它的房间,一个它的预订页面和应用程序领先。 设置写入需要 `me().permissions.schedule.manage`; 窗体响应和 HTML 链接块需要 (a) 管理人或所有人(403人除外).

## 常见问题

### @acms-subir/client 是什么？

为ACMS公共移动API打入,无依赖客户端\.

### 如何安装 @acms-subir/client？

在项目目录运行 npm install @acms-subir/client，或使用 pnpm add / yarn add / bun add。

### @acms-subir/client 支持 TypeScript 吗？

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

### @acms-subir/client 支持 ESM 还是 CommonJS？

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

### @acms-subir/client 需要什么版本的 Node\.js？

package\.json 的 engines 字段要求 Node\.js \>=18。版本不符时 npm 默认只给出警告，开启 engine-strict 后会拒绝安装。

### @acms-subir/client 可以商用吗？

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

### @acms-subir/client 的下载量有多少？

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

### @acms-subir/client 还在维护吗？

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

