# @crowdsource\.you/core 中文文档

规范地址：https://npmcn.com/package/%40crowdsource.you/core/

版本：4\.0\.0
npm 元数据更新时间：2026-11-04T01:39:11.375Z
中文译文更新时间：2026-11-11T01:50:49.523Z
软件包许可：MIT

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

原始 npm 页面：https://www.npmjs.com/package/%40crowdsource.you/core
原文对照：https://npmcn.com/package/%40crowdsource.you/core/?lang=original
项目主页：https://github.com/OxyHQ/CrowdSource/tree/main/packages/core#readme
源码仓库：https://github.com/OxyHQ/CrowdSource

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

- 包体积（解压后）：1.6 MB，346 个文件
- 模块格式：ESM + CJS
- TypeScript 类型：包内自带
- 运行依赖：1 个
- peer 依赖：express \>=4\.18\.0 \<6，postgres ^3\.4\.9，@oxy\.so/db ^1\.4\.0，drizzle-orm ^0\.45\.4，@crowdsource\.you/contracts ^1\.1\.0
- 首次发布：2026-09-19T04:40:40.583Z
- 最新版本发布：2026-11-04T01:39:11.022Z
- 近 30 天下载量：28079（2026-10-11 至 2026-11-09，npm 官方下载统计）
- 已发布版本数：9（近 12 个月 9 个）

## 中文简介

Crowd Source集成:根部的API客户端,以及Express Webhook接收器,交易出柜和子路径后方的测试沙盒

## 安装

```sh
npm install @crowdsource.you/core
```

也可以使用：`pnpm add @crowdsource.you/core`、`yarn add @crowdsource.you/core`、`bun add @crowdsource.you/core`

## README

# @ crowdsource.you/core (英语)

人群源集成,在一个包中. 类型脚本客户端 温和 API 是根导入; Express Webhook 接收器 交易发包和测试沙盒是同一包的切入点.

## 入境点

| 导入 | 这是什么 | 它需要安装的东西 |
| --- | --- | --- |
| `@crowdsource.you/core` | API客户端: 编译一个 Case 信封, 存档报告, 读取一个案件或决定, 注册一个 Webhook endpoint. | 仅此而已 `@crowdsource.you/contracts` |
| `@crowdsource.you/core/express` | 网球接收器:生体捕捉,HMAC验证,回放保护,打出事件. | `express` |
| `@crowdsource.you/core/outbox` | 应用半:交易发包箱,交付后再审,决定申请和一能执行. | `express`, (中文(简体) ), `@oxy.so/db`, (中文(简体) ), `drizzle-orm`, (中文(简体) ), `postgres` |
| `@crowdsource.you/core/outbox/postgres` | 后面的 PostgreSQL 商店 `/outbox`:表定义和商店本身. | 见上文 |
| `@crowdsource.you/core/testing` | Fixtures,一个网钩模拟器和一个在流程中的沙盒. | 合同以外的任何规定 |

**将根导入到其中 `express`, (中文(简体) ), `drizzle-orm`, (中文(简体) ), `postgres` 或者说 `@oxy.so/db`。 。 。 .** 它们是可选同伴依赖关系,仅通过 `/express` 财务报告和审定财务报表 `/outbox`,所以一个应用程序,用于提交报告,没有其他 安装客户端和合同包,然后停在那里。 这里面什么都没有 软件包导入 `@oxy.so/core`: 一个Oxy服务手在自己的 `OxyServer`这是 接收器和收发箱是子路径 而不是秒 软件包 : 此软件包没有任何版本可以与自己产生分歧.

`@crowdsource.you/contracts` 保留一个自己的包,并是一个 **端点 依赖性** 在这里,你宣布它 并拥有它的版本。 这是有意的: 如果存在两个副本,则定义了每个类型 在一棵树中, 编译器在每次发送失败时都会完全不报告 在运行时间。 一份,你选的 也是半个反动的本地UI 导入时不需要在电话捆绑中登陆服务器客户端.

## 曾经被称为什么

5个包裹 `@oxy.so` 成为两个在 `@crowdsource.you`代码 不更改, 没有重命名导出; 相同的版本编号带有相同的 新名称下的代码.

| 在此之前 | 现在 |
| --- | --- |
| `@oxy.so/crowdsource-contracts` | `@crowdsource.you/contracts` |
| `@oxy.so/crowdsource` | `@crowdsource.you/core` |
| `@oxy.so/crowdsource-express` | `@crowdsource.you/core/express` |
| `@oxy.so/crowdsource-app` | `@crowdsource.you/core/outbox` |
| `@oxy.so/crowdsource-app/postgres` | `@crowdsource.you/core/outbox/postgres` |
| `@oxy.so/crowdsource-testing` | `@crowdsource.you/core/testing` |

旧名不发表为shims 永远不会是: 六被抛弃 登记册上的软件包,每个未来的读者花费超过6次迁移 请求花费我们一次.

## 安装

```bash
bun add @crowdsource.you/core @crowdsource.you/contracts
```

添加 `express` 如果你收到网络呼声 `@oxy.so/db drizzle-orm postgres` 若为 你用收发箱.

## 客户

## 整个整合

```bash
CROWDSOURCE_SERVICE_KEY=app_…:csk_…:…
```

```ts
import { CrowdSource } from '@crowdsource.you/core';

const crowdsource = new CrowdSource();

await crowdsource.reports.create({
  externalReportId: report.id,
  reportedBy: { oxyUserId: session.sub },
  subject: {
    externalId: post.id,
    type: 'social.post',
    author: { oxyUserId: post.authorId },
  },
  content: post.text,
  allegations: ['harassment.targeted_abuse'],
});
```

一个环境变量和正在报告的对象。 这就是整个 用于普通案件.

## 客户填写什么,所以你不必

| 为你而作 | 从 |
| --- | --- |
| `applicationId` | ——没有通过一个选项 |
| 大小写信封、其资源标识及其关系 | 您描述的主题、内容、附件和上下文 |
| `sha256` 每个内置资源 | 内容本身 |
| 主要约束及其假名参考 | 你命名的身份 散开 |
| 身份约束证明(第11.14节) | 氧化物 `sub` - 签到Oxy已经生产出来 |
| `policy` | `DEFAULT_POLICY`,一个被固定的不可改变版本 |
| `privacy` | 30天(第13.6节)和第7.5节的社区审查限制 |
| `Idempotency-Key` | `report.<externalReportId>` (附录D) |
| 基础 URL 、 超时、 限定重试 | 默认 |

该表格中的任何内容都可以在每份报告中被推翻。 里面没有东西.

## 环境

| 变量 |  |
| --- | --- |
| `CROWDSOURCE_SERVICE_KEY` | 要求第三方这样做。 一个值是一个集成配置 。 Oxy 服务用途 `crowdSourceForOxyService()` 而不是配置什么——见下文"Oxy自己的服务". |
| `CROWDSOURCE_BASE_URL` | 可选的 覆盖服务主机——将其设定为仅指向本地后端. `http://` 已接受为 `localhost` 并且拒绝了其他任何要求, 因为送达的证明书是必须轮换的证明书. |

有一个 **没有** `CROWDSOURCE_APP_ID` 永远不会有。 申请a 报表属于证书;见下文“服务密钥”.

## 规则

-   **报告从您自己的收发箱发送,从来不从请求处理器发送.** §7.1: 从您的应用程序中取出 2xx 表示报告是用一个 持久地重试路径——而不是一个给 Crowd Source 的呼叫成功. 每一个错误 客户端投掷载体 `retryable`,这是唯一的东西 一个发货箱工人 需要从它.
-   **`applicationId` 来自于证书.** 这不是一个选择 客户端,不是报表上的字段,也不是信封输入可以 携带.
-   **复发同相报已.** 一样 `externalReportId` 带有 _不同_ 机体为409型,不能重试,意味着有效载荷必须 变化.
-   **证据从未从人群来源获得持久URL.** 资产一无所有 氧化 `fileId`; 这里没有方法返回与字节的链接(§12.10). `asset.url` 实值 只有来源,而且从未获得.
-   **仅限服务器侧.** 一个服务证书 是整个温和流; 此软件包取决于 `node:crypto` 并永远不能到达浏览器或浏览器 移动捆绑.

## 奥克西自己的服务

```ts
import { crowdSourceForOxyService } from '@crowdsource.you/core';
import { oxy } from './oxy'; // the process's one OxyServer

const crowdsource = crowdSourceForOxyService({ oxy });
```

`oxy` 需要: 工厂呼叫 `oxy.createServiceClient` 与人群源代码 注册受众 `oxy-service:6a6947b30fe72510b411252f` 永远不要建造一个 `OxyServer` 因此,一个过程保持 完全一个工作特征和一个 符号缓存。 自己的工作量不使用应用程序范围、 API 密钥或权限 提示; 无效或被撤销的工作量权限失败请求并遵循 耐用发件箱重试策略 . `baseUrl` 本地接收器的分数和 `logger` 报告约束. `requester` 接受带有当前账户的账户 会话、 任务或电子请求器上下文; 此客户端属于一个 请求- `dispose()` 当它完成。 每一次通话都会建一个新的客户端,所以请稍等 您的进程共享一个模块级常数.

**4.0.0 已生产 `oxy` 所需经费.** 3.x 建造了一个秒 `OxyServer` 当它是 省略,除了已经运行的应用程序; `resetCrowdSourceForOxyService` 与隐藏的共享客户端 `@oxy.so/core` 财务报告和审定财务报表 `@aws-sdk/credential-provider-node` 不再是同龄人.

接收器使用共享服务中间软件,验证现场身份,以及 解决其租户从活动存储的应用程序和组织约束力. 删除或中止约束拒绝了下一个请求。 审计记录 列出实际工作量证明。 授权申请 表面;工作人员角色和优雅的节制行动仍然分开. 外部 Crowd Source 密钥和外部 Oxy 客户密钥保留其真实 范围限制和撤销检查,包括属于官方应用程序的密钥. 中央有限遗产工作量桥梁是临时性的; 人群来源不 推论自己的权威来自品牌或遗产标志级别.

`applicationId` 解决 `GET /v1/applications/me`; 呼叫者无法选择 租户. `CROWDSOURCE_SERVICE_KEY` 仍然是外部一体化的道路. `OXY_SERVICE_API_KEY` 财务报告和审定财务报表 `OXY_SERVICE_API_SECRET` 不配置自己的访问.

## 服务密钥

人群源代码一起发布三个值——应用程序,证书id 还有秘密 客户把它们当作一个不透明的字符串, `applicationId:credentialId:secret`,所以一个集成配置一个变量 客户端读取应用程序 从证书,而不是被告知 这个. `formatServiceKey()` 从何来构建字符串 `issueApplicationCredential` 返回.

## 附上图像、视频或文档

将字节通过 **氧化介质窒息点** 与您的申请 Oxy certain, 然后通过空文件 ID:

```ts
await crowdsource.reports.create({
  externalReportId: report.id,
  subject: { externalId: post.id, type: 'social.post' },
  content: post.text,
  attachments: [
    {
      type: 'image',
      asset: {
        fileId: post.imageFileId,          // bare Oxy file id — never a URL
        mimeType: 'image/jpeg',
        sha256: `sha256:${digestOf(bytes)}`,
        url: post.remoteImageUrl,          // optional provenance. Never fetched.
      },
    },
  ],
  allegations: ['harassment.targeted_abuse'],
});
```

**人群来源没有自己的上传路线,这是故意的.** 证据 生活在Oxy介质的后方 扼杀了整个生态系统 所以没有 第二位的字节是,没有预先签名的 URL 来泄漏,也没有桶来配置 . 之前的释放运出a `uploads` 用于预签名流程的客户端 它在建成前就被取代了,它已经消失.

`asset.url` 是一个 **出处记录和永远不取目标**联邦 邮局的图像真的住在别处 所以记录它在哪里是有用的 - 但没有什么能解决它。 找到它会告诉主机准确的时间 内容正在审查中,并将提供活字节,而不是第5.6段 要求将案件钉住.

说明 `asset.sha256` 需要,所以应用程序总是已经持有字节 它正在报道。 让他们通过窒息点 要求什么新的.

`reports.create`, (中文(简体) ), `reports.get`, (中文(简体) ), `cases.get`, (中文(简体) ), `decisions.get`, (中文(简体) ), `webhookEndpoints.register` 财务报告和审定财务报表 `webhookEndpoints.rotateSecret` 都准备好了.

## 在网络上注册 您的决定到达

裁决是在网路上提出的,而不是通过投票提出的 `@crowdsource.you/core/express`告诉人群源头在哪里交货,并存储 秘诀是:

```ts
const endpoint = await crowdsource.webhookEndpoints.register({
  url: 'https://example.com/webhooks/crowdsource',
  eventTypes: ['case.decided'],
});

if (endpoint.secret) {
  // The ONLY time this value exists outside CrowdSource. Persist it now, as
  // CROWDSOURCE_WEBHOOK_SECRET, before doing anything else.
  await secrets.put('CROWDSOURCE_WEBHOOK_SECRET', endpoint.secret.value);
}
```

`secret` 只有在这个电话点燃时才出现。 重新登记已存在的 URL 返回终点为 **没有** 秘密,这是为什么 安全运行 在每一个靴子上,它永远不会 无效的秘密,你的运行过程是 核实。 结果是,重新登记不能 _恢复_ 一个秘密 您无法存储 :

```ts
const rotated = await crowdsource.webhookEndpoints.rotateSecret(endpoint.webhookEndpointId, {
  overlapSeconds: 28_800, // 0 for an immediate cutover, which is what a leak needs
});
// Serve both until rotated.previousSecret.expiresAt and no delivery is dropped.
```

证书需要.. `crowdsource:webhooks:manage` 范围。 没有名单, 回读或删除路由——API正好服务于这两个路由——所以是一个集成 目前无法列举其登记的内容.

* * *

# `@crowdsource.you/core/express`

(原始内容存档于2018-10-21). Accessed Crowd Source webhoks, safely, in Express.

## 整个整合

```bash
CROWDSOURCE_WEBHOOK_SECRET=…
```

```ts
import { crowdsourceWebhooks } from '@crowdsource.you/core/express';

app.post('/webhooks/crowdsource', crowdsourceWebhooks({
  on: {
    'case.decided': async (event) => {
      await moderationQueue.add(event.id, event.data);
    },
  },
}));
```

没有 `express.raw`没有身体保护者的命令 没有秘密管道 没有签名密码. `event` 缩小为 `case.decided` 联 合 国 `data.decision` 是输入的 `Decision`。 。 。 .

`express` 是一个可选的对等点( e)`>=4.18.0 <6`)——可选,因为消费者 只有文件报告导入根并从未到达此切入点,以及 安装网络框架来做到这一点是荒谬的。 达到 `/express` 无 `express` 安装失败, 导入时未找到模块, 即 高音版本的交易.

## 秘密从何而来

`CROWDSOURCE_WEBHOOK_SECRET` 已刻入 **一次**,通过答复 `crowdsource.webhookEndpoints.register(...)` 告诉"人群"的电话 何处交付:

```ts
const endpoint = await crowdsource.webhookEndpoints.register({
  url: 'https://example.com/webhooks/crowdsource',
  eventTypes: ['case.decided'],
});
endpoint.secret?.value; // store it now; nothing returns it again
```

重新登记同样的 URL 薄荷 没有什么, 所以它无法恢复一个秘密 您 弃 -- `crowdsource.webhookEndpoints.rotateSecret()` 就是这样.

## 环境

| 变量 |  |
| --- | --- |
| `CROWDSOURCE_WEBHOOK_SECRET` | 活动签名秘密. 也作为 `secret` 选项. |
| `CROWDSOURCE_WEBHOOK_SECRET_PREVIOUS` | 秘密正在退休。 在轮换重叠时设置它——两者均在存在时被接受,这就使得轮换一文不值. 之后清理干净 `previousSecret.expiresAt`。也可以确定为 `previousSecret` 选项. |

## 为什么没有生身的阶梯 你错了

运送破损的网页接收器最相似的方式是验证签名 `JSON.stringify(req.body)`它通过开发者手写的每个有效载荷 并且在格式不同的第一次实际交货上失败——或者更糟的是, 接受一个假身体 碰巧重排相同.

因此,这个中间软件读取请求流本身, 当一些上游的东西 已经消耗了.. `express.json()` 全球挂载,这是正常的 特快应用的形状——它 **拒绝** 通过您的错误处理器而不是 重建字节. `express.raw()` 在它之前,还有 `express.json({ verify })` idiom既得到承认,也得到支持.

## 它的保证

|  |  |
| --- | --- |
| 签署 | HMAC-SHA256 完毕 `timestamp + "." + rawBody`比较 `timingSafeEqual` (第10.8节) |
| 新鲜度 | ±5分钟,在 **两者** 方向 |
| 重播 | 每个事件 id 的一次索赔; 扔出去的处理器 §10.9 的重试仍然有效 |
| 旋转 | `previousSecret` 被与活动对象一起接受,所以旋转不会减少任何东西 |
| 转发相容性 | 承认和忽略该集不处理的事件类型(§10.11) |
| 隐私 | `onRejected` 获得理由,而别无他法——从没有身体,头部或签名 |

拒绝送货的人回答401,而从未回答2xx,所以它停留在发送者的重试上 而不是按处理方式退休.

## 各种情况之间的重复

默认商店正在处理中 。 每个负载平衡器后面有两个实例 他们自己的,所以重新交付降落 在另一个实例上,是不重复的. 这通常很好——第7.6节规定,申请应负责记录什么 所以执行应该是一意孤行的 狦琌 不,通过一个共享 `store` (Redis,您自己的数据库)执行 `claim`编号 :`release`。 。 。 .

## 试试看

`@crowdsource.you/core/testing`模拟器发送真正签名的事件,以及 可以故意送出腐烂,伪造和篡改的. 让你的 接收器 **拒绝** 这些是网球测试的一半 证明了什么.

* * *

# `@crowdsource.you/core/outbox`

PostgreSQL应用程序的应用-侧式Crowd Source集成. 这个 从解剖角度存储一个报告及其发件箱,并发送带有复读的报告, 核实已签名的webhook机构,适用修改顺序和可逆转记录 执行一次.

这个切入点将所有东西存储在 PostgreSQL.

## 安装

```sh
bun add @crowdsource.you/core @crowdsource.you/contracts \
  @oxy.so/db drizzle-orm postgres express
```

4个运行时的对等器在软件包上是可选的,所以这是一条线 让他们变得真实。 一个从不进口的消费者 `/outbox` 没有安装它们.

## 图表和存储

收养申请拥有其报告表。 传播提供的节制 并使用三个软件包自有的表格, 网络事件分类账和执法分类账.

```ts
import postgres from 'postgres';
import { drizzle } from 'drizzle-orm/postgres-js';
import { pgTable, text } from 'drizzle-orm/pg-core';
import { DATABASE_CASING } from '@oxy.so/db';
import {
  moderationReportColumns,
  moderationReportTableExtras,
  moderationTables,
  postgresModerationStore,
} from '@crowdsource.you/core/outbox/postgres';

const REPORT_MODERATION = {
  reportedTypes: ['listing', 'review'],
  categories: ['spam', 'harassment'],
} as const;

const reports = pgTable(
  'reports',
  {
    ...moderationReportColumns(REPORT_MODERATION),
    legacyStatus: text('legacy_status'),
  },
  moderationReportTableExtras(REPORT_MODERATION),
);

const moderation = moderationTables({
  enforcementActions: ['restrict', 'restore', 'review', 'none'] as const,
});

const client = postgres(process.env.DATABASE_URL ?? '', { max: 10 });
const db = drizzle(client, {
  casing: DATABASE_CASING,
  schema: { reports, ...moderation },
});

const store = postgresModerationStore({
  db,
  reportTable: reports,
  tables: moderation,
});
```

生成并应用从收养人完整的 Drizzle 计划中提取的 DDL 先写. `store.ensureSchema()` 验证所需的索引和检查 不取代移徙.

## 一体化

```ts
import express from 'express';
import { createModerationIntegration } from '@crowdsource.you/core/outbox';

const integration = createModerationIntegration({
  store,
  crowdSource: {
    enabled: true,
    serviceKey: process.env.CROWDSOURCE_SERVICE_KEY,
    webhookSecret: process.env.CROWDSOURCE_WEBHOOK_SECRET,
    enforcementMode: 'observe',
  },
  subjects: [listingSubjectProvider(), reviewSubjectProvider()],
  taxonomy: { version: '2026.07', allegationsFor },
  enforcement: commerceEnforcement,
  logger,
});

await store.ensureSchema();

const app = express();
app.use('/webhooks', integration.webhookRouter());
app.use(express.json());
integration.dispatcher.start();
```

挂载webhook 路由器前 `express.json()`: 人群源码验证准确性 已收到字节 。 路由器拒绝了尸体已经解剖的请求.

### 部署的证明

`crowdSource.auth` 两个身份中的一个 默认为 `'service-key'`, (中文(简体) ), 因此,一个在字段存在之前写的集成是没有变化的.

|  |  |
| --- | --- |
| `'service-key'` (默认) | 读取 `crowdSource.serviceKey`。 。 。 . **第三方留着这个** ——它运行于奥克西无法担保的地方,因此它持有一个被克罗德源发行的密钥. |
| `'oxy-service'` | 初会牛行. 配置 **无密钥**:客户端给出了 Oxy 服务符符,该进程已经可以薄荷了( )[氧ADR 0026 (韩语)](https://github.com/OxyHQ/oxy/blob/main/docs/adr/0026-first-party-services-authenticate-as-workloads.md))和"Crowd Source"从"Oxy"应用程序中解决了租户的名字. |

```ts
crowdSource: {
  enabled: true,
  auth: 'oxy-service',            // and no serviceKey, here or in the environment
  oxy,                            // the process's one OxyServer
  webhookSecret: process.env.CROWDSOURCE_WEBHOOK_SECRET,
  enforcementMode: 'observe',
}
```

-   **它被记下来,而不是从是否设置了密钥推断出来.** 缺少的密钥 也是失去一个人的部署 推断为 作为"使用氧化物身份",给予第三方一个每一份请求的客户端 它永远不能薄荷 永远运行的送货回路 毫无结果.
-   **`'oxy-service'` 实值 `crowdSourceForOxyService({ oxy })`**,而不是第二个副本 说吧 共享的 SDK 验证工作量身份; ECS 环境标记 不决定权威。 收发箱曾建客户端.
-   **哪个生效在日志里.** 模式在启动线上运行 整合已写到 `[CrowdSource] client ready`,则 拒绝——所以不需要读出新的内容来回答"哪个证书就是这个".
-   **未配置的仍然是配置错误**数字 : `undefined` 从 `client.get()`,在错误级别上报告一次,两个门都命名。 报告为: 仍保存; 排出箱 从来没有门 因为有地方发送它.
-   **迁移后留下的密钥报告为未使用**删除 这是一个步骤 人们被提醒采取 而不是一个从未提及 再来一次.
-   **`'oxy-service'` 无 `oxy` 这是一个错误,而不是沉默.** 一体化 命名此身份并设置 `enabled: true` 说真的.

## 所需保证

-   每个应用程序域变异及其外框行使用相同的 Drizzle 交易。 一个游泳池手柄被出柜店拒绝.
-   送出和Webhook事件ID是一能键. 重试不创建 第二行或第二个执行效果.
-   索赔要求使用情况 `FOR UPDATE SKIP LOCKED` 和有限度的租赁,所以同时工作的工人 不处理一行两次或等待一行锁定.
-   PostgreSQL 不提供 TTL 索引 。 计划扫描 `moderationExpirySweepTargets()` 并保留所提供的过期索引.
-   永不登录报告内容, Webhook 机密或提供者证书.

* * *

# `@crowdsource.you/core/testing`

Fixtures,一个webhook模拟器和一个在流程中的沙盒,所以应用程序可以 在陪审团开庭之前 与Crowd Source进行融合.

## 完整的道路,没有真正的陪审团或真正的效果

```ts
import { CrowdSource } from '@crowdsource.you/core';
import { createCrowdSourceSandbox } from '@crowdsource.you/core/testing';

const sandbox = createCrowdSourceSandbox();
const crowdsource = new CrowdSource({
  serviceKey: sandbox.serviceKey,
  baseUrl: sandbox.baseUrl,
  fetch: sandbox.fetch,
});

// The sandbox signs with its OWN secret. Point the receiver at it, or every
// delivery below is refused with `signature_mismatch` and the test looks broken.
process.env.CROWDSOURCE_WEBHOOK_SECRET = sandbox.webhookSecret;

const { caseId } = await crowdsource.reports.create({ /* … */ });

const decision = sandbox.decide(caseId, { outcome: 'violation' });
const event = sandbox.eventFor(decision);
await sandbox.deliver('http://localhost:3000/webhooks/crowdsource', event);
```

`eventFor` 每一次通话都会用薄荷薄荷来表示新事件id,所以,如果你想的话,请保持活动 测试一个 REDELIVERY ——两次调用是两个不同的事件,接收器是 处理两个.

报告经过 **真实** 客户端——真实信封构成,真实 一能键,真正的出错映射 —— 回来的 webhook 是 **实际签名**,所以测试中的接收器是将运行在 生产。 陪审员才有份.

## 收发机说不行

```ts
import { WebhookSimulator, caseDecidedEventFixture } from '@crowdsource.you/core/testing';

const simulator = new WebhookSimulator({ secret, url });

await simulator.deliver(caseDecidedEventFixture());                       // 200
await simulator.deliver(caseDecidedEventFixture(), { expired: true });    // must be refused
await simulator.deliver(caseDecidedEventFixture(), { wrongSecret: 'x' }); // must be refused
await simulator.deliver(caseDecidedEventFixture(), { tamperedBody: '…' });// must be refused
```

一个只有发送有效交货的套房 证明接收者可以答应.

## 沙盒实际上执行什么

集成代码的规则 取决于,忠实: `applicationId` 从 证书, 一个返回相同的等效键 `reportId`,一个409 重复使用 `externalReportId` 第7.3节的"两起关于相同的报告" 相同内容的版本是一个案例,一个决定取代了 而不是编辑.

没错 **没有** 服务。 它在过程之间没有任何关系 它回答404 路径 部署的后端既不服务,也不服务于它和后端的位置 后端不对.

## 规则

-   修饰是合成的 真实报告的材料、真实证据和真实审查人 身份从不装入测试包.
-   每一个固定装置都根据公布的合同加以验证。 一个 不再验证是一种失败,而不是放松的东西 整形师知道一个合同移动.

* * *

## 核查

```sh
bun run --cwd packages/core lint
bun run --cwd packages/core build
CROWDSOURCE_APP_TEST_POSTGRES_URL=postgres://... \
  bun run --cwd packages/core test
```

`CROWDSOURCE_APP_TEST_POSTGRES_URL` 保持它的名字 当收发箱是它的 自己的软件包: `/outbox` 套间读取它,它是相同的服务器 作曲 文件开始,重命名会默默地在某人的 壳. 存储行为套件与PostgreSQL 17相对应,包括 交易回滚、同时提出的债权、同理程序、修订令和 执行倒置。 变异跑道清除了每个负载 守卫,并要求命名的测试失败.

## 常见问题

### @crowdsource\.you/core 是什么？

Crowd Source集成:根部的API客户端,以及Express Webhook接收器,交易出柜和子路径后方的测试沙盒

### 如何安装 @crowdsource\.you/core？

在项目目录运行 npm install @crowdsource\.you/core，或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖：@crowdsource\.you/contracts（^1\.1\.0）。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖，Yarn 需要手动安装。

### @crowdsource\.you/core 支持 TypeScript 吗？

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

### @crowdsource\.you/core 支持 ESM 还是 CommonJS？

同时提供 ES Module 与 CommonJS 入口，import 和 require\(\) 都可以使用，打包工具会自动选择合适的版本。

### @crowdsource\.you/core 可以商用吗？

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

### @crowdsource\.you/core 的下载量有多少？

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

### @crowdsource\.you/core 还在维护吗？

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

