diff --git a/packages/twenty-sdk/package.json b/packages/twenty-sdk/package.json index d6e9653870c..fc705f45902 100644 --- a/packages/twenty-sdk/package.json +++ b/packages/twenty-sdk/package.json @@ -32,6 +32,11 @@ "import": "./dist/define/index.mjs", "require": "./dist/define/index.cjs" }, + "./define-runtime-stub": { + "types": "./dist/define/index.d.ts", + "import": "./dist/define-runtime-stub/index.mjs", + "require": "./dist/define-runtime-stub/index.cjs" + }, "./front-component": { "types": "./dist/front-component/index.d.ts", "import": "./dist/front-component/index.mjs", @@ -108,6 +113,9 @@ "define": [ "dist/define/index.d.ts" ], + "define-runtime-stub": [ + "dist/define/index.d.ts" + ], "front-component": [ "dist/front-component/index.d.ts" ], diff --git a/packages/twenty-sdk/project.json b/packages/twenty-sdk/project.json index 4fa38db83f4..164a37df604 100644 --- a/packages/twenty-sdk/project.json +++ b/packages/twenty-sdk/project.json @@ -14,7 +14,7 @@ "options": { "cwd": "{projectRoot}", "commands": [ - "npx rimraf dist && npx vite build -c vite.config.node.ts && npx vite build -c vite.config.define.ts && npx vite build -c vite.config.billing.ts && npx vite build -c vite.config.front-component.ts && npx vite build -c vite.config.browser.ts", + "npx rimraf dist && npx vite build -c vite.config.node.ts && npx vite build -c vite.config.define.ts && npx vite build -c vite.config.define-stub.ts && npx vite build -c vite.config.billing.ts && npx vite build -c vite.config.front-component.ts && npx vite build -c vite.config.browser.ts", "tsgo -p tsconfig.lib.json --declaration --emitDeclarationOnly --noEmit false --outDir dist --rootDir src && npx tsc-alias -p tsconfig.lib.json --outDir dist", "npx rimraf 'dist/sdk' 'dist/define/**/*.d.ts' 'dist/define/**/*.d.ts.map' 'dist/billing/**/*.d.ts' 'dist/billing/**/*.d.ts.map' 'dist/front-component/**/*.d.ts' 'dist/front-component/**/*.d.ts.map' && npx rollup -c rollup.config.sdk-dts.mjs" ], @@ -26,7 +26,7 @@ "dependsOn": ["^build"], "options": { "cwd": "packages/twenty-sdk", - "command": "npx rimraf dist && npx vite build -c vite.config.node.ts && npx vite build -c vite.config.define.ts && npx vite build -c vite.config.billing.ts && npx vite build -c vite.config.front-component.ts && npx vite build -c vite.config.browser.ts && tsgo -p tsconfig.lib.json --declaration --emitDeclarationOnly --noEmit false --outDir dist --rootDir src && npx tsc-alias -p tsconfig.lib.json --outDir dist && npx rimraf 'dist/sdk' 'dist/define/**/*.d.ts' 'dist/define/**/*.d.ts.map' 'dist/billing/**/*.d.ts' 'dist/billing/**/*.d.ts.map' 'dist/front-component/**/*.d.ts' 'dist/front-component/**/*.d.ts.map' && npx rollup -c rollup.config.sdk-dts.mjs && npx vite build -c vite.config.node.ts --watch & npx vite build -c vite.config.define.ts --watch & npx vite build -c vite.config.billing.ts --watch & npx vite build -c vite.config.front-component.ts --watch & npx vite build -c vite.config.browser.ts --watch" + "command": "npx rimraf dist && npx vite build -c vite.config.node.ts && npx vite build -c vite.config.define.ts && npx vite build -c vite.config.define-stub.ts && npx vite build -c vite.config.billing.ts && npx vite build -c vite.config.front-component.ts && npx vite build -c vite.config.browser.ts && tsgo -p tsconfig.lib.json --declaration --emitDeclarationOnly --noEmit false --outDir dist --rootDir src && npx tsc-alias -p tsconfig.lib.json --outDir dist && npx rimraf 'dist/sdk' 'dist/define/**/*.d.ts' 'dist/define/**/*.d.ts.map' 'dist/billing/**/*.d.ts' 'dist/billing/**/*.d.ts.map' 'dist/front-component/**/*.d.ts' 'dist/front-component/**/*.d.ts.map' && npx rollup -c rollup.config.sdk-dts.mjs && npx vite build -c vite.config.node.ts --watch & npx vite build -c vite.config.define.ts --watch & npx vite build -c vite.config.billing.ts --watch & npx vite build -c vite.config.front-component.ts --watch & npx vite build -c vite.config.browser.ts --watch" } }, "start": { @@ -93,11 +93,12 @@ "inputs": ["{projectRoot}/src/sdk/**/*"], "outputs": [ "{projectRoot}/dist/define", + "{projectRoot}/dist/define-runtime-stub", "{projectRoot}/dist/front-component" ], "options": { "cwd": "{projectRoot}", - "command": "npx vite build -c vite.config.define.ts && npx vite build -c vite.config.front-component.ts && npx rollup -c rollup.config.sdk-dts.mjs" + "command": "npx vite build -c vite.config.define.ts && npx vite build -c vite.config.define-stub.ts && npx vite build -c vite.config.front-component.ts && npx rollup -c rollup.config.sdk-dts.mjs" } } } diff --git a/packages/twenty-sdk/src/cli/utilities/build/common/app-build-alias.ts b/packages/twenty-sdk/src/cli/utilities/build/common/app-build-alias.ts new file mode 100644 index 00000000000..e31f7f6c531 --- /dev/null +++ b/packages/twenty-sdk/src/cli/utilities/build/common/app-build-alias.ts @@ -0,0 +1,14 @@ +/** + * esbuild aliases applied when building user app code (logic functions + * and front components). + * + * `twenty-sdk/define` is an authoring surface whose `defineXxx` helpers + * only run build-time validation. Its compiled `dist` also transitively + * imports `zod` (+ every locale) and `@sniptt/guards`. Redirecting to + * `twenty-sdk/define-runtime-stub` — which exposes passthrough identity + * versions of the same surface — lets esbuild tree-shake the zod/sniptt + * dependencies out of user bundles. + */ +export const APP_BUILD_ALIASES: Record = { + 'twenty-sdk/define': 'twenty-sdk/define-runtime-stub', +}; diff --git a/packages/twenty-sdk/src/cli/utilities/build/common/build-application.ts b/packages/twenty-sdk/src/cli/utilities/build/common/build-application.ts index 5b453c6d99e..82274ae6c4a 100644 --- a/packages/twenty-sdk/src/cli/utilities/build/common/build-application.ts +++ b/packages/twenty-sdk/src/cli/utilities/build/common/build-application.ts @@ -8,6 +8,7 @@ import { } from 'twenty-shared/application'; import { FileFolder } from 'twenty-shared/types'; +import { APP_BUILD_ALIASES } from '@/cli/utilities/build/common/app-build-alias'; import { esbuildOneShotBuild } from '@/cli/utilities/build/common/esbuild-one-shot-build'; import { LOGIC_FUNCTION_EXTERNAL_MODULES } from '@/cli/utilities/build/common/esbuild-watcher'; import { getBaseFrontComponentBuildOptions } from '@/cli/utilities/build/common/front-component-build/utils/get-base-front-component-build-options'; @@ -73,6 +74,7 @@ export const buildApplication = async ( platform: 'node', outdir: join(options.appPath, OUTPUT_DIR), outExtension: { '.js': '.mjs' }, + alias: APP_BUILD_ALIASES, external: LOGIC_FUNCTION_EXTERNAL_MODULES, tsconfig: join(options.appPath, 'tsconfig.json'), sourcemap: true, @@ -95,6 +97,7 @@ export const buildApplication = async ( sourcemap: true, metafile: true, logLevel: 'silent', + alias: APP_BUILD_ALIASES, plugins: [...getFrontComponentBuildPlugins()], }, onFileBuilt: collectFileBuilt, diff --git a/packages/twenty-sdk/src/cli/utilities/build/common/esbuild-watcher.ts b/packages/twenty-sdk/src/cli/utilities/build/common/esbuild-watcher.ts index 32831c8d3f4..f7f7bc7f5bf 100644 --- a/packages/twenty-sdk/src/cli/utilities/build/common/esbuild-watcher.ts +++ b/packages/twenty-sdk/src/cli/utilities/build/common/esbuild-watcher.ts @@ -1,3 +1,4 @@ +import { APP_BUILD_ALIASES } from '@/cli/utilities/build/common/app-build-alias'; import { cleanupRemovedFiles } from '@/cli/utilities/build/common/cleanup-removed-files'; import { processEsbuildResult } from '@/cli/utilities/build/common/esbuild-result-processor'; import { FRONT_COMPONENT_EXTERNAL_MODULES } from '@/cli/utilities/build/common/front-component-build/constants/front-component-external-modules'; @@ -163,6 +164,7 @@ export class EsbuildWatcher implements RestartableWatcher { platform: this.config.platform, outdir: outputDir, outExtension: { '.js': '.mjs' }, + alias: APP_BUILD_ALIASES, external: this.config.externalModules, tsconfig: path.join(this.appPath, 'tsconfig.json'), jsx: this.config.jsx, diff --git a/packages/twenty-sdk/src/sdk/define/runtime-stub.ts b/packages/twenty-sdk/src/sdk/define/runtime-stub.ts new file mode 100644 index 00000000000..f5002b9354d --- /dev/null +++ b/packages/twenty-sdk/src/sdk/define/runtime-stub.ts @@ -0,0 +1,259 @@ +/** + * Runtime stub for `twenty-sdk/define`. + * + * `twenty-sdk/define` is an authoring API: its `defineXxx` helpers only run + * build-time validation, and the full module transitively pulls in `zod` + * (including every locale) plus `@sniptt/guards`. In a built logic function + * or front component that only needs `default.config.handler` / + * `default.config.component` at runtime, those extras inflate bundles by + * ~1MB per file. + * + * The CLI aliases `twenty-sdk/define` to this stub when bundling user code, + * so the published SDK continues to do build-time validation while user + * bundles ship only the identity passthroughs and the handful of enum + * constants they actually reference. Tree-shaking removes the rest. + * + * Keep this file free of imports that transitively reach `zod` — i.e. do not + * re-export from the `twenty-shared/types` barrel, which pulls + * `richTextValueSchema` (zod) in along for the ride. + */ + +export { STANDARD_OBJECTS as STANDARD_OBJECT } from 'twenty-shared/metadata'; +export { STANDARD_OBJECTS as STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-shared/metadata'; + +export { generateDefaultFieldUniversalIdentifier } from '@/sdk/define/objects/generate-default-field-universal-identifier'; +export { getPublicAssetUrl } from '@/sdk/define/get-public-asset-url'; + +type PassthroughResult = { + success: true; + config: T; + errors: readonly never[]; +}; + +const passthrough = (config: T): PassthroughResult => ({ + success: true, + config, + errors: [], +}); + +export const defineAgent = passthrough; +export const defineApplication = passthrough; +export const defineField = passthrough; +export const defineFrontComponent = passthrough; +export const defineLogicFunction = passthrough; +export const defineNavigationMenuItem = passthrough; +export const defineObject = passthrough; +export const definePageLayout = passthrough; +export const definePageLayoutTab = passthrough; +export const definePostInstallLogicFunction = passthrough; +export const definePreInstallLogicFunction = passthrough; +export const defineRole = passthrough; +export const defineSkill = passthrough; +export const defineView = passthrough; + +export const createValidationResult = ({ + config, + errors = [], +}: { + config: T; + errors?: string[]; +}) => ({ + success: errors.length === 0, + config, + errors, +}); + +export const validateFields = (): string[] => []; + +// ─── Enum constants ───────────────────────────────────────────────────────── +// Values inlined (rather than re-exported from `twenty-shared/types`) to keep +// this stub free of the zod-using `richTextValueSchema` re-export that lives +// in the same barrel. + +export const AggregateOperations = { + MIN: 'MIN', + MAX: 'MAX', + AVG: 'AVG', + SUM: 'SUM', + COUNT: 'COUNT', + COUNT_UNIQUE_VALUES: 'COUNT_UNIQUE_VALUES', + COUNT_EMPTY: 'COUNT_EMPTY', + COUNT_NOT_EMPTY: 'COUNT_NOT_EMPTY', + COUNT_TRUE: 'COUNT_TRUE', + COUNT_FALSE: 'COUNT_FALSE', + PERCENTAGE_EMPTY: 'PERCENTAGE_EMPTY', + PERCENTAGE_NOT_EMPTY: 'PERCENTAGE_NOT_EMPTY', +} as const; + +export const DateDisplayFormat = { + RELATIVE: 'RELATIVE', + USER_SETTINGS: 'USER_SETTINGS', + CUSTOM: 'CUSTOM', +} as const; + +export const FieldMetadataSettingsOnClickAction = { + COPY: 'COPY', + OPEN_LINK: 'OPEN_LINK', + OPEN_IN_APP: 'OPEN_IN_APP', +} as const; + +export const FieldType = { + ACTOR: 'ACTOR', + ADDRESS: 'ADDRESS', + ARRAY: 'ARRAY', + BOOLEAN: 'BOOLEAN', + CURRENCY: 'CURRENCY', + DATE: 'DATE', + DATE_TIME: 'DATE_TIME', + EMAILS: 'EMAILS', + FILES: 'FILES', + FULL_NAME: 'FULL_NAME', + LINKS: 'LINKS', + MORPH_RELATION: 'MORPH_RELATION', + MULTI_SELECT: 'MULTI_SELECT', + NUMBER: 'NUMBER', + NUMERIC: 'NUMERIC', + PHONES: 'PHONES', + POSITION: 'POSITION', + RATING: 'RATING', + RAW_JSON: 'RAW_JSON', + RELATION: 'RELATION', + RICH_TEXT: 'RICH_TEXT', + SELECT: 'SELECT', + TEXT: 'TEXT', + TS_VECTOR: 'TS_VECTOR', + UUID: 'UUID', +} as const; + +export const HTTPMethod = { + GET: 'GET', + POST: 'POST', + PUT: 'PUT', + PATCH: 'PATCH', + DELETE: 'DELETE', +} as const; + +export const NavigationMenuItemType = { + VIEW: 'VIEW', + FOLDER: 'FOLDER', + LINK: 'LINK', + OBJECT: 'OBJECT', + RECORD: 'RECORD', + PAGE_LAYOUT: 'PAGE_LAYOUT', +} as const; + +export const NumberDataType = { + FLOAT: 'float', + INT: 'int', + BIGINT: 'bigint', +} as const; + +export const ObjectRecordGroupByDateGranularity = { + DAY: 'DAY', + MONTH: 'MONTH', + QUARTER: 'QUARTER', + YEAR: 'YEAR', + WEEK: 'WEEK', + DAY_OF_THE_WEEK: 'DAY_OF_THE_WEEK', + MONTH_OF_THE_YEAR: 'MONTH_OF_THE_YEAR', + QUARTER_OF_THE_YEAR: 'QUARTER_OF_THE_YEAR', + NONE: 'NONE', +} as const; + +export const OnDeleteAction = { + CASCADE: 'CASCADE', + RESTRICT: 'RESTRICT', + SET_NULL: 'SET_NULL', + NO_ACTION: 'NO_ACTION', +} as const; + +export const PageLayoutTabLayoutMode = { + GRID: 'GRID', + VERTICAL_LIST: 'VERTICAL_LIST', + CANVAS: 'CANVAS', +} as const; + +export const PermissionFlag = { + API_KEYS_AND_WEBHOOKS: 'API_KEYS_AND_WEBHOOKS', + WORKSPACE: 'WORKSPACE', + WORKSPACE_MEMBERS: 'WORKSPACE_MEMBERS', + ROLES: 'ROLES', + DATA_MODEL: 'DATA_MODEL', + SECURITY: 'SECURITY', + WORKFLOWS: 'WORKFLOWS', + IMPERSONATE: 'IMPERSONATE', + SSO_BYPASS: 'SSO_BYPASS', + APPLICATIONS: 'APPLICATIONS', + MARKETPLACE_APPS: 'MARKETPLACE_APPS', + LAYOUTS: 'LAYOUTS', + BILLING: 'BILLING', + AI_SETTINGS: 'AI_SETTINGS', + AI: 'AI', + VIEWS: 'VIEWS', + UPLOAD_FILE: 'UPLOAD_FILE', + DOWNLOAD_FILE: 'DOWNLOAD_FILE', + SEND_EMAIL_TOOL: 'SEND_EMAIL_TOOL', + HTTP_REQUEST_TOOL: 'HTTP_REQUEST_TOOL', + CODE_INTERPRETER_TOOL: 'CODE_INTERPRETER_TOOL', + IMPORT_CSV: 'IMPORT_CSV', + EXPORT_CSV: 'EXPORT_CSV', + CONNECTED_ACCOUNTS: 'CONNECTED_ACCOUNTS', + PROFILE_INFORMATION: 'PROFILE_INFORMATION', +} as const; + +export const RelationType = { + MANY_TO_ONE: 'MANY_TO_ONE', + ONE_TO_MANY: 'ONE_TO_MANY', +} as const; + +export const ViewFilterGroupLogicalOperator = { + AND: 'AND', + OR: 'OR', + NOT: 'NOT', +} as const; + +export const ViewFilterOperand = { + IS: 'IS', + IS_NOT_NULL: 'IS_NOT_NULL', + IS_NOT: 'IS_NOT', + LESS_THAN_OR_EQUAL: 'LESS_THAN_OR_EQUAL', + GREATER_THAN_OR_EQUAL: 'GREATER_THAN_OR_EQUAL', + IS_BEFORE: 'IS_BEFORE', + IS_AFTER: 'IS_AFTER', + CONTAINS: 'CONTAINS', + DOES_NOT_CONTAIN: 'DOES_NOT_CONTAIN', + IS_EMPTY: 'IS_EMPTY', + IS_NOT_EMPTY: 'IS_NOT_EMPTY', + IS_RELATIVE: 'IS_RELATIVE', + IS_IN_PAST: 'IS_IN_PAST', + IS_IN_FUTURE: 'IS_IN_FUTURE', + IS_TODAY: 'IS_TODAY', + VECTOR_SEARCH: 'VECTOR_SEARCH', +} as const; + +export const ViewKey = { + INDEX: 'INDEX', +} as const; + +export const ViewOpenRecordIn = { + SIDE_PANEL: 'SIDE_PANEL', + RECORD_PAGE: 'RECORD_PAGE', +} as const; + +export const ViewSortDirection = { + ASC: 'ASC', + DESC: 'DESC', +} as const; + +export const ViewType = { + TABLE: 'TABLE', + KANBAN: 'KANBAN', + CALENDAR: 'CALENDAR', + FIELDS_WIDGET: 'FIELDS_WIDGET', + TABLE_WIDGET: 'TABLE_WIDGET', +} as const; + +export const ViewVisibility = { + WORKSPACE: 'WORKSPACE', + UNLISTED: 'UNLISTED', +} as const; diff --git a/packages/twenty-sdk/tsconfig.json b/packages/twenty-sdk/tsconfig.json index 8cce57bef3b..60952eb7439 100644 --- a/packages/twenty-sdk/tsconfig.json +++ b/packages/twenty-sdk/tsconfig.json @@ -26,6 +26,7 @@ "vite.config.node.ts", "vite.config.browser.ts", "vite.config.define.ts", + "vite.config.define-stub.ts", "vite.config.front-component.ts", "jest.config.mjs" ] diff --git a/packages/twenty-sdk/vite.config.define-stub.ts b/packages/twenty-sdk/vite.config.define-stub.ts new file mode 100644 index 00000000000..4a7ff6eb046 --- /dev/null +++ b/packages/twenty-sdk/vite.config.define-stub.ts @@ -0,0 +1,65 @@ +import path from 'path'; +import { type PackageJson } from 'type-fest'; +import { defineConfig } from 'vite'; +import tsconfigPaths from 'vite-tsconfig-paths'; + +import packageJson from './package.json'; + +export default defineConfig(() => { + return { + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/twenty-sdk-define-stub', + resolve: { + alias: { + '@/': path.resolve(__dirname, 'src') + '/', + }, + }, + plugins: [ + tsconfigPaths({ + root: __dirname, + }), + ], + build: { + emptyOutDir: false, + outDir: 'dist/define-runtime-stub', + sourcemap: true, + lib: { + entry: 'src/sdk/define/runtime-stub.ts', + name: 'twenty-sdk-define-runtime-stub', + formats: ['es', 'cjs'], + fileName: (format) => `index.${format === 'es' ? 'mjs' : 'cjs'}`, + }, + rollupOptions: { + external: (id: string) => { + if (/^node:/.test(id)) { + return true; + } + + const builtins = [ + 'child_process', + 'crypto', + 'fs', + 'fs/promises', + 'module', + 'os', + 'path', + 'stream', + 'url', + 'util', + ]; + + if (builtins.includes(id)) { + return true; + } + + const deps = Object.keys( + (packageJson as PackageJson).dependencies || {}, + ); + + return deps.some((dep) => id === dep || id.startsWith(dep + '/')); + }, + }, + }, + logLevel: 'warn' as const, + }; +});