Deterministic system field universal identifier (#22565)

# Introduction

Close twentyhq/core-team-issues#2641

Auto-provisioned field metadata used to get its `universalIdentifier`
from three unrelated sources: random `v4()` on the server when creating
custom objects, hardcoded values in `STANDARD_OBJECTS`, and an ad-hoc
`v5` derivation in the SDK manifest build. This PR unifies all of them
behind the shared `getFieldUniversalIdentifier` derivation:

```
universalIdentifier = f(applicationUniversalIdentifier, objectUniversalIdentifier, fieldName)
```

## Ownership model

The rollout is built on an explicit split of who owns a field's
universal identifier:

- **The 8 system fields** (`id`, `createdAt`, `updatedAt`, `deletedAt`,
`createdBy`, `updatedBy`, `position`, `searchVector`) are
**server-owned**. Their universal identifiers are always the
deterministic derivation, on **every** application (standard,
workspace-custom, installed). Clients cannot provide custom values: a
temporary check in `validateObjectMetadataSystemFieldsIntegrity` rejects
any non-derived system field identifier at migration build time. This
check stands in until system fields are generated exclusively server
side by the metadata side-effect engine and stripped from client inputs
— at which point it becomes structurally impossible to send one.
- **`name` is a default field, not a system field**: it is
auto-provisioned when absent (server side for custom objects, SDK side
for application objects) but authors can define their own. It is only
derived where it is guaranteed to be auto-provisioned. In particular,
standard objects keep their **historical hardcoded** `name` identifiers:
the standard app authors its `name` fields like any installed app would,
and moving those identifiers would break every installed application
referencing them (e.g. views on `opportunity.name`).
- **User-created and author-provided fields** keep random / explicit
identifiers, untouched.

## Server

- `validateObjectMetadataSystemFieldsIntegrity` now validates, on top of
the existing type/`isSystem` checks, that each system field's
`universalIdentifier` equals the deterministic derivation. Runs for
every object creation going through the migration orchestrator: app
sync, custom object creation, standard provisioning
- `build-default-flat-field-metadatas-for-custom-object.util.ts` derives
the system field identifiers (and the auto-provisioned `name`) with
`getFieldUniversalIdentifier` instead of `v4()`
-
`build-default-relation-flat-field-metadatas-for-custom-object.util.ts`
derives both the forward and the reverse default relation field
identifiers deterministically
- `generateMorphOrRelationFlatFieldMetadataPair` accepts optional
`sourceFieldUniversalIdentifier` / `targetFieldUniversalIdentifier` so
callers can inject deterministic values; user-created relations still
default to `v4()`

## twenty-shared

- `STANDARD_OBJECTS` system field identifiers (the 8) are now computed
at module load via `buildStandardObjectSystemFields`; `name` and every
other identifier keep their hardcoded values
- New snapshot test pinning **every** universal identifier of
`STANDARD_OBJECTS`: any identifier change now requires an explicit
snapshot update and should ship with a coordinated backfill

## SDK (breaking, pre-GA)

- `generateDefaultFieldUniversalIdentifier` delegates to
`getFieldUniversalIdentifier` and now requires
`applicationUniversalIdentifier`
- Reverse default relation field identifiers are derived from the
field's real coordinates (standard object UID + actual field name, e.g.
`targetRocket` on `attachment`) instead of the legacy custom-object UID
+ synthetic `${fieldName}Inverse` hash input. Field *names* are
unchanged
- The manifest build threads the application universal identifier
through default field injection (two-pass over object configs)
- `twenty dev:add` now resolves the application universal identifier
upfront and refuses to scaffold anything until `defineApplication`
declares one — no more `fill-later` placeholder for the app UID in
generated files

## Upgrade

A 2.19 **workspace command** backfills existing
`fieldMetadata.universalIdentifier` rows to the deterministic
derivation. Coverage follows the ownership model:

- **The 8 system fields**: taken over for **every application**,
whatever value they currently hold. This is both safe and required now
that sync rejects non-derived values — leaving a row unconverged would
make its application unsyncable
- **`name`**: workspace-custom app → always taken over
(server-generated, no author to clobber); installed applications → only
rows still carrying the legacy SDK derivation are recomputed,
author-provided identifiers are never touched; standard app → never
touched (hardcoded in `STANDARD_OBJECTS`)
- **Default relation fields**: workspace-custom app → forward fields on
custom objects and reverse fields on the standard relation objects;
installed applications → legacy-derivation probe only

All identifiers of a workspace are updated inside a single transaction,
then the command flushes the field-metadata-related workspace caches and
bumps the metadata version.

Stored `applicationRegistration.manifest` snapshots are intentionally
**not** rewritten: installs and upgrades always sync from the
`manifest.json` inside the resolved package (npm/tarball), the stored
column is only used for display/marketplace purposes.

## Breaking behavior for old packages (fail closed)

Packages built with an older SDK carry legacy system field identifiers
in their tarball `manifest.json`. Installing or upgrading such a package
now fails with an explicit `INVALID_SYSTEM_FIELD` validation error
("universal identifier is not deterministic") instead of silently
mismatching against the backfilled rows and triggering a destructive
delete+create. The remediation is to rebuild the package with the new
SDK; the backfill has already converged the installed rows, so the
rebuilt manifest syncs cleanly.

## Test plan

- [x] `twenty-sdk` unit tests (526 tests) and typecheck
- [x] `twenty-shared` unit tests (1635 tests) including the
`STANDARD_OBJECTS` snapshot; `name` identifiers verified byte-for-byte
identical to `main`
- [x] Lint and typecheck clean on all touched packages
- [x] Integration: create a custom object and verify system + default
relation field identifiers match the deterministic derivation
(`create-one-object-metadata-deterministic-field-universal-identifiers`,
13 assertions passing)
- [x] Integration: `failing-sync-application-object-system-fields`
extended with a non-derived system field identifier case; all
identifiers in the spec pinned deterministically so snapshots embedding
expected/actual values are stable across runs (verified with a double
run)
- [x] Integration: all application sync suites pass with the derived
system field identifiers now required by the
`buildDefaultObjectManifest` test helper (9 suites, 20 tests)
- [x] Full test-database reset: standard app provisioning and seeded
workspaces pass the new validation
- [x] SDK manifest build verified on the postcard example app: all
auto-generated default field identifiers match the derivation
- [ ] Run
`upgrade:2-19:backfill-deterministic-field-universal-identifiers`
(dry-run then real) on a seeded workspace and verify identifier
convergence with a rebuilt app manifest
This commit is contained in:
Paul Rastoin
2026-07-06 15:34:33 +02:00
committed by GitHub
parent 0706c7c1bc
commit 6c40c7b91a
68 changed files with 5060 additions and 1336 deletions
@@ -33,10 +33,10 @@ export const EXPECTED_MANIFEST: Manifest = {
onDelete: RelationOnDeleteAction.SET_NULL,
joinColumnName: 'targetMyNoteId',
},
universalIdentifier: '7e28b1ea-9a94-5d36-bc03-3f12f476867a',
universalIdentifier: '55153e55-1bdc-590d-8fb1-40eafec88ef0',
objectUniversalIdentifier: '20202020-6736-4337-b5c4-8b39fae325a5',
relationTargetFieldMetadataUniversalIdentifier:
'12c84c1c-6e3f-54c5-a9a8-e93c7b46ac35',
'559713af-d9c0-5894-a972-9a3951661a2b',
relationTargetObjectMetadataUniversalIdentifier:
'e1e2e3e4-e5e6-4000-8000-000000000030',
type: FieldMetadataType.MORPH_RELATION,
@@ -53,10 +53,10 @@ export const EXPECTED_MANIFEST: Manifest = {
onDelete: RelationOnDeleteAction.SET_NULL,
joinColumnName: 'targetMyNoteId',
},
universalIdentifier: '721b12d8-7a3d-5e13-b8d5-62b3ad8cb8b5',
universalIdentifier: '80596d88-f896-5108-8cff-302a2eb61668',
objectUniversalIdentifier: '20202020-bd3d-4c60-8dca-571c71d4447a',
relationTargetFieldMetadataUniversalIdentifier:
'56a872c8-7a1e-563d-8772-3978c2c5c220',
'8f8e42e3-77de-5b0a-b444-d96b6d3c7600',
relationTargetObjectMetadataUniversalIdentifier:
'e1e2e3e4-e5e6-4000-8000-000000000030',
type: FieldMetadataType.MORPH_RELATION,
@@ -73,10 +73,10 @@ export const EXPECTED_MANIFEST: Manifest = {
onDelete: RelationOnDeleteAction.SET_NULL,
joinColumnName: 'targetMyNoteId',
},
universalIdentifier: '94b6f269-fc9a-5bcd-af5b-2bdac69f276b',
universalIdentifier: '67bf90bd-f3d0-58ce-a96e-648364249812',
objectUniversalIdentifier: '20202020-fff0-4b44-be82-bda313884400',
relationTargetFieldMetadataUniversalIdentifier:
'011595e8-7524-5ec7-9fbc-4bd3c42d7d24',
'0cbc9a5c-34b2-5d39-97a2-878662cb13d5',
relationTargetObjectMetadataUniversalIdentifier:
'e1e2e3e4-e5e6-4000-8000-000000000030',
type: FieldMetadataType.MORPH_RELATION,
@@ -93,10 +93,10 @@ export const EXPECTED_MANIFEST: Manifest = {
onDelete: RelationOnDeleteAction.SET_NULL,
joinColumnName: 'targetMyNoteId',
},
universalIdentifier: '50da3cfa-dea5-5c9b-b1b7-cb615e1df4c3',
universalIdentifier: '2689365b-e6ce-5b14-a2b5-6daafbfe91f8',
objectUniversalIdentifier: '20202020-5a9a-44e8-95df-771cd06d0fb1',
relationTargetFieldMetadataUniversalIdentifier:
'dd734fa3-26c3-53dd-91aa-577c313e1fa4',
'affdd37c-4460-53fc-998b-ffb04bb3a470',
relationTargetObjectMetadataUniversalIdentifier:
'e1e2e3e4-e5e6-4000-8000-000000000030',
type: FieldMetadataType.MORPH_RELATION,
@@ -129,7 +129,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Id',
name: 'id',
type: FieldMetadataType.UUID,
universalIdentifier: '5bdb4f57-a349-56d0-b421-845fd55b3026',
universalIdentifier: 'a717a3ba-e257-5269-95c4-1b8ca4cb8bcf',
},
{
defaultValue: null,
@@ -139,7 +139,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Name',
name: 'name',
type: FieldMetadataType.TEXT,
universalIdentifier: '86cc7b06-26de-5b7b-bf4a-31fa246dfd90',
universalIdentifier: '9a7f457a-6c15-581e-8f28-e2c680f75f97',
},
{
defaultValue: 'now',
@@ -149,7 +149,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Creation date',
name: 'createdAt',
type: FieldMetadataType.DATE_TIME,
universalIdentifier: 'abe3bc69-1a51-5814-a722-74f5a025e92a',
universalIdentifier: 'f24a236a-e36a-5bab-91db-d43e3f4b8247',
},
{
defaultValue: 'now',
@@ -159,7 +159,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Last update',
name: 'updatedAt',
type: FieldMetadataType.DATE_TIME,
universalIdentifier: 'c296d408-9720-5a86-8586-3490fbf04b86',
universalIdentifier: '875c1f2e-19f0-5200-96ba-ac1cb4c1fd33',
},
{
defaultValue: null,
@@ -169,7 +169,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Deleted at',
name: 'deletedAt',
type: FieldMetadataType.DATE_TIME,
universalIdentifier: '488333a2-f4ff-5790-88ef-21f07f8f1985',
universalIdentifier: 'cc58b328-40e1-5087-b550-ad700e53490b',
},
{
defaultValue: {
@@ -182,7 +182,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Created by',
name: 'createdBy',
type: FieldMetadataType.ACTOR,
universalIdentifier: 'fba6e01b-6579-554e-ad64-a676003c8e68',
universalIdentifier: '6f34f940-966a-52c2-a8f2-f2f04115d88e',
},
{
defaultValue: {
@@ -195,7 +195,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Updated by',
name: 'updatedBy',
type: FieldMetadataType.ACTOR,
universalIdentifier: 'fa60f87d-15c8-5ff7-9da0-191fa14a922f',
universalIdentifier: '37515dfb-8ae0-5326-a391-9e66205f6530',
},
{
defaultValue: 0,
@@ -205,7 +205,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Position',
name: 'position',
type: FieldMetadataType.POSITION,
universalIdentifier: '4a5d7791-ddc9-53ee-bc46-699da7373290',
universalIdentifier: 'a418b503-74d0-500d-b4c0-75f364394e20',
},
{
defaultValue: null,
@@ -215,7 +215,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Search vector',
name: 'searchVector',
type: FieldMetadataType.TS_VECTOR,
universalIdentifier: '0f00bc3b-f313-5206-8496-842af20a538a',
universalIdentifier: '45718beb-e0d7-5bde-811c-29ff01b0fcfd',
},
{
name: 'timelineActivities',
@@ -227,9 +227,9 @@ export const EXPECTED_MANIFEST: Manifest = {
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
},
universalIdentifier: '12c84c1c-6e3f-54c5-a9a8-e93c7b46ac35',
universalIdentifier: '559713af-d9c0-5894-a972-9a3951661a2b',
relationTargetFieldMetadataUniversalIdentifier:
'7e28b1ea-9a94-5d36-bc03-3f12f476867a',
'55153e55-1bdc-590d-8fb1-40eafec88ef0',
relationTargetObjectMetadataUniversalIdentifier:
'20202020-6736-4337-b5c4-8b39fae325a5',
},
@@ -243,9 +243,9 @@ export const EXPECTED_MANIFEST: Manifest = {
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
},
universalIdentifier: '56a872c8-7a1e-563d-8772-3978c2c5c220',
universalIdentifier: '8f8e42e3-77de-5b0a-b444-d96b6d3c7600',
relationTargetFieldMetadataUniversalIdentifier:
'721b12d8-7a3d-5e13-b8d5-62b3ad8cb8b5',
'80596d88-f896-5108-8cff-302a2eb61668',
relationTargetObjectMetadataUniversalIdentifier:
'20202020-bd3d-4c60-8dca-571c71d4447a',
},
@@ -259,9 +259,9 @@ export const EXPECTED_MANIFEST: Manifest = {
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
},
universalIdentifier: '011595e8-7524-5ec7-9fbc-4bd3c42d7d24',
universalIdentifier: '0cbc9a5c-34b2-5d39-97a2-878662cb13d5',
relationTargetFieldMetadataUniversalIdentifier:
'94b6f269-fc9a-5bcd-af5b-2bdac69f276b',
'67bf90bd-f3d0-58ce-a96e-648364249812',
relationTargetObjectMetadataUniversalIdentifier:
'20202020-fff0-4b44-be82-bda313884400',
},
@@ -275,9 +275,9 @@ export const EXPECTED_MANIFEST: Manifest = {
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
},
universalIdentifier: 'dd734fa3-26c3-53dd-91aa-577c313e1fa4',
universalIdentifier: 'affdd37c-4460-53fc-998b-ffb04bb3a470',
relationTargetFieldMetadataUniversalIdentifier:
'50da3cfa-dea5-5c9b-b1b7-cb615e1df4c3',
'2689365b-e6ce-5b14-a2b5-6daafbfe91f8',
relationTargetObjectMetadataUniversalIdentifier:
'20202020-5a9a-44e8-95df-771cd06d0fb1',
},
@@ -292,11 +292,11 @@ export const EXPECTED_MANIFEST: Manifest = {
name: 'targetRootNote',
objectUniversalIdentifier: '20202020-6736-4337-b5c4-8b39fae325a5',
relationTargetFieldMetadataUniversalIdentifier:
'1cdb0b3a-0ae1-59f6-b3f0-59ff99e40525',
'e1478d59-c947-5926-af8b-4b2f0f842c91',
relationTargetObjectMetadataUniversalIdentifier:
'b0b1b2b3-b4b5-4000-8000-000000000001',
type: FieldType.MORPH_RELATION,
universalIdentifier: 'a0f6ffdc-a570-58a6-973d-a9bc9a527de8',
universalIdentifier: '7f891268-9376-5d00-b8ec-bc32a023154d',
universalSettings: {
joinColumnName: 'targetRootNoteId',
onDelete: RelationOnDeleteAction.SET_NULL,
@@ -312,11 +312,11 @@ export const EXPECTED_MANIFEST: Manifest = {
name: 'targetRootNote',
objectUniversalIdentifier: '20202020-bd3d-4c60-8dca-571c71d4447a',
relationTargetFieldMetadataUniversalIdentifier:
'aa05ee89-bd8a-5911-a4d5-3355926186bc',
'34a6b8e9-0187-57e1-86c1-41bacf1b2479',
relationTargetObjectMetadataUniversalIdentifier:
'b0b1b2b3-b4b5-4000-8000-000000000001',
type: FieldType.MORPH_RELATION,
universalIdentifier: '95cad10a-bff6-524d-b6d8-dbc6db7cbf18',
universalIdentifier: 'd72bb002-9952-5651-983e-649436949113',
universalSettings: {
joinColumnName: 'targetRootNoteId',
onDelete: RelationOnDeleteAction.SET_NULL,
@@ -332,11 +332,11 @@ export const EXPECTED_MANIFEST: Manifest = {
name: 'targetRootNote',
objectUniversalIdentifier: '20202020-fff0-4b44-be82-bda313884400',
relationTargetFieldMetadataUniversalIdentifier:
'762ad37e-d624-51d8-989c-3a119eef9e7f',
'7a62bbd6-e697-54b0-8c1c-163ac47c0305',
relationTargetObjectMetadataUniversalIdentifier:
'b0b1b2b3-b4b5-4000-8000-000000000001',
type: FieldType.MORPH_RELATION,
universalIdentifier: 'c327faff-76eb-5baf-8b35-1c31e57306fb',
universalIdentifier: '61252ad6-2311-5054-ae51-b318dfd5fbd8',
universalSettings: {
joinColumnName: 'targetRootNoteId',
onDelete: RelationOnDeleteAction.SET_NULL,
@@ -352,11 +352,11 @@ export const EXPECTED_MANIFEST: Manifest = {
name: 'targetRootNote',
objectUniversalIdentifier: '20202020-5a9a-44e8-95df-771cd06d0fb1',
relationTargetFieldMetadataUniversalIdentifier:
'd4f1b0c8-8cd9-581e-9bf9-917f8c918d76',
'10c1af7e-e541-56bc-b5b3-d0297df3e23d',
relationTargetObjectMetadataUniversalIdentifier:
'b0b1b2b3-b4b5-4000-8000-000000000001',
type: FieldType.MORPH_RELATION,
universalIdentifier: '36843050-e6e3-5759-99b6-e975426bad73',
universalIdentifier: '2f4f3d69-b78e-5465-9e87-17059959fca8',
universalSettings: {
joinColumnName: 'targetRootNoteId',
onDelete: RelationOnDeleteAction.SET_NULL,
@@ -480,11 +480,11 @@ export const EXPECTED_MANIFEST: Manifest = {
name: 'targetPostCardRecipient',
objectUniversalIdentifier: '20202020-6736-4337-b5c4-8b39fae325a5',
relationTargetFieldMetadataUniversalIdentifier:
'02f5efba-4f94-5cc3-9e17-ca7b1fe5f71b',
'8f95b434-ee92-5c0f-95de-9d489af4d40c',
relationTargetObjectMetadataUniversalIdentifier:
'e1a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5e',
type: FieldType.MORPH_RELATION,
universalIdentifier: '18b6354d-8b0f-5be6-9473-d635aed670a1',
universalIdentifier: 'a865f403-3843-5850-9a60-04e832220954',
universalSettings: {
joinColumnName: 'targetPostCardRecipientId',
onDelete: RelationOnDeleteAction.SET_NULL,
@@ -500,11 +500,11 @@ export const EXPECTED_MANIFEST: Manifest = {
name: 'targetPostCardRecipient',
objectUniversalIdentifier: '20202020-bd3d-4c60-8dca-571c71d4447a',
relationTargetFieldMetadataUniversalIdentifier:
'82497ec7-0121-53d6-9518-ae7434ceaf8b',
'0c20df83-8cef-5cd6-9752-b98fb7780904',
relationTargetObjectMetadataUniversalIdentifier:
'e1a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5e',
type: FieldType.MORPH_RELATION,
universalIdentifier: 'ba74dfd1-e7ee-502d-abad-82e14d595874',
universalIdentifier: '501c46b7-7140-59a0-b703-9844bbcb34b9',
universalSettings: {
joinColumnName: 'targetPostCardRecipientId',
onDelete: RelationOnDeleteAction.SET_NULL,
@@ -520,11 +520,11 @@ export const EXPECTED_MANIFEST: Manifest = {
name: 'targetPostCardRecipient',
objectUniversalIdentifier: '20202020-fff0-4b44-be82-bda313884400',
relationTargetFieldMetadataUniversalIdentifier:
'6de8b57e-ed0a-5d35-bd39-bd8874445810',
'b9642a90-2df5-555b-ab83-23cdce7a4142',
relationTargetObjectMetadataUniversalIdentifier:
'e1a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5e',
type: FieldType.MORPH_RELATION,
universalIdentifier: '9d5a9f4b-5431-5b90-810a-1e9d8873b302',
universalIdentifier: '721418ff-1b53-58ef-8dd7-b58927c085c6',
universalSettings: {
joinColumnName: 'targetPostCardRecipientId',
onDelete: RelationOnDeleteAction.SET_NULL,
@@ -540,11 +540,11 @@ export const EXPECTED_MANIFEST: Manifest = {
name: 'targetPostCardRecipient',
objectUniversalIdentifier: '20202020-5a9a-44e8-95df-771cd06d0fb1',
relationTargetFieldMetadataUniversalIdentifier:
'00459e7e-8ef1-5a9b-b180-26bbb54a2696',
'd938a2d0-1279-5c48-b758-ac52f9460a76',
relationTargetObjectMetadataUniversalIdentifier:
'e1a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5e',
type: FieldType.MORPH_RELATION,
universalIdentifier: '85353e69-1e70-51ef-91c3-334d85013a4b',
universalIdentifier: 'feeba797-7ebb-5a36-91cd-7279ff92a2b6',
universalSettings: {
joinColumnName: 'targetPostCardRecipientId',
onDelete: RelationOnDeleteAction.SET_NULL,
@@ -561,11 +561,11 @@ export const EXPECTED_MANIFEST: Manifest = {
name: 'targetPostCard',
objectUniversalIdentifier: '20202020-6736-4337-b5c4-8b39fae325a5',
relationTargetFieldMetadataUniversalIdentifier:
'3f8c70b5-abb0-51ec-9109-0a94ce9c04a6',
'9e58820c-78d7-539d-807c-d84362e1eab6',
relationTargetObjectMetadataUniversalIdentifier:
'54b589ca-eeed-4950-a176-358418b85c05',
type: FieldType.MORPH_RELATION,
universalIdentifier: '1bdc52a0-0339-5360-ba17-b615250ef730',
universalIdentifier: '47d21bb5-075f-5ed4-ba89-c0e7322a57cd',
universalSettings: {
joinColumnName: 'targetPostCardId',
onDelete: RelationOnDeleteAction.SET_NULL,
@@ -581,11 +581,11 @@ export const EXPECTED_MANIFEST: Manifest = {
name: 'targetPostCard',
objectUniversalIdentifier: '20202020-bd3d-4c60-8dca-571c71d4447a',
relationTargetFieldMetadataUniversalIdentifier:
'547c93e4-c9e7-5880-b251-46230fb59bfa',
'3f0d38c8-a50f-51c7-bdcb-84a715f4b92d',
relationTargetObjectMetadataUniversalIdentifier:
'54b589ca-eeed-4950-a176-358418b85c05',
type: FieldType.MORPH_RELATION,
universalIdentifier: 'fecb8484-7e3c-59f8-a5ef-30882c5c1904',
universalIdentifier: '4d3cf1bd-d87d-5325-8fbc-19f72c252050',
universalSettings: {
joinColumnName: 'targetPostCardId',
onDelete: RelationOnDeleteAction.SET_NULL,
@@ -601,11 +601,11 @@ export const EXPECTED_MANIFEST: Manifest = {
name: 'targetPostCard',
objectUniversalIdentifier: '20202020-fff0-4b44-be82-bda313884400',
relationTargetFieldMetadataUniversalIdentifier:
'47deea5c-8559-5ae7-991a-60c9f7102f86',
'def1f10a-847b-574e-a0a2-177caf02129c',
relationTargetObjectMetadataUniversalIdentifier:
'54b589ca-eeed-4950-a176-358418b85c05',
type: FieldType.MORPH_RELATION,
universalIdentifier: 'ca687361-ac2b-5722-9ad9-cd37427d087a',
universalIdentifier: 'bc1f9abe-e27b-574a-9290-a1c9c8255a6b',
universalSettings: {
joinColumnName: 'targetPostCardId',
onDelete: RelationOnDeleteAction.SET_NULL,
@@ -621,11 +621,11 @@ export const EXPECTED_MANIFEST: Manifest = {
name: 'targetPostCard',
objectUniversalIdentifier: '20202020-5a9a-44e8-95df-771cd06d0fb1',
relationTargetFieldMetadataUniversalIdentifier:
'ecdccc0d-7c31-5126-89d5-0bdf3372257c',
'b1238c2d-bc9c-53d6-8bee-eb045b9b9086',
relationTargetObjectMetadataUniversalIdentifier:
'54b589ca-eeed-4950-a176-358418b85c05',
type: FieldType.MORPH_RELATION,
universalIdentifier: '17058c96-d5a4-53b1-96ca-786f8c5fc8bd',
universalIdentifier: 'fb610de1-db20-5e78-8e4a-7630e57148eb',
universalSettings: {
joinColumnName: 'targetPostCardId',
onDelete: RelationOnDeleteAction.SET_NULL,
@@ -642,11 +642,11 @@ export const EXPECTED_MANIFEST: Manifest = {
name: 'targetRecipient',
objectUniversalIdentifier: '20202020-6736-4337-b5c4-8b39fae325a5',
relationTargetFieldMetadataUniversalIdentifier:
'51d6d2d7-2fa8-5f77-b6cf-a96dbb801fa5',
'1118dd8a-9781-5254-b625-2d0f05eb46fe',
relationTargetObjectMetadataUniversalIdentifier:
'd1a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d',
type: FieldType.MORPH_RELATION,
universalIdentifier: 'b01f6937-4859-587b-b3df-92e0a6f65e93',
universalIdentifier: '1ce06ca1-6e9a-53ce-9888-9152b1b2b628',
universalSettings: {
joinColumnName: 'targetRecipientId',
onDelete: RelationOnDeleteAction.SET_NULL,
@@ -662,11 +662,11 @@ export const EXPECTED_MANIFEST: Manifest = {
name: 'targetRecipient',
objectUniversalIdentifier: '20202020-bd3d-4c60-8dca-571c71d4447a',
relationTargetFieldMetadataUniversalIdentifier:
'244a7841-9e45-5ed1-a830-83e73c1f13fa',
'15714c1a-d354-5407-8a15-25c117d605f8',
relationTargetObjectMetadataUniversalIdentifier:
'd1a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d',
type: FieldType.MORPH_RELATION,
universalIdentifier: '9c87e1b8-74a2-5833-9237-281d561d2a7d',
universalIdentifier: '2081700d-7ce6-5698-a7da-a013feda0eb6',
universalSettings: {
joinColumnName: 'targetRecipientId',
onDelete: RelationOnDeleteAction.SET_NULL,
@@ -682,11 +682,11 @@ export const EXPECTED_MANIFEST: Manifest = {
name: 'targetRecipient',
objectUniversalIdentifier: '20202020-fff0-4b44-be82-bda313884400',
relationTargetFieldMetadataUniversalIdentifier:
'eab71eb1-b457-555a-8a2b-f292cef1671d',
'167f6de7-835e-51e1-b751-34055eef6aa5',
relationTargetObjectMetadataUniversalIdentifier:
'd1a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d',
type: FieldType.MORPH_RELATION,
universalIdentifier: '23508618-7ed8-519e-9fee-a57b94dc98e9',
universalIdentifier: 'a93c479c-6206-5714-94ce-affa6e9886aa',
universalSettings: {
joinColumnName: 'targetRecipientId',
onDelete: RelationOnDeleteAction.SET_NULL,
@@ -702,11 +702,11 @@ export const EXPECTED_MANIFEST: Manifest = {
name: 'targetRecipient',
objectUniversalIdentifier: '20202020-5a9a-44e8-95df-771cd06d0fb1',
relationTargetFieldMetadataUniversalIdentifier:
'305f1890-04ba-5101-b18f-a1ca25a1d8a7',
'd0db1844-4fde-5a5a-b037-ebeabd4467af',
relationTargetObjectMetadataUniversalIdentifier:
'd1a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d',
type: FieldType.MORPH_RELATION,
universalIdentifier: '20500ff3-2410-5bfc-b2e7-804785c07770',
universalIdentifier: '45752445-c4b6-5e80-8aa1-f5321a838b43',
universalSettings: {
joinColumnName: 'targetRecipientId',
onDelete: RelationOnDeleteAction.SET_NULL,
@@ -749,7 +749,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Id',
name: 'id',
type: FieldMetadataType.UUID,
universalIdentifier: 'b5830f92-1abe-5ae7-b689-7821dda00b15',
universalIdentifier: 'a4429f96-5745-511a-b246-67da9f920452',
},
{
defaultValue: null,
@@ -759,7 +759,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Name',
name: 'name',
type: FieldMetadataType.TEXT,
universalIdentifier: 'ae2b2e5a-6c8b-5324-b639-8f17e3bc0420',
universalIdentifier: '10452ca3-30cb-56fa-9e58-73f7b1c9fd65',
},
{
defaultValue: 'now',
@@ -769,7 +769,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Creation date',
name: 'createdAt',
type: FieldMetadataType.DATE_TIME,
universalIdentifier: '7c84a501-41a6-531e-bc5e-8062843f6cba',
universalIdentifier: '777fa853-2198-5623-96f3-119ef5707ad3',
},
{
defaultValue: 'now',
@@ -779,7 +779,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Last update',
name: 'updatedAt',
type: FieldMetadataType.DATE_TIME,
universalIdentifier: '72c2903a-91c9-5f36-8668-b53b083ae4cf',
universalIdentifier: 'e1715fe9-34e1-572c-b62c-f39d737a17df',
},
{
defaultValue: null,
@@ -789,7 +789,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Deleted at',
name: 'deletedAt',
type: FieldMetadataType.DATE_TIME,
universalIdentifier: '007f5d8e-d376-519e-bd07-aea41a53f7d9',
universalIdentifier: 'e3b13370-7241-55f1-ab9a-346b57bf4dcf',
},
{
defaultValue: {
@@ -802,7 +802,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Created by',
name: 'createdBy',
type: FieldMetadataType.ACTOR,
universalIdentifier: '44053354-0d8b-5d66-bfa8-7e7d221de3db',
universalIdentifier: 'd2e3e238-9bfd-5845-9fe8-3e58df09f246',
},
{
defaultValue: {
@@ -815,7 +815,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Updated by',
name: 'updatedBy',
type: FieldMetadataType.ACTOR,
universalIdentifier: '38e2c671-cbf5-5773-a04a-e5a2d6c323c8',
universalIdentifier: '690003ae-f649-537e-b124-d21ef0ee86a1',
},
{
defaultValue: 0,
@@ -825,7 +825,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Position',
name: 'position',
type: FieldMetadataType.POSITION,
universalIdentifier: '3b56cdea-cf85-51d4-a7a0-cd701f76141a',
universalIdentifier: '4b6d383d-4e84-592e-b1ae-30ae27139302',
},
{
defaultValue: null,
@@ -835,7 +835,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Search vector',
name: 'searchVector',
type: FieldMetadataType.TS_VECTOR,
universalIdentifier: '7c6d88f5-a348-51dd-8d0b-74a17e626754',
universalIdentifier: 'a92d59aa-23a1-5191-b840-933183c14846',
},
{
description: 'Root notes tied to the RootNote',
@@ -844,11 +844,11 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Timeline Activities',
name: 'timelineActivities',
relationTargetFieldMetadataUniversalIdentifier:
'a0f6ffdc-a570-58a6-973d-a9bc9a527de8',
'7f891268-9376-5d00-b8ec-bc32a023154d',
relationTargetObjectMetadataUniversalIdentifier:
'20202020-6736-4337-b5c4-8b39fae325a5',
type: FieldType.RELATION,
universalIdentifier: '1cdb0b3a-0ae1-59f6-b3f0-59ff99e40525',
universalIdentifier: 'e1478d59-c947-5926-af8b-4b2f0f842c91',
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
},
@@ -860,11 +860,11 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Attachments',
name: 'attachments',
relationTargetFieldMetadataUniversalIdentifier:
'95cad10a-bff6-524d-b6d8-dbc6db7cbf18',
'd72bb002-9952-5651-983e-649436949113',
relationTargetObjectMetadataUniversalIdentifier:
'20202020-bd3d-4c60-8dca-571c71d4447a',
type: FieldType.RELATION,
universalIdentifier: 'aa05ee89-bd8a-5911-a4d5-3355926186bc',
universalIdentifier: '34a6b8e9-0187-57e1-86c1-41bacf1b2479',
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
},
@@ -876,11 +876,11 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Note Targets',
name: 'noteTargets',
relationTargetFieldMetadataUniversalIdentifier:
'c327faff-76eb-5baf-8b35-1c31e57306fb',
'61252ad6-2311-5054-ae51-b318dfd5fbd8',
relationTargetObjectMetadataUniversalIdentifier:
'20202020-fff0-4b44-be82-bda313884400',
type: FieldType.RELATION,
universalIdentifier: '762ad37e-d624-51d8-989c-3a119eef9e7f',
universalIdentifier: '7a62bbd6-e697-54b0-8c1c-163ac47c0305',
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
},
@@ -892,11 +892,11 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Task Targets',
name: 'taskTargets',
relationTargetFieldMetadataUniversalIdentifier:
'36843050-e6e3-5759-99b6-e975426bad73',
'2f4f3d69-b78e-5465-9e87-17059959fca8',
relationTargetObjectMetadataUniversalIdentifier:
'20202020-5a9a-44e8-95df-771cd06d0fb1',
type: FieldType.RELATION,
universalIdentifier: 'd4f1b0c8-8cd9-581e-9bf9-917f8c918d76',
universalIdentifier: '10c1af7e-e541-56bc-b5b3-d0297df3e23d',
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
},
@@ -931,7 +931,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Id',
name: 'id',
type: FieldMetadataType.UUID,
universalIdentifier: '2809d745-deb3-5ac0-8328-ea6256ec22b3',
universalIdentifier: 'fe2cec74-a43b-5910-8dd6-7a737fb869ae',
},
{
defaultValue: null,
@@ -941,7 +941,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Name',
name: 'name',
type: FieldMetadataType.TEXT,
universalIdentifier: 'a6ee6cc8-b9a0-5e23-a39e-bc3fee2aa3d3',
universalIdentifier: 'f485cd3a-4c55-5b8f-b926-a83e5e6ba651',
},
{
defaultValue: 'now',
@@ -951,7 +951,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Creation date',
name: 'createdAt',
type: FieldMetadataType.DATE_TIME,
universalIdentifier: '87ad23b6-b04e-5dc1-9907-c1eca54ea6d6',
universalIdentifier: '729b0d02-9030-5440-bd4e-f34b46d5d031',
},
{
defaultValue: 'now',
@@ -961,7 +961,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Last update',
name: 'updatedAt',
type: FieldMetadataType.DATE_TIME,
universalIdentifier: '6d7523dc-c097-5b75-96eb-37502d09cc19',
universalIdentifier: '42381c24-6337-5654-8c44-6649fc6b06a8',
},
{
defaultValue: null,
@@ -971,7 +971,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Deleted at',
name: 'deletedAt',
type: FieldMetadataType.DATE_TIME,
universalIdentifier: 'bd4fe2fa-373a-5fdd-ba09-cb5435781e4c',
universalIdentifier: '5117f25c-d3a2-533b-b3d6-39a424e88d2a',
},
{
defaultValue: {
@@ -984,7 +984,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Created by',
name: 'createdBy',
type: FieldMetadataType.ACTOR,
universalIdentifier: 'c5026f20-ab7a-54e0-900e-37a2d5381aee',
universalIdentifier: '48df9616-8671-522f-87c7-efcd3f2e2604',
},
{
defaultValue: {
@@ -997,7 +997,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Updated by',
name: 'updatedBy',
type: FieldMetadataType.ACTOR,
universalIdentifier: '1dd75ff5-5fd0-51df-b3a3-a12e3aff30df',
universalIdentifier: '5c8eb640-8c91-5712-8b73-85ce5e71abb9',
},
{
defaultValue: 0,
@@ -1007,7 +1007,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Position',
name: 'position',
type: FieldMetadataType.POSITION,
universalIdentifier: '6c5e91f5-e323-555b-84e0-5a1dd7f36b1b',
universalIdentifier: '5a5800dd-743f-533d-ad5c-f983a4bb6324',
},
{
defaultValue: null,
@@ -1017,7 +1017,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Search vector',
name: 'searchVector',
type: FieldMetadataType.TS_VECTOR,
universalIdentifier: '9fa18b19-41b6-5082-b3e2-4e73f7461164',
universalIdentifier: 'c4d0beaf-9035-514b-977c-5059b544c1bb',
},
{
description: 'Post Card Recipients tied to the PostCardRecipient',
@@ -1026,11 +1026,11 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Timeline Activities',
name: 'timelineActivities',
relationTargetFieldMetadataUniversalIdentifier:
'18b6354d-8b0f-5be6-9473-d635aed670a1',
'a865f403-3843-5850-9a60-04e832220954',
relationTargetObjectMetadataUniversalIdentifier:
'20202020-6736-4337-b5c4-8b39fae325a5',
type: FieldType.RELATION,
universalIdentifier: '02f5efba-4f94-5cc3-9e17-ca7b1fe5f71b',
universalIdentifier: '8f95b434-ee92-5c0f-95de-9d489af4d40c',
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
},
@@ -1042,11 +1042,11 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Attachments',
name: 'attachments',
relationTargetFieldMetadataUniversalIdentifier:
'ba74dfd1-e7ee-502d-abad-82e14d595874',
'501c46b7-7140-59a0-b703-9844bbcb34b9',
relationTargetObjectMetadataUniversalIdentifier:
'20202020-bd3d-4c60-8dca-571c71d4447a',
type: FieldType.RELATION,
universalIdentifier: '82497ec7-0121-53d6-9518-ae7434ceaf8b',
universalIdentifier: '0c20df83-8cef-5cd6-9752-b98fb7780904',
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
},
@@ -1058,11 +1058,11 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Note Targets',
name: 'noteTargets',
relationTargetFieldMetadataUniversalIdentifier:
'9d5a9f4b-5431-5b90-810a-1e9d8873b302',
'721418ff-1b53-58ef-8dd7-b58927c085c6',
relationTargetObjectMetadataUniversalIdentifier:
'20202020-fff0-4b44-be82-bda313884400',
type: FieldType.RELATION,
universalIdentifier: '6de8b57e-ed0a-5d35-bd39-bd8874445810',
universalIdentifier: 'b9642a90-2df5-555b-ab83-23cdce7a4142',
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
},
@@ -1074,11 +1074,11 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Task Targets',
name: 'taskTargets',
relationTargetFieldMetadataUniversalIdentifier:
'85353e69-1e70-51ef-91c3-334d85013a4b',
'feeba797-7ebb-5a36-91cd-7279ff92a2b6',
relationTargetObjectMetadataUniversalIdentifier:
'20202020-5a9a-44e8-95df-771cd06d0fb1',
type: FieldType.RELATION,
universalIdentifier: '00459e7e-8ef1-5a9b-b180-26bbb54a2696',
universalIdentifier: 'd938a2d0-1279-5c48-b758-ac52f9460a76',
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
},
@@ -1086,7 +1086,7 @@ export const EXPECTED_MANIFEST: Manifest = {
],
icon: 'IconLink',
labelIdentifierFieldMetadataUniversalIdentifier:
'a6ee6cc8-b9a0-5e23-a39e-bc3fee2aa3d3',
'f485cd3a-4c55-5b8f-b926-a83e5e6ba651',
labelPlural: 'Post Card Recipients',
labelSingular: 'Post Card Recipient',
namePlural: 'postCardRecipients',
@@ -1180,7 +1180,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Id',
name: 'id',
type: FieldMetadataType.UUID,
universalIdentifier: '56e652a3-fac2-54fd-a379-f01b72758268',
universalIdentifier: 'e61745a2-969c-53d1-a809-6c583e9192d5',
},
{
defaultValue: null,
@@ -1190,7 +1190,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Name',
name: 'name',
type: FieldMetadataType.TEXT,
universalIdentifier: 'c59a149f-5861-51a3-a924-2bd2fa31daaa',
universalIdentifier: '3e14e30f-8271-5a13-9c1f-ce5edb932325',
},
{
defaultValue: 'now',
@@ -1200,7 +1200,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Creation date',
name: 'createdAt',
type: FieldMetadataType.DATE_TIME,
universalIdentifier: '236e2ec5-3636-5b97-b19d-f7799cb88d99',
universalIdentifier: 'b60eaf02-b008-5ebc-8fe5-123f8e92cc9e',
},
{
defaultValue: 'now',
@@ -1210,7 +1210,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Last update',
name: 'updatedAt',
type: FieldMetadataType.DATE_TIME,
universalIdentifier: '2cf640eb-09a7-52f2-820b-419071c5ec24',
universalIdentifier: 'd76f15e3-e877-5b62-af97-9dac7c71065b',
},
{
defaultValue: null,
@@ -1220,7 +1220,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Deleted at',
name: 'deletedAt',
type: FieldMetadataType.DATE_TIME,
universalIdentifier: '2eb62b4a-6888-58ab-9887-a879321fff31',
universalIdentifier: 'a5ec9b5d-90ed-587e-8a9b-cd9d1ba43116',
},
{
defaultValue: {
@@ -1233,7 +1233,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Created by',
name: 'createdBy',
type: FieldMetadataType.ACTOR,
universalIdentifier: '36e95d21-9c86-5b6f-8133-a66d09fc8952',
universalIdentifier: '4c72ab36-ca4a-5870-b8cc-652385020c45',
},
{
defaultValue: {
@@ -1246,7 +1246,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Updated by',
name: 'updatedBy',
type: FieldMetadataType.ACTOR,
universalIdentifier: 'dc5231a4-e128-57b0-ac9d-723678d9b239',
universalIdentifier: '2f481c85-025c-51cf-bccd-3375d252bf26',
},
{
defaultValue: 0,
@@ -1256,7 +1256,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Position',
name: 'position',
type: FieldMetadataType.POSITION,
universalIdentifier: '3ad26df7-6345-5f85-aac1-2306cf10eaed',
universalIdentifier: 'e1178a62-d1a5-5e9c-b730-9118c640711d',
},
{
defaultValue: null,
@@ -1266,7 +1266,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Search vector',
name: 'searchVector',
type: FieldMetadataType.TS_VECTOR,
universalIdentifier: 'b49b1a1c-2f6b-52a3-836b-ece89c698627',
universalIdentifier: 'e8569449-73b0-5f19-abc0-f6ad06c40341',
},
{
description: 'Post cards tied to the PostCard',
@@ -1275,11 +1275,11 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Timeline Activities',
name: 'timelineActivities',
relationTargetFieldMetadataUniversalIdentifier:
'1bdc52a0-0339-5360-ba17-b615250ef730',
'47d21bb5-075f-5ed4-ba89-c0e7322a57cd',
relationTargetObjectMetadataUniversalIdentifier:
'20202020-6736-4337-b5c4-8b39fae325a5',
type: FieldType.RELATION,
universalIdentifier: '3f8c70b5-abb0-51ec-9109-0a94ce9c04a6',
universalIdentifier: '9e58820c-78d7-539d-807c-d84362e1eab6',
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
},
@@ -1291,11 +1291,11 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Attachments',
name: 'attachments',
relationTargetFieldMetadataUniversalIdentifier:
'fecb8484-7e3c-59f8-a5ef-30882c5c1904',
'4d3cf1bd-d87d-5325-8fbc-19f72c252050',
relationTargetObjectMetadataUniversalIdentifier:
'20202020-bd3d-4c60-8dca-571c71d4447a',
type: FieldType.RELATION,
universalIdentifier: '547c93e4-c9e7-5880-b251-46230fb59bfa',
universalIdentifier: '3f0d38c8-a50f-51c7-bdcb-84a715f4b92d',
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
},
@@ -1307,11 +1307,11 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Note Targets',
name: 'noteTargets',
relationTargetFieldMetadataUniversalIdentifier:
'ca687361-ac2b-5722-9ad9-cd37427d087a',
'bc1f9abe-e27b-574a-9290-a1c9c8255a6b',
relationTargetObjectMetadataUniversalIdentifier:
'20202020-fff0-4b44-be82-bda313884400',
type: FieldType.RELATION,
universalIdentifier: '47deea5c-8559-5ae7-991a-60c9f7102f86',
universalIdentifier: 'def1f10a-847b-574e-a0a2-177caf02129c',
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
},
@@ -1323,11 +1323,11 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Task Targets',
name: 'taskTargets',
relationTargetFieldMetadataUniversalIdentifier:
'17058c96-d5a4-53b1-96ca-786f8c5fc8bd',
'fb610de1-db20-5e78-8e4a-7630e57148eb',
relationTargetObjectMetadataUniversalIdentifier:
'20202020-5a9a-44e8-95df-771cd06d0fb1',
type: FieldType.RELATION,
universalIdentifier: 'ecdccc0d-7c31-5126-89d5-0bdf3372257c',
universalIdentifier: 'b1238c2d-bc9c-53d6-8bee-eb045b9b9086',
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
},
@@ -1367,7 +1367,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Id',
name: 'id',
type: FieldMetadataType.UUID,
universalIdentifier: '71aa41d7-54d0-5447-bf98-f37435b62bc4',
universalIdentifier: '6c8d489d-f871-598c-92b7-929b5883ae0f',
},
{
defaultValue: null,
@@ -1377,7 +1377,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Name',
name: 'name',
type: FieldMetadataType.TEXT,
universalIdentifier: 'ff07c707-df79-58fa-91a4-c60c31f90e6e',
universalIdentifier: 'c400ccb8-cd91-5ae1-81e3-2df8b6f2cf53',
},
{
defaultValue: 'now',
@@ -1387,7 +1387,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Creation date',
name: 'createdAt',
type: FieldMetadataType.DATE_TIME,
universalIdentifier: '8dd42a57-7aca-5e60-89c1-6e0e430dad43',
universalIdentifier: '88b7a0a5-f2d2-5dce-89a6-e0f27577dd3f',
},
{
defaultValue: 'now',
@@ -1397,7 +1397,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Last update',
name: 'updatedAt',
type: FieldMetadataType.DATE_TIME,
universalIdentifier: 'a81e915c-f21d-5163-a547-f04029655bf0',
universalIdentifier: '411c5a73-100a-5255-a54d-de20a5c2ae54',
},
{
defaultValue: null,
@@ -1407,7 +1407,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Deleted at',
name: 'deletedAt',
type: FieldMetadataType.DATE_TIME,
universalIdentifier: '33ba5fa8-dbc3-5a08-b0e0-d85b18ff15e0',
universalIdentifier: 'd3d59a1e-2873-594a-a7de-630cde6980a9',
},
{
defaultValue: {
@@ -1420,7 +1420,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Created by',
name: 'createdBy',
type: FieldMetadataType.ACTOR,
universalIdentifier: '7c377262-63b7-5a79-ba54-f7b8acd804dc',
universalIdentifier: 'f358b82e-1254-581e-8629-4fc4302fdbdf',
},
{
defaultValue: {
@@ -1433,7 +1433,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Updated by',
name: 'updatedBy',
type: FieldMetadataType.ACTOR,
universalIdentifier: '8da8e149-b60e-52e2-878d-73d05c8312b2',
universalIdentifier: '8186fca8-89da-5542-99fe-f0ddf90e1fc1',
},
{
defaultValue: 0,
@@ -1443,7 +1443,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Position',
name: 'position',
type: FieldMetadataType.POSITION,
universalIdentifier: '824e930c-ce9f-5267-bebd-f6db215c3c14',
universalIdentifier: '9a81ba2c-e4b2-56f8-a610-015decebd3aa',
},
{
defaultValue: null,
@@ -1453,7 +1453,7 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Search vector',
name: 'searchVector',
type: FieldMetadataType.TS_VECTOR,
universalIdentifier: '1f5df24b-8248-59cf-8bb6-362de121d2e4',
universalIdentifier: '41eed58f-1016-5483-b3ff-979f270dd9a6',
},
{
description: 'Recipients tied to the Recipient',
@@ -1462,11 +1462,11 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Timeline Activities',
name: 'timelineActivities',
relationTargetFieldMetadataUniversalIdentifier:
'b01f6937-4859-587b-b3df-92e0a6f65e93',
'1ce06ca1-6e9a-53ce-9888-9152b1b2b628',
relationTargetObjectMetadataUniversalIdentifier:
'20202020-6736-4337-b5c4-8b39fae325a5',
type: FieldType.RELATION,
universalIdentifier: '51d6d2d7-2fa8-5f77-b6cf-a96dbb801fa5',
universalIdentifier: '1118dd8a-9781-5254-b625-2d0f05eb46fe',
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
},
@@ -1478,11 +1478,11 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Attachments',
name: 'attachments',
relationTargetFieldMetadataUniversalIdentifier:
'9c87e1b8-74a2-5833-9237-281d561d2a7d',
'2081700d-7ce6-5698-a7da-a013feda0eb6',
relationTargetObjectMetadataUniversalIdentifier:
'20202020-bd3d-4c60-8dca-571c71d4447a',
type: FieldType.RELATION,
universalIdentifier: '244a7841-9e45-5ed1-a830-83e73c1f13fa',
universalIdentifier: '15714c1a-d354-5407-8a15-25c117d605f8',
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
},
@@ -1494,11 +1494,11 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Note Targets',
name: 'noteTargets',
relationTargetFieldMetadataUniversalIdentifier:
'23508618-7ed8-519e-9fee-a57b94dc98e9',
'a93c479c-6206-5714-94ce-affa6e9886aa',
relationTargetObjectMetadataUniversalIdentifier:
'20202020-fff0-4b44-be82-bda313884400',
type: FieldType.RELATION,
universalIdentifier: 'eab71eb1-b457-555a-8a2b-f292cef1671d',
universalIdentifier: '167f6de7-835e-51e1-b751-34055eef6aa5',
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
},
@@ -1510,11 +1510,11 @@ export const EXPECTED_MANIFEST: Manifest = {
label: 'Task Targets',
name: 'taskTargets',
relationTargetFieldMetadataUniversalIdentifier:
'20500ff3-2410-5bfc-b2e7-804785c07770',
'45752445-c4b6-5e80-8aa1-f5321a838b43',
relationTargetObjectMetadataUniversalIdentifier:
'20202020-5a9a-44e8-95df-771cd06d0fb1',
type: FieldType.RELATION,
universalIdentifier: '305f1890-04ba-5101-b18f-a1ca25a1d8a7',
universalIdentifier: 'd0db1844-4fde-5a5a-b037-ebeabd4467af',
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
},
@@ -1522,7 +1522,7 @@ export const EXPECTED_MANIFEST: Manifest = {
],
icon: 'IconUser',
labelIdentifierFieldMetadataUniversalIdentifier:
'ff07c707-df79-58fa-91a4-c60c31f90e6e',
'c400ccb8-cd91-5ae1-81e3-2df8b6f2cf53',
labelPlural: 'Recipients',
labelSingular: 'Recipient',
namePlural: 'recipients',
@@ -13,6 +13,7 @@ import {
import { assertUnreachable } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { getApplicationUniversalIdentifierOrThrow } from '@/cli/utilities/application/get-application-universal-identifier-or-throw';
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/current-execution-directory';
import { convertToLabel } from '@/cli/utilities/entity/entity-label';
import { appendServerVariablesToAppConfig } from '@/cli/utilities/file/append-server-variables.util';
@@ -45,6 +46,13 @@ export class EntityAddCommand {
async execute(entityType?: SyncableEntity, path?: string): Promise<void> {
try {
// Generated files embed identifiers derived from the application
// universal identifier, so scaffolding is blocked until it is defined.
const applicationUniversalIdentifier =
await getApplicationUniversalIdentifierOrThrow(
CURRENT_EXECUTION_DIRECTORY,
);
const entity = entityType ?? (await this.getEntity());
const entityName = this.getFolderName(entity);
@@ -55,7 +63,10 @@ export class EntityAddCommand {
await ensureDir(appPath);
const { name, file } = await this.getEntityData(entity);
const { name, file } = await this.getEntityData(
entity,
applicationUniversalIdentifier,
);
const filePath = join(appPath, this.getFileName(name, entity));
@@ -74,7 +85,11 @@ export class EntityAddCommand {
);
if (entity === SyncableEntity.Object) {
await this.promptAndCreateObjectCompanions(name, path);
await this.promptAndCreateObjectCompanions({
objectName: name,
applicationUniversalIdentifier,
customPath: path,
});
}
if (entity === SyncableEntity.ConnectionProvider) {
@@ -89,7 +104,10 @@ export class EntityAddCommand {
}
}
private async getEntityData(entity: SyncableEntity) {
private async getEntityData(
entity: SyncableEntity,
applicationUniversalIdentifier: string,
) {
switch (entity) {
case SyncableEntity.Object: {
const entityData = await this.getObjectData();
@@ -192,6 +210,7 @@ export class EntityAddCommand {
const file = getViewBaseFile({
name,
applicationUniversalIdentifier,
});
return { name, file };
@@ -259,10 +278,15 @@ export class EntityAddCommand {
}
}
private async promptAndCreateObjectCompanions(
objectName: string,
customPath?: string,
): Promise<void> {
private async promptAndCreateObjectCompanions({
objectName,
applicationUniversalIdentifier,
customPath,
}: {
objectName: string;
applicationUniversalIdentifier: string;
customPath?: string;
}): Promise<void> {
const { createCompanions } = await inquirer.prompt<{
createCompanions: boolean;
}>([
@@ -286,6 +310,7 @@ export class EntityAddCommand {
name: `all-${kebabCase(objectName)}`,
universalIdentifier: viewUniversalIdentifier,
objectUniversalIdentifier: this.lastObjectUniversalIdentifier,
applicationUniversalIdentifier,
fields: this.lastNameFieldUniversalIdentifier
? [
{
@@ -335,6 +360,7 @@ export class EntityAddCommand {
name: `${kebabCase(objectName)}-record-page-fields`,
universalIdentifier: fieldsWidgetViewUniversalIdentifier,
objectUniversalIdentifier: this.lastObjectUniversalIdentifier,
applicationUniversalIdentifier,
type: ViewType.FIELDS_WIDGET,
fields: recordPageFieldsViewFields,
});
@@ -0,0 +1,23 @@
import { join } from 'path';
import { pathExists } from '@/cli/utilities/file/fs-utils';
export const APP_CONFIG_CANDIDATE_PATHS = [
'src/application.config.ts',
'src/application-config.ts',
'src/applicationConfig.ts',
];
export const findAppConfigPath = async (
projectRoot: string,
): Promise<string | null> => {
for (const candidate of APP_CONFIG_CANDIDATE_PATHS) {
const absolute = join(projectRoot, candidate);
if (await pathExists(absolute)) {
return absolute;
}
}
return null;
};
@@ -0,0 +1,38 @@
import {
APP_CONFIG_CANDIDATE_PATHS,
findAppConfigPath,
} from '@/cli/utilities/application/find-app-config-path';
import { extractManifestFromFile } from '@/cli/utilities/build/manifest/manifest-extract-config-from-file';
type ApplicationConfigWithUniversalIdentifier = {
universalIdentifier?: string;
};
// Scaffolding derives deterministic identifiers from the application
// universal identifier, so nothing can be generated before defineApplication
// declares one.
export const getApplicationUniversalIdentifierOrThrow = async (
appPath: string,
): Promise<string> => {
const configPath = await findAppConfigPath(appPath);
if (configPath === null) {
throw new Error(
`Could not find the application config file (expected one of: ${APP_CONFIG_CANDIDATE_PATHS.join(', ')}). Create it with defineApplication({ universalIdentifier: ... }) before generating entities.`,
);
}
const { config } =
await extractManifestFromFile<ApplicationConfigWithUniversalIdentifier>({
appPath,
filePath: configPath,
});
if (!config?.universalIdentifier) {
throw new Error(
`defineApplication in ${configPath} must declare a universalIdentifier before generating entities.`,
);
}
return config.universalIdentifier;
};
@@ -14,10 +14,10 @@ import { validateViewFilterOperands } from '@/cli/utilities/build/manifest/utils
import { type ApplicationConfig, type LogicFunctionConfig } from '@/sdk/define';
import { type CommandMenuItemConfig } from '@/sdk/define/command-menu-items/command-menu-item-config';
import { type FrontComponentConfig } from '@/sdk/define/front-component/front-component-config';
import { type IndexConfig } from '@/sdk/define/indexes/index-config';
import { type PostInstallLogicFunctionConfig } from '@/sdk/define/logic-functions/post-install-logic-function-config';
import { type PreInstallLogicFunctionConfig } from '@/sdk/define/logic-functions/pre-install-logic-function-config';
import { type ObjectConfig } from '@/sdk/define/objects/object-config';
import { type IndexConfig } from '@/sdk/define/indexes/index-config';
import { type PageLayoutConfig } from '@/sdk/define/page-layouts/page-layout-config';
import { type PageLayoutTabConfig } from '@/sdk/define/page-layouts/page-layout-tab-config';
import { type RoleConfig } from '@/sdk/define/roles/role-config';
@@ -96,6 +96,7 @@ export const buildManifest = async (
const warnings: string[] = [];
let applicationConfig: ApplicationConfig | undefined;
const objectConfigs: ObjectConfig[] = [];
const objects: ObjectManifest[] = [];
const fields: FieldManifest[] = [];
const indexes: IndexManifest[] = [];
@@ -172,31 +173,7 @@ export const buildManifest = async (
filePath,
});
const {
objectFields: objectFieldsWithDefaults,
fields: reverseRelationFields,
} = getDefaultFieldsInObjectFields(extract.config);
const labelIdentifierFieldMetadataUniversalIdentifier =
extract.config.labelIdentifierFieldMetadataUniversalIdentifier ??
objectFieldsWithDefaults.find((field) => field.name === 'name')
?.universalIdentifier;
if (!labelIdentifierFieldMetadataUniversalIdentifier) {
errors.push(
`No label identifier field found for object ${extract.config.nameSingular}. Please add a field with name "name" to your object.`,
);
break;
}
const objectManifest: ObjectManifest = {
...extract.config,
fields: objectFieldsWithDefaults.map(addMissingFieldOptionIds),
labelIdentifierFieldMetadataUniversalIdentifier,
};
objects.push(objectManifest);
fields.push(...reverseRelationFields);
objectConfigs.push(extract.config);
errors.push(...extract.errors);
warnings.push(...(extract.warnings ?? []));
@@ -522,6 +499,39 @@ export const buildManifest = async (
);
}
if (applicationConfig) {
for (const objectConfig of objectConfigs) {
const {
objectFields: objectFieldsWithDefaults,
fields: reverseRelationFields,
} = getDefaultFieldsInObjectFields({
objectConfig,
applicationUniversalIdentifier: applicationConfig.universalIdentifier,
});
const labelIdentifierFieldMetadataUniversalIdentifier =
objectConfig.labelIdentifierFieldMetadataUniversalIdentifier ??
objectFieldsWithDefaults.find((field) => field.name === 'name')
?.universalIdentifier;
if (!labelIdentifierFieldMetadataUniversalIdentifier) {
errors.push(
`No label identifier field found for object ${objectConfig.nameSingular}. Please add a field with name "name" to your object.`,
);
continue;
}
const objectManifest: ObjectManifest = {
...objectConfig,
fields: objectFieldsWithDefaults.map(addMissingFieldOptionIds),
labelIdentifierFieldMetadataUniversalIdentifier,
};
objects.push(objectManifest);
fields.push(...reverseRelationFields);
}
}
if (postInstallLogicFunctions.length > 1) {
errors.push(
'Only one post install logic function is allowed per application',
@@ -14,15 +14,32 @@ const baseObjectConfig: ObjectConfig = {
fields: [],
};
const applicationUniversalIdentifier = 'b7f8a9c0-1d2e-4f40-8a6b-7c8d9e0f1a2b';
const getMockDefaultFieldsInObjectFields = (objectConfig: ObjectConfig) =>
getDefaultFieldsInObjectFields({
objectConfig,
applicationUniversalIdentifier,
});
const getMockDefaultObjectFields = (objectConfig: ObjectConfig) =>
getDefaultObjectFields({ objectConfig, applicationUniversalIdentifier });
const getMockDefaultRelationObjectFields = (objectConfig: ObjectConfig) =>
getDefaultRelationObjectFields({
objectConfig,
applicationUniversalIdentifier,
});
describe('getDefaultFieldsInObjectFields', () => {
it('should return all default fields when objectConfig has no fields', () => {
const { objectFields, fields } =
getDefaultFieldsInObjectFields(baseObjectConfig);
const defaultFields = getDefaultObjectFields(baseObjectConfig);
getMockDefaultFieldsInObjectFields(baseObjectConfig);
const defaultFields = getMockDefaultObjectFields(baseObjectConfig);
const {
objectFields: defaultRelationObjectFields,
fields: expectedReverseFields,
} = getDefaultRelationObjectFields(baseObjectConfig);
} = getMockDefaultRelationObjectFields(baseObjectConfig);
expect(objectFields).toEqual([
...defaultFields,
@@ -56,10 +73,10 @@ describe('getDefaultFieldsInObjectFields', () => {
fields: [customField],
};
const { objectFields } = getDefaultFieldsInObjectFields(objectConfig);
const defaultFields = getDefaultObjectFields(objectConfig);
const { objectFields } = getMockDefaultFieldsInObjectFields(objectConfig);
const defaultFields = getMockDefaultObjectFields(objectConfig);
const { objectFields: defaultRelationObjectFields } =
getDefaultRelationObjectFields(objectConfig);
getMockDefaultRelationObjectFields(objectConfig);
expect(objectFields[0]).toEqual(customField);
expect(objectFields).toHaveLength(
@@ -80,7 +97,7 @@ describe('getDefaultFieldsInObjectFields', () => {
fields: [customIdField],
};
const { objectFields } = getDefaultFieldsInObjectFields(objectConfig);
const { objectFields } = getMockDefaultFieldsInObjectFields(objectConfig);
const idFields = objectFields.filter((f) => f.name === 'id');
@@ -115,10 +132,10 @@ describe('getDefaultFieldsInObjectFields', () => {
fields: customFields,
};
const { objectFields } = getDefaultFieldsInObjectFields(objectConfig);
const defaultFields = getDefaultObjectFields(objectConfig);
const { objectFields } = getMockDefaultFieldsInObjectFields(objectConfig);
const defaultFields = getMockDefaultObjectFields(objectConfig);
const { objectFields: defaultRelationObjectFields } =
getDefaultRelationObjectFields(objectConfig);
getMockDefaultRelationObjectFields(objectConfig);
const overriddenCount = defaultFields.filter((df) =>
customFields.some((cf) => cf.name === df.name),
).length;
@@ -148,7 +165,7 @@ describe('getDefaultFieldsInObjectFields', () => {
fields: [customField],
};
const { objectFields } = getDefaultFieldsInObjectFields(objectConfig);
const { objectFields } = getMockDefaultFieldsInObjectFields(objectConfig);
expect(objectFields[0]).toEqual(customField);
});
@@ -168,13 +185,13 @@ describe('getDefaultFieldsInObjectFields', () => {
const originalLength = objectConfig.fields.length;
getDefaultFieldsInObjectFields(objectConfig);
getMockDefaultFieldsInObjectFields(objectConfig);
expect(objectConfig.fields).toHaveLength(originalLength);
});
it('should return reverse relation fields for each default relation', () => {
const { fields } = getDefaultFieldsInObjectFields(baseObjectConfig);
const { fields } = getMockDefaultFieldsInObjectFields(baseObjectConfig);
expect(fields).toHaveLength(4);
@@ -12,21 +12,31 @@ const mockObjectConfig: ObjectConfig = {
fields: [],
};
const mockApplicationUniversalIdentifier =
'3d05dbc0-4bd8-4041-a944-8cd0e26c2be1';
const getMockDefaultObjectFields = (objectConfig: ObjectConfig) =>
getDefaultObjectFields({
objectConfig,
applicationUniversalIdentifier: mockApplicationUniversalIdentifier,
});
const expectedUniversalId = (fieldName: string) =>
generateDefaultFieldUniversalIdentifier({
applicationUniversalIdentifier: mockApplicationUniversalIdentifier,
objectUniversalIdentifier: mockObjectConfig.universalIdentifier,
fieldName,
});
describe('getDefaultObjectFields', () => {
it('should return an array of 9 default fields', () => {
const fields = getDefaultObjectFields(mockObjectConfig);
const fields = getMockDefaultObjectFields(mockObjectConfig);
expect(fields).toHaveLength(9);
});
it('should include an id field with UUID type', () => {
const fields = getDefaultObjectFields(mockObjectConfig);
const fields = getMockDefaultObjectFields(mockObjectConfig);
const idField = fields.find((field) => field.name === 'id');
expect(idField).toBeDefined();
@@ -43,7 +53,7 @@ describe('getDefaultObjectFields', () => {
});
it('should include a name field with TEXT type', () => {
const fields = getDefaultObjectFields(mockObjectConfig);
const fields = getMockDefaultObjectFields(mockObjectConfig);
const nameField = fields.find((field) => field.name === 'name');
expect(nameField).toBeDefined();
@@ -60,7 +70,7 @@ describe('getDefaultObjectFields', () => {
});
it('should include createdAt, updatedAt and deletedAt fields with DATE_TIME type', () => {
const fields = getDefaultObjectFields(mockObjectConfig);
const fields = getMockDefaultObjectFields(mockObjectConfig);
const createdAtField = fields.find((field) => field.name === 'createdAt');
const updatedAtField = fields.find((field) => field.name === 'updatedAt');
const deletedAtField = fields.find((field) => field.name === 'deletedAt');
@@ -103,7 +113,7 @@ describe('getDefaultObjectFields', () => {
});
it('should include createdBy and updatedBy fields with ACTOR type', () => {
const fields = getDefaultObjectFields(mockObjectConfig);
const fields = getMockDefaultObjectFields(mockObjectConfig);
const createdByField = fields.find((field) => field.name === 'createdBy');
const updatedByField = fields.find((field) => field.name === 'updatedBy');
@@ -133,7 +143,7 @@ describe('getDefaultObjectFields', () => {
});
it('should include a position field with POSITION type', () => {
const fields = getDefaultObjectFields(mockObjectConfig);
const fields = getMockDefaultObjectFields(mockObjectConfig);
const positionField = fields.find((field) => field.name === 'position');
expect(positionField).toBeDefined();
@@ -150,7 +160,7 @@ describe('getDefaultObjectFields', () => {
});
it('should include a searchVector field with TS_VECTOR type', () => {
const fields = getDefaultObjectFields(mockObjectConfig);
const fields = getMockDefaultObjectFields(mockObjectConfig);
const searchVectorField = fields.find(
(field) => field.name === 'searchVector',
);
@@ -169,8 +179,8 @@ describe('getDefaultObjectFields', () => {
});
it('should generate deterministic universalIdentifiers based on objectConfig', () => {
const firstResult = getDefaultObjectFields(mockObjectConfig);
const secondResult = getDefaultObjectFields(mockObjectConfig);
const firstResult = getMockDefaultObjectFields(mockObjectConfig);
const secondResult = getMockDefaultObjectFields(mockObjectConfig);
firstResult.forEach((field, index) => {
expect(field.universalIdentifier).toBe(
@@ -185,8 +195,8 @@ describe('getDefaultObjectFields', () => {
universalIdentifier: 'ffffffff-ffff-ffff-ffff-ffffffffffff',
};
const fieldsA = getDefaultObjectFields(mockObjectConfig);
const fieldsB = getDefaultObjectFields(otherObjectConfig);
const fieldsA = getMockDefaultObjectFields(mockObjectConfig);
const fieldsB = getMockDefaultObjectFields(otherObjectConfig);
fieldsA.forEach((fieldA, index) => {
expect(fieldA.universalIdentifier).not.toBe(
@@ -196,7 +206,7 @@ describe('getDefaultObjectFields', () => {
});
it('should return fields in the expected order', () => {
const fields = getDefaultObjectFields(mockObjectConfig);
const fields = getMockDefaultObjectFields(mockObjectConfig);
const fieldNames = fields.map((field) => field.name);
expect(fieldNames).toEqual([
@@ -6,12 +6,22 @@ import {
type ObjectFieldManifest,
} from 'twenty-shared/application';
export const getDefaultFieldsInObjectFields = (
objectConfig: ObjectConfig,
): { objectFields: ObjectFieldManifest[]; fields: FieldManifest[] } => {
const defaultObjectFields = getDefaultObjectFields(objectConfig);
export const getDefaultFieldsInObjectFields = ({
objectConfig,
applicationUniversalIdentifier,
}: {
objectConfig: ObjectConfig;
applicationUniversalIdentifier: string;
}): { objectFields: ObjectFieldManifest[]; fields: FieldManifest[] } => {
const defaultObjectFields = getDefaultObjectFields({
objectConfig,
applicationUniversalIdentifier,
});
const { objectFields: defaultRelationObjectFields, fields: reverseFields } =
getDefaultRelationObjectFields(objectConfig);
getDefaultRelationObjectFields({
objectConfig,
applicationUniversalIdentifier,
});
const objectConfigFieldNames = (objectConfig.fields ?? []).map((f) => f.name);
@@ -3,9 +3,13 @@ import { FieldMetadataType } from 'twenty-shared/types';
import { generateDefaultFieldUniversalIdentifier } from '@/sdk/define/objects/generate-default-field-universal-identifier';
import { type ObjectFieldManifest } from 'twenty-shared/application';
export const getDefaultObjectFields = (
objectConfig: ObjectConfig,
): ObjectFieldManifest[] => {
export const getDefaultObjectFields = ({
objectConfig,
applicationUniversalIdentifier,
}: {
objectConfig: ObjectConfig;
applicationUniversalIdentifier: string;
}): ObjectFieldManifest[] => {
const objectUniversalIdentifier = objectConfig.universalIdentifier;
const idField: ObjectFieldManifest = {
@@ -17,6 +21,7 @@ export const getDefaultObjectFields = (
defaultValue: 'uuid',
type: FieldMetadataType.UUID as const,
universalIdentifier: generateDefaultFieldUniversalIdentifier({
applicationUniversalIdentifier,
objectUniversalIdentifier,
fieldName: 'id',
}),
@@ -31,6 +36,7 @@ export const getDefaultObjectFields = (
defaultValue: null,
type: FieldMetadataType.TEXT as const,
universalIdentifier: generateDefaultFieldUniversalIdentifier({
applicationUniversalIdentifier,
objectUniversalIdentifier,
fieldName: 'name',
}),
@@ -45,6 +51,7 @@ export const getDefaultObjectFields = (
defaultValue: 'now',
type: FieldMetadataType.DATE_TIME as const,
universalIdentifier: generateDefaultFieldUniversalIdentifier({
applicationUniversalIdentifier,
objectUniversalIdentifier,
fieldName: 'createdAt',
}),
@@ -59,6 +66,7 @@ export const getDefaultObjectFields = (
defaultValue: 'now',
type: FieldMetadataType.DATE_TIME as const,
universalIdentifier: generateDefaultFieldUniversalIdentifier({
applicationUniversalIdentifier,
objectUniversalIdentifier,
fieldName: 'updatedAt',
}),
@@ -73,6 +81,7 @@ export const getDefaultObjectFields = (
defaultValue: null,
type: FieldMetadataType.DATE_TIME as const,
universalIdentifier: generateDefaultFieldUniversalIdentifier({
applicationUniversalIdentifier,
objectUniversalIdentifier,
fieldName: 'deletedAt',
}),
@@ -87,6 +96,7 @@ export const getDefaultObjectFields = (
defaultValue: { name: "''", source: "'MANUAL'" },
type: FieldMetadataType.ACTOR as const,
universalIdentifier: generateDefaultFieldUniversalIdentifier({
applicationUniversalIdentifier,
objectUniversalIdentifier,
fieldName: 'createdBy',
}),
@@ -101,6 +111,7 @@ export const getDefaultObjectFields = (
defaultValue: { name: "''", source: "'MANUAL'" },
type: FieldMetadataType.ACTOR as const,
universalIdentifier: generateDefaultFieldUniversalIdentifier({
applicationUniversalIdentifier,
objectUniversalIdentifier,
fieldName: 'updatedBy',
}),
@@ -115,6 +126,7 @@ export const getDefaultObjectFields = (
defaultValue: 0,
type: FieldMetadataType.POSITION,
universalIdentifier: generateDefaultFieldUniversalIdentifier({
applicationUniversalIdentifier,
objectUniversalIdentifier,
fieldName: 'position',
}),
@@ -129,6 +141,7 @@ export const getDefaultObjectFields = (
defaultValue: null,
type: FieldMetadataType.TS_VECTOR,
universalIdentifier: generateDefaultFieldUniversalIdentifier({
applicationUniversalIdentifier,
objectUniversalIdentifier,
fieldName: 'searchVector',
}),
@@ -1,5 +1,5 @@
import { generateDefaultFieldUniversalIdentifier } from '@/sdk/define/objects/generate-default-field-universal-identifier';
import { RelationType } from '@/sdk/define';
import { generateDefaultFieldUniversalIdentifier } from '@/sdk/define/objects/generate-default-field-universal-identifier';
import type { ObjectConfig } from '@/sdk/define/objects/object-config';
import {
type FieldManifest,
@@ -119,9 +119,13 @@ const buildReverseField = ({
};
};
export const getDefaultRelationObjectFields = (
objectConfig: ObjectConfig,
): { objectFields: ObjectFieldManifest[]; fields: FieldManifest[] } => {
export const getDefaultRelationObjectFields = ({
objectConfig,
applicationUniversalIdentifier,
}: {
objectConfig: ObjectConfig;
applicationUniversalIdentifier: string;
}): { objectFields: ObjectFieldManifest[]; fields: FieldManifest[] } => {
const objectFields: ObjectFieldManifest[] = [];
const fields: FieldManifest[] = [];
@@ -130,14 +134,16 @@ export const getDefaultRelationObjectFields = (
const forwardFieldUniversalIdentifier =
generateDefaultFieldUniversalIdentifier({
applicationUniversalIdentifier,
objectUniversalIdentifier: objectConfig.universalIdentifier,
fieldName: config.fieldName,
});
const reverseFieldUniversalIdentifier =
generateDefaultFieldUniversalIdentifier({
objectUniversalIdentifier: objectConfig.universalIdentifier,
fieldName: `${config.fieldName}Inverse`,
applicationUniversalIdentifier,
objectUniversalIdentifier: standardObject.universalIdentifier,
fieldName: config.targetFieldName(objectConfig),
});
const forwardField: ObjectFieldManifest = {
@@ -1,8 +1,21 @@
import { getViewBaseFile } from '@/cli/utilities/entity/entity-view-template';
const APPLICATION_UNIVERSAL_IDENTIFIER = 'app-abc-123';
const getTestViewBaseFile = (
overrides: Omit<
Parameters<typeof getViewBaseFile>[0],
'applicationUniversalIdentifier'
>,
) =>
getViewBaseFile({
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
...overrides,
});
describe('getViewBaseFile', () => {
it('should render proper file using defineView', () => {
const result = getViewBaseFile({
const result = getTestViewBaseFile({
name: 'my-view',
universalIdentifier: '71e45a58-41da-4ae4-8b73-a543c0a9d3d4',
objectUniversalIdentifier: 'obj-abc-123',
@@ -20,7 +33,7 @@ describe('getViewBaseFile', () => {
});
it('should use default objectUniversalIdentifier when not provided', () => {
const result = getViewBaseFile({
const result = getTestViewBaseFile({
name: 'default-view',
});
@@ -28,7 +41,7 @@ describe('getViewBaseFile', () => {
});
it('should include commented fields, filters and sorts when no fields provided', () => {
const result = getViewBaseFile({
const result = getTestViewBaseFile({
name: 'empty-view',
});
@@ -38,7 +51,7 @@ describe('getViewBaseFile', () => {
});
it('should render fields block when fields are provided', () => {
const result = getViewBaseFile({
const result = getTestViewBaseFile({
name: 'view-with-fields',
fields: [
{
@@ -61,7 +74,7 @@ describe('getViewBaseFile', () => {
});
it('should apply default values to fields', () => {
const result = getViewBaseFile({
const result = getTestViewBaseFile({
name: 'view-defaults',
fields: [
{
@@ -77,7 +90,7 @@ describe('getViewBaseFile', () => {
});
it('should generate unique UUID when not provided', () => {
const result = getViewBaseFile({
const result = getTestViewBaseFile({
name: 'auto-uuid-view',
});
@@ -87,7 +100,7 @@ describe('getViewBaseFile', () => {
});
it('should use kebab-case for name', () => {
const result = getViewBaseFile({
const result = getTestViewBaseFile({
name: 'all active contacts',
});
@@ -95,7 +108,7 @@ describe('getViewBaseFile', () => {
});
it('should generate UUIDs for fields when not provided', () => {
const result = getViewBaseFile({
const result = getTestViewBaseFile({
name: 'view-auto-field-uuids',
fields: [
{
@@ -115,7 +128,7 @@ describe('getViewBaseFile', () => {
});
it('should emit a generateDefaultFieldUniversalIdentifier call for fields using defaultFieldName', () => {
const result = getViewBaseFile({
const result = getTestViewBaseFile({
name: 'view-default-field',
objectUniversalIdentifier: 'obj-abc-123',
fields: [
@@ -132,12 +145,13 @@ describe('getViewBaseFile', () => {
expect(result).toContain(
'fieldMetadataUniversalIdentifier: generateDefaultFieldUniversalIdentifier({',
);
expect(result).toContain("applicationUniversalIdentifier: 'app-abc-123'");
expect(result).toContain("objectUniversalIdentifier: 'obj-abc-123'");
expect(result).toContain("fieldName: 'createdAt'");
});
it('should not import generateDefaultFieldUniversalIdentifier when no field uses defaultFieldName', () => {
const result = getViewBaseFile({
const result = getTestViewBaseFile({
name: 'view-literal-only',
fields: [
{
@@ -17,10 +17,12 @@ const renderFieldEntry = ({
field,
index,
objectUniversalIdentifier,
applicationUniversalIdentifier,
}: {
field: ViewFieldTemplate;
index: number;
objectUniversalIdentifier: string;
applicationUniversalIdentifier: string;
}) => {
const universalIdentifier = field.universalIdentifier ?? v4();
const position = field.position ?? index;
@@ -30,6 +32,7 @@ const renderFieldEntry = ({
const fieldMetadataUniversalIdentifierLine =
'defaultFieldName' in field
? ` fieldMetadataUniversalIdentifier: generateDefaultFieldUniversalIdentifier({
applicationUniversalIdentifier: '${applicationUniversalIdentifier}',
objectUniversalIdentifier: '${objectUniversalIdentifier}',
fieldName: '${field.defaultFieldName}',
})`
@@ -48,12 +51,14 @@ export const getViewBaseFile = ({
name,
universalIdentifier = v4(),
objectUniversalIdentifier = 'fill-later',
applicationUniversalIdentifier,
fields = [],
type,
}: {
name: string;
universalIdentifier?: string;
objectUniversalIdentifier?: string;
applicationUniversalIdentifier: string;
fields?: ViewFieldTemplate[];
type?: ViewType;
}) => {
@@ -77,7 +82,12 @@ export const getViewBaseFile = ({
? ` fields: [
${fields
.map((field, index) =>
renderFieldEntry({ field, index, objectUniversalIdentifier }),
renderFieldEntry({
field,
index,
objectUniversalIdentifier,
applicationUniversalIdentifier,
}),
)
.join(',\n')},
],`
@@ -1,6 +1,6 @@
import { readFile, writeFile } from 'node:fs/promises';
import { pathExists } from '@/cli/utilities/file/fs-utils';
import { findAppConfigPath } from '@/cli/utilities/application/find-app-config-path';
// One name + description pair to add to defineApplication.serverVariables.
// `isSecret` defaults to false; set true on credentials that should be
@@ -11,12 +11,6 @@ export type ServerVariableSpec = {
isSecret: boolean;
};
const APP_CONFIG_CANDIDATES = [
'src/application.config.ts',
'src/application-config.ts',
'src/applicationConfig.ts',
];
const SERVER_VARIABLES_PATTERN = /serverVariables\s*:\s*\{/;
const DEFINE_APPLICATION_PATTERN = /defineApplication\s*\(\s*\{/;
@@ -102,20 +96,6 @@ export const appendServerVariablesToAppConfig = async ({
return { status: 'created', file: configPath };
};
const findAppConfigPath = async (
projectRoot: string,
): Promise<string | null> => {
for (const candidate of APP_CONFIG_CANDIDATES) {
const absolute = `${projectRoot}/${candidate}`;
if (await pathExists(absolute)) {
return absolute;
}
}
return null;
};
const renderServerVariableEntries = (variables: ServerVariableSpec[]): string =>
variables
.map(