# @appport/services 中文文档

规范地址：https://npmcn.com/package/%40appport/services/

版本：0\.15\.0
npm 元数据更新时间：2026-11-04T13:45:04.440Z
中文译文更新时间：2026-11-06T12:21:55.727Z
软件包许可：ISC

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

原始 npm 页面：https://www.npmjs.com/package/%40appport/services
原文对照：https://npmcn.com/package/%40appport/services/?lang=original

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

- 包体积（解压后）：1.3 MB，206 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- 运行依赖：4 个
- peer 依赖：@feltdb/core \>=0\.16\.1 \<1\.0\.0
- 首次发布：2026-09-11T13:24:36.556Z
- 最新版本发布：2026-11-04T13:45:04.257Z
- 近 30 天下载量：3587（2026-10-07 至 2026-11-05，npm 官方下载统计）
- 已发布版本数：30（近 12 个月 30 个）

## 中文简介

API 密钥、 耐久的 Webhooks 、 背景任务、 以及 AppPort 应用程序的提供者中立密钥

## 安装

```sh
npm install @appport/services
```

也可以使用：`pnpm add @appport/services`、`yarn add @appport/services`、`bun add @appport/services`

## README

# Apport 服务

AppPort Services提供可持久运行的应用能力,它们坐落在AuthPort旁边.

服务集包括API Keys,Jobs, Schedule, Secrets, Webhooks, Files, 以及通知等. 通知由AppPort服务公司储存和交付;它们不是注意管理 层。 Attn可能会消耗它们以分别引起注意.

## 创建和运行应用程序

对于新的申请:

```sh
npx create-appport my-app
cd my-app
npm run dev
```

生成源仅包含业务处理器. 它不创建HTTP服务器,配置持久性,执行CORS/SSE,运行工作员,发送webhoks,或安装信号处理器.

对于现有应用程序:

在应用程序中安装软件包 :

```sh
npm install @appport/runtime
```

初始化每个功能的 AppPort, 或者只选择应用程序的用途 :

```sh
npx @appport/runtime init
npx @appport/runtime init --use api,webhooks,jobs
```

没有旗帜, `init` 询问哪些能力可以启用和默认每个是。 这个 `--use` 表格可用于脚本和 CI.

这就产生了两个应该执行的文件:

-   `appport.toml` 声明您的应用程序使用的 AppPort 能力 .
-   `feltdb.flow` 是您的应用程序的权威 FeltDB 合同 。 AppPort的内部模板留在npm包内;应用程序开发者不编辑或导入AppPort存储内部.

合同中的靴子AppPort:

```javascript
import { appport } from '@appport/runtime';

const app = await appport();

// Only capabilities declared in appport.toml are initialized.
await app.api.keys.createApiKey(/* ... */);
```

`appport()` 读取 `./appport.toml` 并拥有服务建设、持久性、审计基础设施和生命周期。 打电话 `await app.close()` 在优雅的关闭期间 获取未申报的能力将产生 `CapabilityNotDeclaredError` 和必要的声明,使之得以实现.

`appport.toml` 是权威的申请合同。 它在启动时被解析、验证、正常化并被冻结。 它宣布应用程序的身份、部署和州权力、租赁、HTTP/CORS、API密钥、网页快递、工作类型、事件、授权、可观察性、生命周期和发展默认。 哥哥们 `feltdb.flow` 作为权威性国家合同被部署到联邦贸易局.

仅包含的遗留文件 `use api`, (中文(简体) ), `use webhooks`,以及 `use jobs` 继续支持。 将其中一项扩展至可收回备份的峡谷合同,使用:

```sh
npx @appport/runtime config migrate
```

应用程序代码提供行为 :

```javascript
import { appport } from '@appport/runtime';

const application = await appport({
  routes: {
    'POST /invoices': async ({ body, tenantId }) => createInvoice(body, tenantId),
  },
  jobs: {
    'invoice.process': async (job) => processInvoice(job.payload),
  },
});

await application.publish('invoice.created', { id: 'inv-123' });
```

公众 `application.state` 财务报告和审定财务报表 `application.events` API提供提供者中立状态并订阅. 应用程序代码从未通过访问其私人数据库的能力到达.

运行时间拥有稳定管理合同:

```text
/_appport/health
/_appport/overview
/_appport/capabilities
/_appport/events
/_appport/api/keys
/_appport/webhooks
/_appport/jobs
```

卫生为公用. `/_appport/capabilities` 是未经认证的发现表面,并返回运行中的应用程序的挂载能力目录。 其他终点遵循合同的授权和租户规则。 事件使用 SSE 用于 `GET` 并发布域事件 `POST`; API 键,webhooks, 和工作支持运行时拥有的创建/列表操作.

`@appport/services` 提供CLI和能力实施,但只进口应用源 `@appport/runtime`现有应用程序可继续使用 `createServices()` 从 `@appport/services` 作为兼容的 API.

以常规命令运行您的应用程序, 例如 `npm run dev`。通过安装的二进制可操作的 CLI 命令 :

```sh
npx @appport/runtime api-key list --tenant acme
npx @appport/runtime webhook list --tenant acme
npx @appport/runtime job list --tenant acme
```

### 独立管理主机

您不需要应用程序来操作 AppPort 服务 . `serve` 启动拥有持久状态的管理主机, `ServiceGateway`,认证,管理路由器,以及 HTTP 服务器:

```sh
npx @appport/services init     # appport.toml + feltdb.flow for this deployment
npx @appport/services serve    # GET /v1/ui, /services, and the other management pages
```

它的束缚 `127.0.0.1:4100` 默认; 覆盖到 `--host` 编号 : `--port`。状态是相同的持久 FeltDB 部署 。 管理请求使用 AppPort API 密钥进行认证(`Authorization: Bearer <secret>`)或操作员身份适配器;授权留在AuthBoundry。 见 [文件/管理](https://npmcn.com/package/%40appport/services/docs/management.md)。 。 。 .

AppPort 管理其 FeltDB 运行时依赖性; 消费者不导入 `@feltdb/core` 或者AppPort的内部商店.

存储器定义 AppPort 能力 **租户范围 API 密钥**, (中文(简体) ), **持久网点**, (中文(简体) ), **长期工作** (包括附表),提供者中立 **秘诀** 元数据/生命周期和范围解析合同, **持久通知**,以及 **文件**. 遗留的运行时态用途 **`@feltdb/core`** (主机副本:0.16.1或后为0.x. 机密材料仍由经授权的提供者提供,从来就不是可耐用的AppPort状态.

```text
            Application
                 │
     ┌───────────┴───────────┐
     │                       │
  AuthPort             AppPort Services
     │                       │
identity/authz        API Keys, Webhooks, Jobs, Secrets
     │                       │
     └───────────┬───────────┘
                 │
              FeltDB
```

## 消费者 API

### 共享主机应用程序的 FeltDB 实例

已经拥有其应用程序的 FeltDB 实例的主机可以运行其中的每一项服务, 而不是让 AppPort 服务打开二审 :

```javascript
import { createFeltDB } from '@feltdb/core';
import { createServices } from '@appport/services';

const db = createFeltDB({ mode: 'local', namespace: 'invoices', path: './state' });
const services = createServices({ db, application: 'invoices', authorizer: authBoundry });
// Service stores, effect evidence, and services.transaction() all use `db`.
```

主机保留了所有权: AppPort Services从未关闭过它给过的一个实例. 这个 实例本身的部署模式( e)`db.capabilities().deployment`用于 模式依赖行为。 使用一份副本 `@feltdb/core` 对主机和此软件包: 它是 同伴依赖( e)`>=0.16.1 <1.0.0`),所以主机的复制是服务部使用的,npm安装 没有第二个.

要检查服务对象运行在哪个实例( 本地、 内存或远程) , 请使用 `services.db` (只读)或 `services.usesInstance(db)`比较身份; `services.ownsDb` 实值 `false` 对于提供主机的实例:

```javascript
assert.ok(services.usesInstance(db));
```

### 版本、健康、证据和配置转移

```javascript
import { VERSION } from '@appport/services';             // equals package.json "version"
import manifest from '@appport/services/package.json' with { type: 'json' };

const health = await services.health({ tenantId: 'acme' });
// health.status: 'ok' | 'degraded' | 'unconfigured'; health.services.webhooks.state; health.attention.failedWebhookDeliveries

const principal = services.identify({ principalId, principalType: 'user', tenantId, runId: requestId });
await services.invoke('files.write', input, { principal });
const { state, records } = await services.evidence.findByRunId(requestId); // state: 'none' | 'started' | 'completed' | 'failed'

const document = await services.configuration.export({ principal, environment: 'staging' });
const report = await other.configuration.import(document, { principal, dryRun: true });
```

-   `services.health()` ——服务状态和计数需要注意,从有界限的询问中得出. 见 [医生/保健](https://npmcn.com/package/%40appport/services/docs/health.md)。 。 。 .
-   `services.evidence.findByRunId()` ——记录在运行标识下的效果证据(呼叫者的相关标识),以及什么是 `started` 排行是指坠机后. 见 [文件/授权](https://npmcn.com/package/%40appport/services/docs/AUTHORITY.md#evidence)。 。 。 .
-   `services.configuration.export()` 编号 : `import()` ——环境之间的非秘密配置;秘密值永远不会离开. 见 [文件/配置](https://npmcn.com/package/%40appport/services/docs/configuration.md#export-and-import-between-environments)。 。 。 .
-   `webhooks.deliveries.read` 其本身的阅读网页传送的能力(`invoke('webhooks.deliveries.read', { status: 'failed' })`, (中文(简体) ), `GET /_appport/webhooks/deliveries`) (中文(简体) ).

现有认证的Express应用程序可以按其现有的服务实例挂起所支持的管理运行时间:

```javascript
import { createManagementRouter, createServices } from '@appport/services';

const services = createServices({ path: '.appport', application: 'invoices', authorizer: authBoundry, credentials: authBoundryCustody });
app.use(createManagementRouter({
  services,
  authority: services.gateway,
  authenticate: (request) => hostAuthentication(request), // identity only
}));
```

主机拥有认证,AuthBoundry拥有授权,而AppPort Services则在任何效果之前执行AuthBoundry的决定. **@appport/服务执行效果. 它不决定允许谁造成这些后果.** 见 [权力模式](https://npmcn.com/package/%40appport/services/docs/AUTHORITY.md), (中文(简体) ), [网络商店安全](https://npmcn.com/package/%40appport/services/docs/WEBHOOK-SECURITY.md),以及 [就业保障](https://npmcn.com/package/%40appport/services/docs/JOB-SECURITY.md). API 密钥管理需要 `apikeys.read`, (中文(简体) ), `apikeys.create`,以及 `apikeys.revoke` (单位:千美元) `apikeys.update`, (中文(简体) ), `apikeys.rotate`,以及 `apikeys.audit` 用于编辑、轮换和审计),总是使用经认证的租户,并返回仅用于创建和轮换答复的简单文字证书。 见 [可构成的管理运行时间](https://npmcn.com/package/%40appport/services/docs/management.md) 完全合同和独立/嵌入行为。 其他软件包可以通过 : [管理扩展](https://npmcn.com/package/%40appport/services/docs/extensions.md)。 。 。 .

出界证书使用只限服务器范围的协议. AppPort 定义了引用,上下文,生命周期,审计,出错,以及召回合同; AuthBoundry 授权和 AppBoundry 解决提供者持有的材料. AppPort船没有解析器,秘密商店,供应商适配器,或政策引擎,并暴露出没有浏览器的认证-值路由.

当主人的监护权是 `SecretCustody` (单位:千美元)`withSecret` 加号 `store` 财务报告和审定财务报表 `status`, (中文(简体) ), `credential.store` 收集一个值(授权、审计、版本、在FeltDB中从未重复或坚持)并将其交给主机,以及 `credential.status` 报告是否存在材料。 主机仍然拥有物质生活的地方;一个只解决器的主机得到 `CUSTODY_READ_ONLY`。 。 。 .

```javascript
await secrets.withSecret({
  reference: { secretId, tenantId: 'acme', provider: 'pipedrive' },
  context: { tenantId: 'acme', principalId: 'integration:pipedrive', purpose: 'person.lookup', authorizationRef },
}, async ({ value }) => callProvider(value));
```

见 [外出证书向导](https://npmcn.com/package/%40appport/services/docs/credentials.md) 拥有、授权交接、生命周期和故障语义.

见 [能力清单](https://npmcn.com/package/%40appport/services/docs/capabilities.md) 财务报告和审定财务报表 [能力授权摘要](https://npmcn.com/package/%40appport/services/docs/capability-authority.md) 综合能力矩阵和挂载状态规则.

合同确定可用的运行时间API:

```javascript
import { appport } from '@appport/runtime';

const app = await appport({ authorizer: authBoundry, credentials: authBoundryCustody });

// Identity comes from authentication; AuthBoundry authorizes each capability.
const principal = await app.authenticate(request);

// Machine identity (an API key identifies a caller; it carries no scopes)
await app.invoke('apikeys.create', { name: 'server-key' }, { principal });

// Durable outbound notifications, bound to a signing credential in AuthBoundry custody
await app.invoke('webhooks.register', {
  url: 'https://acme.example.com/webhooks',
  events: ['invoice.created'],
  signingCredentialRef: 'credential-ref:whsec_acme',
}, { principal });

// Durable deferred execution; the job runs as this principal and is re-authorized on every run
await app.invoke('jobs.create', {
  type: 'invoice.process',
  payload: { invoiceId: 'inv-123' },
  maxAttempts: 3,
}, { principal });
```

消费者不需要知道FeltDB在下面存在. 只有申报的能力才得以建立,它们共享一个持久的运行时间.

## 什么是Apport服务?

Apport 服务答案 :

> 该应用程序暴露出哪些操作能力?

AuthPort仍然回答身份,认证和授权问题.

## 见它的行动

完整的示例应用程序可在 `examples/services-demo/`它表明:

-   API 以租户范围为主的密钥认证
-   建立具有自动网签和工作意图的持久发票
-   使用重试和重放的 Webhook 送出
-   以租赁为基础的货币控制处理背景工作
-   租户与世隔绝和耐久性

见 [`examples/services-demo/README.md`](https://npmcn.com/package/%40appport/services/examples/services-demo/README.md) 五分钟的快速启动.

## 建筑

Apport 服务使用流程 `@feltdb/core` 合同语言)作为权威的持久计划. 包裹的内部 `appport.flow` 是模板,从中 `appport init` 生成应用程序的权威 `feltdb.flow`数字 :

**API 键垂直 :**

-   `ApiKeys` — API 密钥证书( 密钥仅以 scrypt hash 存储)
-   `ApiKeyPrefixes` ——高效秘密验证前置查询
-   `ApiKeyAuditEvents` ——查阅审计线索

**垂直 Webhoks :**

-   `WebhookEndpoints` - 已注册的网页接收器
-   `WebhookDeliveries` - 有重试状态的出货记录
-   `WebhookAuditEvents` - Webhook生命周期审计线索

**垂直任务 :**

-   `Jobs` 具有执行状态和租赁跟踪的个人工作
-   `JobSchedules` 经常性的工作定义
-   `JobAuditEvents` 工作执行审计线索

**垂直密钥 :**

-   `Secrets` - 租户范围合理的秘密身份和生命周期元数据
-   `SecretVersions` ——提供商参考和明确轮换版本
-   `SecretAuditEvents` - 创建、解决、轮换、撤销、退休和失败审计记录

秘密行动区分 `describeSecret` (仅元数据)来自 `resolveSecret` (授权提供者解决)。 机密值、 平白文本、 已解密的材料和提供者证书不是 `appport.flow` 或审计活动.

所有藏品均通过租户范围 `tenant_id` 字段为 `tenant_idx` 用于高效查询.

### 依赖方向

```
feltdb.flow (generated authoritative application contract)
       ↓
TypeScript implementation
       ↓
FeltDB (@feltdb/core, the host's copy)
```

流程合同在测试时进行解析和验证。 TypeScript商店(FeltDbApiKeyStore,FeltDbWebhook EndpointStore等)直接针对FeltDB收藏执行合同语义.

## 为什么API密钥和AuthPort分开?

API 密钥认证机器主机. 没错 **没有** 替换 AuthPort 授权.

```text
API key
   ↓
machine principal
   ↓
AuthPort authorization
   ↓
resource/action decision
```

## 安装

```bash
npm install
npm run build
```

`@feltdb/core` 是一个对等依赖:在 Services 旁安装( 最近的 0.x 发布; npm 7 和 服务处使用这个副本:

```json
{
  "peerDependencies": {
    "@feltdb/core": ">=0.16.1 <1.0.0"
  }
}
```

## 我如何启用 API 密钥 ?

用 FeltDB 的真正部署模式创建服务 :

```ts
import { createApiKeyService } from '@appport/services';

const service = createApiKeyService({
  mode: 'local',
  namespace: 'appport-services',
  path: './.feltdb/appport-services'
});
```

远程 FeltDB 部署可以使用相同的导出部署字段 `resolveFeltDBDeployment()` 明白吗.

## 我该怎么创造呢?

```ts
// `operator` is a verified principal (host authentication or an existing API key).
const created = await service.createApiKey({ name: 'production' }, operator);

console.log(created.secret); // only returned once
```

API 键可以识别一个呼叫器及其应用. 它没有瞄准镜: AuthBoundry决定 打电话者可以做什么。 通过 `scopes` 以 迁移错误[文件/授权](https://npmcn.com/package/%40appport/services/docs/AUTHORITY.md#migration)) (中文(简体) ).

可以给出密钥 **限制**,这个天花板只缩小什么 AuthBoundry允许,加上一个过期. 密钥可以编辑、旋转并使用 可选重叠,一次或批次撤销一次,并审计:

```ts
const key = await services.apiKeys.createApiKey({
  name: 'reporting-worker',
  restrictions: { capabilities: ['files.read'], resources: [{ type: 'file', attributes: { owner: 'reporting' } }] },
  expiresAt: new Date(Date.now() + 90 * 86_400_000),
}, operator);

const next = await services.apiKeys.rotateApiKey({ id: key.id, overlapSeconds: 300 }, operator); // next.secret shown once
await services.apiKeys.updateApiKey({ id: next!.id, name: 'reporting-worker-v2' }, operator);
const events = await services.apiKeys.listApiKeyAuditEvents({ credentialId: next!.id }, operator);
await services.apiKeys.revokeAllApiKeys({ createdBy: 'ops@example.com' }, operator);
```

撤销和限制更改适用于每次的下一个操作 进程。 见 [文档/按键](https://npmcn.com/package/%40appport/services/docs/api-keys.md) 对于许可模式, 旋转语义,校验键,出错,和迁移注释.

CLI( 通过 `APPPORT_AUTHORITY` 或者说 `APPPORT_API_KEY`:

```bash
APPPORT_AUTHORITY=./authority.mjs appport api-key create --tenant tenant-123 --name production
```

## 应用程序如何认证?

### 备选案文1:框架中立的HTTP适配器

对于使用普通节点HTTP请求/响应语义的应用程序:

```ts
import { createApiKeyAuth } from '@appport/services';

const auth = createApiKeyAuth({ service });

// Optional authentication (returns null if missing/invalid)
const principal = await auth.authenticate(request);

// Required authentication (throws if missing/invalid)
const principal = await auth.require(request);
```

适配器提取 `Authorization: Bearer <api-key>` 页眉并返回一个 `AuthenticatedPrincipal`数字 :

```ts
interface AuthenticatedPrincipal {
  principalId: string;
  principalType: 'api_key';
  tenantId: string;
  applicationId: string;
  credentialId: string;
  verifiedBy: 'api_key';
}
```

校长有品牌:服务只接受校长 认证路径, 绝不是对象字符串或演员字符串 .

### 备选案文2: 中间快件

对快件应用程序:

```ts
import { apiKeyAuth, requireApiKeyAuth } from '@appport/services';

const app = express();

// Optional authentication
app.use(apiKeyAuth(service));

app.get('/invoices', async (req, res) => {
  const principal = req.auth; // null if missing/invalid
  if (!principal) {
    return res.status(401).json({ error: 'Unauthenticated' });
  }
  // Handle request with principal
});

// Or, require authentication
app.use(requireApiKeyAuth(service));

app.get('/protected', async (req, res) => {
  const principal = req.auth; // guaranteed, or middleware rejects
  // Handle request with principal
});
```

中间器件将本金附在 `req.auth` ,并通过 `req.authContext`。 。 。 .

### 租户安全

如果您的申请独立接受租户情况,请对照委托人确认:

```ts
import { assertTenant } from '@appport/services';

assertTenant(principal, tenantIdFromRequest); // throws if mismatch
```

### 低级接头符提取

关于自定义框架:

```ts
import { authenticateBearerToken } from '@appport/services';

const principal = await authenticateBearerToken(
  authorizationHeader,
  service,
);
```

## 授权是怎么发生的?

认证返回一个身份 。 授权属于 AuthBoundry: every 服务效果由配置授权 `ServiceAuthorizer` 摆在它面前 证书只有在决定之后才能解决,证据是 写给 FeltDB 。 见 [文件/授权](https://npmcn.com/package/%40appport/services/docs/AUTHORITY.md)。 。 。 .

## 我该怎么用网球?

Webhooks为外部的HTTP端点提供可持久签名的发送.

### 设置

```ts
import { createServices } from '@appport/services';

const services = createServices({ path: './.appport', application: 'invoices', authorizer: authBoundry, credentials: authBoundryCustody });
const service = services.webhooks;
```

### 注册终点

```ts
const endpoint = await service.createWebhookEndpoint({
  url: 'https://customer.example.com/webhooks',
  events: ['invoice.created', 'invoice.paid'],
  // The signing secret lives in AuthBoundry custody; the endpoint stores only the reference.
  signingCredentialRef: 'credential-ref:whsec_customer',
}, principal);

// Private, loopback, link-local, and metadata destinations are rejected; redirects are never followed.
// See docs/WEBHOOK-SECURITY.md.
```

### 释放事件

```ts
await service.emitWebhookEvent({
  type: 'invoice.created',
  payload: { id: 'inv-456', amount: 100 }
}, principal);

// Delivery records created automatically for matching endpoints
```

### 工人:提供网络呼号

```ts
const delivery = await service.getWebhookDelivery(tenantId, deliveryId);
const result = await service.deliverWebhook(tenantId, deliveryId);

if (result.success) {
  // Delivered
} else {
  // Failed or queued for retry
}
```

交货由HMAC-SHA256签署. 每次交付包括:

```json
{
  "id": "delivery-uuid",
  "eventId": "event-uuid",
  "type": "invoice.created",
  "data": { "id": "inv-456", "amount": 100 },
  "timestamp": "2026-09-10T22:30:00Z"
}
```

页眉 : `X-AppPort-Signature: <hex-encoded-hmac-sha256>`

申请阅读 `webhooks.deliveries.read` (每拨电话最多200次)

```ts
const failed = await services.invoke('webhooks.deliveries.read', { status: 'failed', limit: 50 }, { principal });
const one = await services.invoke('webhooks.deliveries.read', { id: deliveryId }, { principal });
```

消费者核查:

```ts
import crypto from 'node:crypto';

function verify(payload, signature, secret) {
  const expected = Buffer.from(crypto.createHmac('sha256', secret).update(payload).digest('hex'));
  const actual = Buffer.from(signature);
  return expected.length === actual.length && crypto.timingSafeEqual(expected, actual);
}
```

### 重试策略

-   **第2条** 已交付
-   **408 / 429 / 5xx / 网络故障** + 重试,使用有边界的指数回放
-   **3⁄4 ̄ ̧漯b** → 失败( 最终) : 从未遵循过方向
-   **其他 4xx (中文(简体) )** 失败( 最终)
-   **拒绝授权** — 失败; 提供者从未被调用
-   **AuthBundry 不可用/ 超时** ——以后再审;提供者从不叫
-   **最大尝试** − 5 (可配置)

无法执行可手动重放 :

```ts
await service.replayWebhookDelivery(tenantId, deliveryId, principal); // webhooks.replay
```

### 禁用端点

```ts
await service.disableWebhookEndpoint({ id: endpoint.id }, principal); // webhooks.remove

// No new deliveries created; existing pending deliveries blocked
```

网络呼号是 **至少一次交货**消费者应当治疗 `eventId` 编号 : `deliveryId` 作为一能识别符.

## 我该怎么用耐用的工作?

工作提供可靠的背景任务执行,提供再试支助,通过租赁进行货币控制,以及从工人倒闭中自动恢复.

### 设置

```ts
import { createServices } from '@appport/services';

const services = createServices({ path: './.appport', application: 'invoices', authorizer: authBoundry });
const service = services.jobs;
```

### 登记处理器

```ts
service.register('invoice.process', async (job, execution) => {
  const { invoiceId } = job.payload as { invoiceId: string };
  // The job runs as its durable principal; every effect is authorized again.
  await services.invoke('notifications.send', { recipient: 'finance', type: 'invoice.processed', title: invoiceId }, { principal: execution.principal });
});
```

### 选中任务

```ts
const job = await service.enqueue({
  type: 'invoice.process',
  payload: { invoiceId: 'inv-456' },
  maxAttempts: 3,
  delegationId: 'del_quote_agent', // optional AuthBoundry delegation, checked on every run
}, principal);
```

没有永久校长的工作从未被执行,被撤销的代表团 停止下一个运行。 见 [医生/](https://npmcn.com/package/%40appport/services/docs/JOB-SECURITY.md)。 。 。 .

### 执行工作(工人)

```ts
const result = await service.executeJob(tenantId, jobId, 'worker-1');
if (result) {
  // Job completed successfully
}
```

### 经常性工作时间表

```ts
const schedule = await service.scheduleRecurring({
  type: 'invoice.reconcile',
  payload: { batchSize: 100 },
  interval: '1h',
}, principal);
```

### 重试策略

-   **成绩** 工作标志完成
-   **失败** + 用指数反转重试(2^ attremptCount)
-   **最大尝试超过** 工作标记失败
-   **工人租赁期满** • 有资格由另一名工人恢复的工作
-   **手动重试** 将失败的工作重置为待决状态

工作是 **持久和专有**. 工作状态在重新启动过程中幸存下来;工人通过基于版本的乐观锁定要求工作.

## 我怎么发通知?

通知是指通过一个或多个送货渠道传递的持久应用事件. 申请决定发生了什么。 AppPort Services决定如何交付持久通知.

```
Application       = meaning
AppPort Services  = delivery infrastructure
FeltDB            = durable state and evidence
Attn              = attention and judgment
```

```ts
const { notification, deliveries } = await app.notifications.notify({
  tenantId: 'tenant-a',
  recipient: 'user-1',
  type: 'monitor.triggered',
  title: 'Status changed',
  source: { type: 'monitor', id: 'monitor-7', eventId: 'observation-123' },
  channels: ['browser', 'in-app'],
}, principal);

await app.notifications.markRead('tenant-a', notification.id, recipientPrincipal);
await app.notifications.acknowledge('tenant-a', notification.id, recipientPrincipal);
```

-   每个频道都有自己的持久交付记录(`pending`, (中文(简体) ), `delivered`, (中文(简体) ), `failed`, (中文(简体) ), `retrying`) (中文(简体) ).
-   重复相同的源事件返回相同的通知.
-   现有就业基础设施的回收工作.
-   `expiresAt` 停止过时的交货,但保留记录作为证据.
-   证书(密码、饼干、授权信头、信使、API密钥、私人密钥)被拒绝.
-   关闭浏览器不会销毁通知 。 浏览器是一个传送通道,一个新的 会话追赶从 `GET /notifications?unread=true`。 。 。 .

电子邮件、移动推取、短信和网络呼取频道是注册在 `registerChannel()`。 。 。 . 见 [文件/通知.md](https://npmcn.com/package/%40appport/services/docs/notifications.md) 用于资源模型、生命周期、HTTP API、, 和敏感数据边界.

## 耐久状态住在哪里?

Apport 服务公司通过主机直接在 FeltDB 收藏中存储所有状态 `@feltdb/core` (0.16.1或后为0.x).

```text
feltdb.flow (generated Flow contract)
      │
      ▼
AppPort Services
      │
      ├─→ FeltDbApiKeyStore (ApiKeys, ApiKeyPrefixes, ApiKeyAuditEvents)
      ├─→ FeltDbWebhookEndpointStore (WebhookEndpoints, WebhookAuditEvents)
      ├─→ FeltDbWebhookDeliveryStore (WebhookDeliveries)
      ├─→ FeltDbJobStore (Jobs, JobSchedules, JobAuditEvents)
      └─→ Secrets protocol (Secrets, SecretVersions, SecretAuditEvents)
      │
      ▼
 @feltdb/core (the host's copy)
      │
      ▼
 real FeltDB
```

## 秘密怎么办?

对于 AppPort Secrets,边界为:

```text
application .flow → capability contract → AuthBoundry authorization → secret provider
```

申请书 `secrets` ,但从不将暗中值输入 `.flow`AppPort只公布身份、生命周期、租户、审计和提供商参考元数据; AppBoundry 提供商在AuthBoundry授权后执行.

AppPort是宣告性的:它定义了"秘密能力"的含义. AppBoundry决定如何运行,AuthBoundry决定谁可以使用. 保密合同没有规定提供商、储存、缓存、注射或授权执行.

-   以密码随机性生成
-   创建时返回一次
-   持续前用碎屑
-   从未存储在耐用 API- key 记录
-   不包括在审计记录中

## 仓库布局

-   `appport.flow` ——用于生成消费者的内部包模板 `feltdb.flow` 合同
-   `/src/api-keys` ——API-键型和语义服务
-   `/src/authority` - 政策执行点:能力表、经核实的主要负责人、执行背景、AuthBoundry协议、目的地政策、效果证据
-   `/src/webhooks` - Webhook 模型、服务和签名助手
    -   `models.ts` - 终点、交货、活动合同
    -   `service.ts` - Webhook生命周期和交付协调
    -   `secrets.ts` - 秘密生成、加密、HMAC签名
-   `/src/jobs` - 工作模式、服务和执行
    -   `models.ts` - 工作和时间表合同
    -   `service.ts` 工作周期、重试和时间安排
    -   `store.ts` ——FeltDB后置存储并乐观地锁定
    -   `worker.ts` - 与投票同时执行工作
-   `/src/storage` — FeltDB 支持的存储和审计槽
    -   `api-keys.ts` API 密钥存储
    -   `webhooks.ts` — Webhook 端点/交付存储
-   `/src/runtime` ——HTTP/Express认证适配器和取出 Bearer-token
    -   `api-keys.ts` ——取出持证人证
    -   `http-adapter.ts` - 框架中立的HTTP适配器
    -   `express-middleware.ts` ——快递中件
-   `/src/contract` - AuthPort主要合同
-   `/tests` ——节点/脚本测试
    -   `contract.test.ts` ——流量DSL验证和收集验证
    -   `api-keys.test.ts` - 核心服务测试
    -   `http-adapter.test.ts` - HTTP适配器测试(安全、隔离、租户安全)
    -   `integration-app.test.ts` 真正的 HTTP 应用程序固定
    -   `express-integration.test.ts` ——快递中件集成测试
    -   `webhooks.test.ts` - Webhook 生命周期和耐用性测试
    -   `webhooks-delivery.test.ts` - HTTP的交付、签字、重试和货币测试
    -   `jobs.test.ts` 工作周期、时间安排和耐久性测试
    -   `jobs-execution.test.ts` - 同时执行、租赁和回收测试
-   `/docs` ——建筑说明

## 常见问题

### @appport/services 是什么？

API 密钥、 耐久的 Webhooks 、 背景任务、 以及 AppPort 应用程序的提供者中立密钥

### 如何安装 @appport/services？

在项目目录运行 npm install @appport/services，或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖：@feltdb/core（\>=0\.16\.1 \<1\.0\.0）。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖，Yarn 需要手动安装。它还提供命令行工具 appport-services，可以用 npx appport-services 直接运行。

### @appport/services 支持 TypeScript 吗？

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

### @appport/services 支持 ESM 还是 CommonJS？

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

### @appport/services 可以商用吗？

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

### @appport/services 的下载量有多少？

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

### @appport/services 还在维护吗？

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

