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
213 lines
11 KiB
Plaintext
213 lines
11 KiB
Plaintext
---
|
||
title: Referenciando metadados de sistema
|
||
description: Resolva os identificadores universais determinísticos dos metadados que o Twenty provisiona automaticamente em cada objeto, para que seu app possa referenciá-los sem hardcoding.
|
||
icon: gears
|
||
---
|
||
|
||
Todo objeto no Twenty vem com **metadados de sistema** que você nunca declara explicitamente, como um conjunto de campos e uma visualização principal de lista com suas colunas. O servidor cria tudo isso quando o objeto é provisionado, e o conjunto cresce conforme o Twenty cresce.
|
||
|
||
Como você não o declara, não existe nenhuma constante `universalIdentifier` para você importar. Em vez disso, o servidor **deriva** cada identificador de forma determinística, e o `twenty-sdk` expõe a mesma derivação para que seu manifesto possa resolver o valor exato que o servidor usa.
|
||
|
||
## Campos do sistema
|
||
|
||
Os campos escalares presentes em todo objeto, nenhum dos quais você declara com [`defineField()`](/l/pt/developers/extend/apps/data/extending-objects):
|
||
|
||
`id`, `createdAt`, `updatedAt`, `deletedAt`, `createdBy`, `updatedBy`, `position`, `searchVector`
|
||
|
||
Então, como você referencia `createdAt` como uma coluna em uma [visualização](/l/pt/developers/extend/apps/layout/views)?
|
||
|
||
### O Problema
|
||
|
||
Desde o Twenty 2.19, o identificador universal de um campo de sistema é **derivado deterministicamente** pelo servidor a partir de três entradas: o identificador universal do aplicativo, o identificador universal do objeto e o nome do campo. Inventar um id e deixá-lo hardcoded não funciona: ele não corresponde a nada no servidor, e a sincronização rejeita a referência pendente:
|
||
|
||
```
|
||
Dev sync failed: viewField: INVALID_VIEW_DATA: Field metadata not found
|
||
```
|
||
|
||
### A Solução
|
||
|
||
<Note>
|
||
`getFieldUniversalIdentifier` está disponível a partir do `twenty-sdk` 2.21.
|
||
</Note>
|
||
|
||
Use `getFieldUniversalIdentifier` para resolver exatamente o mesmo valor que o servidor usa. Ela recebe as três entradas e retorna o identificador universal do campo:
|
||
|
||
```ts
|
||
import { getFieldUniversalIdentifier } from 'twenty-sdk/define';
|
||
|
||
const createdAtFieldId = getFieldUniversalIdentifier({
|
||
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
|
||
objectUniversalIdentifier: MY_OBJECT_UNIVERSAL_IDENTIFIER,
|
||
name: 'createdAt',
|
||
});
|
||
```
|
||
|
||
* `applicationUniversalIdentifier` é o identificador do seu app, aquele que você passa para [`defineApplication()`](/l/pt/developers/extend/apps/config/application).
|
||
* `objectUniversalIdentifier` é o identificador do objeto ao qual o campo pertence.
|
||
* `name` é o nome do campo de sistema, um dos valores listados acima.
|
||
|
||
### Exemplo: uma coluna createdAt em uma visualização
|
||
|
||
O caso típico é adicionar uma coluna `createdAt` a uma visualização de um dos seus objetos personalizados. Resolva o id do campo e referencie-o como qualquer outro `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,
|
||
},
|
||
],
|
||
});
|
||
```
|
||
|
||
O mesmo id resolvido funciona em qualquer lugar onde se espera um `fieldMetadataUniversalIdentifier`: campos de visualização, filtros, ordenações, agrupamentos e widgets de layout de página.
|
||
|
||
<Note>
|
||
Resolva o id, não o deixe hardcoded. Como o servidor deriva o valor a partir do
|
||
id do aplicativo, do id do objeto e do nome do campo, chamar
|
||
`getFieldUniversalIdentifier` mantém a sua referência correta mesmo que essas
|
||
entradas mudem, e evita divergências se a derivação evoluir no futuro.
|
||
</Note>
|
||
|
||
### Campos de relação do sistema
|
||
|
||
<Note>
|
||
`getSystemRelationFieldUniversalIdentifier` está disponível a partir da versão 2.23 do `twenty-sdk` e requer um servidor Twenty na versão 2.23 ou posterior.
|
||
</Note>
|
||
|
||
Além dos campos escalares de sistema acima, o servidor também provisiona quatro **campos de relação de sistema** em cada objeto: `timelineActivities`, `attachments`, `noteTargets` e `taskTargets`, cada um apontando para o objeto de relação padrão correspondente.
|
||
|
||
Dessa forma, esses campos não são resolvidos com `getFieldUniversalIdentifier`: seu identificador é derivado de forma **independente de nome**, a partir do objeto que hospeda o campo e do objeto para o qual o campo aponta. Dessa forma, renomear um objeto nunca altera os identificadores de seus campos de relação.
|
||
|
||
Use `getSystemRelationFieldUniversalIdentifier` para resolvê-los:
|
||
|
||
```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` é o objeto que **hospeda** o campo.
|
||
* `relationTargetObjectUniversalIdentifier` é o objeto para o qual o campo **aponta**.
|
||
|
||
A direção é codificada pela ordem dos argumentos. Para resolver o lado inverso (por exemplo, `attachment.targetRocket`, o campo morph que o servidor cria no objeto de relação padrão), inverta os dois:
|
||
|
||
```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,
|
||
});
|
||
```
|
||
|
||
Assim como acontece com campos de sistema escalares, o id resolvido funciona em qualquer lugar em que um `fieldMetadataUniversalIdentifier` é esperado.
|
||
|
||
## Visualizações de sistema
|
||
|
||
<Note>
|
||
`getSystemViewUniversalIdentifier` e `getSystemViewFieldUniversalIdentifier`
|
||
estão disponíveis a partir da versão 2.26 do `twenty-sdk` e requerem um servidor Twenty na versão 2.26 ou posterior.
|
||
</Note>
|
||
|
||
O servidor também provisiona uma **visualização de sistema** em cada objeto: a visualização principal de lista (`All {objectLabelPlural}`, com chave `ViewKey.INDEX` (derivada com `SYSTEM_VIEW_KEYS.INDEX`)), com uma coluna por campo exibível. Assim como os campos de relação de sistema, seus identificadores são derivados **sem usar nomes**, então renomear um objeto ou um campo nunca os altera.
|
||
|
||
Use `getSystemViewUniversalIdentifier` para resolvê‑la:
|
||
|
||
```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` é o aplicativo proprietário do **objeto**, que define o namespace da visualização.
|
||
* `objectUniversalIdentifier` é o objeto que a visualização lista.
|
||
* `viewKey` é o discriminador de visualização de sistema: `SYSTEM_VIEW_KEYS.INDEX` para a visualização principal de lista, `SYSTEM_VIEW_KEYS.FIELDS_WIDGET` para a visualização do widget de campos da página de registro. Ele é a chave da derivação; apenas `INDEX` também é armazenado na linha da visualização.
|
||
|
||
O id resolvido funciona em qualquer lugar em que um `viewUniversalIdentifier` seja esperado, como uma entrada de barra lateral [`NavigationMenuItemType.VIEW`](/l/pt/developers/extend/apps/layout/navigation-menu-items). Para simplesmente abrir a lista principal de um objeto, prefira `NavigationMenuItemType.OBJECT` com `targetObjectUniversalIdentifier`: isso não precisa de derivação.
|
||
|
||
`getSystemViewFieldUniversalIdentifier` resolve uma única **coluna** em uma visualização de sistema, a partir da visualização e do campo que ela exibe:
|
||
|
||
```ts
|
||
import { getSystemViewFieldUniversalIdentifier } from 'twenty-sdk/define';
|
||
|
||
const rocketNameColumnId = getSystemViewFieldUniversalIdentifier({
|
||
fieldMetadataApplicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
|
||
viewUniversalIdentifier: rocketIndexViewId,
|
||
fieldMetadataUniversalIdentifier: ROCKET_NAME_FIELD_UNIVERSAL_IDENTIFIER,
|
||
});
|
||
```
|
||
|
||
Observe o primeiro argumento: uma coluna é colocada em namespace pelo aplicativo proprietário do **campo que ela exibe**, não pelo que é proprietário da visualização. Um campo que seu app adiciona a um objeto padrão tem sua coluna derivada sob o seu aplicativo, em uma visualização de propriedade do Twenty.
|
||
|
||
<Warning>
|
||
As visualizações de sistema e suas colunas são **de propriedade do servidor**: resolva seus identificadores
|
||
para referenciá‑las, nunca para declará‑las. `key` em
|
||
[`defineView()`](/l/pt/developers/extend/apps/layout/views) está obsoleto e
|
||
é ignorado, portanto uma visualização de manifesto nunca pode reivindicar a chave `INDEX`, e o servidor
|
||
já provisiona uma coluna para cada campo que você adiciona, então declarar seu próprio
|
||
`defineViewField()` para esse mesmo campo em uma visualização de sistema entra em conflito com ela.
|
||
</Warning>
|
||
|
||
## Objetos Padrão do Twenty
|
||
|
||
Para um objeto **padrão** do Twenty (Person, Company, Opportunity, …), você não precisa derivar nada: os identificadores são constantes pré‑computadas que você pode importar diretamente, tanto para campos quanto para visualizações.
|
||
|
||
```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
|
||
```
|
||
|
||
Use os helpers acima quando o objeto for um que **seu app** define com [`defineObject()`](/l/pt/developers/extend/apps/data/objects), em que não existe tal constante.
|
||
|
||
<Note>
|
||
`name` é um campo **padrão**, não um campo de sistema. Ele mantém seu próprio identificador universal
|
||
hardcoded e não é resolvido por meio de
|
||
`getFieldUniversalIdentifier`. Em objetos que você define, referencie o campo
|
||
`name` pelo identificador que você atribuiu a ele em `defineObject()`.
|
||
</Note>
|