> ## Documentation Index
> Fetch the complete documentation index at: https://docs.twenty.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 键值存储

> 使用内置的应用键值存储持久化中间结果、缓存数据，并在多次逻辑函数运行之间共享状态。

逻辑函数在短生命周期的 Node.js 进程中以沙盒方式运行——一旦一次运行结束，内存中不会保留任何内容。 当你需要在**多次运行之间记住一些东西**时（缓存一次昂贵的 API 响应、存储增量同步的游标、对工作进行防抖处理，或在函数之间传递状态），请将其持久化到内置键值存储中。

每个应用都会获得自己隔离的命名空间：条目根据已认证的应用进行键控，因此你的键永远不会与其他应用发生冲突，也不会被其他应用读取。

```text theme={null}
  ┌─────────────────┐   kv.set(key, value)   ┌──────────────────────────┐
  │ Logic function  │ ─────────────────────▶ │ Application KV store     │
  │ (your handler)  │ ◀───────────────────── │  key (unique)  │  value  │
  └─────────────────┘   kv.get(key)          └──────────────────────────┘
```

## 获取、设置、删除

从 `twenty-sdk/logic-function` 导入 `kv`。 值可以是任何可序列化为 JSON 的负载。

```ts src/logic-functions/sync-linear-issues.ts theme={null}
import { kv } from 'twenty-sdk/logic-function';

// Read a value. Returns null when the key is missing.
const cursor = await kv.get<string>('sync-cursor:linear');

// Write a value. Creates the entry on first write, updates it afterwards.
await kv.set('sync-cursor:linear', newCursor);

// Delete an entry. Returns true when an entry was removed.
await kv.delete('sync-cursor:linear');
```

## 范围

每个条目都有一个作用域，在每次调用时作为选项传入。 默认值为 `WORKSPACE`。

* **`WORKSPACE`**（默认）— 条目对当前工作区中安装的应用是私有的。 每个安装该应用的工作区都会获得自己独立的一组键。 这正是缓存、游标和按工作区存储状态时所需要的。
* **`SERVER`** — 条目在服务器上应用的**每一次安装**之间共享。 服务器条目的行为类似**声明（claim）**：存储的值始终是声明该键的 workspaceId（在 `set` 时省略 `value` 即可为当前工作区声明该键），并且只有该工作区可以覆盖或删除它。 任何一次安装都可以读取该条目。

服务器声明用于跨工作区路由。 [服务器路由解析器](/l/zh/developers/extend/apps/logic/logic-functions#server-route-trigger)在应用注册所有者的工作区中运行，但入站 Webhook 通常只携带外部账户 ID——而不是 Twenty 的 workspaceId。 让每个工作区在连接时声明其外部 id，然后在路由中解析它：

```ts theme={null}
// In the connected workspace, when the external account is linked:
await kv.set(`slack:team:${teamId}`, undefined, { scope: 'SERVER' });

// In the server-route resolver (owner workspace), on each webhook:
const workspaceId = await kv.get<string>(`slack:team:${teamId}`, {
  scope: 'SERVER',
});
```

因为服务器键只能为调用方自己的工作区声明，且永远不会被其他工作区覆盖，所以某个工作区无法劫持属于其他工作区的映射。 当某个键已经被其他工作区声明时，`kv.set` 会抛出异常。

## 使用示例：缓存一次昂贵的调用

一个典型用例是缓存一次缓慢或受限于速率的第三方响应，这样重复的运行就可以重复使用该响应，而不必每次都付出相同的开销。

```ts src/logic-functions/getExchangeRate.logic-function.ts theme={null}
import { defineLogicFunction } from 'twenty-sdk/define';
import { kv } from 'twenty-sdk/logic-function';

const ONE_HOUR_MS = 60 * 60 * 1000;

type CachedRate = { rate: number; fetchedAt: number };

const handler = async (params: { from: string; to: string }) => {
  const cacheKey = `cache:exchange-rate:${params.from}:${params.to}`;
  const cached = await kv.get<CachedRate>(cacheKey);

  if (cached && Date.now() - cached.fetchedAt < ONE_HOUR_MS) {
    return { rate: cached.rate, cached: true };
  }

  const response = await fetch(
    `https://api.example.com/rate?from=${params.from}&to=${params.to}`,
  );
  const { rate } = (await response.json()) as { rate: number };

  await kv.set(cacheKey, { rate, fetchedAt: Date.now() });

  return { rate, cached: false };
};

export default defineLogicFunction({
  universalIdentifier: 'd9b2f4e6-1c83-4a07-9e52-6b1d3c8a0f47',
  name: 'get-exchange-rate',
  timeoutSeconds: 10,
  handler,
});
```

## 模式与技巧

* **命名空间划分。** 给键添加前缀，以区分不同的用途——`sync-cursor:linear`、`cache:exchange-rate:USD:EUR`、`lock:nightly-report`。
* **过期时间（TTL）。** 该存储本身不带有过期机制。 在值中存储时间戳（如缓存示例中所示）并在读取时检查，或者通过[定时任务触发的函数](/l/zh/developers/extend/apps/logic/logic-functions)清理陈旧的键。
* **存什么。** 任何可序列化为 JSON 的值——数字、字符串、数组、对象。 保持条目足够小；此存储用于协调和缓存，而不是用于存放大型二进制对象或文件。 对于文件，请使用 `FILES` 字段和 [`uploadFile`](/l/zh/developers/extend/apps/logic/logic-functions#uploading-files)。
* **可见性。** 条目存在于实例数据库中，而不是作为工作区记录存在——它们不会出现在工作区 UI 中，不属于你应用的数据模型，也不需要角色或对象权限。

## 可选方案：可查询的存储对象

内置存储是刻意设计为不透明的：条目不是记录，因此你无法在 UI 中浏览它们、将它们与其他对象关联，或通过记录查询对它们进行筛选。 当你需要这些能力时——比如可见的同步日志或逐条记录的状态——请定义一个小型的**技术对象**，其中包含唯一的 `key` 字段和一个 `RAW_JSON` 类型的 `value` 字段，并通过[类型化 API 客户端](/l/zh/developers/extend/apps/logic/logic-functions#typed-api-clients-twenty-client-sdk)对其进行查询。 `defineObject` 的参考请参见 [Objects](/l/zh/developers/extend/apps/data/objects)，关于强制键唯一性请参见 [Data → Unique indexes](/l/zh/developers/extend/apps/data/overview#unique-indexes)。

* **将作用域限定到记录。** 从存储对象到目标对象添加一个[关系](/l/zh/developers/extend/apps/data/relations)，而不是把 id 编码进键中。
* **可见性与权限。** 这些行像任何其他记录一样存放在工作区数据库中，因此可以通过 API 查询，并遵循你的应用[角色](/l/zh/developers/extend/apps/config/roles)设置。 要将存储从主 UI 中隐藏，只需不要把它加入到你的[导航菜单](/l/zh/developers/extend/apps/layout/navigation-menu-items)中。

<Note>
  与内置存储不同，自定义对象始终作用于单个工作区——它无法像 `SERVER` 键那样在安装之间共享条目。
</Note>
