snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 2s

This commit is contained in:
upstream-mirror
2026-09-17 23:28:13 +05:00
commit e7da97194a
32688 changed files with 4208681 additions and 0 deletions

View File

@@ -0,0 +1,640 @@
import { normalizePageLayoutTabManifest } from '../normalizePageLayoutTabManifest';
import {
type PageLayoutTabManifest,
type PageLayoutWidgetManifest,
} from '../pageLayoutManifestType';
import {
PageLayoutTabLayoutMode,
PageLayoutType,
PageLayoutWidgetVerticalListHeightBehavior,
} from '../../types';
import { DEFAULT_WIDGET_SIZE } from '../../constants';
const widget: PageLayoutWidgetManifest = {
universalIdentifier: 'widget',
title: 'App',
type: 'FRONT_COMPONENT',
configuration: {
configurationType: 'FRONT_COMPONENT',
frontComponentUniversalIdentifier: 'component',
},
};
const tab: PageLayoutTabManifest = {
universalIdentifier: 'tab',
title: 'Details',
position: 0,
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
widgets: [widget],
};
describe('normalizePageLayoutTabManifest', () => {
describe.each(['UNSUPPORTED', '', 42, {}])(
'with invalid layoutMode %s in JSON input',
(layoutMode) => {
it.each([{ widgets: [] }, { widgets: [widget] }])(
'returns a validation error with widgets $widgets',
({ widgets }) => {
const pageLayoutTabManifest = JSON.parse(
JSON.stringify({ ...tab, layoutMode, widgets }),
);
expect(
normalizePageLayoutTabManifest({
pageLayoutTabManifest,
pageLayoutType: undefined,
}),
).toEqual({
status: 'fail',
errors: [
`Page layout tab "Details" defines unsupported layoutMode "${layoutMode}". Expected GRID, VERTICAL_LIST or CANVAS.`,
],
});
},
);
},
);
it('derives omitted positions and preserves explicit indices without changing its input', () => {
const input: PageLayoutTabManifest = {
...tab,
widgets: [
{ ...widget, heightBehavior: 'FIT_CONTENT' as const },
{
...widget,
universalIdentifier: 'second-widget',
heightBehavior: 'TAB_VIEWPORT' as const,
position: {
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
index: 99,
heightBehavior:
PageLayoutWidgetVerticalListHeightBehavior.FIT_CONTENT,
},
},
],
};
const original = JSON.stringify(input);
const result = normalizePageLayoutTabManifest({
pageLayoutTabManifest: input,
pageLayoutType: 'RECORD_PAGE',
});
expect(result).toEqual({
status: 'success',
pageLayoutTab: {
...tab,
widgets: [
{
...widget,
position: {
layoutMode: 'VERTICAL_LIST',
index: 0,
heightBehavior: 'FIT_CONTENT',
},
},
{
...widget,
universalIdentifier: 'second-widget',
position: {
layoutMode: 'VERTICAL_LIST',
index: 99,
heightBehavior: 'TAB_VIEWPORT',
},
},
],
},
});
expect(JSON.stringify(input)).toBe(original);
});
it('preserves a legacy nested height behavior and index', () => {
const result = normalizePageLayoutTabManifest({
pageLayoutTabManifest: {
...tab,
widgets: [
{
...widget,
position: {
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
index: 99,
heightBehavior:
PageLayoutWidgetVerticalListHeightBehavior.TAB_VIEWPORT,
},
},
],
},
pageLayoutType: undefined,
});
expect(result).toMatchObject({
status: 'success',
pageLayoutTab: {
widgets: [
{
position: {
layoutMode: 'VERTICAL_LIST',
index: 99,
heightBehavior: 'TAB_VIEWPORT',
},
},
],
},
});
});
it('uses explicit indices rather than array order for legacy viewport placement', () => {
const pageLayoutTabManifest: PageLayoutTabManifest = {
...tab,
widgets: [
{
...widget,
position: {
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
index: 99,
heightBehavior:
PageLayoutWidgetVerticalListHeightBehavior.TAB_VIEWPORT,
},
},
{
...widget,
universalIdentifier: 'second-widget',
position: {
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
index: 2,
},
},
],
};
expect(
normalizePageLayoutTabManifest({
pageLayoutTabManifest,
pageLayoutType: undefined,
}),
).toEqual({
status: 'success',
pageLayoutTab: pageLayoutTabManifest,
});
});
it('keeps omitted height behavior implicit', () => {
expect(
normalizePageLayoutTabManifest({
pageLayoutTabManifest: tab,
pageLayoutType: undefined,
}),
).toEqual({
status: 'success',
pageLayoutTab: {
...tab,
widgets: [
{ ...widget, position: { layoutMode: 'VERTICAL_LIST', index: 0 } },
],
},
});
});
describe.each<{ name: string; viewport: Partial<PageLayoutWidgetManifest> }>([
{ name: 'explicit height', viewport: { heightBehavior: 'TAB_VIEWPORT' } },
{
name: 'legacy nested height',
viewport: {
position: {
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
index: 0,
heightBehavior:
PageLayoutWidgetVerticalListHeightBehavior.TAB_VIEWPORT,
},
},
},
{
name: 'widget type default',
viewport: {
type: 'TIMELINE',
configuration: { configurationType: 'TIMELINE' },
},
},
])('viewport constraints with $name', ({ viewport }) => {
it('rejects a fit-content sibling with the same explicit index', () => {
expect(
normalizePageLayoutTabManifest({
pageLayoutTabManifest: {
...tab,
widgets: [
{
...widget,
universalIdentifier: 'fit-content-widget',
position: {
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
index: 0,
},
},
{
...widget,
...viewport,
position: {
...viewport.position,
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
index: 0,
},
},
],
},
pageLayoutType: undefined,
}),
).toEqual({
status: 'fail',
errors: [
'Page layout tab "Details" must place its TAB_VIEWPORT widget last.',
],
});
});
it.each([
{ duplicate: true, error: 'can contain only one TAB_VIEWPORT widget' },
{ duplicate: false, error: 'must place its TAB_VIEWPORT widget last' },
])('rejects a layout that $error', ({ duplicate, error }) => {
expect(
normalizePageLayoutTabManifest({
pageLayoutTabManifest: {
...tab,
widgets: [
{ ...widget, ...viewport },
{
...widget,
...(duplicate ? viewport : {}),
universalIdentifier: 'second-widget',
},
],
},
pageLayoutType: undefined,
}),
).toEqual({
status: 'fail',
errors: [`Page layout tab "Details" ${error}.`],
});
});
});
it('accepts an explicit fit-content override followed by a default viewport widget', () => {
expect(
normalizePageLayoutTabManifest({
pageLayoutTabManifest: {
...tab,
widgets: [
{
...widget,
type: 'TIMELINE',
configuration: { configurationType: 'TIMELINE' },
heightBehavior: 'FIT_CONTENT',
},
{
...widget,
universalIdentifier: 'second-widget',
type: 'TIMELINE',
configuration: { configurationType: 'TIMELINE' },
},
],
},
pageLayoutType: undefined,
}),
).toMatchObject({ status: 'success' });
});
it.each([PageLayoutTabLayoutMode.GRID, PageLayoutTabLayoutMode.CANVAS])(
'rejects a vertical-list position in an authored %s tab',
(layoutMode) => {
expect(
normalizePageLayoutTabManifest({
pageLayoutTabManifest: {
...tab,
layoutMode,
widgets: [
{
...widget,
position: {
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
index: 0,
},
},
{ ...widget, universalIdentifier: 'second-widget' },
],
},
pageLayoutType: undefined,
}),
).toEqual({
status: 'fail',
errors: [
`Page layout widget "App" uses a VERTICAL_LIST position, but its parent tab "Details" uses ${layoutMode}.`,
],
});
},
);
it('rejects a conflicting grid position instead of changing its layout', () => {
expect(
normalizePageLayoutTabManifest({
pageLayoutTabManifest: {
...tab,
widgets: [
{
...widget,
position: {
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 0,
rowSpan: 1,
columnSpan: 12,
},
},
],
},
pageLayoutType: undefined,
}),
).toMatchObject({
status: 'fail',
errors: [
'Page layout widget "App" uses a GRID position, but its parent tab "Details" uses VERTICAL_LIST.',
],
});
});
it.each([
undefined,
PageLayoutType.DASHBOARD,
PageLayoutType.RECORD_PAGE,
PageLayoutType.STANDALONE_PAGE,
])('resolves omitted layout mode for page type %s', (pageLayoutType) => {
const result = normalizePageLayoutTabManifest({
pageLayoutTabManifest: {
...tab,
layoutMode: undefined,
widgets: undefined,
},
pageLayoutType,
});
expect(result).toMatchObject({
status: 'success',
pageLayoutTab: {
layoutMode:
pageLayoutType === PageLayoutType.STANDALONE_PAGE
? 'VERTICAL_LIST'
: 'GRID',
widgets: [],
},
});
});
it('keeps an explicit grid mode and supplies its default position', () => {
expect(
normalizePageLayoutTabManifest({
pageLayoutTabManifest: {
...tab,
layoutMode: PageLayoutTabLayoutMode.GRID,
},
pageLayoutType: 'STANDALONE_PAGE',
}),
).toMatchObject({
status: 'success',
pageLayoutTab: {
layoutMode: 'GRID',
widgets: [
{
position: {
layoutMode: 'GRID',
row: 0,
column: 0,
rowSpan: DEFAULT_WIDGET_SIZE.default.h,
columnSpan: DEFAULT_WIDGET_SIZE.default.w,
},
},
],
},
});
});
it.each(['position', 'gridPosition'])(
'preserves legacy grid coordinates in %s',
(positionKey) => {
const gridPosition = { row: 2, column: 6, rowSpan: 4, columnSpan: 6 };
const result = normalizePageLayoutTabManifest({
pageLayoutTabManifest: {
...tab,
layoutMode: PageLayoutTabLayoutMode.GRID,
widgets: [
{
...widget,
[positionKey]:
positionKey === 'position'
? {
layoutMode: PageLayoutTabLayoutMode.GRID,
...gridPosition,
}
: gridPosition,
},
],
},
pageLayoutType: undefined,
});
expect(result).toEqual({
status: 'success',
pageLayoutTab: {
...tab,
layoutMode: 'GRID',
widgets: [
{ ...widget, position: { layoutMode: 'GRID', ...gridPosition } },
],
},
});
},
);
it.each<PageLayoutWidgetManifest['position']>([
{
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 1,
column: 2,
rowSpan: 3,
columnSpan: 4,
},
{ layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST, index: 0 },
])(
'rejects a conflicting position %j on a single-widget Canvas tab',
(position) => {
expect(
normalizePageLayoutTabManifest({
pageLayoutTabManifest: {
...tab,
layoutMode: PageLayoutTabLayoutMode.CANVAS,
widgets: [{ ...widget, position }],
},
pageLayoutType: undefined,
}),
).toEqual({
status: 'fail',
errors: [
`Page layout widget "App" uses a ${position?.layoutMode} position, but its parent tab "Details" uses CANVAS.`,
],
});
},
);
it.each<PageLayoutWidgetManifest['position']>([
undefined,
{ layoutMode: PageLayoutTabLayoutMode.CANVAS },
])('preserves single-widget Canvas with position %s', (position) => {
const result = normalizePageLayoutTabManifest({
pageLayoutTabManifest: {
...tab,
layoutMode: PageLayoutTabLayoutMode.CANVAS,
widgets: [{ ...widget, position }],
},
pageLayoutType: undefined,
});
expect(result).toMatchObject({
status: 'success',
pageLayoutTab: {
layoutMode: 'CANVAS',
widgets: [
{
position: {
layoutMode: 'CANVAS',
},
},
],
},
});
});
it.each([0, 2])(
'keeps a Canvas tab with %i widgets unchanged',
(widgetCount) => {
const result = normalizePageLayoutTabManifest({
pageLayoutTabManifest: {
...tab,
layoutMode: PageLayoutTabLayoutMode.CANVAS,
widgets: Array.from({ length: widgetCount }, (_, index) => ({
...widget,
universalIdentifier: `widget-${index}`,
})),
},
pageLayoutType: undefined,
});
expect(result).toMatchObject({
status: 'success',
pageLayoutTab: {
layoutMode: 'CANVAS',
widgets: Array.from({ length: widgetCount }, () => ({
position: { layoutMode: 'CANVAS' },
})),
},
});
},
);
it.each([PageLayoutTabLayoutMode.GRID, PageLayoutTabLayoutMode.CANVAS])(
'rejects heightBehavior on authored %s before normalization',
(layoutMode) => {
expect(
normalizePageLayoutTabManifest({
pageLayoutTabManifest: {
...tab,
layoutMode,
widgets: [{ ...widget, heightBehavior: 'TAB_VIEWPORT' }],
},
pageLayoutType: undefined,
}),
).toEqual({
status: 'fail',
errors: [
`Page layout widget "App" defines heightBehavior, but its parent tab "Details" uses ${layoutMode}. heightBehavior is only supported for VERTICAL_LIST tabs.`,
],
});
},
);
it.each(['TAB_VIEPORT', '', 42, {}])(
'rejects invalid heightBehavior %s in JSON input',
(heightBehavior) => {
const pageLayoutTabManifest = JSON.parse(
JSON.stringify({ ...tab, widgets: [{ ...widget, heightBehavior }] }),
);
expect(
normalizePageLayoutTabManifest({
pageLayoutTabManifest,
pageLayoutType: undefined,
}),
).toEqual({
status: 'fail',
errors: [
`Page layout widget "App" defines unsupported heightBehavior "${heightBehavior}". Expected FIT_CONTENT or TAB_VIEWPORT.`,
],
});
},
);
it.each([
{
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 0,
rowSpan: 1,
columnSpan: 1,
},
{ layoutMode: PageLayoutTabLayoutMode.CANVAS },
])(
'rejects nested heightBehavior in $layoutMode JSON positions',
(position) => {
const pageLayoutTabManifest = JSON.parse(
JSON.stringify({
...tab,
layoutMode: position.layoutMode,
widgets: [
{
...widget,
position: { ...position, heightBehavior: 'TAB_VIEWPORT' },
},
],
}),
);
expect(
normalizePageLayoutTabManifest({
pageLayoutTabManifest,
pageLayoutType: undefined,
}),
).toEqual({
status: 'fail',
errors: [
`Page layout widget "App" defines heightBehavior, but its parent tab "Details" uses ${position.layoutMode}. heightBehavior is only supported for VERTICAL_LIST tabs.`,
],
});
},
);
it('rejects an invalid nested height behavior', () => {
const pageLayoutTabManifest = JSON.parse(
JSON.stringify({
...tab,
widgets: [
{
...widget,
position: {
layoutMode: 'VERTICAL_LIST',
index: 0,
heightBehavior: 'INVALID',
},
},
],
}),
);
expect(
normalizePageLayoutTabManifest({
pageLayoutTabManifest,
pageLayoutType: undefined,
}),
).toMatchObject({
status: 'fail',
errors: [expect.stringContaining('unsupported heightBehavior "INVALID"')],
});
});
});

View File

@@ -0,0 +1,13 @@
import { type AgentResponseFormat } from '@/ai/types/agent-response-format.type';
import { type SyncableEntityOptions } from '@/application/syncableEntityOptionsType';
export type AgentManifest = SyncableEntityOptions & {
name: string;
label: string;
icon?: string;
description?: string;
prompt: string;
modelId?: string;
responseFormat?: AgentResponseFormat;
roleUniversalIdentifier?: string;
};

View File

@@ -0,0 +1,46 @@
// Wire shape exchanged between an app's logic-function runtime and the
// `/apps/connections/list` and `/apps/connections/get` endpoints.
//
// Lives in twenty-shared so both sides — the SDK helpers (`listConnections`,
// `getConnection`) and the server controller / DTO — pick from one source
// of truth. Adding a field on either side without updating this type will
// fail typecheck.
export type AppConnection = {
id: string;
// The app-developer-facing provider name (e.g. "linear"), as declared on
// `defineConnectionProvider({ name })`. Useful when listing across providers
// without a filter.
providerName: string;
// User-given (or auto-derived from the OAuth handle) display label.
// Falls back to `handle` when the user never set one. Suitable for logs
// and end-user UI.
name: string;
// OAuth-derived identifier (typically email or login). Stays stable across
// reconnects of the same upstream account.
handle: string;
// Connection-row visibility:
// 'user' = visible only to the user who created it.
// 'workspace' = visible to every user in the workspace.
// Named `visibility` (not `scope`) to disambiguate from the `scopes`
// array below, which is the unrelated set of OAuth permissions granted
// by the upstream provider.
visibility: 'user' | 'workspace';
// The userWorkspace that originally created the credential (also the owner
// for `scope: 'user'` credentials). Match against `event.userWorkspaceId`
// to resolve the request user's connection.
userWorkspaceId: string;
// The workspace member who created the credential. null when the creating
// user is no longer a member of the workspace.
workspaceMemberId: string | null;
accessToken: string;
// OAuth scopes actually granted by the upstream provider on the most recent
// token issuance (may be a subset of what the app requested).
scopes: string[];
// Set when the most recent refresh attempt failed permanently
// (4xx invalid_grant), or when the app reported the credential dead via
// `reportConnectionAuthFailure`; the user must reconnect from the app's
// settings tab. Apps should surface this so users know to take action.
authFailedAt: string | null;
// Human-readable explanation for `authFailedAt`, when the reporter gave one.
authFailedReason: string | null;
};

View File

@@ -0,0 +1 @@
export type AppKeyValueScope = 'WORKSPACE' | 'SERVER';

View File

@@ -0,0 +1,7 @@
import { type AppKeyValueScope } from '@/application/appKeyValueScopeType';
export type AppKeyValue = {
key: string;
value: unknown;
scope: AppKeyValueScope;
};

View File

@@ -0,0 +1,29 @@
import { type MessageChannelVisibility } from '../types/MessageChannelVisibility';
// One messaging channel an app owns, bound to one of its connections.
// Returned from `listMessageChannels` and `createMessageChannel`.
//
// The shape lives in twenty-shared so this and the server-side
// `MessageChannelDTO` projection always agree.
export type AppMessageChannel = {
id: string;
// The account's identity on the provider, as the provider names it (a
// LinkedIn member URN, a WhatsApp number). Opaque to Twenty: it is what
// the app matches inbound payloads against, and what participant handles
// on this channel are compared to when deciding message direction.
handle: string;
// End-user label for the channel. Falls back to `handle` in the UI when null.
displayName: string | null;
// How much of a message on this channel other workspace members may read:
// 'METADATA' = participants and dates only.
// 'SUBJECT' = the above plus the subject.
// 'SHARE_EVERYTHING' = the above plus the body.
// The owner of the underlying connection always reads their own messages
// in full regardless of this value.
visibility: MessageChannelVisibility;
// The app connection whose credential this channel speaks through.
connectedAccountId: string;
// False pauses the channel without deleting it: ingestion is rejected
// while it is off, and existing messages stay readable.
isSyncEnabled: boolean;
};

View File

@@ -0,0 +1,73 @@
import { type BillableOperations } from '@/application/billableOperationsType';
export const RECURRING_CHARGE_PERIODS = ['MONTH'] as const;
export type RecurringChargePeriod = (typeof RECURRING_CHARGE_PERIODS)[number];
export const isRecurringChargePeriod = (
value: unknown,
): value is RecurringChargePeriod =>
RECURRING_CHARGE_PERIODS.includes(value as RecurringChargePeriod);
// Omitted for a flat fee. WORKSPACE_MEMBER multiplies the amount by the number
// of members in the workspace at the moment the period is charged.
export const RECURRING_CHARGE_UNITS = ['WORKSPACE_MEMBER'] as const;
export type RecurringChargeUnit = (typeof RECURRING_CHARGE_UNITS)[number];
export const isRecurringChargeUnit = (
value: unknown,
): value is RecurringChargeUnit =>
RECURRING_CHARGE_UNITS.includes(value as RecurringChargeUnit);
export type RecurringCharge = {
period: RecurringChargePeriod;
amountMicroCredits: number;
per?: RecurringChargeUnit;
label: string;
};
// 1 USD = 1_000_000 micro-credits. Unlike a chargeCredits call, which the app
// makes and the server validates per request, a recurring charge is raised by
// the platform from a jsonb column, so these bound what the platform is willing
// to debit on an app's behalf. PER_UNIT bounds the declared rate, which a
// WORKSPACE_MEMBER charge multiplies by the member count, and PER_PERIOD bounds
// the resulting total.
export const MAX_RECURRING_CHARGE_MICRO_CREDITS_PER_UNIT = 100_000_000;
export const MAX_RECURRING_CHARGE_MICRO_CREDITS_PER_PERIOD = 1_000_000_000;
export const isRecurringChargeAmount = (value: unknown): value is number =>
typeof value === 'number' &&
Number.isSafeInteger(value) &&
value > 0 &&
value <= MAX_RECURRING_CHARGE_MICRO_CREDITS_PER_UNIT;
// The declaration reaches the raise path as untrusted jsonb, so its shape is
// checked at runtime rather than trusted from the RecurringCharge type.
export const isRecurringCharge = (value: unknown): value is RecurringCharge => {
if (typeof value !== 'object' || value === null) {
return false;
}
const { period, amountMicroCredits, per, label } =
value as Partial<RecurringCharge>;
return (
isRecurringChargePeriod(period) &&
isRecurringChargeAmount(amountMicroCredits) &&
(per === undefined || isRecurringChargeUnit(per)) &&
typeof label === 'string' &&
label.trim().length > 0
);
};
// Keyed by a name the application chooses. Unlike operations, the platform
// raises these itself once per billing period, so the app declares the amount
// rather than calling chargeCredits.
export type RecurringCharges = Partial<Record<string, RecurringCharge>>;
export type ApplicationBilling = {
description?: string;
recurring?: RecurringCharges;
operations?: BillableOperations;
};

View File

@@ -0,0 +1,20 @@
export const APPLICATION_CATEGORIES = [
'Communication',
'Productivity',
'Product management',
'Sales',
'Marketing',
'Enrichment',
'Data',
'Search',
'Other',
] as const;
export type KnownApplicationCategory = (typeof APPLICATION_CATEGORIES)[number];
export type ApplicationCategory = KnownApplicationCategory | (string & {});
export const isKnownApplicationCategory = (
category: string,
): category is KnownApplicationCategory =>
(APPLICATION_CATEGORIES as readonly string[]).includes(category);

View File

@@ -0,0 +1,49 @@
import { type PostInstallLogicFunctionApplicationManifest } from '@/application/postInstallLogicFunctionApplicationType';
import { type PreInstallLogicFunctionApplicationManifest } from '@/application/preInstallLogicFunctionApplicationType';
import { type SettingsFrontComponentApplicationManifest } from '@/application/settingsFrontComponentApplicationType';
import { type UninstallLogicFunctionApplicationManifest } from '@/application/uninstallLogicFunctionApplicationType';
import { type FrontComponentSharedDependenciesManifest } from '@/application/frontComponentSharedDependenciesManifestType';
import { type ApplicationBilling } from './applicationBillingType';
import { type ApplicationCategory } from './applicationCategoryType';
import { type ApplicationVariables } from './applicationVariablesType';
import { type ServerVariables } from './server-variables.type';
import { type SyncableEntityOptions } from './syncableEntityOptionsType';
export type ApplicationManifest = SyncableEntityOptions & {
defaultRoleUniversalIdentifier: string;
displayName: string;
description: string;
applicationVariables?: ApplicationVariables;
billing?: ApplicationBilling;
serverVariables?: ServerVariables;
author?: string;
category?: ApplicationCategory;
/**
* @deprecated Use `logo` instead.
*/
logoUrl?: string;
logo?: string;
/**
* @deprecated Use `galleryImages` instead.
*/
screenshots?: string[];
galleryImages?: string[];
aboutDescription?: string;
websiteUrl?: string;
termsUrl?: string;
emailSupport?: string;
issueReportUrl?: string;
postInstallLogicFunction?: PostInstallLogicFunctionApplicationManifest;
preInstallLogicFunction?: PreInstallLogicFunctionApplicationManifest;
uninstallLogicFunction?: UninstallLogicFunctionApplicationManifest;
settingsFrontComponent?: SettingsFrontComponentApplicationManifest;
frontComponentSharedDependencies?: FrontComponentSharedDependenciesManifest;
/**
* @deprecated Use `defineSettingsFrontComponent()` (exposed on the manifest
* as `settingsFrontComponent`) instead. This property is ignored.
*/
settingsCustomTabFrontComponentUniversalIdentifier?: string;
packageJsonChecksum: string | null;
yarnLockChecksum: string | null;
requiredServerVersionRange?: string | null;
};

View File

@@ -0,0 +1,58 @@
import { type SyncableEntityOptions } from '@/application/syncableEntityOptionsType';
import { FieldMetadataType } from '@/types/FieldMetadataType';
export const APPLICATION_VARIABLE_FIELD_METADATA_TYPES = [
FieldMetadataType.TEXT,
FieldMetadataType.ARRAY,
FieldMetadataType.BOOLEAN,
FieldMetadataType.DATE,
FieldMetadataType.DATE_TIME,
FieldMetadataType.NUMBER,
FieldMetadataType.NUMERIC,
FieldMetadataType.RAW_JSON,
FieldMetadataType.RICH_TEXT,
FieldMetadataType.SELECT,
FieldMetadataType.MULTI_SELECT,
] as const;
export type ApplicationVariableType =
(typeof APPLICATION_VARIABLE_FIELD_METADATA_TYPES)[number];
export type ApplicationVariableOption = {
label: string;
value: string;
};
export type ApplicationVariableValue =
| string
| number
| boolean
| string[]
| Record<string, unknown>
| null;
type TypedApplicationVariable = {
label?: string;
type?: ApplicationVariableType;
options?: ApplicationVariableOption[];
isDeprecated?: boolean;
};
type SecretApplicationVariable = SyncableEntityOptions &
TypedApplicationVariable & {
description?: string;
isSecret: true;
};
type NonSecretApplicationVariable = SyncableEntityOptions &
TypedApplicationVariable & {
value?: ApplicationVariableValue;
description?: string;
isSecret?: false;
};
export type ApplicationVariable =
| SecretApplicationVariable
| NonSecretApplicationVariable;
export type ApplicationVariables = Record<string, ApplicationVariable>;

View File

@@ -0,0 +1,6 @@
export type AssetManifest = {
filePath: string;
fileName: string;
fileType: string;
checksum: string | null;
};

View File

@@ -0,0 +1,11 @@
import { type UsageOperationTypeValue } from '@/application/usageOperationTypesType';
export type BillableOperationManifest = {
operationType: UsageOperationTypeValue;
label: string;
};
// Keyed by the operation name the application charges against.
export type BillableOperations = Partial<
Record<string, BillableOperationManifest>
>;

View File

@@ -0,0 +1,12 @@
import { type OAuthConnectionProviderConfig } from '@/application/oauthConnectionProviderConfigType';
import { type SyncableEntityOptions } from '@/application/syncableEntityOptionsType';
export type ConnectionProviderManifest = SyncableEntityOptions & {
name: string;
displayName: string;
logo?: string;
type: 'oauth';
oauth: OAuthConnectionProviderConfig;
onConnectLogicFunction?: SyncableEntityOptions;
onDisconnectLogicFunction?: SyncableEntityOptions;
};

View File

@@ -0,0 +1,12 @@
// Discriminator on `connectionProvider.type`. Each value pairs with a
// typed sub-config column on the entity (today only `oauthConfig`).
//
// Anticipated future values:
// - 'apiKey' — single long-lived token in an applicationVariable
// - 'pat' — per-user personal access token entered at connect time
// - 'basicAuth' — username + password
//
// Adding a value is purely additive: define the new sub-config column on
// the entity, the manifest type, and the SDK; existing 'oauth' callers
// keep working.
export type ConnectionProviderType = 'oauth';

View File

@@ -0,0 +1 @@
export const APPLICATION_FILE_UPLOAD_BATCH_SIZE = 100;

View File

@@ -0,0 +1 @@
export const ASSETS_DIR = 'public';

View File

@@ -0,0 +1 @@
export const DEFAULT_API_KEY_NAME = 'TWENTY_API_KEY';

View File

@@ -0,0 +1 @@
export const DEFAULT_API_URL_NAME = 'TWENTY_API_URL';

View File

@@ -0,0 +1 @@
export const DEFAULT_APP_ACCESS_TOKEN_NAME = 'TWENTY_APP_ACCESS_TOKEN';

View File

@@ -0,0 +1,2 @@
export const DEFAULT_APP_APPLICATION_ACCESS_TOKEN_NAME =
'TWENTY_APP_APPLICATION_ACCESS_TOKEN';

View File

@@ -0,0 +1 @@
export const DEFAULT_FUNCTIONS_URL_NAME = 'TWENTY_FUNCTIONS_URL';

View File

@@ -0,0 +1,2 @@
export const FRONT_COMPONENT_SHARED_DEPENDENCIES_BUILT_PATH =
'front-component-shared-dependencies.mjs';

View File

@@ -0,0 +1,2 @@
export const FRONT_COMPONENT_SHARED_DEPENDENCIES_IMPORT_SPECIFIER =
'twenty:front-component-shared-dependencies';

View File

@@ -0,0 +1 @@
export const GENERATED_DIR = 'generated';

View File

@@ -0,0 +1,3 @@
export const NODE_ESM_CJS_BANNER = {
js: "import { createRequire as __createRequire } from 'module';\nconst require = __createRequire(import.meta.url);",
};

View File

@@ -0,0 +1 @@
export const OUTPUT_DIR = '.twenty/output';

View File

@@ -0,0 +1 @@
export const TWENTY_STANDARD_APPLICATION_NAME = 'Standard';

View File

@@ -0,0 +1,2 @@
export const TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER =
'20202020-64aa-4b6f-b003-9c74b97cee20';

View File

@@ -0,0 +1,17 @@
export const CREDIT_UNAVAILABLE_REASONS = [
'workspace-suspended',
'no-subscription',
'no-credits',
] as const;
export type CreditUnavailableReason =
(typeof CREDIT_UNAVAILABLE_REASONS)[number];
// What an application is told about its workspace's ability to spend. Mirrors
// the gate the platform already applies to AI, workflows and email, so an app
// can make the same decision up front instead of discovering the refusal
// through a downstream call it did not write. Carries no balance: an app learns
// whether it may spend, not how much the workspace has or what it pays.
export type CreditAvailability =
| { hasAvailableCredits: true }
| { hasAvailableCredits: false; reason: CreditUnavailableReason };

View File

@@ -0,0 +1,14 @@
import { getAgentUniversalIdentifier } from '@/application/deterministic-identifier/get-agent-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
describe('getAgentUniversalIdentifier', () => {
it('derives a deterministic id from the agent name within its application', () => {
expect(
getAgentUniversalIdentifier({
applicationUniversalIdentifier: APP,
name: 'My Agent',
}),
).toBe('3d16dbdf-98c9-59c6-b770-81161144fc78');
});
});

View File

@@ -0,0 +1,14 @@
import { getApplicationVariableUniversalIdentifier } from '@/application/deterministic-identifier/get-application-variable-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
describe('getApplicationVariableUniversalIdentifier', () => {
it('derives a deterministic id from the variable key within its application', () => {
expect(
getApplicationVariableUniversalIdentifier({
applicationUniversalIdentifier: APP,
key: 'API_KEY',
}),
).toBe('83da1579-61d6-55f5-8b6c-c6ef698608bf');
});
});

View File

@@ -0,0 +1,14 @@
import { getConnectionProviderUniversalIdentifier } from '@/application/deterministic-identifier/get-connection-provider-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
describe('getConnectionProviderUniversalIdentifier', () => {
it('derives a deterministic id from the provider name within its application', () => {
expect(
getConnectionProviderUniversalIdentifier({
applicationUniversalIdentifier: APP,
name: 'Gmail',
}),
).toBe('69c2371f-465c-57cd-9d36-3fbba8ea8510');
});
});

View File

@@ -0,0 +1,17 @@
import { getFieldPermissionUniversalIdentifier } from '@/application/deterministic-identifier/get-field-permission-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
const ROLE = '55555555-5555-4555-8555-555555555555';
const FIELD = '33333333-3333-4333-8333-333333333333';
describe('getFieldPermissionUniversalIdentifier', () => {
it('derives a deterministic id from the field within its role', () => {
expect(
getFieldPermissionUniversalIdentifier({
applicationUniversalIdentifier: APP,
roleUniversalIdentifier: ROLE,
fieldUniversalIdentifier: FIELD,
}),
).toBe('d94e301d-96c3-559b-a92c-a1f0dbc218e7');
});
});

View File

@@ -0,0 +1,16 @@
import { getFieldUniversalIdentifier } from '@/application/deterministic-identifier/get-field-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
const OBJECT = '22222222-2222-4222-8222-222222222222';
describe('getFieldUniversalIdentifier', () => {
it('derives a deterministic id from the field name within its object', () => {
expect(
getFieldUniversalIdentifier({
applicationUniversalIdentifier: APP,
objectUniversalIdentifier: OBJECT,
name: 'createdAt',
}),
).toBe('b5cc95b6-e78f-531f-b74e-25bf891e8ec7');
});
});

View File

@@ -0,0 +1,14 @@
import { getFrontComponentUniversalIdentifier } from '@/application/deterministic-identifier/get-front-component-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
describe('getFrontComponentUniversalIdentifier', () => {
it('derives a deterministic id from the component name within its application', () => {
expect(
getFrontComponentUniversalIdentifier({
applicationUniversalIdentifier: APP,
componentName: 'HelloWorld',
}),
).toBe('a81d9442-d9ff-557b-b5b7-494b9c629b23');
});
});

View File

@@ -0,0 +1,35 @@
import { getIndexFieldUniversalIdentifier } from '@/application/deterministic-identifier/get-index-field-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
const INDEX = '33333333-3333-4333-8333-333333333333';
const FIELD = '55555555-5555-4555-8555-555555555555';
describe('getIndexFieldUniversalIdentifier', () => {
it('derives a deterministic id from the field and sub-field within its index', () => {
expect(
getIndexFieldUniversalIdentifier({
applicationUniversalIdentifier: APP,
indexUniversalIdentifier: INDEX,
fieldUniversalIdentifier: FIELD,
subFieldName: 'addressCity',
}),
).toBe('221a0c4c-6e1a-5f2f-9f78-6fda4027e1f4');
});
it('treats a missing sub-field like an empty one', () => {
expect(
getIndexFieldUniversalIdentifier({
applicationUniversalIdentifier: APP,
indexUniversalIdentifier: INDEX,
fieldUniversalIdentifier: FIELD,
subFieldName: null,
}),
).toBe(
getIndexFieldUniversalIdentifier({
applicationUniversalIdentifier: APP,
indexUniversalIdentifier: INDEX,
fieldUniversalIdentifier: FIELD,
}),
);
});
});

View File

@@ -0,0 +1,16 @@
import { getIndexUniversalIdentifier } from '@/application/deterministic-identifier/get-index-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
const OBJECT = '22222222-2222-4222-8222-222222222222';
describe('getIndexUniversalIdentifier', () => {
it('derives a deterministic id from the generated index name within its object', () => {
expect(
getIndexUniversalIdentifier({
applicationUniversalIdentifier: APP,
objectUniversalIdentifier: OBJECT,
name: 'IDX_abc',
}),
).toBe('f3668ea9-452b-514f-905f-07375db25906');
});
});

View File

@@ -0,0 +1,14 @@
import { getLogicFunctionUniversalIdentifier } from '@/application/deterministic-identifier/get-logic-function-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
describe('getLogicFunctionUniversalIdentifier', () => {
it('derives a deterministic id from the function name within its application', () => {
expect(
getLogicFunctionUniversalIdentifier({
applicationUniversalIdentifier: APP,
name: 'sendEmail',
}),
).toBe('4a470da0-dd48-5b7f-a1be-4e24fcc5b6c5');
});
});

View File

@@ -0,0 +1,54 @@
import {
getFolderNavigationMenuItemUniversalIdentifier,
getLinkNavigationMenuItemUniversalIdentifier,
getObjectNavigationMenuItemUniversalIdentifier,
getViewNavigationMenuItemUniversalIdentifier,
} from '@/application/deterministic-identifier/get-navigation-menu-item-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
const OBJECT = '22222222-2222-4222-8222-222222222222';
const VIEW = '44444444-4444-4444-8444-444444444444';
describe('getFolderNavigationMenuItemUniversalIdentifier', () => {
it('derives a deterministic id from the folder name within its application', () => {
expect(
getFolderNavigationMenuItemUniversalIdentifier({
applicationUniversalIdentifier: APP,
name: 'Workflows',
}),
).toBe('ca043c51-ac75-515d-902b-dd836626bba4');
});
});
describe('getObjectNavigationMenuItemUniversalIdentifier', () => {
it('derives a deterministic id from the object it targets', () => {
expect(
getObjectNavigationMenuItemUniversalIdentifier({
applicationUniversalIdentifier: APP,
objectUniversalIdentifier: OBJECT,
}),
).toBe('071361bd-47c8-5513-be33-998dd287f8c7');
});
});
describe('getViewNavigationMenuItemUniversalIdentifier', () => {
it('derives a deterministic id from the view it targets', () => {
expect(
getViewNavigationMenuItemUniversalIdentifier({
applicationUniversalIdentifier: APP,
viewUniversalIdentifier: VIEW,
}),
).toBe('6504fd6d-ed38-5f34-87fc-26025130260f');
});
});
describe('getLinkNavigationMenuItemUniversalIdentifier', () => {
it('derives a deterministic id from its target URL', () => {
expect(
getLinkNavigationMenuItemUniversalIdentifier({
applicationUniversalIdentifier: APP,
link: 'https://example.com',
}),
).toBe('635cef97-cc61-53a3-873e-38fecf3ecbd7');
});
});

View File

@@ -0,0 +1,17 @@
import { getObjectPermissionUniversalIdentifier } from '@/application/deterministic-identifier/get-object-permission-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
const ROLE = '55555555-5555-4555-8555-555555555555';
const OBJECT = '22222222-2222-4222-8222-222222222222';
describe('getObjectPermissionUniversalIdentifier', () => {
it('derives a deterministic id from the object within its role', () => {
expect(
getObjectPermissionUniversalIdentifier({
applicationUniversalIdentifier: APP,
roleUniversalIdentifier: ROLE,
objectUniversalIdentifier: OBJECT,
}),
).toBe('8d78f0a2-6ad6-5ee1-a859-a33bddd42324');
});
});

View File

@@ -0,0 +1,14 @@
import { getObjectUniversalIdentifier } from '@/application/deterministic-identifier/get-object-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
describe('getObjectUniversalIdentifier', () => {
it('derives a deterministic id from the object nameSingular within its application', () => {
expect(
getObjectUniversalIdentifier({
applicationUniversalIdentifier: APP,
nameSingular: 'rocket',
}),
).toBe('676819ee-d700-5936-9d6f-58dd7f71ae07');
});
});

View File

@@ -0,0 +1,37 @@
import { getPageLayoutUniversalIdentifier } from '@/application/deterministic-identifier/get-page-layout-universal-identifier.util';
import { getSystemRecordPageLayoutUniversalIdentifier } from '@/application/deterministic-identifier/get-system-record-page-layout-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
const OBJECT = '22222222-2222-4222-8222-222222222222';
describe('getPageLayoutUniversalIdentifier', () => {
it('derives a deterministic id from the layout name within its object', () => {
expect(
getPageLayoutUniversalIdentifier({
applicationUniversalIdentifier: APP,
objectUniversalIdentifier: OBJECT,
name: 'My Dashboard',
}),
).toBe('dd8a6bdb-4f8e-57c7-8a34-120d5e6430ae');
});
it('derives a deterministic id from the layout name alone when standalone', () => {
expect(
getPageLayoutUniversalIdentifier({
applicationUniversalIdentifier: APP,
name: 'Standalone',
}),
).toBe('d382c85c-204a-5648-a101-e65575c3a853');
});
});
describe('getSystemRecordPageLayoutUniversalIdentifier', () => {
it('derives a deterministic id from the RECORD_PAGE role within its object', () => {
expect(
getSystemRecordPageLayoutUniversalIdentifier({
objectMetadataApplicationUniversalIdentifier: APP,
objectUniversalIdentifier: OBJECT,
}),
).toBe('80289fa4-d714-5883-bd1d-bc59c7b9a1c2');
});
});

View File

@@ -0,0 +1,14 @@
import { getPermissionFlagUniversalIdentifier } from '@/application/deterministic-identifier/get-permission-flag-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
describe('getPermissionFlagUniversalIdentifier', () => {
it('derives a deterministic id from the flag key within its application', () => {
expect(
getPermissionFlagUniversalIdentifier({
applicationUniversalIdentifier: APP,
key: 'WORKFLOWS',
}),
).toBe('e9f8bad8-54da-51f7-b7d7-77ed600a589d');
});
});

View File

@@ -0,0 +1,17 @@
import { getRolePermissionFlagUniversalIdentifier } from '@/application/deterministic-identifier/get-role-permission-flag-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
const ROLE = '55555555-5555-4555-8555-555555555555';
const PERMISSION_FLAG = '99999999-9999-4999-8999-999999999999';
describe('getRolePermissionFlagUniversalIdentifier', () => {
it('derives a deterministic id from the permission flag within its role', () => {
expect(
getRolePermissionFlagUniversalIdentifier({
applicationUniversalIdentifier: APP,
roleUniversalIdentifier: ROLE,
permissionFlagUniversalIdentifier: PERMISSION_FLAG,
}),
).toBe('e1b994dc-25c7-5363-82b3-ac77b697ef6b');
});
});

View File

@@ -0,0 +1,14 @@
import { getRoleUniversalIdentifier } from '@/application/deterministic-identifier/get-role-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
describe('getRoleUniversalIdentifier', () => {
it('derives a deterministic id from the role label within its application', () => {
expect(
getRoleUniversalIdentifier({
applicationUniversalIdentifier: APP,
label: 'Admin',
}),
).toBe('135023fe-0587-5897-ad22-ce2ee8ab91a0');
});
});

View File

@@ -0,0 +1,15 @@
import { getSearchFieldUniversalIdentifier } from '@/application/deterministic-identifier/get-search-field-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
const FIELD = '33333333-3333-4333-8333-333333333333';
describe('getSearchFieldUniversalIdentifier', () => {
it('derives a deterministic id from the field it makes searchable', () => {
expect(
getSearchFieldUniversalIdentifier({
applicationUniversalIdentifier: APP,
fieldMetadataUniversalIdentifier: FIELD,
}),
).toBe('db4e5b93-15b6-5c83-a0b3-6c031e0ca072');
});
});

View File

@@ -0,0 +1,16 @@
import { getSelectOptionUniversalIdentifier } from '@/application/deterministic-identifier/get-select-option-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
const FIELD = '33333333-3333-4333-8333-333333333333';
describe('getSelectOptionUniversalIdentifier', () => {
it('derives a deterministic id from the option value within its field', () => {
expect(
getSelectOptionUniversalIdentifier({
applicationUniversalIdentifier: APP,
fieldUniversalIdentifier: FIELD,
value: 'OPTION_A',
}),
).toBe('656c6ef0-1b13-5cc0-8c65-99700f364fa9');
});
});

View File

@@ -0,0 +1,14 @@
import { getSkillUniversalIdentifier } from '@/application/deterministic-identifier/get-skill-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
describe('getSkillUniversalIdentifier', () => {
it('derives a deterministic id from the skill name within its application', () => {
expect(
getSkillUniversalIdentifier({
applicationUniversalIdentifier: APP,
name: 'search',
}),
).toBe('91cd3731-e098-5a62-86af-6fe9ac79812b');
});
});

View File

@@ -0,0 +1,15 @@
import { getSystemNavigationCommandMenuItemUniversalIdentifier } from '@/application/deterministic-identifier/get-system-navigation-command-menu-item-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
const OBJECT = '22222222-2222-4222-8222-222222222222';
describe('getSystemNavigationCommandMenuItemUniversalIdentifier', () => {
it('derives a deterministic id from the navigation role within its object', () => {
expect(
getSystemNavigationCommandMenuItemUniversalIdentifier({
objectMetadataApplicationUniversalIdentifier: APP,
objectUniversalIdentifier: OBJECT,
}),
).toBe('ec4626f3-3170-5ddc-a309-bdf2a18d4e84');
});
});

View File

@@ -0,0 +1,16 @@
import { getSystemPageLayoutTabUniversalIdentifier } from '@/application/deterministic-identifier/get-system-page-layout-tab-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
const PAGE_LAYOUT = '66666666-6666-4666-8666-666666666666';
describe('getSystemPageLayoutTabUniversalIdentifier', () => {
it('derives a deterministic id from the tab title within its page layout', () => {
expect(
getSystemPageLayoutTabUniversalIdentifier({
objectMetadataApplicationUniversalIdentifier: APP,
pageLayoutUniversalIdentifier: PAGE_LAYOUT,
title: 'Home',
}),
).toBe('f90464e0-2082-540b-8a02-1e59be4cadf4');
});
});

View File

@@ -0,0 +1,16 @@
import { getSystemPageLayoutWidgetUniversalIdentifier } from '@/application/deterministic-identifier/get-system-page-layout-widget-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
const TAB = '77777777-7777-4777-8777-777777777777';
describe('getSystemPageLayoutWidgetUniversalIdentifier', () => {
it('derives a deterministic id from the widget title within its tab', () => {
expect(
getSystemPageLayoutWidgetUniversalIdentifier({
objectMetadataApplicationUniversalIdentifier: APP,
pageLayoutTabUniversalIdentifier: TAB,
title: 'Fields',
}),
).toBe('09cc205a-0cdf-52f0-9917-3ac5a75e69c5');
});
});

View File

@@ -0,0 +1,52 @@
import { getFieldUniversalIdentifier } from '@/application/deterministic-identifier/get-field-universal-identifier.util';
import { getSystemRelationFieldUniversalIdentifier } from '@/application/deterministic-identifier/get-system-relation-field-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
const HOST_OBJECT = '22222222-2222-4222-8222-222222222222';
const RELATION_TARGET_OBJECT = '33333333-3333-4333-8333-333333333333';
describe('getSystemRelationFieldUniversalIdentifier', () => {
it('derives a deterministic id from the host and relation target object identifiers', () => {
expect(
getSystemRelationFieldUniversalIdentifier({
applicationUniversalIdentifier: APP,
objectUniversalIdentifier: HOST_OBJECT,
relationTargetObjectUniversalIdentifier: RELATION_TARGET_OBJECT,
}),
).toBe('d3a641a1-f0fd-5213-a28e-af81e00c991e');
});
it('derives distinct ids for the two sides of the same relation (host and target swapped)', () => {
const reverseSide = getSystemRelationFieldUniversalIdentifier({
applicationUniversalIdentifier: APP,
objectUniversalIdentifier: HOST_OBJECT,
relationTargetObjectUniversalIdentifier: RELATION_TARGET_OBJECT,
});
const forwardSide = getSystemRelationFieldUniversalIdentifier({
applicationUniversalIdentifier: APP,
objectUniversalIdentifier: RELATION_TARGET_OBJECT,
relationTargetObjectUniversalIdentifier: HOST_OBJECT,
});
expect(reverseSide).not.toBe(forwardSide);
});
it('cannot collide with a name-based field identifier for any real field name', () => {
// The seed collides only if a field name equals
// `systemRelation:${relationTargetUID}`, which is unreachable because field
// names cannot contain colons. Any real (colon-free) field name on the host
// object therefore derives a distinct id.
const systemRelationIdentifier = getSystemRelationFieldUniversalIdentifier({
applicationUniversalIdentifier: APP,
objectUniversalIdentifier: HOST_OBJECT,
relationTargetObjectUniversalIdentifier: RELATION_TARGET_OBJECT,
});
const nameBasedIdentifier = getFieldUniversalIdentifier({
applicationUniversalIdentifier: APP,
objectUniversalIdentifier: HOST_OBJECT,
name: 'targetCompany',
});
expect(systemRelationIdentifier).not.toBe(nameBasedIdentifier);
});
});

View File

@@ -0,0 +1,16 @@
import { getSystemViewFieldGroupUniversalIdentifier } from '@/application/deterministic-identifier/get-system-view-field-group-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
const VIEW = '44444444-4444-4444-8444-444444444444';
describe('getSystemViewFieldGroupUniversalIdentifier', () => {
it('derives a deterministic id from the group name within its view', () => {
expect(
getSystemViewFieldGroupUniversalIdentifier({
objectMetadataApplicationUniversalIdentifier: APP,
viewUniversalIdentifier: VIEW,
name: 'General',
}),
).toBe('26350cf1-bec3-5617-b525-c0115811963e');
});
});

View File

@@ -0,0 +1,34 @@
import { getSystemViewFieldUniversalIdentifier } from '@/application/deterministic-identifier/get-system-view-field-universal-identifier.util';
import { getViewFieldUniversalIdentifier } from '@/application/deterministic-identifier/get-view-field-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
const VIEW = '44444444-4444-4444-8444-444444444444';
const FIELD = '33333333-3333-4333-8333-333333333333';
describe('getSystemViewFieldUniversalIdentifier', () => {
it('derives a deterministic id from the field it displays within its view', () => {
expect(
getSystemViewFieldUniversalIdentifier({
fieldMetadataApplicationUniversalIdentifier: APP,
viewUniversalIdentifier: VIEW,
fieldMetadataUniversalIdentifier: FIELD,
}),
).toBe('fe533473-ef93-5ed9-b709-b344aa94fb8d');
});
it('derives the same id as getViewFieldUniversalIdentifier', () => {
expect(
getSystemViewFieldUniversalIdentifier({
fieldMetadataApplicationUniversalIdentifier: APP,
viewUniversalIdentifier: VIEW,
fieldMetadataUniversalIdentifier: FIELD,
}),
).toBe(
getViewFieldUniversalIdentifier({
applicationUniversalIdentifier: APP,
viewUniversalIdentifier: VIEW,
fieldMetadataUniversalIdentifier: FIELD,
}),
);
});
});

View File

@@ -0,0 +1,19 @@
import {
getSystemViewUniversalIdentifier,
SYSTEM_VIEW_KEYS,
} from '@/application/deterministic-identifier/get-system-view-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
const OBJECT = '22222222-2222-4222-8222-222222222222';
describe('getSystemViewUniversalIdentifier', () => {
it('derives a deterministic id from the stable INDEX view key within its object', () => {
expect(
getSystemViewUniversalIdentifier({
objectMetadataApplicationUniversalIdentifier: APP,
objectUniversalIdentifier: OBJECT,
viewKey: SYSTEM_VIEW_KEYS.INDEX,
}),
).toBe('3803a536-0158-554c-bfb4-e5492323e57f');
});
});

View File

@@ -0,0 +1,17 @@
import { getViewFieldUniversalIdentifier } from '@/application/deterministic-identifier/get-view-field-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
const VIEW = '44444444-4444-4444-8444-444444444444';
const FIELD = '33333333-3333-4333-8333-333333333333';
describe('getViewFieldUniversalIdentifier', () => {
it('derives a deterministic id from the field it displays within its view', () => {
expect(
getViewFieldUniversalIdentifier({
applicationUniversalIdentifier: APP,
viewUniversalIdentifier: VIEW,
fieldMetadataUniversalIdentifier: FIELD,
}),
).toBe('fe533473-ef93-5ed9-b709-b344aa94fb8d');
});
});

View File

@@ -0,0 +1,19 @@
import { getViewFilterUniversalIdentifier } from '@/application/deterministic-identifier/get-view-filter-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
const VIEW = '44444444-4444-4444-8444-444444444444';
const FIELD = '33333333-3333-4333-8333-333333333333';
describe('getViewFilterUniversalIdentifier', () => {
it('derives a deterministic id from the field, operand and sub-field within its view', () => {
expect(
getViewFilterUniversalIdentifier({
applicationUniversalIdentifier: APP,
viewUniversalIdentifier: VIEW,
fieldMetadataUniversalIdentifier: FIELD,
operand: 'is',
subFieldName: null,
}),
).toBe('d53c51f5-4717-5e81-8ac0-6062b8fdcff6');
});
});

View File

@@ -0,0 +1,16 @@
import { getViewGroupUniversalIdentifier } from '@/application/deterministic-identifier/get-view-group-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
const VIEW = '44444444-4444-4444-8444-444444444444';
describe('getViewGroupUniversalIdentifier', () => {
it('derives a deterministic id from the field value it groups by within its view', () => {
expect(
getViewGroupUniversalIdentifier({
applicationUniversalIdentifier: APP,
viewUniversalIdentifier: VIEW,
fieldValue: 'OPTION_A',
}),
).toBe('1d0462d5-8fc2-52b0-84f9-b297b785fa4f');
});
});

View File

@@ -0,0 +1,17 @@
import { getViewSortUniversalIdentifier } from '@/application/deterministic-identifier/get-view-sort-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
const VIEW = '44444444-4444-4444-8444-444444444444';
const FIELD = '33333333-3333-4333-8333-333333333333';
describe('getViewSortUniversalIdentifier', () => {
it('derives a deterministic id from the field it sorts within its view', () => {
expect(
getViewSortUniversalIdentifier({
applicationUniversalIdentifier: APP,
viewUniversalIdentifier: VIEW,
fieldMetadataUniversalIdentifier: FIELD,
}),
).toBe('e6e591ad-8a29-553b-9a6b-eaaa74667661');
});
});

View File

@@ -0,0 +1,16 @@
import { getViewUniversalIdentifier } from '@/application/deterministic-identifier/get-view-universal-identifier.util';
const APP = '11111111-1111-4111-8111-111111111111';
const OBJECT = '22222222-2222-4222-8222-222222222222';
describe('getViewUniversalIdentifier', () => {
it('derives a deterministic id from the view name within its object', () => {
expect(
getViewUniversalIdentifier({
applicationUniversalIdentifier: APP,
objectUniversalIdentifier: OBJECT,
name: 'My View',
}),
).toBe('125140c3-eca9-560d-9020-dad4ac1ebab6');
});
});

View File

@@ -0,0 +1,13 @@
import { v5 } from 'uuid';
import { type DeterministicEntityNamespace } from '@/application/deterministic-identifier/deterministic-entity-namespace.type';
export const computeDeterministicUuid = ({
entityNamespace,
value,
applicationUniversalIdentifier,
}: {
entityNamespace: DeterministicEntityNamespace;
value: string;
applicationUniversalIdentifier: string;
}): string => v5(`${entityNamespace}:${value}`, applicationUniversalIdentifier);

View File

@@ -0,0 +1,7 @@
import { type AllMetadataName } from '@/metadata/types/all-metadata-name.type';
// selectOption is not a syncable metadata entity yet; it will join AllMetadataName once it is.
export type DeterministicEntityNamespace =
| AllMetadataName
| 'selectOption'
| 'indexField';

View File

@@ -0,0 +1,14 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
export const getAgentUniversalIdentifier = ({
applicationUniversalIdentifier,
name,
}: {
applicationUniversalIdentifier: string;
name: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'agent',
value: name,
applicationUniversalIdentifier,
});

View File

@@ -0,0 +1,14 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
export const getApplicationVariableUniversalIdentifier = ({
applicationUniversalIdentifier,
key,
}: {
applicationUniversalIdentifier: string;
key: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'applicationVariable',
value: key,
applicationUniversalIdentifier,
});

View File

@@ -0,0 +1,14 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
export const getConnectionProviderUniversalIdentifier = ({
applicationUniversalIdentifier,
name,
}: {
applicationUniversalIdentifier: string;
name: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'connectionProvider',
value: name,
applicationUniversalIdentifier,
});

View File

@@ -0,0 +1,16 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
export const getFieldPermissionUniversalIdentifier = ({
applicationUniversalIdentifier,
roleUniversalIdentifier,
fieldUniversalIdentifier,
}: {
applicationUniversalIdentifier: string;
roleUniversalIdentifier: string;
fieldUniversalIdentifier: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'fieldPermission',
value: `${roleUniversalIdentifier}:${fieldUniversalIdentifier}`,
applicationUniversalIdentifier,
});

View File

@@ -0,0 +1,16 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
export const getFieldUniversalIdentifier = ({
applicationUniversalIdentifier,
objectUniversalIdentifier,
name,
}: {
applicationUniversalIdentifier: string;
objectUniversalIdentifier: string;
name: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'fieldMetadata',
value: `${objectUniversalIdentifier}:${name}`,
applicationUniversalIdentifier,
});

View File

@@ -0,0 +1,14 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
export const getFrontComponentUniversalIdentifier = ({
applicationUniversalIdentifier,
componentName,
}: {
applicationUniversalIdentifier: string;
componentName: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'frontComponent',
value: componentName,
applicationUniversalIdentifier,
});

View File

@@ -0,0 +1,18 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
export const getIndexFieldUniversalIdentifier = ({
applicationUniversalIdentifier,
indexUniversalIdentifier,
fieldUniversalIdentifier,
subFieldName,
}: {
applicationUniversalIdentifier: string;
indexUniversalIdentifier: string;
fieldUniversalIdentifier: string;
subFieldName?: string | null;
}): string =>
computeDeterministicUuid({
entityNamespace: 'indexField',
value: `${indexUniversalIdentifier}:${fieldUniversalIdentifier}:${subFieldName ?? ''}`,
applicationUniversalIdentifier,
});

View File

@@ -0,0 +1,18 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
// An index is identified by its generated Postgres name (table + ordered columns
// + uniqueness + where-clause); a name change means a different index.
export const getIndexUniversalIdentifier = ({
applicationUniversalIdentifier,
objectUniversalIdentifier,
name,
}: {
applicationUniversalIdentifier: string;
objectUniversalIdentifier: string;
name: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'index',
value: `${objectUniversalIdentifier}:${name}`,
applicationUniversalIdentifier,
});

View File

@@ -0,0 +1,14 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
export const getLogicFunctionUniversalIdentifier = ({
applicationUniversalIdentifier,
name,
}: {
applicationUniversalIdentifier: string;
name: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'logicFunction',
value: name,
applicationUniversalIdentifier,
});

View File

@@ -0,0 +1,61 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
// NavigationMenuItem is a single polymorphic entity: every variant shares
// name/icon/color/position/folder, but each type is identified by a different
// target, so each variant has its own discriminator (prefixed by the type to
// keep the shared `navigationMenuItem` namespace collision-free).
export const getFolderNavigationMenuItemUniversalIdentifier = ({
applicationUniversalIdentifier,
name,
}: {
applicationUniversalIdentifier: string;
name: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'navigationMenuItem',
value: `FOLDER:${name}`,
applicationUniversalIdentifier,
});
// An OBJECT navigation item is identified by the object it targets.
export const getObjectNavigationMenuItemUniversalIdentifier = ({
applicationUniversalIdentifier,
objectUniversalIdentifier,
}: {
applicationUniversalIdentifier: string;
objectUniversalIdentifier: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'navigationMenuItem',
value: `OBJECT:${objectUniversalIdentifier}`,
applicationUniversalIdentifier,
});
// A VIEW navigation item is identified by the view it targets.
export const getViewNavigationMenuItemUniversalIdentifier = ({
applicationUniversalIdentifier,
viewUniversalIdentifier,
}: {
applicationUniversalIdentifier: string;
viewUniversalIdentifier: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'navigationMenuItem',
value: `VIEW:${viewUniversalIdentifier}`,
applicationUniversalIdentifier,
});
// A LINK navigation item is identified by its target URL.
export const getLinkNavigationMenuItemUniversalIdentifier = ({
applicationUniversalIdentifier,
link,
}: {
applicationUniversalIdentifier: string;
link: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'navigationMenuItem',
value: `LINK:${link}`,
applicationUniversalIdentifier,
});

View File

@@ -0,0 +1,16 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
export const getObjectPermissionUniversalIdentifier = ({
applicationUniversalIdentifier,
roleUniversalIdentifier,
objectUniversalIdentifier,
}: {
applicationUniversalIdentifier: string;
roleUniversalIdentifier: string;
objectUniversalIdentifier: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'objectPermission',
value: `${roleUniversalIdentifier}:${objectUniversalIdentifier}`,
applicationUniversalIdentifier,
});

View File

@@ -0,0 +1,14 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
export const getObjectUniversalIdentifier = ({
applicationUniversalIdentifier,
nameSingular,
}: {
applicationUniversalIdentifier: string;
nameSingular: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'objectMetadata',
value: nameSingular,
applicationUniversalIdentifier,
});

View File

@@ -0,0 +1,19 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
// A page layout is identified by its name within its object (or the app for standalone layouts).
export const getPageLayoutUniversalIdentifier = ({
applicationUniversalIdentifier,
objectUniversalIdentifier,
name,
}: {
applicationUniversalIdentifier: string;
objectUniversalIdentifier?: string;
name: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'pageLayout',
value: objectUniversalIdentifier
? `${objectUniversalIdentifier}:${name}`
: name,
applicationUniversalIdentifier,
});

View File

@@ -0,0 +1,14 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
export const getPermissionFlagUniversalIdentifier = ({
applicationUniversalIdentifier,
key,
}: {
applicationUniversalIdentifier: string;
key: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'permissionFlag',
value: key,
applicationUniversalIdentifier,
});

View File

@@ -0,0 +1,17 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
// A role permission flag is identified by its permission flag, within its role.
export const getRolePermissionFlagUniversalIdentifier = ({
applicationUniversalIdentifier,
roleUniversalIdentifier,
permissionFlagUniversalIdentifier,
}: {
applicationUniversalIdentifier: string;
roleUniversalIdentifier: string;
permissionFlagUniversalIdentifier: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'rolePermissionFlag',
value: `${roleUniversalIdentifier}:${permissionFlagUniversalIdentifier}`,
applicationUniversalIdentifier,
});

View File

@@ -0,0 +1,14 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
export const getRoleTargetUniversalIdentifier = ({
applicationUniversalIdentifier,
agentUniversalIdentifier,
}: {
applicationUniversalIdentifier: string;
agentUniversalIdentifier: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'roleTarget',
value: agentUniversalIdentifier,
applicationUniversalIdentifier,
});

View File

@@ -0,0 +1,14 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
export const getRoleUniversalIdentifier = ({
applicationUniversalIdentifier,
label,
}: {
applicationUniversalIdentifier: string;
label: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'role',
value: label,
applicationUniversalIdentifier,
});

View File

@@ -0,0 +1,15 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
// A search field is 1:1 with the field it makes searchable, so it is keyed by that field.
export const getSearchFieldUniversalIdentifier = ({
applicationUniversalIdentifier,
fieldMetadataUniversalIdentifier,
}: {
applicationUniversalIdentifier: string;
fieldMetadataUniversalIdentifier: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'searchFieldMetadata',
value: fieldMetadataUniversalIdentifier,
applicationUniversalIdentifier,
});

View File

@@ -0,0 +1,17 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
// An option is identified by its stable value (not its renameable label) within its field.
export const getSelectOptionUniversalIdentifier = ({
applicationUniversalIdentifier,
fieldUniversalIdentifier,
value,
}: {
applicationUniversalIdentifier: string;
fieldUniversalIdentifier: string;
value: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'selectOption',
value: `${fieldUniversalIdentifier}:${value}`,
applicationUniversalIdentifier,
});

View File

@@ -0,0 +1,14 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
export const getSkillUniversalIdentifier = ({
applicationUniversalIdentifier,
name,
}: {
applicationUniversalIdentifier: string;
name: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'skill',
value: name,
applicationUniversalIdentifier,
});

View File

@@ -0,0 +1,16 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
export const getSystemFormFieldPageLayoutWidgetUniversalIdentifier = ({
fieldMetadataApplicationUniversalIdentifier,
pageLayoutTabUniversalIdentifier,
fieldMetadataUniversalIdentifier,
}: {
fieldMetadataApplicationUniversalIdentifier: string;
pageLayoutTabUniversalIdentifier: string;
fieldMetadataUniversalIdentifier: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'pageLayoutWidget',
value: `${pageLayoutTabUniversalIdentifier}:${fieldMetadataUniversalIdentifier}`,
applicationUniversalIdentifier: fieldMetadataApplicationUniversalIdentifier,
});

View File

@@ -0,0 +1,17 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
const NAVIGATION_COMMAND_DISCRIMINATOR = 'navigation';
export const getSystemNavigationCommandMenuItemUniversalIdentifier = ({
objectMetadataApplicationUniversalIdentifier,
objectUniversalIdentifier,
}: {
objectMetadataApplicationUniversalIdentifier: string;
objectUniversalIdentifier: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'commandMenuItem',
value: `${objectUniversalIdentifier}:${NAVIGATION_COMMAND_DISCRIMINATOR}`,
applicationUniversalIdentifier:
objectMetadataApplicationUniversalIdentifier,
});

View File

@@ -0,0 +1,17 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
export const getSystemPageLayoutTabUniversalIdentifier = ({
objectMetadataApplicationUniversalIdentifier,
pageLayoutUniversalIdentifier,
title,
}: {
objectMetadataApplicationUniversalIdentifier: string;
pageLayoutUniversalIdentifier: string;
title: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'pageLayoutTab',
value: `${pageLayoutUniversalIdentifier}:${title}`,
applicationUniversalIdentifier:
objectMetadataApplicationUniversalIdentifier,
});

View File

@@ -0,0 +1,17 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
export const getSystemPageLayoutWidgetUniversalIdentifier = ({
objectMetadataApplicationUniversalIdentifier,
pageLayoutTabUniversalIdentifier,
title,
}: {
objectMetadataApplicationUniversalIdentifier: string;
pageLayoutTabUniversalIdentifier: string;
title: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'pageLayoutWidget',
value: `${pageLayoutTabUniversalIdentifier}:${title}`,
applicationUniversalIdentifier:
objectMetadataApplicationUniversalIdentifier,
});

View File

@@ -0,0 +1,16 @@
import { getPageLayoutUniversalIdentifier } from '@/application/deterministic-identifier/get-page-layout-universal-identifier.util';
import { PageLayoutType } from '@/types';
export const getSystemRecordFormPageLayoutUniversalIdentifier = ({
objectMetadataApplicationUniversalIdentifier,
objectUniversalIdentifier,
}: {
objectMetadataApplicationUniversalIdentifier: string;
objectUniversalIdentifier: string;
}): string =>
getPageLayoutUniversalIdentifier({
applicationUniversalIdentifier:
objectMetadataApplicationUniversalIdentifier,
objectUniversalIdentifier,
name: PageLayoutType.RECORD_FORM,
});

View File

@@ -0,0 +1,19 @@
import { getPageLayoutUniversalIdentifier } from '@/application/deterministic-identifier/get-page-layout-universal-identifier.util';
import { PageLayoutType } from '@/types';
// An object's singleton record page layout (its name is server-generated),
// keyed on the object + the reserved RECORD_PAGE discriminator through the
// generic name-keyed derivation, so both stay on a single scheme.
export const getSystemRecordPageLayoutUniversalIdentifier = ({
objectMetadataApplicationUniversalIdentifier,
objectUniversalIdentifier,
}: {
objectMetadataApplicationUniversalIdentifier: string;
objectUniversalIdentifier: string;
}): string =>
getPageLayoutUniversalIdentifier({
applicationUniversalIdentifier:
objectMetadataApplicationUniversalIdentifier,
objectUniversalIdentifier,
name: PageLayoutType.RECORD_PAGE,
});

View File

@@ -0,0 +1,16 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
export const getSystemRelationFieldUniversalIdentifier = ({
applicationUniversalIdentifier,
objectUniversalIdentifier,
relationTargetObjectUniversalIdentifier,
}: {
applicationUniversalIdentifier: string;
objectUniversalIdentifier: string;
relationTargetObjectUniversalIdentifier: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'fieldMetadata',
value: `${objectUniversalIdentifier}:systemRelation:${relationTargetObjectUniversalIdentifier}`,
applicationUniversalIdentifier,
});

View File

@@ -0,0 +1,17 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
export const getSystemViewFieldGroupUniversalIdentifier = ({
objectMetadataApplicationUniversalIdentifier,
viewUniversalIdentifier,
name,
}: {
objectMetadataApplicationUniversalIdentifier: string;
viewUniversalIdentifier: string;
name: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'viewFieldGroup',
value: `${viewUniversalIdentifier}:${name}`,
applicationUniversalIdentifier:
objectMetadataApplicationUniversalIdentifier,
});

View File

@@ -0,0 +1,16 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
export const getSystemViewFieldUniversalIdentifier = ({
fieldMetadataApplicationUniversalIdentifier,
viewUniversalIdentifier,
fieldMetadataUniversalIdentifier,
}: {
fieldMetadataApplicationUniversalIdentifier: string;
viewUniversalIdentifier: string;
fieldMetadataUniversalIdentifier: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'viewField',
value: `${viewUniversalIdentifier}:${fieldMetadataUniversalIdentifier}`,
applicationUniversalIdentifier: fieldMetadataApplicationUniversalIdentifier,
});

View File

@@ -0,0 +1,25 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
export const SYSTEM_VIEW_KEYS = {
INDEX: 'INDEX',
FIELDS_WIDGET: 'FIELDS_WIDGET',
} as const;
export type SystemViewKey =
(typeof SYSTEM_VIEW_KEYS)[keyof typeof SYSTEM_VIEW_KEYS];
export const getSystemViewUniversalIdentifier = ({
objectMetadataApplicationUniversalIdentifier,
objectUniversalIdentifier,
viewKey,
}: {
objectMetadataApplicationUniversalIdentifier: string;
objectUniversalIdentifier: string;
viewKey: SystemViewKey;
}): string =>
computeDeterministicUuid({
entityNamespace: 'view',
value: `${objectUniversalIdentifier}:${viewKey}`,
applicationUniversalIdentifier:
objectMetadataApplicationUniversalIdentifier,
});

View File

@@ -0,0 +1,16 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
export const getViewFieldUniversalIdentifier = ({
applicationUniversalIdentifier,
viewUniversalIdentifier,
fieldMetadataUniversalIdentifier,
}: {
applicationUniversalIdentifier: string;
viewUniversalIdentifier: string;
fieldMetadataUniversalIdentifier: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'viewField',
value: `${viewUniversalIdentifier}:${fieldMetadataUniversalIdentifier}`,
applicationUniversalIdentifier,
});

View File

@@ -0,0 +1,21 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
// A view filter is identified by its field + operand (+ sub-field), within its view.
export const getViewFilterUniversalIdentifier = ({
applicationUniversalIdentifier,
viewUniversalIdentifier,
fieldMetadataUniversalIdentifier,
operand,
subFieldName,
}: {
applicationUniversalIdentifier: string;
viewUniversalIdentifier: string;
fieldMetadataUniversalIdentifier: string;
operand: string;
subFieldName?: string | null;
}): string =>
computeDeterministicUuid({
entityNamespace: 'viewFilter',
value: `${viewUniversalIdentifier}:${fieldMetadataUniversalIdentifier}:${operand}:${subFieldName ?? ''}`,
applicationUniversalIdentifier,
});

View File

@@ -0,0 +1,16 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
export const getViewGroupUniversalIdentifier = ({
applicationUniversalIdentifier,
viewUniversalIdentifier,
fieldValue,
}: {
applicationUniversalIdentifier: string;
viewUniversalIdentifier: string;
fieldValue: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'viewGroup',
value: `${viewUniversalIdentifier}:${fieldValue}`,
applicationUniversalIdentifier,
});

View File

@@ -0,0 +1,16 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
export const getViewSortUniversalIdentifier = ({
applicationUniversalIdentifier,
viewUniversalIdentifier,
fieldMetadataUniversalIdentifier,
}: {
applicationUniversalIdentifier: string;
viewUniversalIdentifier: string;
fieldMetadataUniversalIdentifier: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'viewSort',
value: `${viewUniversalIdentifier}:${fieldMetadataUniversalIdentifier}`,
applicationUniversalIdentifier,
});

View File

@@ -0,0 +1,16 @@
import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util';
export const getViewUniversalIdentifier = ({
applicationUniversalIdentifier,
objectUniversalIdentifier,
name,
}: {
applicationUniversalIdentifier: string;
objectUniversalIdentifier: string;
name: string;
}): string =>
computeDeterministicUuid({
entityNamespace: 'view',
value: `${objectUniversalIdentifier}:${name}`,
applicationUniversalIdentifier,
});

View File

@@ -0,0 +1,53 @@
export type EnqueueJobOptions = {
retryLimit?: number;
delayMs?: number;
};
export type EnqueueJobInput = EnqueueJobOptions & {
logicFunctionUniversalIdentifier: string;
payload?: Record<string, unknown>;
jobId?: string;
};
export type EnqueueJobResult = {
enqueued: boolean;
logicFunctionUniversalIdentifier: string;
jobId: string;
};
export type EnqueueJobItem = {
payload?: Record<string, unknown>;
jobId?: string;
};
export type EnqueueJobsInput = EnqueueJobOptions & {
logicFunctionUniversalIdentifier: string;
payloads?: Record<string, unknown>[];
jobs?: EnqueueJobItem[];
};
export type EnqueueJobsResult = {
enqueued: boolean;
logicFunctionUniversalIdentifier: string;
enqueuedJobsCount: number;
jobIds: string[];
};
export type JobStatusState =
| 'COMPLETED'
| 'FAILED'
| 'ACTIVE'
| 'WAITING'
| 'DELAYED'
| 'PRIORITIZED'
| 'WAITING_CHILDREN';
export type JobStatusResult = {
jobId: string;
state: JobStatusState;
attemptsMade: number;
failedReason?: string | null;
enqueuedAt: number;
startedAt?: number | null;
finishedAt?: number | null;
};

View File

@@ -0,0 +1,18 @@
export enum SyncableEntity {
Object = 'object',
Field = 'field',
LogicFunction = 'logicFunction',
FrontComponent = 'frontComponent',
Role = 'role',
Skill = 'skill',
Agent = 'agent',
ConnectionProvider = 'connectionProvider',
View = 'view',
ViewField = 'viewField',
NavigationMenuItem = 'navigationMenuItem',
PageLayout = 'pageLayout',
PageLayoutTab = 'pageLayoutTab',
PageLayoutWidget = 'pageLayoutWidget',
CommandMenuItem = 'commandMenuItem',
TimelineActivityType = 'timelineActivityType',
}

View File

@@ -0,0 +1,14 @@
import {
type FieldMetadataComplexOption,
type FieldMetadataOptions,
} from '@/types/FieldMetadataOptions';
import { type FieldMetadataType } from '@/types/FieldMetadataType';
import { type TagColor } from '@/types/TagColor';
export type FieldManifestOptions<
TFieldMetadataType extends FieldMetadataType = FieldMetadataType,
> = TFieldMetadataType extends
| FieldMetadataType.SELECT
| FieldMetadataType.MULTI_SELECT
? (Omit<FieldMetadataComplexOption, 'color'> & { color?: TagColor | null })[]
: FieldMetadataOptions<TFieldMetadataType>;

Some files were not shown because too many files have changed in this diff Show More