@xentom/integration-framework 0.0.0 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,203 @@
1
+ ![Banner](https://github.com/user-attachments/assets/91ba6f77-5d57-4c2e-9d70-132a5e0c1d99)
2
+
3
+ # Xentom Integration Framework
4
+
5
+ > Build powerful, type-safe workflow integrations with a declarative API
6
+
7
+ Welcome to the Xentom Integration Framework! This package provides everything you need to create composable, type-safe workflow integrations that process data through interconnected nodes.
8
+
9
+ ## Why This Framework?
10
+
11
+ - **🔒 Type Safety** - Heavy use of TypeScript generics and inference means fewer bugs and better IDE support
12
+ - **📝 Declarative** - Define what you want, not how to achieve it
13
+ - **🧩 Composable** - Build complex workflows from simple, reusable components
14
+ - **✅ Standard Schema** - Compatible with any validation library using the Standard Schema spec
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ bun install @xentom/integration-framework
20
+ ```
21
+
22
+ ## Quick Start
23
+
24
+ ```typescript
25
+ import * as i from '@xentom/integration-framework';
26
+
27
+ export default i.integration({
28
+ // Authentication configuration
29
+ auth: i.auth.token({
30
+ control: i.controls.text({
31
+ label: 'API Key',
32
+ placeholder: 'key-...',
33
+ }),
34
+ }),
35
+
36
+ // Environment variables
37
+ env: {
38
+ SERVER_URL: i.env({
39
+ control: i.controls.text({
40
+ label: 'Server URL',
41
+ placeholder: 'https://example.com',
42
+ }),
43
+ }),
44
+ },
45
+
46
+ // Your workflow nodes
47
+ nodes: {
48
+ // Trigger: starts the workflow
49
+ webhook: i.nodes.trigger({
50
+ outputs: {
51
+ payload: i.pins.data(),
52
+ },
53
+ subscribe({ next, webhook }) {
54
+ const unsubscribe = webhook.subscribe(async (req) => {
55
+ const payload = await req.json();
56
+ next({ payload });
57
+ return new Response('OK');
58
+ });
59
+ return () => unsubscribe();
60
+ },
61
+ }),
62
+
63
+ // Callable: processes data with side effects
64
+ apiCall: i.nodes.callable({
65
+ inputs: {
66
+ url: i.pins.data({
67
+ control: i.controls.text({
68
+ label: 'API Endpoint',
69
+ }),
70
+ }),
71
+ },
72
+ outputs: {
73
+ data: i.pins.data(),
74
+ },
75
+ async run({ inputs, next }) {
76
+ const response = await fetch(inputs.url);
77
+ const data = await response.json();
78
+ next({ data });
79
+ },
80
+ }),
81
+
82
+ // Pure: transforms data without side effects
83
+ transform: i.nodes.pure({
84
+ inputs: {
85
+ data: i.pins.data(),
86
+ },
87
+ outputs: {
88
+ result: i.pins.data(),
89
+ },
90
+ run({ inputs, outputs }) {
91
+ outputs.result = inputs.data.toUpperCase();
92
+ },
93
+ }),
94
+ },
95
+ });
96
+ ```
97
+
98
+ ## Core Concepts
99
+
100
+ ### Node Types
101
+
102
+ The framework provides three types of nodes:
103
+
104
+ - **Trigger Nodes** - Entry points that listen for events and start workflows
105
+ - **Callable Nodes** - Processing units that perform operations with side effects
106
+ - **Pure Nodes** - Computational units that transform data without side effects
107
+
108
+ ### Pin System
109
+
110
+ - **Data Pins** - Handle information flow between nodes
111
+ - **Exec Pins** - Control execution flow for branching and iteration
112
+
113
+ ### Controls
114
+
115
+ Rich UI controls for user input:
116
+
117
+ - `i.controls.text()` - Single/multi-line text input
118
+ - `i.controls.expression()` - JavaScript expression editor
119
+ - `i.controls.select()` - Dropdown with static or dynamic options
120
+ - `i.controls.switch()` - Boolean toggle
121
+
122
+ ### Lifecycle Hooks
123
+
124
+ ```typescript
125
+ export default i.integration({
126
+ // Initialize shared resources
127
+ async start({ state, env }) {
128
+ state.apiClient = new ApiClient(env.API_KEY);
129
+ },
130
+
131
+ // Clean up resources
132
+ async stop({ state }) {
133
+ await state.apiClient.close();
134
+ },
135
+
136
+ nodes: {
137
+ /* ... */
138
+ },
139
+ });
140
+ ```
141
+
142
+ ## Development
143
+
144
+ ```bash
145
+ # Build your integration
146
+ bun run build
147
+
148
+ # Type checking
149
+ bun run typecheck
150
+
151
+ # Linting
152
+ bun run lint
153
+ ```
154
+
155
+ ## Documentation
156
+
157
+ For comprehensive documentation, see [Xentom Integration Docs](https://xentom.com/docs/integration) which includes:
158
+
159
+ - Detailed node type explanations
160
+ - Pin system deep dive
161
+ - Control system reference
162
+ - Error handling guidelines
163
+ - Advanced patterns and examples
164
+ - Testing guidelines
165
+
166
+ ## Features
167
+
168
+ - **Type Inference** - Full TypeScript support with automatic type inference
169
+ - **Environment Variables** - Secure configuration with validation
170
+ - **State Management** - Shared state across all nodes
171
+ - **Webhook Support** - Built-in webhook handling for trigger nodes
172
+ - **Schema Validation** - Standard Schema compatible validation
173
+ - **Dynamic Options** - Async option loading for select controls
174
+ - **Rich Controls** - Multiple control types with syntax highlighting
175
+ - **Error Handling** - Automatic error propagation and handling
176
+
177
+ ## Example Use Cases
178
+
179
+ - **API Integrations** - Connect to external APIs and process responses
180
+ - **Data Processing** - Transform and validate data through pipelines
181
+ - **Webhook Handlers** - Receive and process webhook events
182
+ - **Scheduled Tasks** - Run workflows on timers or schedules
183
+ - **ETL Workflows** - Extract, transform, and load data
184
+
185
+ ## Philosophy
186
+
187
+ This framework is built on three core principles:
188
+
189
+ 1. **Type Safety First** - Catch errors at compile time, not runtime
190
+ 2. **Declarative Over Imperative** - Focus on what, not how
191
+ 3. **Composition Over Configuration** - Build complex from simple
192
+
193
+ ## Contributing
194
+
195
+ We welcome contributions! Please ensure all tests pass and code is properly typed.
196
+
197
+ ## License
198
+
199
+ See the root package for license information.
200
+
201
+ ---
202
+
203
+ Built with ❤️ by the Xentom team
@@ -0,0 +1,56 @@
1
+ import { type StandardSchemaV1 } from '@standard-schema/spec';
2
+ import { type AuthType } from '.';
3
+ import { type TextControl } from '../controls';
4
+ export interface BasicAuth {
5
+ type: AuthType.Basic;
6
+ username?: {
7
+ /**
8
+ * Optional configuration that defines how the username field appears in the user interface.
9
+ * Only text input is supported.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * control: i.controls.text({
14
+ * label: 'Username',
15
+ * })
16
+ * ```
17
+ *
18
+ * @remarks Uses `TextControl`
19
+ */
20
+ control?: TextControl<string>;
21
+ };
22
+ password?: {
23
+ /**
24
+ * Optional configuration that defines how the password field appears in the user interface.
25
+ * Only text input is supported.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * control: i.controls.text({
30
+ * label: 'Password',
31
+ * })
32
+ * ```
33
+ *
34
+ * @remarks Uses `TextControl`
35
+ */
36
+ control?: TextControl<string>;
37
+ };
38
+ /**
39
+ * Validation schema compatible with the Standard Schema specification.
40
+ * Can be used with any validator that conforms to the Standard Schema interface.
41
+ *
42
+ * @see {@link https://github.com/standard-schema/standard-schema}
43
+ *
44
+ * @remarks `StandardSchemaV1`
45
+ */
46
+ schema?: StandardSchemaV1<{
47
+ username: string;
48
+ password: string;
49
+ }>;
50
+ }
51
+ export interface BasicAuthResponse {
52
+ type: AuthType.Basic;
53
+ username: string;
54
+ password: string;
55
+ }
56
+ export type BasicAuthBuilder = (definition?: Omit<BasicAuth, 'type'>) => BasicAuth;
File without changes
@@ -0,0 +1,18 @@
1
+ import { type BasicAuth, type BasicAuthBuilder, type BasicAuthResponse } from './basic';
2
+ import { type OAuth2, type OAuth2Builder, type OAuth2Response } from './oauth2';
3
+ import { type TokenAuth, type TokenAuthBuilder, type TokenAuthResponse } from './token';
4
+ export * from './basic';
5
+ export * from './oauth2';
6
+ export * from './token';
7
+ export declare enum AuthType {
8
+ Basic = "basic",
9
+ OAuth2 = "oauth2",
10
+ Token = "token"
11
+ }
12
+ export type Auth = BasicAuth | OAuth2 | TokenAuth;
13
+ export type AuthResponse<A extends Auth> = A extends BasicAuth ? BasicAuthResponse : A extends OAuth2 ? OAuth2Response : A extends TokenAuth ? TokenAuthResponse : never;
14
+ export declare const auth: {
15
+ basic: BasicAuthBuilder;
16
+ oauth2: OAuth2Builder;
17
+ token: TokenAuthBuilder;
18
+ };
@@ -0,0 +1,23 @@
1
+ export * from './basic';
2
+ export * from './oauth2';
3
+ export * from './token';
4
+ export var AuthType;
5
+ (function (AuthType) {
6
+ AuthType["Basic"] = "basic";
7
+ AuthType["OAuth2"] = "oauth2";
8
+ AuthType["Token"] = "token";
9
+ })(AuthType || (AuthType = {}));
10
+ export const auth = {
11
+ basic: (definition) => ({
12
+ ...definition,
13
+ type: AuthType.Basic,
14
+ }),
15
+ oauth2: (definition) => ({
16
+ ...definition,
17
+ type: AuthType.OAuth2,
18
+ }),
19
+ token: (definition) => ({
20
+ ...definition,
21
+ type: AuthType.Token,
22
+ }),
23
+ };
@@ -0,0 +1,26 @@
1
+ import { type AuthType } from '.';
2
+ import { type IntegrationOptions } from '../integration';
3
+ export declare enum OAuth2GrantType {
4
+ AuthorizationCode = "authorization_code",
5
+ ClientCredentials = "client_credentials"
6
+ }
7
+ export declare enum OAuth2PKCEMethod {
8
+ Plain = "plain",
9
+ S256 = "S256"
10
+ }
11
+ export interface OAuth2 {
12
+ type: AuthType.OAuth2;
13
+ authUrl: string;
14
+ tokenUrl: string;
15
+ scopes: string[];
16
+ grantType?: OAuth2GrantType;
17
+ pkce?: boolean;
18
+ pkceMethod?: OAuth2PKCEMethod;
19
+ onAccessTokenUpdated?: (opts: Pick<IntegrationOptions<OAuth2>, 'auth' | 'state'>) => void;
20
+ }
21
+ export interface OAuth2Response {
22
+ type: AuthType.OAuth2;
23
+ accessToken: string;
24
+ accessTokenExpiresAt?: Date;
25
+ }
26
+ export type OAuth2Builder = (definition: Omit<OAuth2, 'type'>) => OAuth2;
@@ -0,0 +1,10 @@
1
+ export var OAuth2GrantType;
2
+ (function (OAuth2GrantType) {
3
+ OAuth2GrantType["AuthorizationCode"] = "authorization_code";
4
+ OAuth2GrantType["ClientCredentials"] = "client_credentials";
5
+ })(OAuth2GrantType || (OAuth2GrantType = {}));
6
+ export var OAuth2PKCEMethod;
7
+ (function (OAuth2PKCEMethod) {
8
+ OAuth2PKCEMethod["Plain"] = "plain";
9
+ OAuth2PKCEMethod["S256"] = "S256";
10
+ })(OAuth2PKCEMethod || (OAuth2PKCEMethod = {}));
@@ -0,0 +1,40 @@
1
+ import { type StandardSchemaV1 } from '@standard-schema/spec';
2
+ import { type AuthType } from '.';
3
+ import { type TextControl } from '../controls';
4
+ export interface TokenAuth {
5
+ type: AuthType.Token;
6
+ /**
7
+ * Validation schema compatible with the Standard Schema specification.
8
+ * Can be used with any validator that conforms to the Standard Schema interface.
9
+ *
10
+ * @see {@link https://github.com/standard-schema/standard-schema}
11
+ *
12
+ * @example
13
+ * Using Valibot for validation:
14
+ * ```ts
15
+ * v.pipe(v.string(), v.startsWith('sk-'))
16
+ * ```
17
+ *
18
+ * @remarks `StandardSchemaV1`
19
+ */
20
+ schema?: StandardSchemaV1<string>;
21
+ /**
22
+ * Optional control configuration that defines how the token should be rendered in the UI.
23
+ * Supports only text input.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * control: i.controls.text({
28
+ * label: 'API Key',
29
+ * })
30
+ * ```
31
+ *
32
+ * @remarks `TextControl`
33
+ */
34
+ control?: TextControl<string>;
35
+ }
36
+ export interface TokenAuthResponse {
37
+ type: AuthType.Token;
38
+ token: string;
39
+ }
40
+ export type TokenAuthBuilder = (definition?: Omit<TokenAuth, 'type'>) => TokenAuth;
File without changes
@@ -9,6 +9,8 @@ export interface BaseControl<S = never> {
9
9
  description?: string;
10
10
  /**
11
11
  * The default value used when no user input is provided.
12
+ *
13
+ * @remarks `unknown`
12
14
  */
13
15
  defaultValue?: S;
14
16
  }
@@ -1,6 +1,9 @@
1
1
  import { type ControlType } from '.';
2
2
  import { type BaseControl } from './base';
3
- export interface ExpressionControl<S = never> extends BaseControl<S> {
3
+ export interface ExpressionControl<S = unknown> extends BaseControl<S> {
4
+ /**
5
+ * @internal
6
+ */
4
7
  type: ControlType.Expression;
5
8
  /**
6
9
  * Placeholder text displayed when the input is empty.
@@ -12,4 +15,4 @@ export interface ExpressionControl<S = never> extends BaseControl<S> {
12
15
  */
13
16
  rows?: number;
14
17
  }
15
- export type ExpressionControlBuilder = <S = never>(definition?: Omit<ExpressionControl<S>, 'type'>) => ExpressionControl<NoInfer<S>>;
18
+ export type ExpressionControlBuilder = <S = any>(definition?: Omit<ExpressionControl<S>, 'type'>) => ExpressionControl<NoInfer<S>>;
@@ -13,7 +13,7 @@ export declare enum ControlType {
13
13
  Select = "select",
14
14
  Switch = "switch"
15
15
  }
16
- export type Control<S = never> = TextControl<S> | ExpressionControl<S> | SelectControl<S> | SwitchControl<S>;
16
+ export type Control<S = never, MultiSelect extends boolean = boolean> = TextControl<S> | ExpressionControl<S> | SelectControl<S, MultiSelect> | SwitchControl<S>;
17
17
  export declare const controls: {
18
18
  /**
19
19
  * Text control fields allow users to input plain text, either for an node pin or an environment variable.
@@ -96,6 +96,19 @@ export declare const controls: {
96
96
  * { value: 'option2', label: 'Option 2' },
97
97
  * ],
98
98
  * });
99
+ * ```
100
+ *
101
+ * @example
102
+ * Static options with multiple selections:
103
+ * ```ts
104
+ * i.controls.select({
105
+ * multiple: true,
106
+ * options: [
107
+ * { value: 'option1', label: 'Option 1' },
108
+ * { value: 'option2', label: 'Option 2' },
109
+ * ],
110
+ * });
111
+ * ```
99
112
  *
100
113
  * @example
101
114
  * Dynamic options using a callback:
@@ -1,23 +1,48 @@
1
1
  import { type ControlType } from '.';
2
+ import { type Auth } from '../auth';
2
3
  import { type IntegrationOptions } from '../integration';
3
4
  import { type BaseControl } from './base';
4
- export interface SelectControl<S = never, Options = SelectControlOptions<S> | SelectControlOptionsCallback<S>> extends BaseControl<S> {
5
+ type ArrayElementType<T> = T extends readonly (infer U)[] ? U : T;
6
+ export interface SelectControl<S = never, Multiple extends boolean = boolean> extends BaseControl<S> {
7
+ /**
8
+ * @internal
9
+ */
5
10
  type: ControlType.Select;
6
11
  /**
7
12
  * Defines the options available for selection.
8
13
  * Can be a static array of options or a callback function that returns options dynamically.
14
+ *
15
+ * @remarks `SelectControlOption[] | (opts: SelectControlOptionsCallbackOptions) => SelectControlOption[]`
16
+ */
17
+ options: SelectControlOptions<ArrayElementType<S>> | SelectControlOptionsCallback<ArrayElementType<S>>;
18
+ /**
19
+ * Whether the select control allows multiple selections.
20
+ *
21
+ * @remarks `boolean`
9
22
  */
10
- options: Options;
23
+ multiple?: Multiple;
11
24
  /**
12
25
  * Placeholder text displayed when no option is selected.
13
26
  */
14
27
  placeholder?: string;
15
28
  }
16
29
  export type SelectControlOptions<S = never> = SelectControlOption<S>[];
17
- export type SelectControlOptionsCallback<S = never> = (opts: IntegrationOptions) => Promise<SelectControlOption<S>[]> | SelectControlOption<S>[];
30
+ export type SelectControlOptionsCallback<S = never> = (opts: SelectControlOptionsCallbackOptions) => Promise<SelectControlOption<S>[]> | SelectControlOption<S>[];
31
+ export interface SelectControlOptionsCallbackOptions extends IntegrationOptions<Auth> {
32
+ node: {
33
+ inputs: Record<string, unknown>;
34
+ outputs: Record<string, unknown>;
35
+ };
36
+ /**
37
+ * The search query used to filter the options.
38
+ */
39
+ search?: string;
40
+ }
18
41
  export interface SelectControlOption<S = never> {
19
42
  /**
20
43
  * The value associated with the option, which will be used as the pin's value.
44
+ *
45
+ * @remarks `unknown`
21
46
  */
22
47
  value: S;
23
48
  /**
@@ -25,8 +50,19 @@ export interface SelectControlOption<S = never> {
25
50
  */
26
51
  label?: string;
27
52
  /**
28
- * An optional description providing additional context about the option.
53
+ * Text or symbol to display before the option label.
54
+ * This is only shown in the option list, not when the option is selected.
55
+ */
56
+ prefix?: string;
57
+ /**
58
+ * Text or symbol to display after the option label.
59
+ * This is only shown in the option list, not when the option is selected.
60
+ */
61
+ suffix?: string;
62
+ /**
63
+ * Additional text that gives more context or detail about the option.
29
64
  */
30
65
  description?: string;
31
66
  }
32
- export type SelectControlBuilder = <const S = never>(definition: Omit<SelectControl<S>, 'type'>) => SelectControl<S>;
67
+ export type SelectControlBuilder = <S = never, Multiple extends boolean = false>(definition: Omit<SelectControl<Multiple extends true ? S[] : S, Multiple>, 'type'>) => SelectControl<Multiple extends true ? S[] : S, Multiple>;
68
+ export {};
@@ -1,6 +1,9 @@
1
1
  import { type ControlType } from '.';
2
2
  import { type BaseControl } from './base';
3
3
  export interface SwitchControl<S = never> extends BaseControl<S> {
4
+ /**
5
+ * @internal
6
+ */
4
7
  type: ControlType.Switch;
5
8
  }
6
9
  export type SwitchControlBuilder = (definition?: Omit<SwitchControl<boolean>, 'type'>) => SwitchControl<boolean>;
@@ -1,6 +1,9 @@
1
1
  import { type ControlType } from '.';
2
2
  import { type BaseControl } from './base';
3
3
  export interface TextControl<S = string> extends BaseControl<S> {
4
+ /**
5
+ * @internal
6
+ */
4
7
  type: ControlType.Text;
5
8
  /**
6
9
  * Specifies the syntax highlighting language used in the text control, if applicable.
package/dist/env.d.ts CHANGED
@@ -1,11 +1,13 @@
1
1
  import { type StandardSchemaV1 } from '@standard-schema/spec';
2
- import { type SelectControl, type SelectControlOptions, type SwitchControl, type TextControl } from './controls';
2
+ import { type SelectControl, type SwitchControl, type TextControl } from './controls';
3
3
  export interface Env<O = unknown> {
4
4
  /**
5
5
  * Defines the UI control used to configure the environment variable.
6
6
  * Can be a text input, a switch (boolean), or a select dropdown.
7
+ *
8
+ * @remarks `TextControl | SwitchControl | SelectControl`
7
9
  */
8
- control: TextControl<string> | SwitchControl<boolean> | SelectControl<string, SelectControlOptions<string>>;
10
+ control: TextControl<string> | SwitchControl<boolean> | SelectControl<string, false>;
9
11
  /**
10
12
  * Validation schema compatible with the Standard Schema specification.
11
13
  * Can be used with any validator that conforms to the Standard Schema interface.
@@ -17,8 +19,21 @@ export interface Env<O = unknown> {
17
19
  * ```ts
18
20
  * v.pipe(v.string(), v.startsWith('sk-'))
19
21
  * ```
22
+ *
23
+ * @remarks `StandardSchemaV1`
20
24
  */
21
25
  schema?: StandardSchemaV1<string | undefined, O>;
26
+ /**
27
+ * Whether the environment variable is optional.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * optional: true,
32
+ * ```
33
+ *
34
+ * @remarks `boolean`
35
+ */
36
+ optional?: boolean;
22
37
  }
23
38
  export type EnvRecord = Record<string, Env>;
24
39
  export type InferEnvRecordOutput<ER extends EnvRecord> = {
package/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
- export * from './nodes';
1
+ export * from './auth';
2
2
  export * from './controls';
3
+ export * from './nodes';
4
+ export * from './pins';
3
5
  export * from './env';
4
6
  export * from './generic';
5
7
  export * from './integration';
6
- export * from './pins';
7
8
  export * from './utils';
8
9
  export * from './webhook';
package/dist/index.js CHANGED
@@ -1,8 +1,9 @@
1
- export * from './nodes';
1
+ export * from './auth';
2
2
  export * from './controls';
3
+ export * from './nodes';
4
+ export * from './pins';
3
5
  export * from './env';
4
6
  export * from './generic';
5
7
  export * from './integration';
6
- export * from './pins';
7
8
  export * from './utils';
8
9
  export * from './webhook';