snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 2s

This commit is contained in:
upstream-mirror
2026-09-17 23:28:13 +05:00
commit e7da97194a
32688 changed files with 4208681 additions and 0 deletions

View File

@@ -0,0 +1,52 @@
---
title: オブジェクトの拡張
description: 標準の Twenty オブジェクト(Person、Company など)にフィールドを追加します。 または、`defineField` を使用して他のアプリのオブジェクトにフィールドを追加します。
icon: wand-magic-sparkles
---
`defineField()` を使用すると、あなたが所有していないオブジェクト ― Person や Company のような標準の Twenty オブジェクトや、他のインストール済みアプリから提供されるオブジェクト ― にフィールドを追加できます。 [`defineObject`](/l/ja/developers/extend/apps/data/objects) 内で宣言されるインラインフィールドと異なり、スタンドアロンフィールドでは、どのオブジェクトを拡張するかを指定するために `objectUniversalIdentifier` が必要です。
```ts src/fields/company-loyalty-tier.field.ts
import { defineField, FieldType } from 'twenty-sdk/define';
export default defineField({
universalIdentifier: 'f2a1b3c4-d5e6-7890-abcd-ef1234567890',
objectUniversalIdentifier: '701aecb9-eb1c-4d84-9d94-b954b231b64b', // Company object
name: 'loyaltyTier',
type: FieldType.SELECT,
label: 'Loyalty Tier',
icon: 'IconStar',
options: [
{ value: 'BRONZE', label: 'Bronze', position: 0, color: 'orange' },
{ value: 'SILVER', label: 'Silver', position: 1, color: 'gray' },
{ value: 'GOLD', label: 'Gold', position: 2, color: 'yellow' },
],
});
```
## 主なポイント
* `objectUniversalIdentifier` は対象オブジェクトを識別します。 標準の Twenty オブジェクトの場合は、`twenty-sdk` から定数を import します:
```ts
import { STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk/define';
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity.universalIdentifier
// …
```
* フィールドを **`defineObject()` 内でインライン定義する** 場合、`objectUniversalIdentifier` は **不要** です — 親オブジェクトから継承されます。
* `defineField()` は、`defineObject()` で作成していないオブジェクトにフィールドを追加する唯一の方法です。
* ファイルの配置場所は任意です。 慣例としては `src/fields/\<name>.field.ts` ですが、SDK は `src/` 内の任意の場所にあるフィールドを検出します。
* `writability` はフィールド単位でも設定できます。`MetadataWritability.APPLICATION` は書き込みをアプリ自身のロジック関数に制限し、`MetadataWritability.SYSTEM` はプラットフォームによって管理されるデータ専用に予約されています。 フィールドの書き込み可能性は、そのフィールドが属するオブジェクトのレベルよりも厳しくすることしかできず、読み取りには影響しません。 詳細な仕様については [Objects](/l/ja/developers/extend/apps/data/objects) を参照してください。
* 標準ページレイアウト(例:Task や Company の詳細ページ)にタブを追加するには、`twenty-sdk/define` の `STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS` とともに [`definePageLayoutTab`](/l/ja/developers/extend/apps/layout/page-layouts#definepagelayouttab) を使用します。
## 既存のオブジェクトへのリレーションの追加
リレーションフィールド(例: カスタムオブジェクトを標準の `Person` にリンクする)を追加するには、`FieldType.RELATION` を指定して `defineField()` を使用します。 パターンはインラインのリレーションと同じですが、`objectUniversalIdentifier` を明示的に設定します。 双方向のパターンについては、[Relations](/l/ja/developers/extend/apps/data/relations) を参照してください。

View File

@@ -0,0 +1,143 @@
---
title: オブジェクト
description: defineObject を使用して、新しいレコードタイプ(独自のフィールドを持つカスタムテーブル)を宣言します。
icon: table
---
カスタム**オブジェクト**は、アプリがワークスペースに追加する新しいレコードタイプです。ポストカード、請求書、サブスクリプションなど、あなたのドメインに特化したあらゆるものを定義できます。 各オブジェクトは、そのスキーマ(フィールド、リレーション、デフォルト値)と、同期やデプロイをまたいで維持される安定したユニバーサル識別子を宣言します。
```ts src/objects/post-card.object.ts
import { defineObject, FieldType } from 'twenty-sdk/define';
enum PostCardStatus {
DRAFT = 'DRAFT',
SENT = 'SENT',
DELIVERED = 'DELIVERED',
RETURNED = 'RETURNED',
}
export default defineObject({
universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05',
nameSingular: 'postCard',
namePlural: 'postCards',
labelSingular: 'Post Card',
labelPlural: 'Post Cards',
description: 'A post card object',
icon: 'IconMail',
fields: [
{
universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b',
name: 'content',
type: FieldType.TEXT,
label: 'Content',
description: "Postcard's content",
icon: 'IconAbc',
},
{
universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac',
name: 'recipientName',
type: FieldType.FULL_NAME,
label: 'Recipient name',
icon: 'IconUser',
},
{
universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266',
name: 'recipientAddress',
type: FieldType.ADDRESS,
label: 'Recipient address',
icon: 'IconHome',
},
{
universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e',
name: 'status',
type: FieldType.SELECT,
label: 'Status',
icon: 'IconSend',
defaultValue: `'${PostCardStatus.DRAFT}'`,
options: [
{ value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' },
{ value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' },
{ value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' },
{ value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' },
],
},
{
universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433',
name: 'deliveredAt',
type: FieldType.DATE_TIME,
label: 'Delivered at',
icon: 'IconCheck',
isNullable: true,
defaultValue: null,
},
],
});
```
## 主なポイント
* `universalIdentifier` は、デプロイをまたいで一意かつ安定している必要があります。
* 各フィールドには、`name`、`type`、`label`、および自身の安定した `universalIdentifier` が必要です。
* `fields` 配列は任意です。カスタムフィールドなしでオブジェクトを定義できます。
* `openRecordIn` は、このオブジェクトのレコードがクリックされたときにどこで開くかを設定します。`ObjectOpenRecordIn.USER_CHOICE`(デフォルト。各ワークスペースメンバーの[Settings → Experience]での設定に従います)、`ObjectOpenRecordIn.SIDE_PANEL`、または `ObjectOpenRecordIn.RECORD_PAGE` のいずれかです。 ワークフローやダッシュボードのように、レコードを使うためにフルページが必要な場合には `RECORD_PAGE` にピン留めします。カレンダーイベントのように、クイックパネルとして表示する方が適しているレコードの場合には `SIDE_PANEL` にピン留めします。
* `writability` は、ロール権限が適用される前の段階で、そのオブジェクトのレコードを書き込むことができる対象を制御します。`MetadataWritability.OPEN`(デフォルト — ワークスペースロールが決定)、`MetadataWritability.APPLICATION`(あなたのアプリ独自のロジック関数だけがレコードを作成・更新・削除できます。これは、そのレコードが挙動を付与する設定的なオブジェクトに使用し、幅広いレコードアクセス権を持つワークスペースメンバーが API 経由でそれらを編集できないようにします)、`MetadataWritability.SYSTEM`(プラットフォームで管理されるデータ用に予約)があります。 読み取りには影響しません。これはサーバー側で強制されるのに対し、`isUIEditable` は UI 上の操作手段を非表示にするだけです。 これはフィールド単位でも存在し、その場合はオブジェクトレベルよりも厳しく設定することしかできません。
* ここで定義されたインラインフィールドには `objectUniversalIdentifier` は**不要**です — 親オブジェクトから継承されます。 所有していないオブジェクトにフィールドを追加するには、[`defineField()`](/l/ja/developers/extend/apps/data/extending-objects) を使用します。
* `yarn twenty dev:add object` を使用すれば、新しいオブジェクトをスキャフォルドできます。名前、フィールド、リレーションシップの設定をガイドしてくれます。 [アーキテクチャ → エンティティのスキャフォールディング](/l/ja/developers/extend/apps/getting-started/scaffolding) を参照してください。
<Note>
**ベースフィールドは自動的に追加されます。** カスタムオブジェクトを定義すると、Twenty は `id`、`name`、`createdAt`、`updatedAt`、`createdBy`、`updatedBy`、`deletedAt` などの標準フィールドを自動的に作成します。 これらを `fields` 配列で宣言する必要はありません。カスタムフィールドのみを追加してください。 同じ名前でフィールドを宣言することでデフォルトフィールドを上書きすることもできますが、これはほとんどの場合お勧めできません。
</Note>
## フィールドタイプ
`twenty-sdk/define` からエクスポートされる、`FieldType` 値の完全な一覧:
| カテゴリ | タイプ |
| ---------- | ------------------------------------------------------------------------------------------------------------ |
| テキスト | `TEXT`、`RICH_TEXT`、`ARRAY`(文字列の配列)、`RAW_JSON` |
| 数値 | `NUMBER`(`universalSettings.dataType`: `'float'` / `'int'` / `'bigint'`)、`NUMERIC`(任意精度)、`RATING`、`POSITION` |
| 日付 | `DATE`, `DATE_TIME` |
| 選択 | `BOOLEAN`、`SELECT`、`MULTI_SELECT` |
| 複合 | `FULL_NAME`、`ADDRESS`、`EMAILS`、`PHONES`、`LINKS`、`CURRENCY`、`ACTOR`、`FILES` |
| 識別子とリレーション | `UUID`、`RELATION`、`MORPH_RELATION`([Relations](/l/ja/developers/extend/apps/data/relations) を参照) |
| システム | `TS_VECTOR`(サーバーによって管理される全文検索ベクター) |
複合タイプは複数のサブフィールドを保持します(例: `FULL_NAME` = 名 + 姓、`CURRENCY` = `amountMicros` + `currencyCode`)。 `SELECT` と `MULTI_SELECT` は、上記の例のように `options` 配列を必要とします。
## デフォルト値
文字列リテラルのデフォルト値は、文字列**の内部で**シングルクォートで囲む必要があります。つまり、`defaultValue: "'Draft'"` のように書き、`defaultValue: "Draft"` のようには書きません。 そのため上記の `status` フィールドでは、`` `'${PostCardStatus.DRAFT}'` `` を使用しています。
引用符で囲まれていない文字列は、レコード作成時に評価される計算済みデフォルト値用に予約されています。
* `'uuid'` — `UUID` フィールド用に UUID を生成します。
* `'now'` — 現在のタイムスタンプ(`DATE_TIME` フィールド用)を表します。
同じ規則が、複合デフォルトの文字列サブフィールド(例: `ACTOR` フィールド上の `{ source: "'MANUAL'" }`)および `SELECT` / `MULTI_SELECT` の値にも適用されます。 引用符で囲まれていないリテラル文字列のデフォルト値がある場合、アプリのビルド時に警告が発生します。
## NULL 許容性
`isNullable` は、フィールドが `NULL` を受け付けるかどうかを制御します。 デフォルトは `true` で、任意のフィールドでは省略できます。 データベースレベルでフィールドを必須にするには、`isNullable: false` を設定します。
`isNullable` への変更は、既存フィールドを更新する同期を含め、すべての同期で適用されます。そのため、マニフェストを編集して再同期することで、フィールドの NULL 許容性を切り替えることができます。
<Note>
**既存のフィールドを NULL 非許容にするには、デフォルト値が必要です。** フィールドを `isNullable: false` に変更する場合は、NULL ではない `defaultValue` も指定する必要があります。 デフォルト値は、`NOT NULL` 制約が適用される前に既存の `NULL` 行をすべてバックフィルします。これがない場合、同期は `Default value cannot be null for non-nullable fields` というエラーで失敗します。 リレーションフィールドおよび `TS_VECTOR` フィールドは常に NULL を許容するため、`isNullable` はそれらには影響しません。
</Note>
```ts
{
universalIdentifier: 'b1a7c0de-1234-4f00-9abc-000000000000',
name: 'reference',
type: FieldType.TEXT,
label: 'Reference',
isNullable: false,
defaultValue: "'N/A'",
}
```
## 次のステップ
* **このオブジェクトを他のオブジェクトに接続する** — 双方向リレーションパターンについては [Relations](/l/ja/developers/extend/apps/data/relations) を参照してください。
* **他のアプリのオブジェクトにフィールドを追加する** — `defineField()` については [Extending Objects](/l/ja/developers/extend/apps/data/extending-objects) を参照してください。
* **このオブジェクトを UI に表示します** — サイドバー項目を追加するには [Navigation Menu Items](/l/ja/developers/extend/apps/layout/navigation-menu-items) を参照し、カスタムリスト構成を追加するには [Views](/l/ja/developers/extend/apps/layout/views) を参照してください。

View File

@@ -0,0 +1,101 @@
---
title: 概要
description: ワークスペースにアプリが追加するデータ ― オブジェクト、フィールド、リレーション ― の形を整えます。
icon: database
---
Twenty アプリの**データレイヤー**とは、アプリがワークスペースに*追加*するデータのことです。宣言する新しいレコードタイプ、既存オブジェクトに追加するカラム、およびそれらのレコード同士がどのように接続されるかを指します。
```text
┌──────────────────────────────────────────────────┐
│ Object — a record type, e.g. PostCard │
│ ├─ Field (name, type, label) │
│ ├─ Field │
│ └─ Relation (link to another object) │
└──────────────────────────────────────────────────┘
│
├── lives in your app, OR
│
▼
┌──────────────────────────────────────────────────┐
│ Standard / other apps' objects │
│ └─ Field added by your app via defineField │
└──────────────────────────────────────────────────┘
```
## このセクションについて
<CardGroup cols={2}>
<Card title="オブジェクト" icon="table" href="/l/ja/developers/extend/apps/data/objects">
`defineObject` — 固有のフィールドを持つ新しいレコードタイプを宣言します。
</Card>
<Card title="オブジェクトの拡張" icon="wand-magic-sparkles" href="/l/ja/developers/extend/apps/data/extending-objects">
`defineField` — 標準オブジェクトや他のアプリのオブジェクトにフィールドを追加します。
</Card>
<Card title="関連" icon="diagram-project" href="/l/ja/developers/extend/apps/data/relations">
オブジェクト間の双方向 `MANY_TO_ONE` / `ONE_TO_MANY` 接続。
</Card>
<Card title="タイムライン活動タイプ" icon="clock-rotate-left" href="/l/ja/developers/extend/apps/data/timeline-activity-types">
レコードタイムライン用の自動監査イベントと明示的なアプリイベントを定義します。
</Card>
</CardGroup>
## エンティティの概要
| エンティティ | 目的 | 定義方法 |
| --------------- | ------------------------------------------------------------------------------- | -------------------------------------- |
| **オブジェクト** | 固有のフィールドを持つ新しいカスタムレコードタイプ(例: PostCard、Invoice) | `defineObject()` |
| **フィールド** | オブジェクト上のカラム。 スタンドアロンフィールドは、作成していないオブジェクトも拡張できます(例: Company に `loyaltyTier` を追加) | `defineField()` |
| **リレーション** | 2 つのオブジェクト間の双方向リンク ― 両側がフィールドとして宣言されます | `defineField()` と `FieldType.RELATION` |
| **インデックス** | オブジェクトの 1 つに対する定期的なクエリを高速化するためのデータベースインデックス | `defineIndex()` |
| **タイムライン活動タイプ** | レコードタイムラインに表示される自動監査イベントまたは明示的なドメインイベント | `defineTimelineActivityType()` |
SDK はビルド時に AST 解析を通じてこれらを検出するため、ファイル構成は自由です — 慣例としては `src/objects/`、`src/fields/`、`src/indexes/`、`src/timeline-activity-types/` です。 安定した `universalIdentifier` UUID が、デプロイをまたいであらゆるものを結び付けます。
## インデックス(任意)
アプリは、オブジェクトと一緒にインデックスを同梱しておくことで、繰り返し実行されるクエリを高速に保つことができます。 最も一般的なケースは、頻繁に読み取るステータス列や外部キー列です。
```ts src/indexes/post-card-status.index.ts
import { defineIndex } from 'twenty-sdk/define';
import {
POST_CARD_UNIVERSAL_IDENTIFIER,
STATUS_FIELD_UNIVERSAL_IDENTIFIER,
} from '../objects/post-card.object';
export default defineIndex({
universalIdentifier: 'b6e9d2a1-5a4c-46ca-9d52-42c8f02d1ff0',
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
fields: [
{
universalIdentifier: 'b6e9d2a1-5a4c-46ca-9d52-42c8f02d1ff1',
fieldUniversalIdentifier: STATUS_FIELD_UNIVERSAL_IDENTIFIER,
},
],
});
```
### 一意のインデックス
`defineIndex` は、単一列と複数列のどちらの一意性でも、`isUnique: true` を受け付けます。 これは推奨されるプリミティブです — `defineField({ isUnique: true })` は非推奨であり、今後のリリースで削除されます。
```ts
defineIndex({
universalIdentifier: '…',
objectUniversalIdentifier: PERSON_UNIVERSAL_IDENTIFIER,
isUnique: true,
fields: [{ universalIdentifier: '…', fieldUniversalIdentifier: EMAIL_FIELD_UNIVERSAL_IDENTIFIER }],
});
```
### その他の制約
* 部分的な `WHERE` 句は管理者の管理下にあり、アプリ側で宣言することはできません。
* 各オブジェクトにはカスタムインデックスを最大 10 個まで設定できます(フレームワーク自身のインデックスはカウントされません)。
`fields` 配列は、Postgres に使わせたい順序に並べてください ― 電話帳のように、最も左の列が先になります。 インデックスにはコストがかかります。テーブルへの書き込みのたびに更新されます。 必要とするクエリがある場合にのみインデックスを追加してください。
<Note>
**Application Config** や **Roles & Permissions** をお探しですか? それらは、アプリが追加するデータではなくアプリ自体を記述するものであり、[Config](/l/ja/developers/extend/apps/config/overview) 配下にあります。 **Connections**(Linear、GitHub、Slack OAuth)をお探しですか? それらはロジック関数*から*呼び出されるために存在し、[Logic](/l/ja/developers/extend/apps/logic/connections) 配下にあります。
</Note>

View File

@@ -0,0 +1,184 @@
---
title: 関連
description: 双方向の MANY_TO_ONE / ONE_TO_MANY リレーションでオブジェクト同士を接続します。
icon: diagram-project
---
リレーションは 2 つのオブジェクト同士を関連付けます。 Twenty におけるリレーションは常に **双方向** です — すべてのリレーションには 2 つの側面があり、それぞれが相手を参照するフィールドとして宣言されます。
| リレーションタイプ | 説明 | 外部キーあり? |
| ------------- | --------------------------------- | -------------------- |
| `MANY_TO_ONE` | このオブジェクトの多数のレコードが、対象の 1 件のレコードを指す | はい(`joinColumnName`) |
| `ONE_TO_MANY` | このオブジェクトの 1 件のレコードが、対象の多数のレコードを持つ | いいえ(逆側) |
## リレーションの仕組み
すべてのリレーションには、互いを参照する**2 つのフィールド**が必要です:
1. **MANY_TO_ONE** 側 — 外部キーを保持するオブジェクト側に存在します。
2. **ONE_TO_MANY** 側 — コレクションを所有するオブジェクト側に存在します。
両方のフィールドは `FieldType.RELATION` を使用し、`relationTargetFieldMetadataUniversalIdentifier` を介して相互参照します。
## 例: PostCard は多数の Recipient を持つ
`PostCard` は多数の `PostCardRecipient` レコードに送信できます。 各受取人は厳密に 1 件の PostCard に属します。
**ステップ 1: PostCard に ONE_TO_MANY 側を定義**(「1」の側):
```ts src/fields/post-card-recipients-on-post-card.field.ts
import { defineField, FieldType, RelationType } from 'twenty-sdk/define';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object';
// Export so the other side can reference it
export const POST_CARD_RECIPIENTS_FIELD_ID = 'a1111111-1111-1111-1111-111111111111';
// Import from the other side
import { POST_CARD_FIELD_ID } from './post-card-on-post-card-recipient.field';
export default defineField({
universalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID,
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
type: FieldType.RELATION,
name: 'postCardRecipients',
label: 'Post Card Recipients',
icon: 'IconUsers',
relationTargetObjectMetadataUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER,
relationTargetFieldMetadataUniversalIdentifier: POST_CARD_FIELD_ID,
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
},
});
```
**ステップ 2: PostCardRecipient に MANY_TO_ONE 側を定義**(「多」の側 — 外部キーを保持):
```ts src/fields/post-card-on-post-card-recipient.field.ts
import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk/define';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object';
// Export so the other side can reference it
export const POST_CARD_FIELD_ID = 'b2222222-2222-2222-2222-222222222222';
// Import from the other side
import { POST_CARD_RECIPIENTS_FIELD_ID } from './post-card-recipients-on-post-card.field';
export default defineField({
universalIdentifier: POST_CARD_FIELD_ID,
objectUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER,
type: FieldType.RELATION,
name: 'postCard',
label: 'Post Card',
icon: 'IconMail',
relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID,
universalSettings: {
relationType: RelationType.MANY_TO_ONE,
onDelete: OnDeleteAction.CASCADE,
joinColumnName: 'postCardId',
},
});
```
<Note>
**循環インポート:** 両方のリレーションフィールドは互いの `universalIdentifier` を参照します。 循環インポートの問題を避けるため、各ファイルからフィールド ID を名前付き定数としてエクスポートし、相手のファイルでインポートしてください。 ビルドシステムがコンパイル時にこれらを解決します。
</Note>
## 中間リレーション
中間オブジェクトは、1つのソースレコードを1つ以上のターゲットレコードに接続します。 たとえば、`PostCardRecipient` は `postCard` リレーションと `recipient` リレーションの両方を保持できます。 ソースオブジェクトの `ONE_TO_MANY` フィールドで、`junctionTargetFieldUniversalIdentifier` を、ターゲットを指す中間オブジェクト上のリレーションフィールドに設定します:
```ts
export default defineField({
universalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID,
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
type: FieldType.RELATION,
name: 'postCardRecipients',
label: 'Post Card Recipients',
relationTargetObjectMetadataUniversalIdentifier:
POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER,
relationTargetFieldMetadataUniversalIdentifier: POST_CARD_FIELD_ID,
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
junctionTargetFieldUniversalIdentifier: RECIPIENT_FIELD_ID,
},
});
```
`RECIPIENT_FIELD_ID` は、中間オブジェクト上の `MANY_TO_ONE` または morph リレーションを識別する必要があります。 これにより、[タイムラインアクティビティのファンアウト](/l/ja/developers/extend/apps/data/timeline-activity-types#automatic-and-explicit-events)などの機能が中間オブジェクトをたどる際に、どの関連レコードがターゲットであるかをTwentyに伝えます。
## 標準オブジェクトとのリレーション
組み込みの Twenty オブジェクト(Person、Company など)とリレーションを作成するには、`STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` を使用します:
```ts src/fields/person-on-self-hosting-user.field.ts
import {
defineField,
FieldType,
RelationType,
OnDeleteAction,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk/define';
import { SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER } from '../objects/self-hosting-user.object';
export const PERSON_FIELD_ID = 'c3333333-3333-3333-3333-333333333333';
export const SELF_HOSTING_USER_REVERSE_FIELD_ID = 'd4444444-4444-4444-4444-444444444444';
export default defineField({
universalIdentifier: PERSON_FIELD_ID,
objectUniversalIdentifier: SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER,
type: FieldType.RELATION,
name: 'person',
label: 'Person',
description: 'Person matching with the self hosting user',
isNullable: true,
relationTargetObjectMetadataUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier,
relationTargetFieldMetadataUniversalIdentifier: SELF_HOSTING_USER_REVERSE_FIELD_ID,
universalSettings: {
relationType: RelationType.MANY_TO_ONE,
onDelete: OnDeleteAction.SET_NULL,
joinColumnName: 'personId',
},
});
```
## リレーションフィールドのプロパティ
| プロパティ | 必須 | 説明 |
| ---------------------------------------------------------- | ------------------- | ------------------------------------------------------------------- |
| `type` | はい | `FieldType.RELATION` でなければなりません |
| `relationTargetObjectMetadataUniversalIdentifier` | はい | 対象オブジェクトの `universalIdentifier` |
| `relationTargetFieldMetadataUniversalIdentifier` | はい | 対象オブジェクト上の対応するフィールドの `universalIdentifier` |
| `universalSettings.relationType` | はい | `RelationType.MANY_TO_ONE` または `RelationType.ONE_TO_MANY` |
| `universalSettings.onDelete` | MANY_TO_ONE のみ | 参照先レコードが削除されたときの動作: `CASCADE`、`SET_NULL`、`RESTRICT`、または `NO_ACTION` |
| `universalSettings.joinColumnName` | MANY_TO_ONE のみ | 外部キーのデータベース列名(例: `postCardId`) |
| `universalSettings.junctionTargetFieldUniversalIdentifier` | 中間 `ONE_TO_MANY` のみ | ファンアウトターゲットを指す、中間オブジェクト上のリレーションフィールド |
## インラインのリレーションフィールド
[`defineObject`](/l/ja/developers/extend/apps/data/objects) の中で、リレーションを直接宣言することもできます。 インラインで宣言する場合は、`objectUniversalIdentifier` を省略します — 親オブジェクトから継承されます:
```ts
export default defineObject({
universalIdentifier: '...',
nameSingular: 'postCardRecipient',
// ...
fields: [
{
universalIdentifier: POST_CARD_FIELD_ID,
type: FieldType.RELATION,
name: 'postCard',
label: 'Post Card',
relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID,
universalSettings: {
relationType: RelationType.MANY_TO_ONE,
onDelete: OnDeleteAction.CASCADE,
joinColumnName: 'postCardId',
},
},
// … other fields
],
});
```

View File

@@ -0,0 +1,201 @@
---
title: システムメタデータのターゲット指定
description: Twenty がすべてのオブジェクトに自動的にプロビジョニングするメタデータの決定論的なユニバーサル識別子を解決し、アプリがハードコーディングせずに参照できるようにします。
icon: gears
---
Twenty のすべてのオブジェクトには、フィールドのセットや、そのカラムを持つメインのリストビューなど、あなた自身が宣言する必要のない **システムメタデータ** が付属しています。 オブジェクトがプロビジョニングされるときに、そのすべてをサーバーが作成し、そのセットは Twenty の拡張に合わせて増えていきます。
あなたがそれを宣言しないため、インポートできる `universalIdentifier` 定数は存在しません。 その代わりに、サーバーはそれぞれの識別子を決定論的に**導出**し、`twenty-sdk` が同じ導出処理を公開することで、マニフェストがサーバーで使用される正確な値を解決できるようにします。
## システムフィールド
すべてのオブジェクトに存在し、いずれも [`defineField()`](/l/ja/developers/extend/apps/data/extending-objects) で宣言しないスカラー フィールドは次のとおりです。
`id`, `createdAt`, `updatedAt`, `deletedAt`, `createdBy`, `updatedBy`, `position`, `searchVector`
では、[view](/l/ja/developers/extend/apps/layout/views) で `createdAt` をカラムとしてどのように参照すればよいのでしょうか。
### 問題
Twenty 2.19 以降、システムフィールドのユニバーサル識別子は、アプリケーションのユニバーサル識別子、オブジェクトのユニバーサル識別子、およびフィールド名という 3 つの入力からサーバーによって **決定的に導出** されます。 id を独自に作成してハードコードしても機能しません。サーバー上のどの値とも一致せず、同期時にそのぶら下がった参照は拒否されます。
```
Dev sync failed: viewField: INVALID_VIEW_DATA: Field metadata not found
```
### 解決策
<Note>
`getFieldUniversalIdentifier` は `twenty-sdk` 2.21 以降で利用可能です。
</Note>
`getFieldUniversalIdentifier` を使用して、サーバーが使用するものとまったく同じ値を解決します。 この関数は 3 つの入力を受け取り、フィールドのユニバーサル識別子を返します。
```ts
import { getFieldUniversalIdentifier } from 'twenty-sdk/define';
const createdAtFieldId = getFieldUniversalIdentifier({
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier: MY_OBJECT_UNIVERSAL_IDENTIFIER,
name: 'createdAt',
});
```
* `applicationUniversalIdentifier` はあなたのアプリの識別子であり、[`defineApplication()`](/l/ja/developers/extend/apps/config/application) に渡すものです。
* `objectUniversalIdentifier` は、そのフィールドが属するオブジェクトの識別子です。
* `name` はシステムフィールド名であり、上に挙げた値のいずれかです。
### 例: ビュー内の createdAt カラム
典型的なケースは、カスタムオブジェクトの 1 つのビューに `createdAt` カラムを追加することです。 フィールド id を解決し、他の `fieldMetadataUniversalIdentifier` と同様にそれを参照します。
```ts src/views/example-view.ts
import {
defineView,
getFieldUniversalIdentifier,
} from 'twenty-sdk/define';
const APPLICATION_UNIVERSAL_IDENTIFIER =
'0b04e15c-27b2-4741-9046-b32e07469072';
const MY_OBJECT_UNIVERSAL_IDENTIFIER =
'c782b61c-70fd-4c88-9cd6-4e61ab8d7591';
export default defineView({
universalIdentifier: '70f10d44-144a-4da8-8c6f-3ec2422138c0',
name: 'All records',
objectUniversalIdentifier: MY_OBJECT_UNIVERSAL_IDENTIFIER,
icon: 'IconList',
position: 0,
fields: [
{
universalIdentifier: '75a90bc4-d901-4df4-85e0-af29db5e0104',
fieldMetadataUniversalIdentifier: getFieldUniversalIdentifier({
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier: MY_OBJECT_UNIVERSAL_IDENTIFIER,
name: 'createdAt',
}),
position: 0,
isVisible: true,
size: 200,
},
],
});
```
同じように解決された id は、`fieldMetadataUniversalIdentifier` が必要とされるあらゆる場所、すなわちビューのフィールド、フィルター、ソート、グループ、ページレイアウトウィジェットでそのまま使用できます。
<Note>
id は解決して、ハードコードしないでください。 サーバーはその値をアプリケーション id、オブジェクト id、フィールド名から導出するため、`getFieldUniversalIdentifier` を呼び出すことで、それらの入力が変更された場合でも参照を正しい状態に保ち、導出方法が将来変わった場合のずれも防ぐことができます。
</Note>
### システムリレーションフィールド
<Note>
`getSystemRelationFieldUniversalIdentifier` は `twenty-sdk`
2.23 以降で利用可能であり、バージョン 2.23 以降の Twenty サーバーが必要です。
</Note>
上記のスカラー型システムフィールドに加えて、サーバーはすべてのオブジェクトに対して 4 つの**システムリレーションフィールド**を提供します。`timelineActivities`、`attachments`、`noteTargets`、`taskTargets` であり、それぞれが対応する標準リレーションオブジェクトを参照します。
これらのフィールドは `getFieldUniversalIdentifier` では解決されません。フィールドを保持しているオブジェクトと、そのフィールドが参照するオブジェクトから、**名前に依存せず**識別子が導出されます。 この方法により、オブジェクトの名称を変更しても、そのリレーションフィールドの識別子は決して変わりません。
それらを解決するには、`getSystemRelationFieldUniversalIdentifier` を使用します。
```ts
import {
getSystemRelationFieldUniversalIdentifier,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk/define';
// rocket.attachments — the relation field hosted on your custom object
const rocketAttachmentsFieldId = getSystemRelationFieldUniversalIdentifier({
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier: ROCKET_OBJECT_UNIVERSAL_IDENTIFIER,
relationTargetObjectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment.universalIdentifier,
});
```
* `objectUniversalIdentifier` は、そのフィールドを **保持している** オブジェクトです。
* `relationTargetObjectUniversalIdentifier` は、そのフィールドが **参照している** オブジェクトです。
方向は引数の順序によってエンコードされます。 逆方向(例: `attachment.targetRocket`。これは、標準リレーションオブジェクト上でサーバーが作成する morph フィールド)を解決するには、2 つを入れ替えます。
```ts
// attachment.targetRocket — the reverse morph field on Attachment
const attachmentTargetRocketFieldId =
getSystemRelationFieldUniversalIdentifier({
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment.universalIdentifier,
relationTargetObjectUniversalIdentifier: ROCKET_OBJECT_UNIVERSAL_IDENTIFIER,
});
```
スカラーのシステムフィールドと同様に、解決された ID は `fieldMetadataUniversalIdentifier` が想定されるあらゆる場所で使用できます。
## システムビュー
<Note>
`getSystemViewUniversalIdentifier` と `getSystemViewFieldUniversalIdentifier` は `twenty-sdk` 2.26 以降で利用可能であり、バージョン 2.26 以降の Twenty サーバーが必要です。
</Note>
サーバーは、すべてのオブジェクトに対して **システムビュー** もプロビジョニングします。つまり、表示可能なフィールドごとに 1 つのカラムを持つメインのリストビュー(`All {objectLabelPlural}`、キーは `ViewKey.INDEX`(`SYSTEM_VIEW_KEYS.INDEX` から導出))です。 システムリレーションフィールドと同様に、それらの識別子は **名前に依存しない** 形で導出されるため、オブジェクトやフィールドの名前を変更しても識別子は変わりません。
ビューを解決するには、`getSystemViewUniversalIdentifier` を使用します。
```ts
import {
getSystemViewUniversalIdentifier,
SYSTEM_VIEW_KEYS,
} from 'twenty-sdk/define';
const rocketIndexViewId = getSystemViewUniversalIdentifier({
objectMetadataApplicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier: ROCKET_OBJECT_UNIVERSAL_IDENTIFIER,
viewKey: SYSTEM_VIEW_KEYS.INDEX,
});
```
* `objectMetadataApplicationUniversalIdentifier` は **オブジェクト** を所有しているアプリケーションであり、ビューが名前空間分割されている単位です。
* `objectUniversalIdentifier` は、そのビューがリストするオブジェクトです。
* `viewKey` はシステムビューを識別するための判別子です。メインのリストビューには `SYSTEM_VIEW_KEYS.INDEX`、レコードページのフィールドウィジェットのビューには `SYSTEM_VIEW_KEYS.FIELDS_WIDGET` を使用します。 これは導出のキーとなる値であり、ビューの行に保存されるのは `INDEX` のみです。
解決された ID は、[`NavigationMenuItemType.VIEW`](/l/ja/developers/extend/apps/layout/navigation-menu-items) のサイドバーエントリなど、`viewUniversalIdentifier` が必要とされるあらゆる場所で使用できます。 オブジェクトのメインリストを単に開くだけであれば、導出が不要な `NavigationMenuItemType.OBJECT` と `targetObjectUniversalIdentifier` を優先して使用してください。
`getSystemViewFieldUniversalIdentifier` は、ビューとそのビューが表示するフィールドから、システムビュー上の 1 つの**カラム**を解決します。
```ts
import { getSystemViewFieldUniversalIdentifier } from 'twenty-sdk/define';
const rocketNameColumnId = getSystemViewFieldUniversalIdentifier({
fieldMetadataApplicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
viewUniversalIdentifier: rocketIndexViewId,
fieldMetadataUniversalIdentifier: ROCKET_NAME_FIELD_UNIVERSAL_IDENTIFIER,
});
```
最初の引数に注目してください。カラムは、そのビューではなく、**表示しているフィールド** を所有するアプリケーションで名前空間分割されます。 あなたのアプリが標準オブジェクトに追加したフィールドは、Twenty が所有するビュー上で、そのアプリケーションの名前空間の下にカラムが導出されます。
<Warning>
システムビューとそのカラムは **サーバー所有** です。これらの識別子は、宣言ではなく参照のためにのみ解決してください。 [`defineView()`](/l/ja/developers/extend/apps/layout/views) の `key` は非推奨で無視されます。そのため、マニフェストのビューが `INDEX` キーを主張することはできません。また、サーバーはあなたが追加するすべてのフィールドに対してすでにカラムをプロビジョニングしているため、同じフィールドについてシステムビュー上で独自の `defineViewField()` を宣言すると競合が発生します。
</Warning>
## 標準の Twenty オブジェクト
**標準** の Twenty オブジェクト(Person、Company、Opportunity など)の場合、何も導出する必要はありません。フィールドおよびビューの識別子は、あらかじめ計算された定数として用意されており、直接インポートできます。
```ts
import { STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk/define';
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.createdAt.universalIdentifier
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.fields.updatedAt.universalIdentifier
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.views.allPeople.universalIdentifier
```
[`defineObject()`](/l/ja/developers/extend/apps/data/objects) で **あなたのアプリ** が定義するオブジェクトのように、そのような定数が存在しない場合には、ここまでで紹介したヘルパーを使用してください。
<Note>
`name` はシステムフィールドではなく、**デフォルト** フィールドです。 このフィールドは独自のハードコードされたユニバーサル識別子を持ち、`getFieldUniversalIdentifier` で解決されることはありません。 あなたが定義したオブジェクトでは、`name` フィールドは `defineObject()` で与えた識別子で参照してください。
</Note>

View File

@@ -0,0 +1,184 @@
---
title: タイムライン活動タイプ
description: 自動監査イベントと、レコードタイムラインでレンダリングされる明示的なアプリイベントを定義します。
icon: clock-rotate-left
---
タイムラインアクティビティタイプは、レコードのタイムラインに表示されるイベントの安定したボキャブラリーを定義します。 標準オブジェクトとアプリオブジェクトは同じコントラクトを使用します。タイプにはラベルとアイコンがあり、出力時にオプションで宣言できます。 アプリの [front components](/l/ja/developers/extend/apps/layout/front-components) を使ってレンダリングすることもできます。
<Note>
タイムラインアクティビティタイプはベータ版で、Twenty 2.34では間もなく公開されます。
APIは、アプリ開発者のユースケースから学びながら進化することができます。
</Note>
足場を使って1つ作成します:
```bash filename="Terminal"
yarn twenty dev:add timelineActivityType
```
または直接定義します。
```ts filename="src/timeline-activity-types/post-card-created.ts"
import { defineTimelineActivityType } from 'twenty-sdk/define';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
export default defineTimelineActivityType({
universalIdentifier: 'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7500',
name: 'postCardCreated',
label: 'created a post card',
icon: 'IconMail',
emit: {
on: 'created',
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
},
});
```
## 自動および明示的なイベント
`emit` を追加すると、このタイプが自動的に作成されます。 `emit.on`は`created`、`updated`、`deleted`、`restored`、`linked`、`unlinked`、`emit.objectUniversalIdentifier`はソースオブジェクトを識別します。 ワークスペースで同じエミットキー(`on`、オブジェクト、オプション)を扱うことができるのは、有効な型が 1 つだけです。
`emit.through `がなければ、イベントはソースレコードの独自のタイムラインに書き込まれます。 関連するレコードにファンアウトするには、 \`emit.through を設定します。 elationFieldUniversalIdentifier: 直接多対一のリレーション、またはソースオブジェクトの [1対多の接合関係](/l/ja/developers/extend/apps/data/relations#junction-relations) のいずれかへ。 Morphリレーションは彼らのmorphグループのすべてのメンバーにファンアウトするので、1つの宣言はいくつかのオブジェクトタイプをターゲットにすることができます。
直接のリレーションのため、ソースレコードを作成または復元すると、 `linked` が生成され、削除すると `unlinked` が生成されます。 リレーションは前のターゲットに `unlinked` を加え、新しいターゲットに `linked` を加えます。 その他のソースアップデートではリンクイベントは発生しません。 これは添付ファイルが使用する契約で、ターゲットは直接モーフ関係です。
ジャンクションリレーションでは、`universalSettings.junctionTargetFieldUniversalIdentifier` はジャンクションオブジェクトからターゲットへのリレーションを識別します。 ジャンクション行を作成または削除すると、`linked` または `unlinked` が生成されます。 ジャンクション行のいずれかの側を再挿入すると、リンクイベントも生成されます。関連しないジャンクションフィールドへの更新は行われません。
`linked` と `unlinked` イベントは `emit.through `が必要です。トリガーは設定された直接またはジャンクションの関係を変更するためです。
例えば、これはメモ、タスク、メッセージ、カレンダーイベントで使用される一般的なコントラクトと同じです。
```ts filename="src/timeline-activity-types/post-card-linked.ts"
import { defineTimelineActivityType } from 'twenty-sdk/define';
import { POST_CARD_RECIPIENTS_FIELD_UNIVERSAL_IDENTIFIER } from '../fields/post-card-recipients-on-post-card.field';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
export default defineTimelineActivityType({
universalIdentifier: 'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7502',
name: 'postCardLinked',
label: 'received a post card',
icon: 'IconMail',
emit: {
on: 'linked',
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
through: {
relationFieldUniversalIdentifier:
POST_CARD_RECIPIENTS_FIELD_UNIVERSAL_IDENTIFIER,
},
},
});
```
デフォルトでは、ソースの更新ごとにイベントの `updated` が発行されます。 選択したソースフィールドにのみ変更する場合、ターゲットタイムラインに「emit.through.triggerFieldUniversalIdentifiers」を設定します。
ロジック関数がそれ自身を作成する明示的なドメインイベントに対して「emit」を省略します。 これにより、同じ操作に対して自動監査行と明示的な行の両方を生成することができません。
```ts filename="src/timeline-activity-types/post-card-sent.ts"
import { defineTimelineActivityType } from 'twenty-sdk/define';
export default defineTimelineActivityType({
universalIdentifier: 'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7501',
name: 'postCardSent',
label: 'sent a post card',
icon: 'IconSend',
});
```
[`createTimelineActivity()`] (/developers/extend/apps/logic/logic-functions#create-a-timeline-activity ) を使用して明示的なイベントを作成します。 アプリコードは安定したユニバーサル識別子を使用します; Twenty はインストール固有のメタデータ ID を解決します。
## カスタムレンダリング
Twenty は、フロントコンポーネントがない場合、タイプラベル、アイコン、およびリンクオブジェクトメタデータからネイティブの一般的な行をレンダリングします。 これはオブジェクト固有のレンダラーを持たない標準オブジェクトおよびカスタムオブジェクトに対して機能します。
カスタムの詳細については、 `frontComponentUniversalIdentifier` を同じアプリが所有するフロントコンポーネントに設定します。
```ts
export default defineTimelineActivityType({
universalIdentifier: 'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7501',
name: 'postCardSent',
label: 'sent a post card',
icon: 'IconSend',
frontComponentUniversalIdentifier: '88c15ae2-5f87-4a6b-b48f-1974bbe62eb7',
});
```
ネイティブの行は、折りたたまれたプレゼンテーションのままです。 Twenty は、ユーザーがその行を展開した後にのみフロントコンポーネントをマウントし、すべての表示イベントに対して Sandbox と worker を回避します。 コンポーネント内で、`二十-sdk/front-component` から `useTimelineActivityId()` を呼び出し、行IDを読み込み、プレゼンテーションの必要なデータを取得します。 コンポーネントがタイムライン行の外側にレンダリングされると、 `null` を返します。
## 別のアプリケーションのプレゼンテーションを上書きする
アプリケーションは、独自のオブジェクトの自動タイムライン契約を所有します。 別のアプリが所有するオブジェクトでイベントをカスタマイズするには、既存の型を `replaceTimelineActivityTypeUniversalIdentifier` で明示的に宣言します。
```ts
export default defineTimelineActivityType({
universalIdentifier: 'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7503',
name: 'companyCreatedWithDeploymentContext',
label: 'was created from a deployment',
emit: {
on: 'created',
objectUniversalIdentifier: COMPANY_UNIVERSAL_IDENTIFIER,
},
replacesTimelineActivityTypeUniversalIdentifier:
RECORD_CREATED_TIMELINE_ACTIVITY_TYPE_UNIVERSAL_IDENTIFIER,
frontComponentUniversalIdentifier: '88c15ae2-5f87-4a6b-b48f-1974bbe62eb7',
});
```
explicit-only 型ではなく、既存の自動エミットスロットを置き換えるため、オーバーライドは `emit` を含める必要があります。 参照される型は、ターゲットオブジェクトのアプリケーションに属し、同じアクションとルートを記述する必要があります。 オーバーライドアプリを削除すると、ベースタイプが復元され、ベースタイプが削除されるとオーバーライドは無効になります。
## ワークスペースの上書きとミュート
ワークスペース管理者は、タイプのプレゼンテーションを変更したり、アプリケーションをフォークすることなく、自動および明示的なイベントをミュートすることができます。 インストール固有の型 ID を持つメタデータ API の `updateTimelineActivityType` 変更を使用します。
```graphql
mutation CustomizeTimelineActivityType(
$id: UUID!
$label: String
$icon: String
$isActive: Boolean
) {
updateTimelineActivityType(
input: { id: $id, label: $label, icon: $icon, isActive: $isActive }
) {
id
label
icon
isActive
}
}
```
`label`と`icon`はワークスペースの上書きとして保存されるため、後でアプリケーションの更新は管理者の選択肢を上書きしません。 自動発行を停止し、そのタイプの新しい明示的イベントを拒否するには、`isActive` を `false` に設定します。 既存の行は表示されたままです。
アクティブ状態を含むアプリケーションのデフォルトを復元するには、次を使用します。
```graphql
mutation ResetTimelineActivityType($id: UUID!) {
resetTimelineActivityType(id: $id) {
id
label
icon
isActive
}
}
```
## 設定フィールド
| フィールド | 必須 | 説明 |
| ------------------------------------------------ | ------------ | --------------------------------------------- |
| `universalIdentifier` | はい | インストールおよびアップグレードをまたいで安定したタイプの UUID |
| `name` | はい | アプリ内のプログラム用名前。アプリ内で一意である必要があります。 |
| `label` | はい | ネイティブレンダラーで使用されるユーザー向けアクションテキスト |
| `icon` | いいえ | イベントの横に表示される Twenty アイコン名 |
| `emit` | いいえ | 自動発行の宣言。明示的イベント専用のタイプでは省略します |
| `emit.on` | emit を使用する場合 | このタイプが書き込まれる原因となる監査アクション |
| `emit.objectUniversalIdentifier` | emit を使用する場合 | レコードがこのタイプを発行するオブジェクト |
| `emit.through.relationFieldUniversalIdentifier` | いいえ | 関連するタイムラインにファンアウトするために使用されるダイレクトまたはジャンクションの関係 |
| `emit.through.triggerFieldUniversalIdentifiers` | いいえ | `updated` 経由イベントをトリガーできるソースフィールド |
| `frontComponentUniversalIdentifier` | いいえ | 行が展開されたときにマウントされたアプリ所有のフロントコンポーネントです |
| `replaceTimelineActivityTypeUniversalIdentifier` | いいえ | 既存のエミットタイプを置き換えるには、 `emit` が必要です |
イベントが作成されたときに意味的なアイデンティティと耐久性のあるプレゼンテーションのフォールバックを20枚のスナップショット。 歴史的な行は、元のアクションとオブジェクトの意味を保持します。 タイプがインストールされている間は、現在翻訳されているラベル、アイコン、およびフロントコンポーネントがライブレンダリングされます。アンインストール後、スナップショットのフォールバックは行を読み取ることができます。 解像度は普遍的な識別子を使用するため、プレゼンテーションはアプリの再インストールも残ります。