# @codaco/network-exporters 中文文档

规范地址：https://npmcn.com/package/%40codaco/network-exporters/

版本：4\.0\.0
npm 元数据更新时间：2026-10-13T12:15:22.548Z
中文译文更新时间：2026-11-10T05:55:03.405Z
软件包许可：GPL-3\.0-or-later

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

原始 npm 页面：https://www.npmjs.com/package/%40codaco/network-exporters
原文对照：https://npmcn.com/package/%40codaco/network-exporters/?lang=original
源码仓库：https://github.com/complexdatacollective/network-canvas-monorepo

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

- 包体积（解压后）：142 kB，73 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- 运行依赖：8 个
- peer 依赖：effect ^4\.0\.0，@codaco/app-i18n ^0\.3\.0
- 首次发布：2022-04-05T15:03:55.146Z
- 最新版本发布：2026-10-13T12:15:22.169Z
- 近 30 天下载量：9410（2026-10-10 至 2026-11-08，npm 官方下载统计）
- 已发布版本数：18（近 12 个月 16 个）

## 中文简介

用于输出网络 Canvas 的 Effect-TS 管道访谈数据为 CSV 和 GraphML .

## 安装

```sh
npm install @codaco/network-exporters
```

也可以使用：`pnpm add @codaco/network-exporters`、`yarn add @codaco/network-exporters`、`bun add @codaco/network-exporters`

## README

# @codaco/ 网络出口商

运行时间不可知 [效应-TS](https://effect.website/) 向GraphML 和 CSV 输出 Canvas 的访谈会。 主机插入了三层( H)`InterviewRepository`, (中文(简体) ), `ProtocolRepository`, (中文(简体) ), `Output`)和打电话 `exportPipeline`。该软件包没有持久性,没有 `node:*` 在节点、浏览器和Cloudflare Workers上运行.

## 安装

`effect` 是一个对等依赖,所以在软件包旁边安装它。 效果4还没有实现稳定的释放,所以通过标签要求释放候选人:

```bash
npm install @codaco/network-exporters effect@rc
```

```ts
import { Effect, Layer, Queue } from 'effect';
import { exportPipeline } from '@codaco/network-exporters/pipeline';
import type { ExportEvent } from '@codaco/network-exporters/events';
import { makeZipOutput } from '@codaco/network-exporters/layers/ZipOutput';

const layer = Layer.mergeAll(
  MyInterviewRepository, // workspace-specific
  MyProtocolRepository, // workspace-specific
  makeZipOutput(mySink), // ships with this package
);

const program = Effect.gen(function* () {
  const queue = yield* Queue.unbounded<ExportEvent>();
  return yield* exportPipeline(interviewIds, exportOptions, queue);
});

const result = await Effect.runPromise(program.pipe(Effect.provide(layer)));
// → { status, successfulExports, failedExports, output }
```

* * *

## 为什么是包裹

导出逻辑是历史上嵌入的 [弗雷斯科](https://github.com/complexdatacollective/Fresco) 和其他网络Canvas应用程序。 此软件包将管道提取为自成一体,主机不可知库,这样:

-   多个应用程序可以共享相同的导出语义和文件格式.
-   管道可隔离测试(没有Prisma,没有S3,没有临时文件).
-   数据库、协议存储和输出目的地可通过Effect Layers连接.
-   流到端 `AsyncIterable<Uint8Array>` 整个节点的最低共同分母 `Readable`网络 `ReadableStream`和工人——所以同一个包裹在每一个运行时间运行.

* * *

## 建筑

```text
┌──────────────────── consumer (host application) ─────────────────────┐
│                                                                       │
│  Provides Layers for these Tags ─────────────┐                        │
│                                              ▼                        │
│              ┌───────────────────── exportPipeline ─────────────────┐ │
│              │                                                     │ │
│              │  fetching → formatting → generating → outputting    │ │
│              │     ▲           ▲             ▲            ▲        │ │
│              │     │           │             │            │        │ │
│   Interview ─┘  Protocol     pure         per-format    Output     │ │
│   Repository    Repository  (in-process)   fan-out      lifecycle  │ │
│                                                       (begin/write/│ │
│                                                          end)      │ │
│              └─────────────────────────────────────────────────────┘ │
│                                              │                        │
│                                              ▼                        │
│                            ExportReturn { status,                     │
│                                           successfulExports,          │
│                                           failedExports,              │
│                                           output: OutputResult }      │
└───────────────────────────────────────────────────────────────────────┘
```

3个注入服务(`Context.Service`s) 构成包的输入/输出边界:

| 服务 | 由 | 责任 |
| --- | --- | --- |
| `InterviewRepository` | 东道主 | `getForExport(ids)` 返回按散列引用协议的会话 |
| `ProtocolRepository` | 东道主 | `getProtocols(hashes)` 返回时 `Record<hash, ProtocolExportInput>` 用于独特的散列 |
| `Output` | 主机(或已发运) `makeZipOutput`(中文(简体) ) | 状态 `begin → writeEntry × N → end`; 消费 `OutputEntry { name, data }` 每个条目 |

通过散列排列的会话参考协议,所以管道在每个运行时都会完全获取每个独特的协议一次,不管有多少会话共享. 这个 `Output` 服务状态 : `begin` 生产不透明的手柄,管道每成功生成一个条目 `end` 返回自定义的主机 `OutputResult` 流出通过 `ExportReturn.output`。 。 。 .

东道主的唯一责任是:

1.  执行三个服务层.
2.  打电话 `exportPipeline(ids, options, queue)` 和表面进展事件.

* * *

## Sparse 属性和稳定的输出计划

格式化阶段分析每个会话网络 `NcNetworkSchema` 在此之前 任何 CSV 或 GraphML 物质都收到。 没有新输入项 属性,而定义的值如 `false`, (中文(简体) ), `0`, (中文(简体) ), `''`,以及 `[]` 留下来. 无效的定义值产生 `session-processing` 失败 会话; 它不中止运行中的其他会话 .

输出计划既来自协议编码本,也来自稀有的实体数据:

-   代码表声明保持完全未解变量的可见. 这样一来 文件结构稳定,而不是取决于对参与者的提问 恰好接了电话.
-   没有可适用的代码簿声明的被定义的实体属性是 外出属性. 它们留在CSV和GraphML中而不是 丢弃.
-   缺失仅控制数据输出; 它不会去除已声明的列或 键.

### 简历

节点和边缘 CSV 文件被代表的实体类型分割. 每个文件 标题包括为该类型声明的每个变量,即使变量 每行都缺席。 Ego CSV 信头同样包括每一个宣称的自负 变量。 当一个会话有零节点或零边缘时,仅以页眉为主的文件 使用代码簿中的所有节点或边缘定义.

分类变量扩展为每个已声明选项一列,并布局 变量扩展以协调列。 一个缺失值会产生空单元格 . 定义的空绝对数组产生显式 `false` 选项单元格,所以 仍可区别于未解的绝对变量。 现 外部属性使用其属性ID来添加由数据驱动的列.

### 图表ML

GraphML 在代码簿中声明所有自负、节点和边缘变量的密钥, 包括属于无人任职实体类型的变量和与 零节点或零边缘。 因此,一个完全未答复的已宣布变量保留 联合国 `<key>` 但表示没有 `<data>` 键.

当前的外部属性得到无法确定的散列密钥ID 与代码簿变量ID相撞. 整个密钥分配一次 文档 `<data>` 元素使用分配的ID。 如果同一个外部 属性出现在多个实体类型上, GraphML 释放出一个 `for="all"` 键,而不是相冲突的节点和边缘键.

* * *

## 公众表面

进口使用子路径——该套件没有桶出口.

| 子路径 | 出口 |
| --- | --- |
| `@codaco/network-exporters/pipeline` | `exportPipeline`类型 `ExportedProtocol` |
| `@codaco/network-exporters/options` | `ExportOptions`, (中文(简体) ), `ExportOptionsSchema`类型 `ExportFormat` |
| `@codaco/network-exporters/input` | `InterviewExportInput`, (中文(简体) ), `ProtocolExportInput`,加上会话形状类型(`FormattedSession`, (中文(简体) ), `SessionVariables`, (中文(简体) ), `SessionWithNetworkEgo`, (中文(简体) ), `NodeWithResequencedID`, (中文(简体) ), `EdgeWithResequencedID`, (中文(简体) ), `SessionWithResequencedIDs`(中文(简体) ) |
| `@codaco/network-exporters/output` | `ExportResult`, (中文(简体) ), `ExportSuccess`, (中文(简体) ), `ExportFailure`, (中文(简体) ), `ExportReturn`, (中文(简体) ), `OutputEntry`, (中文(简体) ), `OutputResult`, (中文(简体) ), `OutputHandle` |
| `@codaco/network-exporters/events` | `ExportEvent`, (中文(简体) ), `stageMessages` |
| `@codaco/network-exporters/errors` | `DatabaseError`, (中文(简体) ), `OutputError`, (中文(简体) ), `ExportGenerationError`, (中文(简体) ), `ProtocolNotFoundError`, (中文(简体) ), `SessionProcessingError`类型 `ExportError`, (中文(简体) ), `describeExportError` |
| `@codaco/network-exporters/services/InterviewRepository` | `InterviewRepository` 标记 |
| `@codaco/network-exporters/services/ProtocolRepository` | `ProtocolRepository` 标记 |
| `@codaco/network-exporters/services/Output` | `Output` 标记 |
| `@codaco/network-exporters/layers/ZipOutput` | `makeZipOutput`类型 `ZipSink` |

其他一切(formaters,会话帮助器,调度逻辑,内部zip流帮助器)都是内部的,而不是导出.

* * *

## 使用量

### 1\. 提供 `InterviewRepository`

```ts
import { Effect, Layer } from 'effect';
import { NcNetworkSchema } from '@codaco/shared-consts';
import { DatabaseError } from '@codaco/network-exporters/errors';
import type { InterviewExportInput } from '@codaco/network-exporters/input';
import { InterviewRepository } from '@codaco/network-exporters/services/InterviewRepository';

export const PrismaInterviewRepository = Layer.succeed(InterviewRepository, {
  getForExport: (ids) =>
    Effect.gen(function* () {
      const rows = yield* Effect.tryPromise({
        try: () =>
          prisma.interview.findMany({
            where: { id: { in: [...ids] } },
            include: { participant: true },
          }),
        catch: (error) => new DatabaseError({ cause: error }),
      });

      const inputs: InterviewExportInput[] = rows.map((row) => ({
        id: row.id,
        participantIdentifier: row.participant.identifier,
        startTime: row.startTime,
        finishTime: row.finishTime,
        network: NcNetworkSchema.parse(row.network),
        protocolHash: row.protocolHash,
      }));

      return inputs;
    }),
});
```

存储器返回引用协议的会话 **通过散列**没有 `protocol: true` 加入——管道通过 `ProtocolRepository`。主机验证网络 `NcNetworkSchema.parse()` 直接; 货包不再装载 a `parseNcNetwork` 帮手.

### 2\. 提供a `ProtocolRepository`

```ts
import { Effect, Layer } from 'effect';
import { DatabaseError } from '@codaco/network-exporters/errors';
import type { ProtocolExportInput } from '@codaco/network-exporters/input';
import { ProtocolRepository } from '@codaco/network-exporters/services/ProtocolRepository';

export const PrismaProtocolRepository = Layer.succeed(ProtocolRepository, {
  getProtocols: (hashes) =>
    Effect.gen(function* () {
      const rows = yield* Effect.tryPromise({
        try: () =>
          prisma.protocol.findMany({ where: { hash: { in: [...hashes] } } }),
        catch: (error) => new DatabaseError({ cause: error }),
      });

      const map: Record<string, ProtocolExportInput> = {};
      for (const row of rows) {
        map[row.hash] = {
          hash: row.hash,
          name: row.name,
          codebook: row.codebook,
        };
      }
      return map;
    }),
});
```

输油管呼叫 `getProtocols` 使用从会话列表中提取的已调试的散列集运行一次。 Hashes 主持人不返回 被处理为缺失 —— 引用它们的会话路由 `failedExports` 与 `kind: "protocol-missing"` 而不是中止运行.

### 3\. 提供 `Output`

`Output` 替换旧的 `FileStorage` + 键 `FileSystem` 具有单一状态生命周期的标记 。 货船 `makeZipOutput(sink)` 对于想要今天捆绑-zip行为的主机: 它通过纯 JS 流出条目 `fflate` 并转发所产生的zip字节(作为 `AsyncIterable<Uint8Array>`)给一个主机提供的汇回调. 不希望拉链执行的主机 `Output` 直接说.

#### S3为 `ZipOutput` (节点)

```ts
import { Readable } from 'node:stream';
import { Upload } from '@aws-sdk/lib-storage';
import { Effect } from 'effect';
import { OutputError } from '@codaco/network-exporters/errors';
import { makeZipOutput } from '@codaco/network-exporters/layers/ZipOutput';

const S3ZipOutput = makeZipOutput((stream, fileName) =>
  Effect.tryPromise({
    try: async () => {
      await new Upload({
        client: s3,
        params: { Bucket, Key: fileName, Body: Readable.from(stream) },
      }).done();
      return { key: fileName, url: await presign(fileName) };
    },
    catch: (cause) => new OutputError({ cause }),
  }),
);
```

`Readable.from(asyncIterable)` 调整软件包 `AsyncIterable<Uint8Array>` 转到节点 `Readable` aWS SDK 的预期. `@aws-sdk/lib-storage`是因为 `Upload` 处理多段上传和每段内部重试.

#### 浏览器 blob 与 `ZipOutput`

```ts
import { Effect } from 'effect';
import { OutputError } from '@codaco/network-exporters/errors';
import { makeZipOutput } from '@codaco/network-exporters/layers/ZipOutput';

const BlobZipOutput = makeZipOutput((stream) =>
  Effect.tryPromise({
    try: async () => {
      const chunks: Uint8Array[] = [];
      for await (const chunk of stream) chunks.push(chunk);
      const blob = new Blob(chunks, { type: 'application/zip' });
      return { blob, url: URL.createObjectURL(blob) };
    },
    catch: (cause) => new OutputError({ cause }),
  }),
);
```

`fflate` 纯JS,所以这个层工作 在浏览器和Cloudflare工人 没有多填充.

#### OPFS 文件夹( 无拉链)

```ts
import { Effect, Layer } from 'effect';
import { OutputError } from '@codaco/network-exporters/errors';
import { Output } from '@codaco/network-exporters/services/Output';

const OPFSFolderOutput = Layer.succeed(Output, {
  begin: () =>
    Effect.tryPromise({
      try: async () => ({
        dir: await navigator.storage
          .getDirectory()
          .then((root) =>
            root.getDirectoryHandle(`export-${Date.now()}`, { create: true }),
          ),
      }),
      catch: (cause) => new OutputError({ cause }),
    }),
  writeEntry: (handle, entry) =>
    Effect.tryPromise({
      try: async () => {
        const file = await handle.dir.getFileHandle(entry.name, {
          create: true,
        });
        const writable = await file.createWritable();
        for await (const chunk of entry.data) await writable.write(chunk);
        await writable.close();
      },
      catch: (cause) => new OutputError({ cause }),
    }),
  end: (handle) => Effect.succeed({ folderHandle: handle.dir }),
});
```

此执行跳过 `makeZipOutput` 完全: 每一个成功的生成结果都写为一个 OPFS 文件, 返回 `OutputResult` 将目录控件带回主机。 它拼写成相同的 `Output` 标记 —— 管道中没有特殊标记 .

### 4\. 运行管道

```ts
import { type Cause, Effect, Fiber, Layer, Queue, Stream } from 'effect';
import { exportPipeline } from '@codaco/network-exporters/pipeline';
import type { ExportEvent } from '@codaco/network-exporters/events';
import {
  describeExportError,
  type ExportError,
} from '@codaco/network-exporters/errors';

const exportLayer = Layer.mergeAll(
  PrismaInterviewRepository,
  PrismaProtocolRepository,
  S3ZipOutput,
);

const result = await Effect.scoped(
  Effect.gen(function* () {
    // `Cause.Done` in the error type is what lets `Queue.end` close the queue
    // below: `Queue.end` takes an `Enqueue<A, E | Done>`.
    const queue = yield* Queue.unbounded<ExportEvent, Cause.Done>();

    // Drain the queue into the UI (e.g. an SSE response). Hold the fiber:
    // finishing the export is not the same as having delivered every event,
    // and the join below is what makes delivery a property rather than a
    // matter of how the scheduler happened to interleave.
    const drain = yield* Effect.forkScoped(
      Stream.fromQueue(queue).pipe(Stream.runForEach(renderEventToClient)),
    );

    const pipelineResult = yield* exportPipeline(
      ['interview-1', 'interview-2'],
      {
        exportGraphML: true,
        exportCSV: true,
        globalOptions: {
          useScreenLayoutCoordinates: true,
          screenLayoutHeight: 1080,
          screenLayoutWidth: 1920,
        },
        // optional: defaults to os.cpus().length on Node, 4 elsewhere
        concurrency: 4,
        // optional: only used to populate session metadata
        appVersion: '3.0.0',
        commitHash: process.env.COMMIT_HASH,
      },
      queue,
    );

    // `end` lets the buffered events drain and then terminates the stream;
    // `Queue.shutdown` would discard whatever is still buffered.
    yield* Queue.end(queue);
    yield* Fiber.join(drain);

    return pipelineResult;
  }),
).pipe(
  Effect.provide(exportLayer),
  Effect.catch((error: ExportError) =>
    Effect.succeed({
      status: 'error' as const,
      message: describeExportError(error, 'running export'),
    }),
  ),
  Effect.runPromise,
);
```

`result.output` 随心所欲 `Output.end` 返回执行 `S3ZipOutput` 上面,这是 `{ key, url }`没有 `zipUrl` 或者说 `zipKey` 字段打开 `ExportReturn`; 想要读取 URL 的主机 `result.output.url`。 。 。 .

* * *

## 管道阶段

| 阶段 | 发生什么事 | 已发射事件 |
| --- | --- | --- |
| `fetching` | `InterviewRepository.getForExport(ids)` 决定: `InterviewExportInput[]` | `{ type: "stage", stage: "fetching" }` |
| `formatting` | 每阶段效果: `getProtocols`,分区缺失,构建会话变量,插入自负,按协议散列组合,重排编号. 每届抓取路径差 `failedExports` | `{ type: "stage", stage: "formatting" }` |
| `generating` | 每个文件扇出( CSV 生成器 + GraphML doc), 有边形的货币, 每个都产生一个 `AsyncIterable<Uint8Array>` | `{ type: "stage", stage: "generating" }` 加号 `{ type: "progress", stage: "generating", current, total }` |
| `outputting` | `Output.begin()`,则 `Output.writeEntry(handle, entry)` 每一个成功的文件,那么 `Output.end(handle)` | `{ type: "stage", stage: "outputting" }` 加号 `{ type: "progress", stage: "outputting", current, total }` |

四个阶段,四个阶段 `stage` 事件值. `outputting` 覆盖 `begin`,每个 `writeEntry`,以及 `end`. 管道没有持续状态——没有单独的清理阶段.

* * *

## 货币

`generateOutputFilesEffect` 粉丝跨出交叉产品(会场×格式×分型). 汇兑方式为: `ExportOptions.concurrency`默认为 `os.cpus().length` 在节点上(通过防守的动态导入解决)和在其他地方的小型固定值。 为内存受约束的运行时间(小服务器功能)设定更低,为基准设定更高.

```ts
{ ...options, concurrency: 2 }
```

这个 `outputting` 阶段本身是相接的——一个拉链流,按进入顺序写出.

* * *

## 错误模式

两个明显的错误轨道, 都标记了 .

### 致命错误 — 效果失败通道

管道升入无法恢复条件的 Effect 失败通道: 数据库获取失败, 输出开始/ 写入/ 结束失败 。 每个都有标记 `Data.TaggedError` 实例:

| 类 | 标记 | 升为 |
| --- | --- | --- |
| `DatabaseError` | `NetworkExporters/DatabaseError` | `InterviewRepository`, (中文(简体) ), `ProtocolRepository` |
| `OutputError` | `NetworkExporters/OutputError` | `Output.begin` 编号 : `Output.writeEntry` 编号 : `Output.end` (涵盖前 `FileStorageError` 财务报告和审定财务报表 `ArchiveError`(中文(简体) ) |

这个 `ExportError` 工会类型涵盖两个致命的班级.

内部失败 `Output.writeEntry` 是致命的——一旦部分字节在主机的捆绑中,重试内线是主机的工作,而不是管道的工作. 需要重试语义的主机在其内部执行 `Output` 层; `fflate` 失败和主机回调失败 `OutputError`,则原始错误可用于 `error.cause`。 。 。 .

`describeExportError(error, stage?)` 产生人能读取的信息。 它发送标签和检查 `error.cause` 用于已知运行时模式( q)`code === "ENOSPC"`, OOM 消息, 超时, 连接被拒绝), 然后再返回到标签意识默认状态 。 在消费边界上使用——包本身从不构建用户化的字符串.

```ts
Effect.catch((error) =>
  Effect.succeed({
    status: 'error' as const,
    message: describeExportError(error, 'running export'),
  }),
);
```

### 部分失败—— `ExportFailure[]`

单个错误会话或单个失败文件确实存在 **没有** 中止输油管。 三样东西可以降落在 `failedExports`数字 :

| `kind` | 源错误 | 何时 |
| --- | --- | --- |
| `protocol-missing` | `ProtocolNotFoundError` | 会议 `protocolHash` 未见于 `ProtocolRepository.getProtocols` 结果 |
| `session-processing` | `SessionProcessingError` | 按会期分列的渔获量 `format`, (中文(简体) ), `insertEgo`,或 `resequence` 阶段 |
| `generation` | `ExportGenerationError` | 每个文件生成器( 例如一个 CSV 分区) 扔出, 而其余部分成功 |

成功的文件仍然被写入 `Output` 并报告.

```ts
const result = await Effect.runPromise(...);
//   ↓ ExportReturn
// {
//   status: "success" | "partial",
//   successfulExports: ExportSuccess[],
//   failedExports:     ExportFailure[],
//   output:            OutputResult,   // host-defined
// }

if (result.status === "partial") {
    for (const failure of result.failedExports) {
        switch (failure.kind) {
            case "protocol-missing":
            case "session-processing":
            case "generation":
                console.warn(describeExportError(failure.error));
                break;
        }
    }
}
```

`status` 实值 `"partial"` 任何时候 `failedExports.length > 0`不管它属于哪种人 这种分解——致命-Vs-部分——是故意的:消费者可以给用户一个可用的捆绑,即使一个CSV失败,同时仍然可以表面显示丢失的东西.

* * *

## 进展活动

`exportPipeline` 写入 `ExportEvent`s 到您提供的队列。 两个事件形状:

```ts
type ExportStageEvent = {
  type: 'stage';
  stage: 'fetching' | 'formatting' | 'generating' | 'outputting';
  message: string;
};

type ExportProgressEvent = {
  type: 'progress';
  stage: 'generating' | 'outputting';
  current: number;
  total: number;
};
```

每一次入境 `progress` 事件在两个阶段中都释放出来 `generating` (每个文件)和 `outputting` (每个文件编写). 绘制栏的当前 UI 代码 `generating` 工作情况不变 `outputting` - 形状是一样的,只有 `stage` 值不同.

软件包只发出这两种事件类型——其他任何类型(例如SSE形状) `complete`编号 :`error` 客户端交付的事件)是主机的关注.

`stageMessages` 导出为 a `Record<ExportStage, string>` 预先本地化的英语字符串; 消费者可能忽略它并释放自己的字符串.

* * *

## 地方发展

这个包裹住在 `network-canvas-monorepo` 并遵循标准脚本.

```bash
# from monorepo root
pnpm --filter @codaco/network-exporters build       # vite build
pnpm --filter @codaco/network-exporters test        # vitest --run
pnpm --filter @codaco/network-exporters test:watch
pnpm --filter @codaco/network-exporters typecheck   # tsc --noEmit
```

内部消费者决心 `package.json#exports` 直接反对 `src/*.ts` ——不需要建设步骤来取出源变化. 这个 `build` 脚本仍然运行 `vite build`,该输出 `dist/<entry>.{js,d.ts}` 定义的每个公共子路径 `vite.config.ts`;该产出仅由已公布npm文物消耗,通过 `package.json#publishConfig.exports` 在打包时间交换。 全部运行时间依赖关系( E)`effect`, (中文(简体) ), `fflate`, (中文(简体) ), `@codaco/shared-consts`, (中文(简体) ), `@codaco/protocol-validation`, (中文(简体) ), `es-toolkit`, (中文(简体) ), `sanitize-filename`, (中文(简体) ), `zod`, (中文(简体) ), `@xmldom/xmldom`, (中文(简体) ), `ohash`)在构建时被外部化. `effect` 是一个同行而不是依赖,所以在同样直接使用Effect的应用程序中,完全有一个副本——和一个纤维运行时间.

### 添加新的切入点

1.  添加源文件于 `src/<path>.ts`。 。 。 .
2.  将条目添加到 `vite.config.ts` `build.lib.entry` (出版物的需要) `dist/` 建楼).
3.  将相应的子路径添加到 `package.json#exports`,指着新的 `src/<path>.ts` 文档.
4.  将匹配的子路径添加到 `package.json#publishConfig.exports`,指着 `./dist/<path>.js`编号 :`.d.ts`因此,出版的文物 不断揭露它.
5.  运行 `pnpm --filter @codaco/network-exporters build` 并验证匹配 `dist/<path>.js` + 键 `.d.ts` 土地(这只验证了公布的文物——内部消费者已经通过下列途径看到新的条目: `src/`) (中文(简体) ).

### 添加运行时间依赖

喜欢工作空间目录。 如果 Dep 已经有一个目录条目 `pnpm-workspace.yaml`,将其称为 `"<dep>": "catalog:"`。否则,在目录中添加一个被绑定的版本,然后引用它。 一定要把它外部化进去 `vite.config.ts` 所以不是捆绑的 浏览器/工人兼容性表示避免 `node:*` 从任何新代码导入 `src/` —— 说 `node:os` 在被监视的动态导入中 `session/generateOutputFiles.ts` 这是唯一的例外.

* * *

## 版本

此软件包使用 [更改](https://github.com/changesets/changesets)就像其他的单体一样 在修改用户后 :

```bash
pnpm changeset
```

选择 `@codaco/network-exporters` 并选择凸起类型。 别撞了 `package.json#version` 手边.

* * *

## 许可证

GPL-3.0-或-后传. 见 `LICENSE` 在单发根.

## 常见问题

### @codaco/network-exporters 是什么？

用于输出网络 Canvas 的 Effect-TS 管道访谈数据为 CSV 和 GraphML \.

### 如何安装 @codaco/network-exporters？

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

### @codaco/network-exporters 支持 TypeScript 吗？

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

### @codaco/network-exporters 支持 ESM 还是 CommonJS？

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

### @codaco/network-exporters 可以商用吗？

@codaco/network-exporters 使用 GPL-3\.0-or-later 许可。强 Copyleft：分发包含该代码的软件时，整个作品需以 GPL-3\.0 兼容许可开源，并附带专利与反锁定条款。仅内部使用不触发此义务。以上为概括，不构成法律意见，请以许可原文为准。

### @codaco/network-exporters 的下载量有多少？

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

### @codaco/network-exporters 还在维护吗？

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

