## Summary
- Moves current version to previous versions array
- Sets TWENTY_CURRENT_VERSION to the new version
- Updates TWENTY_NEXT_VERSIONS with the next minor version
- Bumps twenty-client-sdk, twenty-sdk, and create-twenty-app to the same
version
## Checklist
- [ ] Verify version constants are correct
- [ ] Verify npm package versions match
Co-authored-by: Github Action Deploy <github-action-deploy@twenty.com>
## Summary
Follow-up to #20876. That PR bumped `esbuild` to `^0.27.3` to address
the Go-stdlib CVEs the self-hoster reported, but only one of the two Go
CVEs is actually fixed at that level. This PR closes the remaining gap.
### Why 0.27.3 wasn't enough
`esbuild` ships a Go-built binary inside the `@esbuild/<platform>`
packages. The vulnerability lives in the bundled Go toolchain, not in
any JavaScript. Verified by reading the Go `buildinfo` section from
`node_modules/@esbuild/<platform>/bin/esbuild`:
- `esbuild@0.27.7` → built with **Go 1.23.8**
- `esbuild@0.28.0` → built with **Go 1.26.1**
CVE-2024-24790 (IPv6 zone parsing) is fixed in Go 1.21.11 / 1.22.4, so
0.27.x covers it.
**CVE-2025-68121** (crypto/tls cert validation bypass via TLS session
resumption, **CVSS 10.0 / Critical** per
[NVD](https://nvd.nist.gov/vuln/detail/cve-2025-68121)) is fixed only in
Go 1.24.13, 1.25.7, and 1.26.0-rc.3+. Go 1.23.x is past Go's support
window and will not receive this fix. So `esbuild@0.27.x` still ships a
Go binary that Trivy correctly flags as vulnerable.
### Reachable risk in Twenty
Low. `esbuild` does not use `crypto/tls` at runtime — it reads files,
parses, transforms, and writes. The vulnerable code path is dead code
inside the binary, present but never executed. The scan finding is what
we are clearing, not an exploitation risk.
### Fix
Bump `twenty-client-sdk`'s `esbuild` from `^0.27.3` to `^0.28.0`
(resolves to 0.28.0, built with Go 1.26.1).
### Verification
Ran `yarn workspaces focus --production twenty twenty-server
twenty-emails twenty-shared twenty-client-sdk` (the same install the
Dockerfile uses) and confirmed:
- `node_modules/esbuild/` resolves to `esbuild@0.28.0` (single copy)
- The bundled `node_modules/@esbuild/<platform>/bin/esbuild` binary
reports `go1.26.1` in its `buildinfo`
## Test plan
- [x] `nx typecheck twenty-server` passes
- [x] `nx build twenty-client-sdk` passes (esbuild's `build()` API is
stable across 0.27 → 0.28)
- [x] Production focus install shows Go 1.26.1 in the shipped binary
- [ ] CI green
- [ ] Re-run Trivy against the resulting image; confirm CVE-2025-68121
no longer appears
## Summary
A self-hoster reported that Trivy blocks the `twentycrm/twenty:v2.7.x`
image on three fixed-critical CVEs. The reachable risk is low (none of
the vulnerable code paths are exposed to attacker-controlled input in
our deployment), but the findings are real and easy to clear by bumping
the affected dependencies in their owning workspaces.
### CVE-2026-41242 — `protobufjs` < 7.5.5
Pulled transitively into the production image via
`@opentelemetry/sdk-node`, `@opentelemetry/auto-instrumentations-node`,
and `@grpc/grpc-js` → `@grpc/proto-loader`. Lockfile was on 7.5.3; this
matches dismissed dependabot alert #1009 (Critical 9.4).
**Fix:** add `protobufjs: ^7.5.5` as a direct dep of `twenty-server`
(the workspace that exercises it via the OpenTelemetry gRPC exporters)
and run `yarn dedupe protobufjs` to collapse the residual transitive
7.5.3 copy. Resolves to 7.6.0.
### CVE-2024-24790 and CVE-2025-68121 — Go stdlib in bundled binaries
Present in the Go-built `bin/esbuild` shipped by `@esbuild/<platform>`
packages. Two paths put esbuild into the production image:
1. `twenty-client-sdk` declares `esbuild` as a runtime dep (used by its
`./generate` entry point).
2. `twenty-server` had `@lingui/vite-plugin` in `dependencies`, which
pulls `@lingui/cli` as a runtime sub-dep, which bundles `esbuild@0.21.5`
nested under `node_modules/@lingui/cli/node_modules/esbuild/`.
**Fix:**
- Bump `twenty-client-sdk`'s `esbuild` from `^0.25.0` to `^0.27.3`
(resolves to 0.27.7, built with patched Go).
- Move `@lingui/vite-plugin` from `dependencies` to `devDependencies` in
`twenty-server`. The plugin is not imported by any source file — it was
misclassified.
### Verification
Ran `yarn workspaces focus --production twenty twenty-server
twenty-emails twenty-shared twenty-client-sdk` (the same command the
Dockerfile uses) and inventoried the resulting `node_modules`. After all
three changes:
- `node_modules/esbuild/` → **0.27.7 only** (Go-patched)
- `node_modules/protobufjs/` → **7.6.0 only** (CVE-patched)
No nested copies of either package remain in the production install.
### Follow-up worth tracking separately
`esbuild` should arguably not be in `twenty-client-sdk`'s `dependencies`
at all — only the `./generate` entry point uses it, and the server never
imports that entry. Moving it to optional `peerDependencies` would stop
shipping a Go binary into the production image entirely. Out of scope
for this PR.
## Test plan
- [x] `yarn install` succeeds; `protobufjs` and `esbuild` each resolve
to a single version in production focus
- [x] `nx build twenty-client-sdk` passes
- [x] `nx typecheck twenty-server` passes
- [x] `nx build twenty-server` passes
- [x] Production focus install confirmed clean (`node_modules/esbuild`
and `node_modules/protobufjs` both single-version, both patched)
- [ ] CI green
- [ ] Re-run Trivy against the resulting image; confirm the three CVEs
no longer appear
- Replace inline SVG icons with proper Avatar and icon components
(IconBox, IconHierarchy, IconLayout, IconSettingsAutomation) from
twenty-sdk/ui in the scaffolded front component
template
- Strip trailing slashes from workspace/API URLs in both
create-twenty-app CLI and twenty-sdk remote commands to prevent
malformed requests
- Fix the application settings link to navigate to #installed anchor
- Bump twenty-sdk, twenty-client-sdk, and create-twenty-app versions to
2.6.0
<img width="1512" height="824" alt="image"
src="https://github.com/user-attachments/assets/8561d7bb-3458-46c4-b01e-664321634b4c"
/>
## Summary
- Bumps `twenty-sdk` from `2.4.2` to `2.5.0`.
- Bumps `twenty-client-sdk` from `2.4.2` to `2.5.0`.
- Bumps `create-twenty-app` from `2.4.2` to `2.5.0`.
## Simplify `create-twenty-app` for zero-interaction use
Makes `npx create-twenty-app@latest my-app` a fully non-interactive,
single-command experience suitable for automated environments (Codex,
Claude plugins).
### Changes
- **Remove all interactive prompts** — app name, display name,
description, and scaffold confirmation are now derived from CLI args
with sensible defaults. `inquirer` dependency removed
entirely.
- **Replace OAuth with API key auth** — use the seeded dev API key
(`DEV_API_KEY`) to authenticate against the Docker instance as
`tim@apple.dev`, eliminating the browser-based OAuth
flow.
- **Docker-first with early validation** — check Docker is installed
before scaffolding; if missing, print the install URL and exit. Detect
alternative runtimes (Podman, nerdctl).
- **Parallel image pull** — `docker pull` runs in the background during
scaffold + dependency install, saving 10-30s on typical runs.
- **Always pull latest image** — ensures the dev server is up-to-date on
every run.
- **Stop detecting port 3000** — only check port 2020 (Docker instance).
- **Update CLI flags** — remove `--skip-local-instance` and `--yes`; add
`--skip-docker`.
- **Update CI workflows and docs** — align e2e workflows, package
README, and template README/cd.yml with the new flow.
##
The command pulls the image, compares it against the one the container
was created from, and only recreates the container if the image actually
changed. Your data volumes are preserved — only the container is
replaced.
## Summary
- Bumps `twenty-sdk` from `2.1.0-canary.1` to `2.1.0`.
- Bumps `twenty-client-sdk` from `2.1.0-canary.1` to `2.1.0`.
- Bumps `create-twenty-app` from `2.1.0-canary.1` to `2.1.0`.
## Summary
- Bumps `twenty-sdk` from `2.1.0` to `2.1.0-canary.1`.
- Bumps `twenty-client-sdk` from `2.0.0` to `2.1.0-canary.1`.
- Bumps `create-twenty-app` from `2.0.0` to `2.1.0-canary.1`.
## Summary
- Bump `twenty-sdk` from `1.23.0` to `2.0.0`
- Bump `twenty-client-sdk` from `1.23.0` to `2.0.0`
- Bump `create-twenty-app` from `1.23.0` to `2.0.0`
## Summary
- Bump `twenty-sdk` from `1.23.0-canary.9` to `1.23.0`
- Bump `twenty-client-sdk` from `1.23.0-canary.9` to `1.23.0`
- Bump `create-twenty-app` from `1.23.0-canary.9` to `1.23.0`
Made with [Cursor](https://cursor.com)
## Summary
- Bumps `twenty-sdk`, `twenty-client-sdk`, and `create-twenty-app` from
`1.23.0-canary.2` to `1.23.0-canary.9`.
## Test plan
- [ ] Canary publish workflow succeeds for the three packages.
Made with [Cursor](https://cursor.com)
## Summary
- Bumps `twenty-sdk`, `twenty-client-sdk`, and `create-twenty-app` from
`1.22.0` to `1.23.0-canary.1`.
## Test plan
- [ ] CI green
Made with [Cursor](https://cursor.com)
## Summary
- Bump `twenty-sdk` from `1.22.0-canary.6` to `1.22.0`
- Bump `twenty-client-sdk` from `1.22.0-canary.6` to `1.22.0`
- Bump `create-twenty-app` from `1.22.0-canary.6` to `1.22.0`
## Summary
- Follows up on #19610 which exported view/page-layout types but missed
field settings types
- Adds re-exports for `DateDisplayFormat`, `NumberDataType`, and
`FieldMetadataSettingsOnClickAction` from the SDK public API
- These enums are referenced in the return type of `defineObject` (via
field settings types like `FieldMetadataSettingsDate`,
`FieldMetadataSettingsNumber`, `FieldMetadataSettingsMultiItem`) but
were not publicly exported
- This causes TS4082 ("private name") errors for SDK consumers that have
`declaration: true` in their tsconfig, specifically when using
`defineObject` with fields that have settings (e.g. SELECT, DATE, NUMBER
fields)
## Summary
- Fixes TS4082 errors for SDK consumers with `declaration: true` in
their tsconfig
- The `define*` functions (e.g. `defineView`, `definePageLayout`,
`defineLogicFunction`) return `ValidationResult<T>` where `T` references
types from `twenty-shared` that were not re-exported from the SDK's
public barrel
- TypeScript cannot generate `.d.ts` files when the return type
references "private names" — types that exist in the package but aren't
publicly exported
### Added re-exports
**Enums** (used in `ViewManifest`, `HttpRouteTriggerSettings`):
- `ViewType`, `ViewFilterOperand`, `ViewFilterGroupLogicalOperator`,
`ViewOpenRecordIn`, `ViewVisibility`
- `HTTPMethod`
**Types** (used in `PageLayoutWidgetManifest`, `LogicFunctionConfig`):
- `GridPosition`, `PageLayoutWidgetConditionalDisplay`
- `InputJsonSchema`
## Summary
- `twenty-shared` is private and never published to npm, so SDK
consumers couldn't resolve type imports like `from
'twenty-shared/types'` in the generated `.d.ts` files
- Replace `vite.config.sdk.ts` with `rollup-plugin-dts` which compiles
`src/sdk/index.ts` directly into a self-contained `dist/sdk/index.d.ts`
with all `twenty-shared` types inlined
- The JS output from `vite.config.sdk.ts` (`dist/sdk/*.js`) was unused —
the main export already maps to `dist/index.mjs` from the node Vite
config
## Changes
- **Deleted** `vite.config.sdk.ts` — its preserved-module JS output
wasn't referenced by any `package.json` export
- **Added** `rollup.config.dts.mjs` — uses `rollup-plugin-dts` to
compile SDK types from source with `twenty-shared` inlined (~850ms)
- **Updated** `project.json` — build/dev/build:sdk targets now use
rollup instead of the removed vite config
- **Updated** `tsconfig.json` — removed `vite.config.sdk.ts` from
include
## Test plan
- [ ] Run `npx nx build twenty-sdk` and verify `dist/sdk/index.d.ts`
contains no `twenty-shared` references
- [ ] Verify `dist/index.mjs` and `dist/index.cjs` are still produced
correctly
- [ ] Verify CLI (`dist/cli.cjs`) still works
- [ ] Verify `npx nx build:sdk twenty-sdk` works standalone
Made with [Cursor](https://cursor.com)
## Summary
- Bump `twenty-sdk`, `twenty-client-sdk`, and `create-twenty-app` from
`1.22.0-canary.2` to `1.22.0-canary.3` for publishing.
## Test plan
- Version-only change, no code modifications.
Made with [Cursor](https://cursor.com)
## Summary
- **Fix `createApplicationRegistration` flow**: The server's
`createApplicationRegistration` mutation returns a `clientSecret`, not
`accessToken`/`refreshToken` directly. The SDK now correctly requests
`clientSecret` and immediately performs an OAuth `client_credentials`
exchange to obtain `appAccessToken` and `appRefreshToken`, then stores
them in config.
- **New `exchangeCredentialsForTokens` helper**: Shared by both `dev`
and `dev --once` flows. Takes `clientId` + `clientSecret`, calls
`/oauth/token` with `client_credentials` grant, and persists the
resulting tokens.
- **Bump `twenty-sdk`, `twenty-client-sdk`, `create-twenty-app` to
`1.22.0-canary.2`**
## Context
The `1.22.0-canary.1` SDK release expected
`createApplicationRegistration` to return `accessToken`/`refreshToken`
directly, but the `v1.22.0` server returns `clientSecret`. This caused
`yarn twenty dev` and `yarn twenty dev --once` to fail with "No
registration found" errors.
## Summary
- Bumps `twenty-sdk`, `twenty-client-sdk`, and `create-twenty-app`
package versions from `0.9.0` to `1.22.0-canary.1` for the 1.22 canary
release.
## Test plan
- [ ] Verify packages build successfully (`npx nx build twenty-sdk`,
`npx nx build twenty-client-sdk`, `npx nx build create-twenty-app`)
- [ ] Verify `create-twenty-app` scaffolds new apps with the correct SDK
version
Made with [Cursor](https://cursor.com)
## Summary
- **Make app-synced objects searchable**: `isSearchable` was hardcoded
to `false` and the `searchVector` field was missing the `GENERATED
ALWAYS AS (...)` expression, causing all records to have a `NULL` search
vector and be excluded from search results. Fixed by defaulting
`isSearchable` to `true` (configurable via the object manifest),
computing the `asExpression` from the label identifier field, and
allowing the update-field-action-handler to handle the `null` → defined
`asExpression` transition.
- **Make `isSearchable` updatable on an object**: The property had
`toCompare: false` in the entity properties configuration, so updates
via the API were silently ignored and never persisted. Fixed by setting
`toCompare: true`.
<img width="1946" height="792" alt="image"
src="https://github.com/user-attachments/assets/d0abf62b-85d4-4f5f-b6dc-f2cc1c691f7e"
/>
Avoid re-exporting twenty-ui icons bundle that are massive ~4MB
As discussed with @charlesBochet the problem should rather be treated at
twenty-ui level at some point, that's quite a quick workaround in order
to avoid overloading the twenty-sdk build size
When time comes, where twenty-ui is mature enough to get published we
will work on its bundle size
## Summary
### Externalize `twenty-client-sdk` from `twenty-sdk`
Previously, `twenty-client-sdk` was listed as a `devDependency` of
`twenty-sdk`, which caused Vite to bundle it inline into the dist
output. This meant end-user apps had two copies of `twenty-client-sdk`:
one hidden inside `twenty-sdk`'s bundle, and one installed explicitly in
their `node_modules`. These copies could drift apart since they weren't
guaranteed to be the same version.
**Change:** Moved `twenty-client-sdk` from `devDependencies` to
`dependencies` in `twenty-sdk/package.json`. Vite's `external` function
now recognizes it and keeps it as an external `require`/`import` in the
dist output. End users get a single deduplicated copy resolved by their
package manager.
### Externalize `twenty-sdk` from `create-twenty-app`
Similarly, `create-twenty-app` had `twenty-sdk` as a `devDependency`
(bundled inline). After refactoring `create-twenty-app` to
programmatically import operations from `twenty-sdk` (instead of
shelling out via `execSync`), it became a proper runtime dependency.
**Change:** Moved `twenty-sdk` from `devDependencies` to `dependencies`
in `create-twenty-app/package.json`.
### Switch E2E CI to `yarn npm publish`
The `workspace:*` protocol in `dependencies` is a Yarn-specific feature.
`npm publish` publishes it as-is (which breaks for consumers), while
`yarn npm publish` automatically replaces `workspace:*` with the
resolved version at publish time (e.g., `workspace:*` becomes `=1.2.3`).
**Change:** Replaced `npm publish` with `yarn npm publish` in
`.github/workflows/ci-create-app-e2e.yaml`.
### Replace `execSync` with programmatic SDK calls in
`create-twenty-app`
`create-twenty-app` was shelling out to `yarn twenty remote add` and
`yarn twenty server start` via `execSync`, which assumed the `twenty`
binary was already installed in the scaffolded app. This was fragile and
created an implicit circular dependency.
**Changes:**
- Replaced `execSync('yarn twenty remote add ...')` with a direct call
to `authLoginOAuth()` from `twenty-sdk/cli`
- Replaced `execSync('yarn twenty server start')` with a direct call to
`serverStart()` from `twenty-sdk/cli`
- Deleted the duplicated `setup-local-instance.ts` from
`create-twenty-app`
### Centralize `serverStart` as a dedicated operation
The Docker server start logic was previously inline in the `server
start` CLI command handler (`server.ts`), and `setup-local-instance.ts`
was shelling out to `yarn twenty server start` to invoke it -- meaning
`twenty-sdk` was calling itself via a child process.
**Changes:**
- Extracted the Docker container management logic into a new
`serverStart` operation (`cli/operations/server-start.ts`)
- Merged the detect-or-start flow from `setup-local-instance.ts` into
`serverStart` (detect across multiple ports, start Docker if needed,
poll for health)
- Deleted `setup-local-instance.ts` from `twenty-sdk`
- Added `onProgress` callback (consistent with other operations like
`appBuild`) instead of direct `console.log` calls
- Both the `server start` CLI command and `create-twenty-app` now call
`serverStart()` programmatically
related to https://github.com/twentyhq/twenty-infra/pull/525
## 1. The `twenty-client-sdk` Package (Source of Truth)
The monorepo package at `packages/twenty-client-sdk` ships with:
- A **pre-built metadata client** (static, generated from a fixed
schema)
- A **stub core client** that throws at runtime (`CoreApiClient was not
generated...`)
- Both ESM (`.mjs`) and CJS (`.cjs`) bundles in `dist/`
- A `package.json` with proper `exports` map for
`twenty-client-sdk/core`, `twenty-client-sdk/metadata`, and
`twenty-client-sdk/generate`
## 2. Generation & Upload (Server-Side, at Migration Time)
**When**: `WorkspaceMigrationRunnerService.run()` executes after a
metadata schema change.
**What happens in `SdkClientGenerationService.generateAndStore()`**:
1. Copies the stub `twenty-client-sdk` package from the server's assets
(resolved via `SDK_CLIENT_PACKAGE_DIRNAME` — from
`dist/assets/twenty-client-sdk/` in production, or from `node_modules`
in dev)
2. Filters out `node_modules/` and `src/` during copy — only
`package.json` + `dist/` are kept (like an npm publish)
3. Calls `replaceCoreClient()` which uses `@genql/cli` to introspect the
**application-scoped** GraphQL schema and generates a real
`CoreApiClient`, then compiles it to ESM+CJS and overwrites
`dist/core.mjs` and `dist/core.cjs`
4. Archives the **entire package** (with `package.json` + `dist/`) into
`twenty-client-sdk.zip`
5. Uploads the single archive to S3 under
`FileFolder.GeneratedSdkClient`
6. Sets `isSdkLayerStale = true` on the `ApplicationEntity` in the
database
## 3. Invalidation Signal
The `isSdkLayerStale` boolean column on `ApplicationEntity` is the
invalidation mechanism:
- **Set to `true`** by `generateAndStore()` after uploading a new client
archive
- **Checked** by both logic function drivers before execution — if
`true`, they rebuild their local layer
- **Set back to `false`** by `markSdkLayerFresh()` after the driver has
successfully consumed the new archive
Default is `false` so existing applications without a generated client
aren't affected.
## 4a. Logic Functions — Local Driver
**`ensureSdkLayer()`** is called before every execution:
1. Checks if the local SDK layer directory exists AND `isSdkLayerStale`
is `false` → early return
2. Otherwise, cleans the local layer directory
3. Calls `downloadAndExtractToPackage()` which streams the zip from S3
directly to disk and extracts the full package into
`<tmpdir>/sdk/<workspaceId>-<appId>/node_modules/twenty-client-sdk/`
4. Calls `markSdkLayerFresh()` to set `isSdkLayerStale = false`
**At execution time**, `assembleNodeModules()` symlinks everything from
the deps layer's `node_modules/` **except** `twenty-client-sdk`, which
is symlinked from the SDK layer instead. This ensures the logic
function's `import ... from 'twenty-client-sdk/core'` resolves to the
generated client.
## 4b. Logic Functions — Lambda Driver
**`ensureSdkLayer()`** is called during `build()`:
1. Checks if `isSdkLayerStale` is `false` and an existing Lambda layer
ARN exists → early return
2. Otherwise, deletes all existing layer versions for this SDK layer
name
3. Calls `downloadArchiveBuffer()` to get the raw zip from S3 (no disk
extraction)
4. Calls `reprefixZipEntries()` which streams the zip entries into a
**new zip** with the path prefix
`nodejs/node_modules/twenty-client-sdk/` — this is the Lambda layer
convention path. All done in memory, no disk round-trip
5. Publishes the re-prefixed zip as a new Lambda layer via
`publishLayer()`
6. Calls `markSdkLayerFresh()`
**At function creation**, the Lambda is created with **two layers**:
`[depsLayerArn, sdkLayerArn]`. The SDK layer is listed last so it
overwrites the stub `twenty-client-sdk` from the deps layer (later
layers take precedence in Lambda's `/opt` merge).
## 5. Front Components
Front components are built by `app:build` with `twenty-client-sdk/core`
and `twenty-client-sdk/metadata` as **esbuild externals**. The stored
`.mjs` in S3 has unresolved bare import specifiers like `import {
CoreApiClient } from 'twenty-client-sdk/core'`.
SDK import resolution is split between the **frontend host** (fetching &
caching SDK modules) and the **Web Worker** (rewriting imports):
**Server endpoints**:
- `GET /rest/front-components/:id` —
`FrontComponentService.getBuiltComponentStream()` returns the **raw
`.mjs`** directly from file storage. No bundling, no SDK injection.
- `GET /rest/sdk-client/:applicationId/:moduleName` —
`SdkClientController` reads a single file (e.g. `dist/core.mjs`) from
the generated SDK archive via
`SdkClientGenerationService.readFileFromArchive()` and serves it as
JavaScript.
**Frontend host** (`FrontComponentRenderer` in `twenty-front`):
1. Queries `FindOneFrontComponent` which returns `applicationId`,
`builtComponentChecksum`, `usesSdkClient`, and `applicationTokenPair`
2. If `usesSdkClient` is `true`, renders
`FrontComponentRendererWithSdkClient` which calls the
`useApplicationSdkClient` hook
3. `useApplicationSdkClient({ applicationId, accessToken })` checks the
Jotai atom family cache for existing blob URLs. On cache miss, fetches
both SDK modules from `GET /rest/sdk-client/:applicationId/core` and
`/metadata`, creates **blob URLs** for each, and stores them in the atom
family
4. Once the blob URLs are cached, passes them as `sdkClientUrls`
(already blob URLs, not server URLs) to `SharedFrontComponentRenderer` →
`FrontComponentWorkerEffect` → worker's `render()` call via
`HostToWorkerRenderContext`
**Worker** (`remote-worker.ts` in `twenty-sdk`):
1. Fetches the raw component `.mjs` source as text
2. If `sdkClientUrls` are provided and the source contains SDK import
specifiers (`twenty-client-sdk/core`, `twenty-client-sdk/metadata`),
**rewrites** the bare specifiers to the blob URLs received from the host
(e.g. `'twenty-client-sdk/core'` → `'blob:...'`)
3. Creates a blob URL for the rewritten source and `import()`s it
4. Revokes only the component blob URL after the module is loaded — the
SDK blob URLs are owned and managed by the host's Jotai cache
This approach eliminates server-side esbuild bundling on every request,
caches SDK modules per application in the frontend, and keeps the
worker's job to a simple string rewrite.
## Summary Diagram
```
app:build (SDK)
└─ twenty-client-sdk stub (metadata=real, core=stub)
│
▼
WorkspaceMigrationRunnerService.run()
└─ SdkClientGenerationService.generateAndStore()
├─ Copy stub package (package.json + dist/)
├─ replaceCoreClient() → regenerate core.mjs/core.cjs
├─ Zip entire package → upload to S3
└─ Set isSdkLayerStale = true
│
┌────────┴────────────────────┐
▼ ▼
Logic Functions Front Components
│ │
├─ Local Driver ├─ GET /rest/sdk-client/:appId/core
│ └─ downloadAndExtract │ → core.mjs from archive
│ → symlink into │
│ node_modules ├─ Host (useApplicationSdkClient)
│ │ ├─ Fetch SDK modules
└─ Lambda Driver │ ├─ Create blob URLs
└─ downloadArchiveBuffer │ └─ Cache in Jotai atom family
→ reprefixZipEntries │
→ publish as Lambda ├─ GET /rest/front-components/:id
layer │ → raw .mjs (no bundling)
│
└─ Worker (browser)
├─ Fetch component .mjs
├─ Rewrite imports → blob URLs
└─ import() rewritten source
```
## Next PR
- Estimate perf improvement by implementing a redis caching for front
component client storage ( we don't even cache front comp initially )
- Implem frontent blob invalidation sse event from server
---------
Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>