## Summary
Fixes `EMFILE: too many open files, watch` crash that most of the team
is hitting on macOS when running `yarn start` or `npx nx start
twenty-server`.
Adds `rimraf dist` before `nest start --watch` in the `start` and
`start:debug` targets, so the watcher starts with a clean output
directory.
## Root cause
The NestJS SWC compiler (`@nestjs/cli@11`) creates **three overlapping
chokidar watchers** when `nest start --watch` runs:
| Watcher | Watches | Purpose | Handles |
|---|---|---|---|
| SWC CLI (`@swc/cli`) | `src/` | Detects file changes → recompiles |
~1,730 |
| NestJS `watchFilesInSrcDir` | `src/` | Workaround: SWC misses new
files | shared with above |
| NestJS `watchFilesInOutDir` | **`dist/`** | Detects compiled `.js` →
restarts server | **~3,548** |
`@nestjs/cli@11` ships with **chokidar v4**, which dropped macOS
`fsevents` support and uses `fs.watch()` instead — creating **one file
descriptor per directory**. Chokidar v3 used a single `fsevents` kernel
subscription per directory tree.
Total: **~5,000+ `fs.watch()` handles**, far exceeding the default macOS
`ulimit -n` of ~2,560.
### Why it broke now
PR #17851 (`15fc850212`) changed the `start` target from `dependsOn:
["build"]` to `dependsOn: ["^build"]`, removing the `rimraf dist && nest
build` pre-step. Without that cleanup, `dist/` accumulated stale
directories from code reorganizations (e.g. `application-layer/` →
`application/` rename), growing to ~3,548 directories vs ~1,730 in a
clean build.
## What this PR does
Adds `rimraf dist &&` before `nest start --watch` in the `start` and
`start:debug` commands. This ensures `dist/` starts empty and only
contains directories matching the current `src/` structure (~1,730),
keeping watcher count in the ~3,400 range.
We still get the startup speed improvement from #17851 (no redundant
full SWC build), since `rimraf dist` is ~instant while the removed `nest
build` step took 30-60s.
## Future considerations
As the codebase grows, even a clean `dist/` will eventually approach the
macOS default `ulimit -n` (~2,560). Options to consider if that happens:
1. **Yarn resolution to force chokidar 3.6.0** — restores `fsevents`,
reducing watcher count from ~5,000 to ~3-5. This is what Vite 7 does
internally. Simple and effective, but pins to an older major version.
2. **Patch `@nestjs/cli`** to skip the `dist/` watcher — the
`watchFilesInOutDir` watcher accounts for ~65% of all handles and only
exists because NestJS doesn't have a direct hook into SWC's
compilation-complete event. Could be removed via `yarn patch`.
3. **Replace `nest start --watch` entirely** — use `node
--watch-path=src` (Node 22+) with `@swc-node/register` for on-the-fly
compilation. Uses a single native watcher regardless of directory count.
Requires rethinking asset copying (`watchAssets` in `nest-cli.json`).
4. **Wait for upstream fix** — NestJS CLI should either re-add
`fsevents` support or use Node's recursive `fs.watch()` option
(available since Node 20) instead of per-directory watchers.
## Test plan
- [ ] Run `npx nx start twenty-server` on macOS — server starts without
EMFILE error
- [ ] Run `npx nx start:debug twenty-server` — debug mode starts without
EMFILE error
- [ ] Edit a `.ts` file while server is running — hot reload still works
- [ ] Run `yarn start` (frontend + backend + worker) — no crashes
Made with [Cursor](https://cursor.com)
Co-authored-by: Cursor <cursoragent@cursor.com>
# Introduction
Related https://github.com/twentyhq/core-team-issues/issues/2227
On a field name update side effect leading to an index field mutation it
wouldn't get caught by the builder leading to an index field desync
We should land on a standard pattern regarding the field index either
jsonb or syncableEntity so this would not occur anymore as it would have
been strictly typed
Bumps
[@types/bytes](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/bytes)
from 3.1.4 to 3.1.5.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/bytes">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Abdullah <125115953+mabdullahabaid@users.noreply.github.com>
## PR description
This PR:
- Creates a TypeScript-based extractor that discovers all exported
twenty-ui components by scanning barrel files, extracting
props/slots/events via ts-morph type analysis, and generating the remote
DOM bindings automatically.
- Adds a new ESLint rule which enforces all *Props types in twenty-ui
components to be exported, which is required for the extractor to
discover component prop types. Existing twenty-ui components are updated
to comply with this rule.
- Extends the remote DOM generation to support slots, per-component
events, forwardRef wrappers, and richer property types (array, object,
function)
## Edge cases to fix in another PR
- Icons cannot be rendered inside buttons
- IconButtons throw an error when mounted
- MenuItems are not displayed correctly
- MenuItemNavigate throws on click
## Video Demo
https://github.com/user-attachments/assets/c2ed67cf-6a15-4896-9fec-e83fac0e862b
## Reduce type leakage between GraphQL schemas
### Why
Twenty runs two separate GraphQL schemas: **core** and **metadata**.
NestJS's `@nestjs/graphql` uses a global `TypeMetadataStorage` that
accumulates all decorated types across all modules. When each schema is
built, every registered type leaks into both schemas regardless of which
module it belongs to.
This means the core schema's generated TypeScript
(`generated/graphql.ts`) contained ~2,700 lines of types that only
belong to the metadata schema (and vice versa). This creates confusion
about type ownership, inflates generated code, and makes it harder to
reason about which API surface each schema actually exposes.
### How
**1. Patch `@nestjs/graphql` to support schema-scoped type resolution**
- **(Already done)** Added a `resolverSchemaScope` option to
`GqlModuleOptions`, allowing each schema to declare a scope (e.g.
`'metadata'`)
- `ResolversExplorerService` now filters resolvers by a
`RESOLVER_SCHEMA_SCOPE` metadata key, so each schema only sees its own
resolvers
- `GraphQLSchemaFactory` now performs a **reachability walk**
(`computeReachableTypes`) starting from scoped resolver return types and
arguments, only including types that are transitively referenced —
handling unions, interfaces, and prototype chains
- Type definition storage and orphaned reference registry are cleared
between schema builds to prevent cross-contamination
**2. Register `ClientConfig` as orphaned type in metadata schema**
Since `ClientConfig` is needed in the metadata schema but not directly
returned by a resolver, it's explicitly declared via
`buildSchemaOptions.orphanedTypes`.
**3. Regenerate frontend types and fix imports**
- `generated/graphql.ts` shrank by ~2,700 lines (types moved to where
they belong)
- `generated-metadata/graphql.ts` gained types like `ClientConfig` that
were previously missing
- ~500 frontend files updated to import from the correct generated file
## Context
Introduces a new viewFieldGroup entity that allows grouping view fields
into sections (e.g. "General", "Additional", "Other") within a view.
The page layout fields widget needs a way to organize fields into
sections. Today, views have no concept of field grouping. This PR
introduces the viewFieldGroup entity which sits between a view and its
viewFields, enabling section-based organization.
<img width="401" height="724" alt="Layout - V2 (customize visibility)"
src="https://github.com/user-attachments/assets/6376e2ab-44db-42bf-9d2c-758f56f6b548"
/>
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
## Add application-scoped GraphQL schema generation
When an application token is used to authenticate, the `/graphql` schema
is now dynamically filtered to only include entities belonging to that
application (plus the Twenty Standard Application). This enables
third-party applications and the SDK to introspect a schema that is
relevant to their scope, rather than seeing the full workspace schema
with all custom objects.
### Changes
- **New `generateApplicationToken` mutation** on the `/metadata`
endpoint, allowing callers to exchange an API key for an
application-scoped JWT token
- **Schema filtering by application** in `WorkspaceSchemaFactory` — when
`request.application` is present (from an application token), flat
entity maps are filtered by `[appId, standardAppId]` before schema
generation
- **Per-app caching** — both the Yoga in-memory cache and Redis cache
now include the `appId` in their keys to avoid serving wrong schemas
- **Consolidated `getSubFlatEntityMapsByApplicationIdsOrThrow`** —
unified the single-ID and multi-ID filtering utilities into one
- **Integration tests** covering token generation (admin + API key auth)
and schema introspection filtering (standard app token excludes custom
objects)
Schema generated on seeds with applicationToken (see that pets is
missing)
<img width="782" height="994" alt="image"
src="https://github.com/user-attachments/assets/82510031-0965-435d-bc26-77c9f5d74e1f"
/>
## Summary
- Changed 6 Nx targets (`start`, `start:debug`, `typeorm`, `ts-node`,
`database:migrate`, `database:migrate:revert`) from `dependsOn:
["build"]` to `dependsOn: ["^build"]` to eliminate a redundant full SWC
compilation step (~30-60s per invocation).
- These targets all use `nest start --watch`, `ts-node`, or TypeORM's
CLI (which runs under ts-node), so they already compile TypeScript
themselves. The `build` dependency was causing `rimraf dist && nest
build` to run first, only for the target's own command to recompile
everything again.
- Fixed `start:debug` to call `nest start --watch --debug` directly
instead of routing through `nx start --debug`, which would trigger yet
another build cycle.
Note: targets that run pre-compiled code from `dist/` (like
`database:reset`) intentionally keep `dependsOn: ["build"]`.
## Test plan
- [ ] Run `npx nx start twenty-server` and verify the server starts with
only one SWC compilation pass instead of two
- [ ] Run `npx nx start:debug twenty-server` and verify the debugger
attaches correctly
- [ ] Run `npx nx run twenty-server:database:migrate` and verify
migrations run correctly
- [ ] Run `npx nx run twenty-server:database:reset twenty-server` and
verify it still builds before running (unchanged)
Made with [Cursor](https://cursor.com)
Co-authored-by: Cursor <cursoragent@cursor.com>
## Context
Fix broken record page layout seeding. This was not detected by the CI
because it doesn't have the env variable yet.
Following the same mechanism as labelIdentifier in object for circular
dependency resolution
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Devessier <baptiste@devessier.fr>
- AI still often forgets to update the code step after creating it.
Adding a next step
- Starting by loading logic functions, so it avoids creating code steps
when a function exists
- Fix create complete workflow logic. Should not create code steps
directly
- Migration command
- Check IS_FILES_FIELD_MIGRATED:false
- Check or create avatarFile field
- Fetch all people with avatarUrl
- Move (Copy/move) file in storage
- Create core.file record
- Update person record
- bonus : attachment migration : fullPath > file (same logic)
- BE logic
- Add avatarFile field on person
- FE logic
- Adapt logic to upload on/display avatarFile data
The whole imageIdentifier logic will be done later
## Fix resolver schema leaking between `/metadata` and `/graphql`
endpoints
### Summary
- Patch `@nestjs/graphql` to support a `resolverSchemaScope` option that
filters resolvers at both schema generation and runtime, preventing
cross-endpoint leaking
- Introduce `@CoreResolver()` and `@MetadataResolver()` decorators to
explicitly scope each resolver to its endpoint
- Move most resolvers (auth, billing, workspace, user, etc.) to the
metadata schema where the frontend expects them; only workflow and
timeline calendar/messaging resolvers remain on `/graphql`
- Fix frontend `SSEQuerySubscribeEffect` to use the default (metadata)
Apollo client instead of the core client
### Problem
NestJS GraphQL's module-based resolver discovery traverses transitive
imports, causing resolvers from `/metadata` modules to leak into the
`/graphql` schema and vice versa. This made the schemas unpredictable
and tightly coupled to module import order.
### Approach
- Added `resolverSchemaScope` to `GqlModuleOptions` via a patch on
`@nestjs/graphql`, filtering in both `filterResolvers()` (runtime
binding) and `getAllCtors()` (schema generation)
- Each resolver is explicitly decorated with `@CoreResolver()` or
`@MetadataResolver()`
- Organized decorator, constant, and type files under `graphql-config/`
following project conventions
Core GQL Schema: (see: no more fields!)
<img width="827" height="894" alt="image"
src="https://github.com/user-attachments/assets/668f3f0f-485e-43f0-92be-4345aeccacb6"
/>
Metadata GQL Schema (see no more getTimelineCalendarEventsFromCompany)
<img width="827" height="894" alt="image"
src="https://github.com/user-attachments/assets/443913db-e5fe-4161-b0e7-4a971cc80a71"
/>
# Introduction
Followup https://github.com/twentyhq/twenty/pull/17622
Refactoring the actions handler to be returning a metadata event
It has to be done incrementally, as if not update metadata event would
be stale as depends on the incremental action execution order and
optimistic application
## What's next
- Builder should consume and regroup each metadata even in order to
batch emit them ( within a single metadata actions batch order matters )
=> refactoring https://github.com/twentyhq/twenty/pull/17622 in order to
consume runner returned metadata events
## Summary
- `transformRichTextV2Value` was calling `await
import('@blocknote/server-util')` and `ServerBlockNoteEditor.create()`
on **every single invocation**, adding ~90ms of overhead each time
(visible as the highest avg-duration frame in profiling at 93.49ms).
- Cache the `ServerBlockNoteEditor` instance at module level so the
dynamic import + creation only happens once for the lifetime of the
process.
- Also removes debug timing instrumentation (`performance.now()`,
`calculateInputSize`, `Logger`) that is no longer needed.
## Test plan
- [ ] Verify rich text fields (blocknote/markdown) still round-trip
correctly on create and update
- [ ] Confirm reduced CPU time for `transformRichTextV2Value` in
profiling
Made with [Cursor](https://cursor.com)
Co-authored-by: Cursor <cursoragent@cursor.com>
Order should be
1. We delete all the file records (from core.file table)
2. We add the foreign key file / applicationId (pg constraint)
3. We further update the table structure: fullPath is deleted; path is
created; unicity constraint between workspaceId/applicationId/path is
created (pg constraint)
4. we migrate the workflow steps (this will create files in core.file)
5. we backfill the application package (same)
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
## Summary
Fixes#17667
- **Root cause**: `ActivityQueryResultGetterHandler` called
`JSON.parse()` on the `blocknote` field and assumed the result was
always an array. When the stored value was valid JSON but not an array
(e.g., `"{}"`), `blocknote.map()` crashed with `blocknote.map is not a
function`, breaking the entire notes page.
- **Fix**: Replaced the object-level `ActivityQueryResultGetterHandler`
(hardcoded for `note`/`task` only) with a generic field-level
`RichTextV2FieldQueryResultGetterHandler` that safely parses blocknote
JSON with `Array.isArray` validation and gracefully skips malformed
values instead of crashing.
- **Bonus**: The new handler works for **all** objects with
`RICH_TEXT_V2` fields (not just `note`/`task`), following the same
pattern as the existing `FilesFieldQueryResultGetterHandler`.
## Changes
| File | Change |
|------|--------|
| `rich-text-v2-field-query-result-getter.handler.ts` | New field-level
handler with safe blocknote parsing |
| `common-result-getters.service.ts` | Register new handler, remove
`note`/`task` object handlers |
| `activity-query-result-getter.handler.ts` | Deleted (replaced by
field-level handler) |
| `rich-text-v2-field-query-result-getter.handler.spec.ts` | 9 tests
covering all edge cases |
## Test plan
- [x] Unit tests pass (9 tests covering: null blocknote, non-string
blocknote, invalid JSON, non-array JSON like `"{}"`, no images, external
URLs, internal URLs, multiple fields)
- [x] Lint passes (`lint:diff-with-main`)
- [x] Typecheck passes
Made with [Cursor](https://cursor.com)
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
# Introduction
Completely finalize the universal migration at builder and runner
levels.
Which mean that from now on the builder only compares
`universalFlatEntity` and produces universal workspace migration that
the runner ingest
## What's done
Migrated all create metadata remaining for
- agent
- skill
- commandMenuItem
- navigationMenuItem
- fieldMetadata
- objectMetadata
- view
- viewField
- viewFilter
- viewGroup
- viewFilterGroup
- index
- logicFunction
- role
- roleTarget
- pageLayout
- pageLayoutTab
- pageLayoutWidget
- rowLevelPermissionPredicate
- rowLevelPermissionPredicateGroup
- frontComponent
## Fix
Replaced direct database queries with existing flat entity map caches
for the two that already have cache infrastructure:
- **CallDatabaseEventTriggerJobsJob** - now uses flatLogicFunctionMaps
cache via WorkspaceCacheService.getOrRecompute(), filtering in memory
for non-deleted logic functions with databaseEventTriggerSettings.
- **CallWebhookJobsJob** - now uses flatWebhookMaps cache via
WorkspaceCacheService.getOrRecompute(), filtering in memory for webhooks
matching the event's operations.
- Note: **WorkflowDatabaseEventTriggerListener** - left as-is since
WorkflowAutomatedTriggerWorkspaceEntity extends BaseWorkspaceEntity (not
SyncableEntity) and has no flat entity map cache infrastructure yet.
## Summary
- **Extract `SecureHttpClientService`** from `tool` module into a
dedicated `core-modules/secure-http-client/` module with proper NestJS
module encapsulation
- **Fix module hygiene**: 12 modules that incorrectly listed
`SecureHttpClientService` as a direct provider now properly import
`SecureHttpClientModule`
- **Add structured logging** for outbound HTTP requests with
workspace/user context (for GuardDuty alert correlation)
- **Rename type files** to follow one-export-per-file convention
(`get-secure-axios-adapter.types.ts` ->
`secure-adapter-dependencies.type.ts`, new
`outbound-request-context.type.ts` / `outbound-request-source.type.ts`)
### Why
`SecureHttpClientService` is a cross-cutting concern (used by auth,
captcha, file upload, geo-map, telemetry, admin-panel, REST API, contact
creation, webhooks, and workflow tools) but was bundled inside the
`tool` module. Most consumers worked around this by listing it as a
direct provider instead of importing a module, which is fragile and not
idiomatic NestJS.
## Test plan
- [x] All 60 unit tests pass (`secure-http-client.service.spec.ts`,
`get-secure-axios-adapter.util.spec.ts`, `is-private-ip.util.spec.ts`)
- [x] Related module tests pass (admin-panel, contact-creation, tool)
- [x] `npx nx typecheck twenty-server` passes
- [x] `npx nx lint:diff-with-main twenty-server` passes
- [x] Server compiles and bootstraps successfully
Made with [Cursor](https://cursor.com)
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Fix after resolveEntityRelationUniversalIdentifiers introduction
```
FlatEntityMapsException [Error]: Could not find rowLevelPermissionPredicateGroup for given rowLevelPermissionPredicateGroupId
at resolveEntityRelationUniversalIdentifiers
```
- Creating util for RLS flat entity creation to be aligned with other
entities
- Progressively update the flat entity maps as each new group is built,
following the same optimistic pattern used by
computeUniversalFlatEntityMapsFromTo in the validate build and run. The
updated maps are now passed to computePredicateOperations so predicates
can also resolve references to the newly created groups.
- Adding more coverage
Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
# What this PR does
Overall naming `universal` versus `flat` is not always the most updated
and so on
Will make a big cleaning tour after I've finished the whole migration
Migrating all `view` and ( filter fields etc ) `field` `object` `index`
to the universal pattern on all `services`, `builder` and `runner`
levels
## Universal and flat optimistic tooling
`addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow`
and its delete counterpart maintain the consistency of
`UniversalFlatEntityMaps` when an entity is created or removed. Beyond
inserting/removing the entity from its own maps, they walk through
`ALL_UNIVERSAL_METADATA_RELATIONS` to update the **aggregator arrays**
on related parent entities — the add appends the new entity's
`universalIdentifier` to the parent's aggregator (e.g. a new viewField's
identifier gets appended to its parent view's
`viewFieldUniversalIdentifiers`), and the delete filters it out. This
keeps the maps in sync so that diff computations and relation lookups
remain accurate throughout the migration building process.
## ALL_UNIVERSAL_METADATA_RELATIONS
`ALL_UNIVERSAL_METADATA_RELATIONS` is the universal counterpart of
`ALL_METADATA_RELATIONS`. It maps each metadata entity to its
many-to-one and one-to-many relations using universal foreign keys
(`*UniversalIdentifier`) instead of database IDs (`*Id`). This allows
migration actions to reference related entities in a workspace-agnostic
way. Relations that are workspace-specific (e.g. `workspace`,
`dataSource`, `userWorkspace`) are set to `null` and skipped during
resolution.
## `workspaceMigrationCreateIdEnrichment`
Reserved to API metadata ( will be able to validate at app installation
lvl )
- Workspace migration `create` actions now carry an optional `id` (and
`fieldIdByUniversalIdentifier` for object/field actions) so that
caller-provided IDs flow through the entire build-validate-run pipeline.
- New `enrichCreateWorkspaceMigrationActionsWithIds` utility resolves
`universalIdentifier → id` mappings after the builder runs and injects
them into the migration actions before the runner persists entities.
- Runner action handlers use the provided IDs instead of generating new
UUIDs, enabling deterministic entity creation for synchronization
workflows.
## `resolveUniversalUpdateRelationIdentifiersToIds`
`resolveUniversalUpdateRelationIdentifiersToIds` converts universal
identifiers (workspace-agnostic, stable keys) in a migration update
payload into concrete database UUIDs, so the update can be applied to a
specific workspace.
It iterates over the many-to-one relations defined in
`ALL_UNIVERSAL_METADATA_RELATIONS` for the given entity type, replaces
each `*UniversalIdentifier` property with its corresponding `*Id` by
looking up the target entity in `allFlatEntityMaps`, and throws if a
non-null identifier can't be resolved.
Used by all `update` action handlers in
`transpileUniversalActionToFlatAction`, avoiding duplicated resolution
logic across handlers.
## What this PR does not
- Migrating twenty-standard declaration to universal
- Migrating all the inputs transpilers to universal
- Migrating all metadata to be fully universal ( we still need to
de-scope the type of all of them and refactor their validator very close
)
---------
Co-authored-by: Charles Bochet <charles@twenty.com>