mirror of
https://github.com/twentyhq/twenty
synced 2026-04-21 13:37:22 +00:00
1774 extensibility v1 create an exhaustive documentation readme or dedicated section in twenty contributing doc (#16751)
As title <img width="1108" height="894" alt="image" src="https://github.com/user-attachments/assets/e2dc7e12-72e3-4ca3-ac7b-a94de547f82a" />
This commit is contained in:
parent
50b0665c44
commit
bb73cbc380
25 changed files with 669 additions and 283 deletions
|
|
@ -23,6 +23,7 @@ Create Twenty App is the official scaffolding CLI for building apps on top of [T
|
|||
- A Twenty workspace and an API key (create one at https://app.twenty.com/settings/api-webhooks)
|
||||
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
npx create-twenty-app@latest my-twenty-app
|
||||
cd my-twenty-app
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "create-twenty-app",
|
||||
"version": "0.2.3",
|
||||
"version": "0.2.4",
|
||||
"description": "Command-line interface to create Twenty application",
|
||||
"main": "dist/cli.cjs",
|
||||
"bin": "dist/cli.cjs",
|
||||
|
|
|
|||
|
|
@ -1,137 +1,29 @@
|
|||
import js from '@eslint/js';
|
||||
import typescriptEslint from '@typescript-eslint/eslint-plugin';
|
||||
import typescriptParser from '@typescript-eslint/parser';
|
||||
import importPlugin from 'eslint-plugin-import';
|
||||
import preferArrowPlugin from 'eslint-plugin-prefer-arrow';
|
||||
import prettierPlugin from 'eslint-plugin-prettier';
|
||||
import unusedImportsPlugin from 'eslint-plugin-unused-imports';
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
export default [
|
||||
// Base JS rules
|
||||
// Base JS recommended rules
|
||||
js.configs.recommended,
|
||||
|
||||
// Global ignores
|
||||
{
|
||||
ignores: ['**/node_modules/**', '**/dist/**', '**/coverage/**'],
|
||||
},
|
||||
// TypeScript recommended rules
|
||||
...tseslint.configs.recommended,
|
||||
|
||||
// Base config for TS/JS files
|
||||
{
|
||||
files: ['**/*.{js,jsx,ts,tsx}'],
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: {
|
||||
prettier: prettierPlugin,
|
||||
import: importPlugin,
|
||||
'prefer-arrow': preferArrowPlugin,
|
||||
'unused-imports': unusedImportsPlugin,
|
||||
},
|
||||
rules: {
|
||||
// General rules (aligned with main project)
|
||||
'func-style': ['error', 'declaration', { allowArrowFunctions: true }],
|
||||
'no-console': [
|
||||
'warn',
|
||||
{ allow: ['group', 'groupCollapsed', 'groupEnd'] },
|
||||
],
|
||||
'no-control-regex': 0,
|
||||
'no-debugger': 'error',
|
||||
'no-duplicate-imports': 'error',
|
||||
'no-undef': 'off',
|
||||
'no-unused-vars': 'off',
|
||||
|
||||
// Import rules
|
||||
'import/no-relative-packages': 'error',
|
||||
'import/no-useless-path-segments': 'error',
|
||||
'import/no-duplicates': ['error', { considerQueryString: true }],
|
||||
|
||||
// Prefer arrow functions
|
||||
'prefer-arrow/prefer-arrow-functions': [
|
||||
'error',
|
||||
{
|
||||
disallowPrototype: true,
|
||||
singleReturnOnly: false,
|
||||
classPropertiesAllowed: false,
|
||||
},
|
||||
],
|
||||
|
||||
// Unused imports
|
||||
'unused-imports/no-unused-imports': 'warn',
|
||||
'unused-imports/no-unused-vars': [
|
||||
'warn',
|
||||
{
|
||||
vars: 'all',
|
||||
varsIgnorePattern: '^_',
|
||||
args: 'after-used',
|
||||
argsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
|
||||
// Prettier (formatting as lint errors if you want)
|
||||
'prettier/prettier': 'error',
|
||||
},
|
||||
},
|
||||
|
||||
// TypeScript-specific configuration
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
languageOptions: {
|
||||
parser: typescriptParser,
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
project: true,
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
'@typescript-eslint': typescriptEslint,
|
||||
},
|
||||
rules: {
|
||||
// Turn off base rule and use TS-aware versions
|
||||
'no-redeclare': 'off',
|
||||
'@typescript-eslint/no-redeclare': 'error',
|
||||
|
||||
'@typescript-eslint/ban-ts-comment': 'error',
|
||||
'@typescript-eslint/consistent-type-imports': [
|
||||
'error',
|
||||
{
|
||||
prefer: 'type-imports',
|
||||
fixStyle: 'inline-type-imports',
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/interface-name-prefix': 'off',
|
||||
'@typescript-eslint/no-empty-interface': [
|
||||
'error',
|
||||
{
|
||||
allowSingleExtends: true,
|
||||
},
|
||||
// Common TypeScript-friendly tweaks
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'warn',
|
||||
{ argsIgnorePattern: '^_' },
|
||||
],
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
},
|
||||
},
|
||||
|
||||
// Test files (Jest)
|
||||
{
|
||||
files: ['**/*.spec.@(ts|tsx|js|jsx)', '**/*.test.@(ts|tsx|js|jsx)'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
jest: true,
|
||||
describe: true,
|
||||
it: true,
|
||||
expect: true,
|
||||
beforeEach: true,
|
||||
afterEach: true,
|
||||
beforeAll: true,
|
||||
afterAll: true,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'no-unused-vars': 'off', // handled by TS rule
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ const createDefaultServerlessFunctionRoleConfig = async ({
|
|||
appDirectory: string;
|
||||
defaultServerlessFunctionRoleUniversalIdentifier: string;
|
||||
}) => {
|
||||
const content = `import { PermissionFlag, type RoleConfig } from 'twenty-sdk';
|
||||
const content = `import { type RoleConfig } from 'twenty-sdk';
|
||||
|
||||
export const functionRole: RoleConfig = {
|
||||
universalIdentifier: '${defaultServerlessFunctionRoleUniversalIdentifier}',
|
||||
|
|
@ -168,13 +168,17 @@ const createPackageJson = async ({
|
|||
uninstall: 'twenty app uninstall',
|
||||
help: 'twenty help',
|
||||
auth: 'twenty auth login',
|
||||
lint: 'eslint',
|
||||
'lint-fix': 'eslint --fix',
|
||||
},
|
||||
dependencies: {
|
||||
'twenty-sdk': '0.2.3',
|
||||
'twenty-sdk': '0.2.4',
|
||||
},
|
||||
devDependencies: {
|
||||
'@types/node': '^24.7.2',
|
||||
typescript: '^5.9.3',
|
||||
'@types/node': '^24.7.2',
|
||||
eslint: '^9.32.0',
|
||||
'typescript-eslint': '^8.50.0',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "hello-world",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^24.5.0",
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
"auth": "twenty auth login"
|
||||
},
|
||||
"dependencies": {
|
||||
"twenty-sdk": "0.2.2"
|
||||
"twenty-sdk": "0.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.7.2"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ export const functionRole: RoleConfig = {
|
|||
canBeAssignedToAgents: false,
|
||||
canBeAssignedToUsers: false,
|
||||
canBeAssignedToApiKeys: false,
|
||||
canBeAssignedToApplications: true,
|
||||
objectPermissions: [
|
||||
{
|
||||
objectNameSingular: 'postCard',
|
||||
|
|
|
|||
|
|
@ -1,89 +1,8 @@
|
|||
# Deprecated: twenty-cli
|
||||
|
||||
This package is deprecated. Please install and use twenty-sdk instead:
|
||||
This package is deprecated. Please install and use [twenty-sdk](https://www.npmjs.com/package/twenty-sdk) instead:
|
||||
|
||||
```bash
|
||||
npm uninstall twenty-cli
|
||||
npm install -g twenty-sdk
|
||||
```
|
||||
|
||||
The command name remains the same: twenty.
|
||||
|
||||
A command-line interface to easily scaffold, develop, and publish applications that extend Twenty CRM (now provided by twenty-sdk).
|
||||
|
||||
## Requirements
|
||||
- yarn >= 4.9.2
|
||||
- an `apiKey`. Go to `https://twenty.com/settings/api-webhooks` to generate one
|
||||
|
||||
## Quick example project
|
||||
|
||||
```bash
|
||||
# Authenticate using your apiKey (CLI will prompt for your <apiKey>)
|
||||
twenty auth login
|
||||
|
||||
# Init a new application called hello-world
|
||||
twenty app init hello-world
|
||||
|
||||
# Go to your app
|
||||
cd hello-world
|
||||
|
||||
# Add a serverless function to your application
|
||||
twenty app add serverlessFunction
|
||||
|
||||
# Add a trigger to your serverless function
|
||||
twenty app add trigger
|
||||
|
||||
# Add axios to your application
|
||||
yarn add axios
|
||||
|
||||
# Start dev mode: automatically syncs changes to your Twenty workspace, so you can test new functions/objects instantly.
|
||||
twenty app dev
|
||||
|
||||
# Or use one time sync (also generates SDK automatically)
|
||||
twenty app sync
|
||||
|
||||
# List all available commands
|
||||
twenty help
|
||||
```
|
||||
|
||||
## Application Structure
|
||||
|
||||
Each application in this package follows the standard application structure:
|
||||
|
||||
```
|
||||
app-name/
|
||||
├── package.json
|
||||
├── README.md
|
||||
├── serverlessFunctions # Custom backend logic (runs on demand)
|
||||
└── ...
|
||||
```
|
||||
|
||||
## Publish your application
|
||||
|
||||
Applications are currently stored in twenty/packages/twenty-apps.
|
||||
|
||||
You can share your application with all twenty users.
|
||||
|
||||
```bash
|
||||
# pull twenty project
|
||||
git clone https://github.com/twentyhq/twenty.git
|
||||
cd twenty
|
||||
|
||||
# create a new branch
|
||||
git checkout -b feature/my-awesome-app
|
||||
```
|
||||
|
||||
- copy your app folder into twenty/packages/twenty-apps
|
||||
- commit your changes and open a pull request on https://github.com/twentyhq/twenty
|
||||
|
||||
```bash
|
||||
git commit -m "Add new application"
|
||||
git push
|
||||
```
|
||||
|
||||
Our team reviews contributions for quality, security, and reusability before merging.
|
||||
|
||||
## Contributing
|
||||
|
||||
- see our [Hacktoberfest 2025 notion page](https://twentycrm.notion.site/Hacktoberfest-27711d8417038037a149d4638a9cc510)
|
||||
- our [Discord](https://discord.gg/cx5n4Jzs57)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "twenty-cli",
|
||||
"version": "0.3.0",
|
||||
"version": "0.3.1",
|
||||
"description": "[DEPRECATED] Use twenty-sdk instead: https://www.npmjs.com/package/twenty-sdk",
|
||||
"scripts": {
|
||||
"build": "echo 'use npx nx build'",
|
||||
|
|
|
|||
|
|
@ -19,5 +19,498 @@ Apps let you build and manage Twenty customizations **as code**. Instead of conf
|
|||
**Coming soon:**
|
||||
- Custom UI layouts and components
|
||||
|
||||
## Prerequisites
|
||||
|
||||
## Getting Started (Coming Soon)
|
||||
- Node.js 24+ and Yarn 4
|
||||
- A Twenty workspace and an API key (create one at https://app.twenty.com/settings/api-webhooks)
|
||||
|
||||
## Getting Started
|
||||
|
||||
Create a new app using the official scaffolder, then authenticate and start developing:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Scaffold a new app
|
||||
npx create-twenty-app@latest my-twenty-app
|
||||
cd my-twenty-app
|
||||
|
||||
# Authenticate using your API key (you'll be prompted)
|
||||
yarn auth
|
||||
|
||||
# Start dev mode: automatically syncs local changes to your workspace
|
||||
yarn dev
|
||||
```
|
||||
|
||||
From here you can:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Add a new entity to your application (guided)
|
||||
yarn create-entity
|
||||
|
||||
# Generate a typed Twenty client and workspace entity types
|
||||
yarn generate
|
||||
|
||||
# Run a one‑time sync (instead of watch mode)
|
||||
yarn sync
|
||||
|
||||
# Watch your application's functions logs
|
||||
yarn logs
|
||||
|
||||
# Uninstall the application from the current workspace
|
||||
yarn uninstall
|
||||
|
||||
# Display commands' help
|
||||
yarn help
|
||||
```
|
||||
|
||||
See also: the CLI reference pages for [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) and [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
|
||||
|
||||
## Project structure (scaffolded)
|
||||
|
||||
When you run `npx create-twenty-app@latest my-twenty-app`, the scaffolder:
|
||||
|
||||
- Copies a minimal base application into `my-twenty-app/`
|
||||
- Adds a local `twenty-sdk` dependency and Yarn 4 configuration
|
||||
- Creates config files and scripts wired to the `twenty` CLI
|
||||
- Generates a default application config and a default function role
|
||||
|
||||
A freshly scaffolded app looks like this:
|
||||
|
||||
```text filename="my-twenty-app/"
|
||||
my-twenty-app/
|
||||
package.json
|
||||
yarn.lock
|
||||
.gitignore
|
||||
.nvmrc
|
||||
.yarnrc.yml
|
||||
.yarn/
|
||||
releases/
|
||||
yarn-4.9.2.cjs
|
||||
install-state.gz
|
||||
eslint.config.mjs
|
||||
tsconfig.json
|
||||
README.md
|
||||
src/
|
||||
application.config.ts
|
||||
role.config.ts
|
||||
// your entities, actions, and other app files
|
||||
```
|
||||
|
||||
At a high level:
|
||||
|
||||
- **package.json**: Declares the app name, version, engines (Node 24+, Yarn 4), and adds `twenty-sdk` plus scripts like `dev`, `sync`, `generate`, `create-entity`, `logs`, `uninstall`, and `auth` that delegate to the local `twenty` CLI.
|
||||
- **.gitignore**: Ignores common artifacts such as `node_modules`, `.yarn`, `generated/` (typed client), `dist/`, `build/`, coverage folders, log files, and `.env*` files.
|
||||
- **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Lock and configure the Yarn 4 toolchain used by the project.
|
||||
- **.nvmrc**: Pins the Node.js version expected by the project.
|
||||
- **eslint.config.mjs** and **tsconfig.json**: Provide linting and TypeScript configuration for your app’s TypeScript sources.
|
||||
- **README.md**: A short README in the app root with basic instructions.
|
||||
- **src/**: The main place where you define your application-as-code:
|
||||
- `application.config.ts`: Global configuration for your app (metadata and runtime wiring). See “Application config” below.
|
||||
- `role.config.ts`: Default function role used by your serverless functions. See “Default function role” below.
|
||||
- Future entities, actions/functions, and any supporting code you add.
|
||||
|
||||
Later commands will add more files and folders:
|
||||
|
||||
- `yarn generate` will create a `generated/` folder (typed Twenty client + workspace types).
|
||||
- `yarn create-entity` will add entity definition files under `src/` for your custom objects.
|
||||
|
||||
|
||||
## Authentication
|
||||
|
||||
The first time you run `yarn auth`, you'll be prompted for:
|
||||
|
||||
- API URL (defaults to http://localhost:3000 or your current workspace profile)
|
||||
- API key
|
||||
|
||||
Your credentials are stored per-user in `~/.twenty/config.json`. You can maintain multiple profiles and switch using `--workspace <name>`.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Login interactively (recommended)
|
||||
yarn auth
|
||||
|
||||
# Use a specific workspace profile
|
||||
yarn auth --workspace my-custom-workspace
|
||||
```
|
||||
|
||||
## Use the SDK resources (types & config)
|
||||
|
||||
The twenty-sdk provides typed building blocks you use inside your app. Below are the key pieces you'll touch most often.
|
||||
|
||||
### Defining objects
|
||||
|
||||
Custom objects are regular TypeScript classes annotated with decorators from `twenty-sdk`. They live under `src/objects/` in your app and describe both schema and behavior for records in your workspace.
|
||||
|
||||
Here is an example `postCard` object from the Hello World app:
|
||||
|
||||
```typescript
|
||||
import { type Note } from '../../generated';
|
||||
|
||||
import {
|
||||
type AddressField,
|
||||
Field,
|
||||
FieldType,
|
||||
type FullNameField,
|
||||
Object,
|
||||
OnDeleteAction,
|
||||
Relation,
|
||||
RelationType,
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
|
||||
} from 'twenty-sdk';
|
||||
|
||||
enum PostCardStatus {
|
||||
DRAFT = 'DRAFT',
|
||||
SENT = 'SENT',
|
||||
DELIVERED = 'DELIVERED',
|
||||
RETURNED = 'RETURNED',
|
||||
}
|
||||
|
||||
@Object({
|
||||
universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05',
|
||||
nameSingular: 'postCard',
|
||||
namePlural: 'postCards',
|
||||
labelSingular: 'Post card',
|
||||
labelPlural: 'Post cards',
|
||||
description: ' A post card object',
|
||||
icon: 'IconMail',
|
||||
})
|
||||
export class PostCard {
|
||||
@Field({
|
||||
universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b',
|
||||
type: FieldType.TEXT,
|
||||
label: 'Content',
|
||||
description: "Postcard's content",
|
||||
icon: 'IconAbc',
|
||||
})
|
||||
content: string;
|
||||
|
||||
@Field({
|
||||
universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac',
|
||||
type: FieldType.FULL_NAME,
|
||||
label: 'Recipient name',
|
||||
icon: 'IconUser',
|
||||
})
|
||||
recipientName: FullNameField;
|
||||
|
||||
@Field({
|
||||
universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266',
|
||||
type: FieldType.ADDRESS,
|
||||
label: 'Recipient address',
|
||||
icon: 'IconHome',
|
||||
})
|
||||
recipientAddress: AddressField;
|
||||
|
||||
@Field({
|
||||
universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e',
|
||||
type: FieldType.SELECT,
|
||||
label: 'Status',
|
||||
icon: 'IconSend',
|
||||
defaultValue: `'${PostCardStatus.DRAFT}'`,
|
||||
options: [
|
||||
{ value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' },
|
||||
{ value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' },
|
||||
{ value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' },
|
||||
{ value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' },
|
||||
],
|
||||
})
|
||||
status: PostCardStatus;
|
||||
|
||||
@Relation({
|
||||
universalIdentifier: 'c9e2b4f4-b9ad-4427-9b42-9971b785edfe',
|
||||
type: RelationType.ONE_TO_MANY,
|
||||
label: 'Notes',
|
||||
icon: 'IconComment',
|
||||
inverseSideTargetUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.note,
|
||||
onDelete: OnDeleteAction.CASCADE,
|
||||
})
|
||||
notes: Note[];
|
||||
|
||||
@Field({
|
||||
universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433',
|
||||
type: FieldType.DATE_TIME,
|
||||
label: 'Delivered at',
|
||||
icon: 'IconCheck',
|
||||
isNullable: true,
|
||||
defaultValue: null,
|
||||
})
|
||||
deliveredAt?: Date;
|
||||
}
|
||||
```
|
||||
|
||||
Key points:
|
||||
|
||||
- The `@Object` decorator defines the object identity and labels used across the workspace; its `universalIdentifier` must be unique and stable across deployments.
|
||||
- Each `@Field` decorator defines a field on the object with a type, label, and its own stable `universalIdentifier`.
|
||||
- `@Relation` wires this object to other objects (standard or custom) and controls cascade behavior with `onDelete`.
|
||||
- You can scaffold new objects using `yarn create-entity`, which guides you through naming, fields, and relationships, then generates object files similar to the `postCard` example.
|
||||
|
||||
|
||||
### Application config (application.config.ts)
|
||||
|
||||
Every app has a single `application.config.ts` file that describes:
|
||||
|
||||
- **Who the app is**: identifiers, display name, and description.
|
||||
- **How its functions run**: which role they use for permissions.
|
||||
- **(Optional) variables**: key–value pairs exposed to your functions as environment variables.
|
||||
|
||||
When you scaffold a new app, you start with a minimal config:
|
||||
|
||||
```typescript
|
||||
import { type ApplicationConfig } from 'twenty-sdk';
|
||||
|
||||
const config: ApplicationConfig = {
|
||||
universalIdentifier: '<generated-app-uuid>',
|
||||
displayName: 'My Twenty App',
|
||||
description: 'My first Twenty app',
|
||||
functionRoleUniversalIdentifier: '<generated-role-uuid>',
|
||||
};
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
You can gradually extend this file as your app grows. For example, you can add an icon and application-scoped variables:
|
||||
|
||||
```typescript
|
||||
import { type ApplicationConfig } from 'twenty-sdk';
|
||||
|
||||
const config: ApplicationConfig = {
|
||||
universalIdentifier: '<your-app-uuid>',
|
||||
displayName: 'My App',
|
||||
description: 'What your app does',
|
||||
icon: 'IconWorld', // Choose an icon by name
|
||||
applicationVariables: {
|
||||
DEFAULT_RECIPIENT_NAME: {
|
||||
universalIdentifier: '<uuid>',
|
||||
description: 'Default recipient used by functions',
|
||||
value: 'Jane Doe',
|
||||
isSecret: false,
|
||||
},
|
||||
},
|
||||
functionRoleUniversalIdentifier: '<your-role-uuid>',
|
||||
};
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
Notes:
|
||||
- `universalIdentifier` fields are deterministic IDs you own; generate them once and keep them stable across syncs.
|
||||
- `applicationVariables` become environment variables for your functions (for example, `DEFAULT_RECIPIENT_NAME` is available as `process.env.DEFAULT_RECIPIENT_NAME`).
|
||||
- `functionRoleUniversalIdentifier` must match the role you define in `role.config.ts` (see below).
|
||||
|
||||
#### Roles and permissions
|
||||
|
||||
Applications can define roles that encapsulate permissions on your workspace’s objects and actions. The field `functionRoleUniversalIdentifier` in `application.config.ts` designates the default role used by your app’s serverless functions.
|
||||
|
||||
- The runtime API key injected as `TWENTY_API_KEY` is derived from this default function role.
|
||||
- The typed client will be restricted to the permissions granted to that role.
|
||||
- Follow least‑privilege: create a dedicated role with only the permissions your functions need, then reference its universal identifier.
|
||||
|
||||
##### Default function role (role.config.ts)
|
||||
|
||||
When you scaffold a new app, the CLI also creates `src/role.config.ts`. This file exports the default role your serverless functions will use at runtime:
|
||||
|
||||
```typescript
|
||||
import { PermissionFlag, type RoleConfig } from 'twenty-sdk';
|
||||
|
||||
export const functionRole: RoleConfig = {
|
||||
universalIdentifier: '<generated-role-uuid>',
|
||||
label: 'My Twenty App default function role',
|
||||
description: 'My Twenty App default function role',
|
||||
canReadAllObjectRecords: true,
|
||||
canUpdateAllObjectRecords: true,
|
||||
canSoftDeleteAllObjectRecords: true,
|
||||
canDestroyAllObjectRecords: false,
|
||||
};
|
||||
```
|
||||
|
||||
The `universalIdentifier` of this role is automatically wired into `application.config.ts` as `functionRoleUniversalIdentifier`. In other words:
|
||||
|
||||
- **role.config.ts** defines what the default function role can do.
|
||||
- **application.config.ts** points to that role so your functions inherit its permissions.
|
||||
|
||||
As you move beyond the initial scaffold, you should tighten this role and make it explicit about what it can access. A more production-ready role might look closer to:
|
||||
|
||||
```typescript
|
||||
import { PermissionFlag, type RoleConfig } from 'twenty-sdk';
|
||||
|
||||
export const functionRole: RoleConfig = {
|
||||
universalIdentifier: '<your-role-uuid>',
|
||||
label: 'Default function role',
|
||||
description: 'Default role for function Twenty client',
|
||||
canReadAllObjectRecords: false,
|
||||
canUpdateAllObjectRecords: false,
|
||||
canSoftDeleteAllObjectRecords: false,
|
||||
canDestroyAllObjectRecords: false,
|
||||
canUpdateAllSettings: false,
|
||||
canBeAssignedToAgents: false,
|
||||
canBeAssignedToUsers: false,
|
||||
canBeAssignedToApiKeys: false,
|
||||
objectPermissions: [
|
||||
{
|
||||
objectNameSingular: 'postCard',
|
||||
canReadObjectRecords: true,
|
||||
canUpdateObjectRecords: true,
|
||||
canSoftDeleteObjectRecords: false,
|
||||
canDestroyObjectRecords: false,
|
||||
},
|
||||
],
|
||||
fieldPermissions: [
|
||||
{
|
||||
objectNameSingular: 'postCard',
|
||||
fieldName: 'content',
|
||||
canReadFieldValue: false,
|
||||
canUpdateFieldValue: false,
|
||||
},
|
||||
],
|
||||
permissionFlags: ['APPLICATIONS'],
|
||||
};
|
||||
```
|
||||
|
||||
Notes:
|
||||
- Start from the scaffolded role, then progressively restrict it following least‑privilege.
|
||||
- Replace the `objectPermissions` and `fieldPermissions` with the objects/fields your functions need.
|
||||
- `permissionFlags` control access to platform-level capabilities. Keep them minimal; add only what you need.
|
||||
- See a working example in the Hello World app: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
|
||||
|
||||
### Serverless function config and entrypoint
|
||||
|
||||
Each function exports a main handler and a config describing its triggers. You can mix multiple trigger types.
|
||||
|
||||
```typescript
|
||||
// src/actions/create-new-post-card.ts
|
||||
import type {
|
||||
FunctionConfig,
|
||||
DatabaseEventPayload,
|
||||
ObjectRecordCreateEvent,
|
||||
CronPayload,
|
||||
} from 'twenty-sdk';
|
||||
import Twenty, { type Person } from '../generated';
|
||||
|
||||
// main handler can accept parameters from route, cron, or database events
|
||||
export const main = async (
|
||||
params:
|
||||
| { name?: string }
|
||||
| DatabaseEventPayload<ObjectRecordCreateEvent<Person>>
|
||||
| CronPayload,
|
||||
) => {
|
||||
const client = new Twenty(); // generated typed client
|
||||
const name = 'name' in params
|
||||
? params.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world'
|
||||
: 'Hello world';
|
||||
|
||||
const result = await client.mutation({
|
||||
createPostCard: {
|
||||
__args: { data: { name } },
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
export const config: FunctionConfig = {
|
||||
universalIdentifier: '<function-uuid>',
|
||||
name: 'create-new-post-card',
|
||||
timeoutSeconds: 2,
|
||||
triggers: [
|
||||
// Public HTTP route trigger '/s/post-card/create'
|
||||
{
|
||||
universalIdentifier: '<route-trigger-uuid>',
|
||||
type: 'route',
|
||||
path: '/post-card/create',
|
||||
httpMethod: 'GET',
|
||||
isAuthRequired: false,
|
||||
},
|
||||
// Cron trigger (CRON pattern)
|
||||
{
|
||||
universalIdentifier: '<cron-trigger-uuid>',
|
||||
type: 'cron',
|
||||
pattern: '0 0 1 1 *',
|
||||
},
|
||||
// Database event trigger
|
||||
{
|
||||
universalIdentifier: '<db-trigger-uuid>',
|
||||
type: 'databaseEvent',
|
||||
eventName: 'person.created',
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
Common trigger types:
|
||||
- route: Exposes your function on an HTTP path and method **under the `/s/` endpoint**:
|
||||
> e.g. `path: '/post-card/create',` -> call on `<APP_URL>/s/post-card/create`
|
||||
- cron: Runs your function on a schedule using a CRON expression.
|
||||
- databaseEvent: Runs on workspace object lifecycle events
|
||||
> e.g. `person.created`
|
||||
|
||||
You can create new functions in two ways:
|
||||
|
||||
- **Scaffolded**: Run `yarn create-entity --path <custom-path>` and choose the option to add a new function. This generates a starter file under `<custom-path>` with a `main` handler and a `config` block similar to the example above.
|
||||
- **Manual**: Create a new file and export `main` and `config` yourself, following the same pattern.
|
||||
|
||||
### Generated typed client
|
||||
|
||||
Run yarn generate to create a local typed client in generated/ based on your workspace schema. Use it in your functions:
|
||||
|
||||
```typescript
|
||||
import Twenty from './generated';
|
||||
|
||||
const client = new Twenty();
|
||||
const { me } = await client.query({ me: { id: true, displayName: true } });
|
||||
```
|
||||
|
||||
The client is re-generated by `yarn generate`. Re-run after changing your objects and `yarn sync` or when onboarding to a new workspace.
|
||||
|
||||
#### Runtime credentials in serverless functions
|
||||
|
||||
When your function runs on Twenty, the platform injects credentials as environment variables before your code executes:
|
||||
|
||||
- `TWENTY_API_URL`: Base URL of the Twenty API your app targets.
|
||||
- `TWENTY_API_KEY`: Short‑lived key scoped to your application’s default function role.
|
||||
|
||||
Notes:
|
||||
- You do not need to pass URL or API key to the generated client. It reads `TWENTY_API_URL` and `TWENTY_API_KEY` from process.env at runtime.
|
||||
- The API key’s permissions are determined by the role referenced in your `application.config.ts` via `functionRoleUniversalIdentifier`. This is the default role used by serverless functions of your application.
|
||||
- Applications can define roles to follow least‑privilege. Grant only the permissions your functions need, then point `functionRoleUniversalIdentifier` to that role’s universal identifier.
|
||||
|
||||
|
||||
### Hello World example
|
||||
|
||||
Explore a minimal, end-to-end example that demonstrates objects, functions, and multiple triggers [here](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
|
||||
|
||||
## Manual setup (without the scaffolder)
|
||||
|
||||
While we recommend using `create-twenty-app` for the best getting-started experience, you can also set up a project manually. Do not install the CLI globally. Instead, add `twenty-sdk` as a local dependency and wire scripts in your package.json:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn add -D twenty-sdk
|
||||
```
|
||||
|
||||
Then add scripts like these:
|
||||
|
||||
```json filename="package.json"
|
||||
{
|
||||
"scripts": {
|
||||
"auth": "twenty auth login",
|
||||
"generate": "twenty app generate",
|
||||
"dev": "twenty app dev",
|
||||
"sync": "twenty app sync",
|
||||
"uninstall": "twenty app uninstall",
|
||||
"logs": "twenty app logs",
|
||||
"create-entity": "twenty app add",
|
||||
"help": "twenty --help"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now you can run the same commands via Yarn, e.g. `yarn dev`, `yarn sync`, etc.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Authentication errors: run `yarn auth` and ensure your API key has the required permissions.
|
||||
- Cannot connect to server: verify the API URL and that the Twenty server is reachable.
|
||||
- Types or client missing/outdated: run `yarn generate` and then `yarn dev`.
|
||||
- Dev mode not syncing: ensure `yarn dev` is running and that changes are not ignored by your environment.
|
||||
|
||||
Discord Help Channel: https://discord.com/channels/1130383047699738754/1130386664812982322
|
||||
|
|
|
|||
|
|
@ -915,7 +915,6 @@ export type CreateRoleInput = {
|
|||
canAccessAllTools?: InputMaybe<Scalars['Boolean']>;
|
||||
canBeAssignedToAgents?: InputMaybe<Scalars['Boolean']>;
|
||||
canBeAssignedToApiKeys?: InputMaybe<Scalars['Boolean']>;
|
||||
canBeAssignedToApplications?: InputMaybe<Scalars['Boolean']>;
|
||||
canBeAssignedToUsers?: InputMaybe<Scalars['Boolean']>;
|
||||
canDestroyAllObjectRecords?: InputMaybe<Scalars['Boolean']>;
|
||||
canReadAllObjectRecords?: InputMaybe<Scalars['Boolean']>;
|
||||
|
|
@ -4424,7 +4423,6 @@ export type UpdateRolePayload = {
|
|||
canAccessAllTools?: InputMaybe<Scalars['Boolean']>;
|
||||
canBeAssignedToAgents?: InputMaybe<Scalars['Boolean']>;
|
||||
canBeAssignedToApiKeys?: InputMaybe<Scalars['Boolean']>;
|
||||
canBeAssignedToApplications?: InputMaybe<Scalars['Boolean']>;
|
||||
canBeAssignedToUsers?: InputMaybe<Scalars['Boolean']>;
|
||||
canDestroyAllObjectRecords?: InputMaybe<Scalars['Boolean']>;
|
||||
canReadAllObjectRecords?: InputMaybe<Scalars['Boolean']>;
|
||||
|
|
|
|||
|
|
@ -898,7 +898,6 @@ export type CreateRoleInput = {
|
|||
canAccessAllTools?: InputMaybe<Scalars['Boolean']>;
|
||||
canBeAssignedToAgents?: InputMaybe<Scalars['Boolean']>;
|
||||
canBeAssignedToApiKeys?: InputMaybe<Scalars['Boolean']>;
|
||||
canBeAssignedToApplications?: InputMaybe<Scalars['Boolean']>;
|
||||
canBeAssignedToUsers?: InputMaybe<Scalars['Boolean']>;
|
||||
canDestroyAllObjectRecords?: InputMaybe<Scalars['Boolean']>;
|
||||
canReadAllObjectRecords?: InputMaybe<Scalars['Boolean']>;
|
||||
|
|
@ -4253,7 +4252,6 @@ export type UpdateRolePayload = {
|
|||
canAccessAllTools?: InputMaybe<Scalars['Boolean']>;
|
||||
canBeAssignedToAgents?: InputMaybe<Scalars['Boolean']>;
|
||||
canBeAssignedToApiKeys?: InputMaybe<Scalars['Boolean']>;
|
||||
canBeAssignedToApplications?: InputMaybe<Scalars['Boolean']>;
|
||||
canBeAssignedToUsers?: InputMaybe<Scalars['Boolean']>;
|
||||
canDestroyAllObjectRecords?: InputMaybe<Scalars['Boolean']>;
|
||||
canReadAllObjectRecords?: InputMaybe<Scalars['Boolean']>;
|
||||
|
|
|
|||
|
|
@ -30,66 +30,152 @@ npm install twenty-sdk
|
|||
yarn add twenty-sdk
|
||||
```
|
||||
|
||||
## Getting started
|
||||
You can either scaffold a new app or add the SDK to an existing one.
|
||||
## Usage
|
||||
|
||||
- Start new (recommended):
|
||||
```bash
|
||||
npx create-twenty-app@latest my-twenty-app
|
||||
cd my-twenty-app
|
||||
```
|
||||
- Existing project: install the SDK as shown above, then use the CLI below.
|
||||
```
|
||||
Usage: twenty [options] [command]
|
||||
|
||||
CLI for Twenty application development
|
||||
|
||||
Options:
|
||||
--workspace <name> Use a specific workspace configuration (default: "default")
|
||||
-V, --version output the version number
|
||||
-h, --help display help for command
|
||||
|
||||
Commands:
|
||||
auth Authentication commands
|
||||
app Application development commands
|
||||
help [command] display help for command
|
||||
```
|
||||
|
||||
## Global Options
|
||||
|
||||
- `--workspace <name>`: Use a specific workspace configuration profile. Defaults to `default`. See Configuration for details.
|
||||
|
||||
## Commands
|
||||
|
||||
### Auth
|
||||
|
||||
Authenticate the CLI against your Twenty workspace.
|
||||
|
||||
- `twenty auth login` — Authenticate with Twenty.
|
||||
- Options:
|
||||
- `--api-key <key>`: API key for authentication.
|
||||
- `--api-url <url>`: Twenty API URL (defaults to your current profile's value or `http://localhost:3000`).
|
||||
- Behavior: Prompts for any missing values, persists them to the active workspace profile, and validates the credentials.
|
||||
|
||||
- `twenty auth logout` — Remove authentication credentials for the active workspace profile.
|
||||
|
||||
- `twenty auth status` — Print the current authentication status (API URL, masked API key, validity).
|
||||
|
||||
Examples:
|
||||
|
||||
## CLI quickstart
|
||||
```bash
|
||||
# Authenticate using your API key (CLI will prompt for it)
|
||||
# Login interactively (recommended)
|
||||
twenty auth login
|
||||
|
||||
# Add a new entity to your application (guided prompts)
|
||||
twenty app add
|
||||
# Provide values in flags
|
||||
twenty auth login --api-key $TWENTY_API_KEY --api-url https://api.twenty.com
|
||||
|
||||
# Generate a typed Twenty client and TypeScript definitions for your workspace entities
|
||||
twenty app generate
|
||||
# Login interactively for a specific workspace profile
|
||||
twenty auth login --workspace my-custom-workspace
|
||||
|
||||
# Start dev mode: automatically syncs changes to your workspace for instant testing
|
||||
# Check status
|
||||
twenty auth status
|
||||
|
||||
# Logout current profile
|
||||
twenty auth logout
|
||||
```
|
||||
|
||||
### App
|
||||
|
||||
Application development commands.
|
||||
|
||||
- `twenty app sync [appPath]` — One-time sync of the application to your Twenty workspace.
|
||||
- Behavior: Compute your application's manifest and send it to your workspace to sync your application
|
||||
|
||||
- `twenty app dev [appPath]` — Watch and sync local application changes.
|
||||
- Options:
|
||||
- `-d, --debounce <ms>`: Debounce delay in milliseconds (default: `1000`).
|
||||
- Behavior: Performs an initial sync, then watches the directory for changes and re-syncs after debounced edits. Press Ctrl+C to stop.
|
||||
|
||||
- `twenty app uninstall [appPath]` — Uninstall the application from the current workspace.
|
||||
- Note: `twenty app delete` exists as a hidden alias for backward compatibility.
|
||||
|
||||
- `twenty app add [entityType]` — Add a new entity to your application.
|
||||
- Arguments:
|
||||
- `entityType`: one of `function` or `object`. If omitted, an interactive prompt is shown.
|
||||
- Options:
|
||||
- `--path <path>`: The path where the entity file should be created (relative to the current directory).
|
||||
- Behavior:
|
||||
- `object`: prompts for singular/plural names and labels, then creates a new object definition file.
|
||||
- `function`: prompts for a name and scaffolds a serverless function file.
|
||||
|
||||
- `twenty app generate [appPath]` — Generate the typed Twenty client for your application.
|
||||
|
||||
- `twenty app logs [appPath]` — Stream application function logs.
|
||||
- Options:
|
||||
- `-u, --functionUniversalIdentifier <id>`: Only show logs for a specific function universal ID.
|
||||
- `-n, --functionName <name>`: Only show logs for a specific function name.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
# Start dev mode with default debounce
|
||||
twenty app dev
|
||||
|
||||
# One‑time sync of local changes
|
||||
# Start dev mode with custom workspace profile
|
||||
twenty app dev --workspace my-custom-workspace
|
||||
|
||||
# Dev mode with custom debounce
|
||||
twenty app dev --debounce 1500
|
||||
|
||||
# One-time sync of the current directory
|
||||
twenty app sync
|
||||
|
||||
# Uninstall the application from the current workspace
|
||||
twenty app uninstall
|
||||
# Add a new object interactively
|
||||
twenty app add
|
||||
|
||||
# Generate client types
|
||||
twenty app generate
|
||||
|
||||
# Watch all function logs
|
||||
twenty app logs
|
||||
|
||||
# Watch logs for a specific function by name
|
||||
twenty app logs -n my-function
|
||||
```
|
||||
|
||||
## Usage (SDK)
|
||||
```typescript
|
||||
// Example: import what you need from the SDK
|
||||
import { /* your exports */ } from 'twenty-sdk';
|
||||
## Configuration
|
||||
|
||||
The CLI stores configuration per user in a JSON file:
|
||||
|
||||
- Location: `~/.twenty/config.json`
|
||||
- Structure: Profiles keyed by workspace name. The active profile is selected with `--workspace <name>`.
|
||||
|
||||
Example configuration file:
|
||||
|
||||
```json
|
||||
{
|
||||
"profiles": {
|
||||
"default": {
|
||||
"apiUrl": "http://localhost:3000",
|
||||
"apiKey": "<your-api-key>"
|
||||
},
|
||||
"prod": {
|
||||
"apiUrl": "https://api.twenty.com",
|
||||
"apiKey": "<your-api-key>"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Publish your application
|
||||
Applications are currently stored in [`twenty/packages/twenty-apps`](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps).
|
||||
Notes:
|
||||
|
||||
You can share your application with all Twenty users:
|
||||
- If a profile is missing, `apiUrl` defaults to `http://localhost:3000` until set.
|
||||
- `twenty auth login` writes the `apiUrl` and `apiKey` for the default profile.
|
||||
- `twenty auth login --workspace custom-workspace` writes the `apiUrl` and `apiKey` for a custom `custom-workspace` profile.
|
||||
|
||||
```bash
|
||||
# pull the Twenty project
|
||||
git clone https://github.com/twentyhq/twenty.git
|
||||
cd twenty
|
||||
|
||||
# create a new branch
|
||||
git checkout -b feature/my-awesome-app
|
||||
```
|
||||
|
||||
- Copy your app folder into `twenty/packages/twenty-apps`.
|
||||
- Commit your changes and open a pull request on https://github.com/twentyhq/twenty
|
||||
|
||||
```bash
|
||||
git commit -m "Add new application"
|
||||
git push
|
||||
```
|
||||
|
||||
Our team reviews contributions for quality, security, and reusability.
|
||||
|
||||
## Troubleshooting
|
||||
- Auth errors: run `twenty auth login` again and ensure the API key has the required permissions.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "twenty-sdk",
|
||||
"version": "0.2.3",
|
||||
"version": "0.2.4",
|
||||
"main": "dist/index.cjs",
|
||||
"module": "dist/index.mjs",
|
||||
"types": "dist/index.d.ts",
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ export class AppCommand {
|
|||
});
|
||||
|
||||
appCommand
|
||||
.command('generate [outputPath]')
|
||||
.command('generate [appPath]')
|
||||
.description('Generate Twenty client')
|
||||
.action(async (appPath?: string) => {
|
||||
await this.generateCommand.execute(formatPath(appPath));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
import { type MigrationInterface, type QueryRunner } from 'typeorm';
|
||||
|
||||
export class RemoveCanBeAssignedToApplications1766077618558
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'RemoveCanBeAssignedToApplications1766077618558';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."role" DROP COLUMN "canBeAssignedToApplications"`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."role" ADD "canBeAssignedToApplications" boolean NOT NULL DEFAULT true`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -13,5 +13,4 @@ export const FLAT_ROLE_EDITABLE_PROPERTIES: (keyof FlatRole)[] = [
|
|||
'canBeAssignedToUsers',
|
||||
'canBeAssignedToAgents',
|
||||
'canBeAssignedToApiKeys',
|
||||
'canBeAssignedToApplications',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -44,8 +44,6 @@ export const fromCreateRoleInputToFlatRoleToCreate = ({
|
|||
canBeAssignedToUsers: createRoleInput.canBeAssignedToUsers ?? true,
|
||||
canBeAssignedToAgents: createRoleInput.canBeAssignedToAgents ?? true,
|
||||
canBeAssignedToApiKeys: createRoleInput.canBeAssignedToApiKeys ?? true,
|
||||
canBeAssignedToApplications:
|
||||
createRoleInput.canBeAssignedToApplications ?? true,
|
||||
isEditable: true,
|
||||
workspaceId,
|
||||
createdAt: now,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ export const fromRoleEntityToFlatRole = (role: RoleEntity): FlatRole => {
|
|||
canBeAssignedToUsers: role.canBeAssignedToUsers,
|
||||
canBeAssignedToAgents: role.canBeAssignedToAgents,
|
||||
canBeAssignedToApiKeys: role.canBeAssignedToApiKeys,
|
||||
canBeAssignedToApplications: role.canBeAssignedToApplications,
|
||||
workspaceId: role.workspaceId,
|
||||
createdAt: role.createdAt.toISOString(),
|
||||
updatedAt: role.updatedAt.toISOString(),
|
||||
|
|
|
|||
|
|
@ -70,9 +70,4 @@ export class CreateRoleInput {
|
|||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
canBeAssignedToApiKeys?: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
canBeAssignedToApplications?: boolean;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,11 +71,6 @@ export class UpdateRolePayload {
|
|||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
canBeAssignedToApiKeys?: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
canBeAssignedToApplications?: boolean;
|
||||
}
|
||||
|
||||
@InputType()
|
||||
|
|
|
|||
|
|
@ -73,9 +73,6 @@ export class RoleEntity extends SyncableEntity implements Required<RoleEntity> {
|
|||
@Column({ nullable: false, default: true })
|
||||
canBeAssignedToApiKeys: boolean;
|
||||
|
||||
@Column({ nullable: false, default: true })
|
||||
canBeAssignedToApplications: boolean;
|
||||
|
||||
@OneToMany(
|
||||
() => RoleTargetEntity,
|
||||
(roleTargets: RoleTargetEntity) => roleTargets.role,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ export const STANDARD_FLAT_ROLE_METADATA_BUILDERS_BY_ROLE_NAME = {
|
|||
canBeAssignedToUsers: true,
|
||||
canBeAssignedToAgents: false,
|
||||
canBeAssignedToApiKeys: true,
|
||||
canBeAssignedToApplications: false,
|
||||
},
|
||||
}),
|
||||
} satisfies {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ export type CreateStandardRoleContext = {
|
|||
canBeAssignedToUsers: boolean;
|
||||
canBeAssignedToAgents: boolean;
|
||||
canBeAssignedToApiKeys: boolean;
|
||||
canBeAssignedToApplications: boolean;
|
||||
};
|
||||
|
||||
export type CreateStandardRoleArgs = StandardBuilderArgs<'role'> & {
|
||||
|
|
@ -43,7 +42,6 @@ export const createStandardRoleFlatMetadata = ({
|
|||
canBeAssignedToUsers,
|
||||
canBeAssignedToAgents,
|
||||
canBeAssignedToApiKeys,
|
||||
canBeAssignedToApplications,
|
||||
},
|
||||
workspaceId,
|
||||
twentyStandardApplicationId,
|
||||
|
|
@ -68,7 +66,6 @@ export const createStandardRoleFlatMetadata = ({
|
|||
canBeAssignedToUsers,
|
||||
canBeAssignedToAgents,
|
||||
canBeAssignedToApiKeys,
|
||||
canBeAssignedToApplications,
|
||||
workspaceId,
|
||||
applicationId: twentyStandardApplicationId,
|
||||
createdAt: now,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,5 @@ export const ADMIN_ROLE: StandardRoleDefinition = {
|
|||
canBeAssignedToUsers: true,
|
||||
canBeAssignedToAgents: false,
|
||||
canBeAssignedToApiKeys: true,
|
||||
canBeAssignedToApplications: false,
|
||||
applicationId: null, // TODO: Replace with Twenty application ID
|
||||
};
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@ export type RoleManifest = SyncableEntityOptions & {
|
|||
canBeAssignedToUsers?: boolean;
|
||||
canBeAssignedToAgents?: boolean;
|
||||
canBeAssignedToApiKeys?: boolean;
|
||||
canBeAssignedToApplications?: boolean;
|
||||
objectPermissions?: ObjectPermission[];
|
||||
fieldPermissions?: FieldPermission[];
|
||||
permissionFlags?: PermissionFlagType[];
|
||||
|
|
|
|||
Loading…
Reference in a new issue