@xentom/integration-framework 0.0.1 → 0.0.3

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
@@ -1,6 +1,6 @@
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
4
  /**
5
5
  * @internal
6
6
  */
@@ -15,4 +15,4 @@ export interface ExpressionControl<S = never> extends BaseControl<S> {
15
15
  */
16
16
  rows?: number;
17
17
  }
18
- 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,7 +1,9 @@
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> {
5
7
  /**
6
8
  * @internal
7
9
  */
@@ -10,16 +12,32 @@ export interface SelectControl<S = never, Options = SelectControlOptions<S> | Se
10
12
  * Defines the options available for selection.
11
13
  * Can be a static array of options or a callback function that returns options dynamically.
12
14
  *
13
- * @remarks `SelectControlOption[] | (opts: IntegrationOptions) => SelectControlOption[]`
15
+ * @remarks `SelectControlOption[] | (opts: SelectControlOptionsCallbackOptions) => SelectControlOption[]`
14
16
  */
15
- options: Options;
17
+ options: SelectControlOptions<ArrayElementType<S>> | SelectControlOptionsCallback<ArrayElementType<S>>;
18
+ /**
19
+ * Whether the select control allows multiple selections.
20
+ *
21
+ * @remarks `boolean`
22
+ */
23
+ multiple?: Multiple;
16
24
  /**
17
25
  * Placeholder text displayed when no option is selected.
18
26
  */
19
27
  placeholder?: string;
20
28
  }
21
29
  export type SelectControlOptions<S = never> = SelectControlOption<S>[];
22
- 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
+ }
23
41
  export interface SelectControlOption<S = never> {
24
42
  /**
25
43
  * The value associated with the option, which will be used as the pin's value.
@@ -32,8 +50,19 @@ export interface SelectControlOption<S = never> {
32
50
  */
33
51
  label?: string;
34
52
  /**
35
- * 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.
36
64
  */
37
65
  description?: string;
38
66
  }
39
- 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 {};
package/dist/env.d.ts CHANGED
@@ -1,5 +1,5 @@
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.
@@ -7,7 +7,7 @@ export interface Env<O = unknown> {
7
7
  *
8
8
  * @remarks `TextControl | SwitchControl | SelectControl`
9
9
  */
10
- control: TextControl<string> | SwitchControl<boolean> | SelectControl<string, SelectControlOptions<string>>;
10
+ control: TextControl<string> | SwitchControl<boolean> | SelectControl<string, false>;
11
11
  /**
12
12
  * Validation schema compatible with the Standard Schema specification.
13
13
  * Can be used with any validator that conforms to the Standard Schema interface.
@@ -23,6 +23,17 @@ export interface Env<O = unknown> {
23
23
  * @remarks `StandardSchemaV1`
24
24
  */
25
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;
26
37
  }
27
38
  export type EnvRecord = Record<string, Env>;
28
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';
@@ -1,8 +1,9 @@
1
+ import { type Auth, type AuthResponse } from './auth';
1
2
  import { type EnvRecord, type InferEnvRecordOutput } from './env';
2
- import { type InferNodeRecordOutput, type NodeRecord } from './nodes';
3
- import { type Serialize } from './utils';
3
+ import { type NodeRecord } from './nodes';
4
+ import { type PartialKeyValues, type Serialize } from './utils';
4
5
  import { type Webhook } from './webhook';
5
- export interface Integration<NR extends NodeRecord = NodeRecord, E extends EnvRecord = EnvRecord> {
6
+ export interface Integration<NR extends NodeRecord = NodeRecord, A extends Auth = Auth, E extends EnvRecord = EnvRecord> {
6
7
  /**
7
8
  * Nodes that are available in this integration.
8
9
  *
@@ -20,6 +21,47 @@ export interface Integration<NR extends NodeRecord = NodeRecord, E extends EnvRe
20
21
  * @remarks `Record<string, Node>`
21
22
  */
22
23
  nodes: NR;
24
+ /**
25
+ * Authentication method for this integration.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * auth: i.auth.basic(),
30
+ * ```
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * auth: i.auth.basic({
35
+ * username: { label: 'Username', description: 'Your username for authentication' },
36
+ * password: { label: 'Password', description: 'Your password for authentication' },
37
+ * }),
38
+ * ```
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * auth: i.auth.oauth2({
43
+ * authUrl: 'https://example.com/oauth/authorize',
44
+ * tokenUrl: 'https://example.com/oauth/token',
45
+ * scopes: ['read', 'write'],
46
+ * }),
47
+ * ```
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * auth: i.auth.token(),
52
+ * ```
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * auth: i.auth.token({
57
+ * token: { label: 'Token', description: 'Your token for authentication' },
58
+ * }),
59
+ * ```
60
+ *
61
+ * @remarks `Auth`
62
+ *
63
+ */
64
+ auth?: A;
23
65
  /**
24
66
  * Environment variables that are required by this integration.
25
67
  * These variables will be prompted for during integration setup
@@ -53,7 +95,7 @@ export interface Integration<NR extends NodeRecord = NodeRecord, E extends EnvRe
53
95
  * },
54
96
  * ```
55
97
  */
56
- start?: (opts: IntegrationOptions) => Promise<void> | void;
98
+ start?: (opts: IntegrationOptions<A, InferEnvRecordOutput<E>>) => Promise<void> | void;
57
99
  /**
58
100
  * This function is called when the integration stops.
59
101
  * You can clean up resources or save state here.
@@ -65,9 +107,11 @@ export interface Integration<NR extends NodeRecord = NodeRecord, E extends EnvRe
65
107
  * },
66
108
  * ```
67
109
  */
68
- stop?: (opts: IntegrationOptions) => Promise<void> | void;
110
+ stop?: (opts: PartialKeyValues<IntegrationOptions<A, InferEnvRecordOutput<E>>, 'state'>) => Promise<void> | void;
69
111
  }
70
- export interface IntegrationOptions {
112
+ export interface IntegrationOptions<A extends Auth = never, E extends Record<string, unknown> = Record<string, unknown>> {
113
+ auth: AuthResponse<A>;
114
+ env: E;
71
115
  state: IntegrationState;
72
116
  webhook: Webhook;
73
117
  }
@@ -79,10 +123,4 @@ export interface IntegrationOptions {
79
123
  export interface IntegrationState {
80
124
  }
81
125
  export type IntegrationDefinition = Serialize<Integration>;
82
- export interface InferIntegrationOutput<I extends Integration> {
83
- nodes: InferNodeRecordOutput<I['nodes']>;
84
- env: InferEnvRecordOutput<NonNullable<I['env']>>;
85
- }
86
- export declare function integration<NR extends NodeRecord<any>, E extends EnvRecord>(definition: Integration<NR, E>): Integration<NR, E> & {
87
- $infer: InferIntegrationOutput<Integration<NR, E>>;
88
- };
126
+ export declare function integration<NR extends NodeRecord<any>, A extends Auth = never, E extends EnvRecord = never>(definition: Integration<NR, A, E>): Integration<NR, A, E>;
@@ -1,5 +1,3 @@
1
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2
1
  export function integration(definition) {
3
- // @ts-expect-error - '$infer' is used solely for type inference and does not affect runtime
4
2
  return definition;
5
3
  }
@@ -6,12 +6,11 @@ export type BaseNodeOutputs = PinRecord<DataPin | ExecPin>;
6
6
  */
7
7
  export interface BaseNode<I extends BaseNodeInputs = BaseNodeInputs, O extends BaseNodeOutputs = BaseNodeOutputs> {
8
8
  /**
9
- * Category used to organize the node in a hierarchical structure.
10
- * Helps with grouping and filtering nodes in the UI.
11
- *
12
- * @remarks `{ path: string[] }`
9
+ * Specifies the group that this node belongs to within a hierarchy.
10
+ * Groups make it easier to organize and filter nodes in the user interface.
11
+ * You can create nested or expandable groups by separating levels with a slash (/).
13
12
  */
14
- category?: NodeCategory;
13
+ group?: string;
15
14
  /**
16
15
  * Optional display name for the node, used in the UI.
17
16
  * If not specified, the node's exported identifier will be used and automatically converted to title case.
@@ -39,13 +38,3 @@ export interface BaseNode<I extends BaseNodeInputs = BaseNodeInputs, O extends B
39
38
  */
40
39
  outputs?: O;
41
40
  }
42
- export interface NodeCategory {
43
- /**
44
- * Defines the category path used to group the node in the UI.
45
- * Each segment in the array represents a level in the category hierarchy.
46
- *
47
- * @example
48
- * ['AI', 'Text']
49
- */
50
- path: string[];
51
- }
@@ -28,15 +28,7 @@ export declare enum NodeType {
28
28
  */
29
29
  Pure = "pure"
30
30
  }
31
- /**
32
- * Collection of utilities for defining workflow nodes.
33
- *
34
- * Includes:
35
- * - `trigger`: Defines a trigger that starts a workflow in response to internal or external events.
36
- * - `callable`: Defines a node that performs side effects and explicitly control workflow execution.
37
- * - `pure`: Defines a side-effect-free, deterministic node that computes outputs from inputs.
38
- */
39
- export declare const nodes: {
31
+ export interface Nodes {
40
32
  /**
41
33
  * Defines a trigger that starts a workflow in response to internal or external events
42
34
  * such as incoming webhooks, scheduled timers, or custom event sources.
@@ -108,4 +100,17 @@ export declare const nodes: {
108
100
  * ```
109
101
  */
110
102
  pure: PureNodeBuilder;
111
- };
103
+ /**
104
+ * Creates a group of nodes with a common prefix.
105
+ */
106
+ group: (name: string) => Omit<Nodes, 'group'>;
107
+ }
108
+ /**
109
+ * Collection of utilities for defining workflow nodes.
110
+ *
111
+ * Includes:
112
+ * - `trigger`: Defines a trigger that starts a workflow in response to internal or external events.
113
+ * - `callable`: Defines a node that performs side effects and explicitly control workflow execution.
114
+ * - `pure`: Defines a side-effect-free, deterministic node that computes outputs from inputs.
115
+ */
116
+ export declare const nodes: Nodes;
@@ -45,4 +45,21 @@ export const nodes = {
45
45
  ...definition,
46
46
  type: NodeType.Pure,
47
47
  }),
48
+ group: (name) => ({
49
+ trigger: (definition) => ({
50
+ group: name,
51
+ ...definition,
52
+ type: NodeType.Trigger,
53
+ }),
54
+ callable: (definition) => ({
55
+ group: name,
56
+ ...definition,
57
+ type: NodeType.Callable,
58
+ }),
59
+ pure: (definition) => ({
60
+ group: name,
61
+ ...definition,
62
+ type: NodeType.Pure,
63
+ }),
64
+ }),
48
65
  };
@@ -70,7 +70,7 @@ export interface TriggerNode<I extends TriggerNodeInputs = TriggerNodeInputs, O
70
70
  */
71
71
  subscribe(opts: TriggerSubscribeOptions<I, O>): TriggerSubscribeCleanup;
72
72
  }
73
- export type TriggerSubscribeCleanup = Promise<(() => void) | void> | (() => void) | void;
73
+ export type TriggerSubscribeCleanup = (() => Promise<void>) | (() => void) | Promise<void> | Promise<() => Promise<void>> | Promise<() => void> | void;
74
74
  export interface TriggerSubscribeOptions<I extends TriggerNodeInputs, O extends TriggerNodeOutputs> {
75
75
  /**
76
76
  * Information about the node instance being executed.
@@ -1,6 +1,7 @@
1
1
  import { type Node, type NodeRecord } from '.';
2
2
  import { type InferPinRecordInput, type InferPinRecordOutput, type PinRecord } from '../pins';
3
3
  export declare const DefaultExecPinName = "__exec";
4
+ export declare const DefaultErrorPinName = "__error";
4
5
  export type InferNodeRecordOutput<R extends NodeRecord> = {
5
6
  [K in keyof R]: InferNodeOutput<R[K]>;
6
7
  };
@@ -1 +1,2 @@
1
1
  export const DefaultExecPinName = '__exec';
2
+ export const DefaultErrorPinName = '__error';
@@ -1,10 +1,11 @@
1
1
  import { type StandardSchemaV1 } from '@standard-schema/spec';
2
2
  import { type PinType } from '.';
3
+ import { type Auth } from '../auth';
3
4
  import { type Control } from '../controls';
4
5
  import { type IntegrationOptions } from '../integration';
5
6
  import { type ConditionalOptional } from '../utils';
6
7
  import { type BasePin } from './base';
7
- export interface DataPin<Input = any, Output = Input, Optional extends boolean = boolean> extends BasePin {
8
+ export interface DataPin<Input = any, Output = Input, Optional extends boolean = boolean, MultiSelect extends boolean = boolean> extends BasePin {
8
9
  /**
9
10
  * @internal
10
11
  */
@@ -24,7 +25,7 @@ export interface DataPin<Input = any, Output = Input, Optional extends boolean =
24
25
  *
25
26
  * @remarks `StandardSchemaV1 | (opts: IntegrationOptions) => StandardSchemaV1`
26
27
  */
27
- schema?: StandardSchemaV1<Input, Output> | ((opts: IntegrationOptions) => StandardSchemaV1<Input, Output>);
28
+ schema?: DataPinSchema<Input, Output>;
28
29
  /**
29
30
  * Optional control configuration that defines how the pin should be rendered in the UI.
30
31
  * Supports various input types such as text fields, select dropdowns, and toggles.
@@ -38,7 +39,7 @@ export interface DataPin<Input = any, Output = Input, Optional extends boolean =
38
39
  *
39
40
  * @remarks `Control | false`
40
41
  */
41
- control?: false | Control<ConditionalOptional<Optional, Input & {}>>;
42
+ control?: false | Control<ConditionalOptional<Optional, Input>, MultiSelect>;
42
43
  /**
43
44
  * Optional examples that provide users with predefined values for the pin.
44
45
  * Each example includes a title and a value to illustrate the expected input format
@@ -81,6 +82,22 @@ export interface DataPin<Input = any, Output = Input, Optional extends boolean =
81
82
  */
82
83
  optional?: Optional;
83
84
  }
85
+ export interface DataPinExtendable<Input = any, Output = Input, Optional extends boolean = boolean, MultiSelect extends boolean = boolean> extends DataPin<Input, Output, Optional, MultiSelect> {
86
+ /**
87
+ * A function that builds a new pin with the given definition.
88
+ *
89
+ * @example
90
+ * ```ts
91
+ * i.pins.data().with({
92
+ * description: 'A message to send',
93
+ * })
94
+ * ```
95
+ *
96
+ * @remarks `DataPinBuilder`
97
+ */
98
+ with: DataPinBuilder<Input, Output, Optional, MultiSelect>;
99
+ }
100
+ export type DataPinSchema<Input = any, Output = Input> = StandardSchemaV1<Input, Output> | ((opts: IntegrationOptions<Auth>) => StandardSchemaV1<Input, Output>);
84
101
  export interface DataPinExample<I> {
85
102
  /**
86
103
  * A human-readable title for the example, providing context or a brief description.
@@ -94,18 +111,4 @@ export interface DataPinExample<I> {
94
111
  */
95
112
  value: I;
96
113
  }
97
- export type DataPinBuilder<ParentInput = any, ParentOutput = ParentInput, ParentOptional extends boolean = false> = <Input = ParentInput, Output = undefined extends ParentOutput ? Input : ParentOutput, Optional extends boolean = ParentOptional>(definition?: Omit<DataPin<Input, Output, Optional>, 'type' | 'with'>) => NoInfer<DataPin<ConditionalOptional<Optional, Input>, ConditionalOptional<Optional, Output>, Optional> & {
98
- /**
99
- * A function that builds a new pin with the given definition.
100
- *
101
- * @example
102
- * ```ts
103
- * i.pins.data().with({
104
- * description: 'A message to send',
105
- * })
106
- * ```
107
- *
108
- * @remarks `DataPinBuilder`
109
- */
110
- with: DataPinBuilder<Input, Output, Optional>;
111
- }>;
114
+ export type DataPinBuilder<ParentInput = any, ParentOutput = ParentInput, ParentOptional extends boolean = false, ParentMultiSelect extends boolean = false> = <Input = ParentInput, Output = undefined extends ParentOutput ? Input : ParentOutput, Optional extends boolean = ParentOptional, MultiSelect extends boolean = ParentMultiSelect>(definition?: Omit<DataPin<Input, Output, Optional, MultiSelect>, 'type' | 'with'>) => NoInfer<DataPinExtendable<ConditionalOptional<Optional, Input>, ConditionalOptional<Optional, Output>, Optional, MultiSelect>>;
@@ -52,7 +52,7 @@ export interface ExecPin<PR extends PinRecord<DataPin> = PinRecord<DataPin>> ext
52
52
  */
53
53
  outputs?: PR;
54
54
  }
55
- export type ExecPinBuilder<ParentPR extends PinRecord<DataPin> = PinRecord<DataPin>> = <PR extends PinRecord<DataPin> = ParentPR>(definition?: Omit<ExecPin<PR>, 'type' | 'with'>) => ExecPin<PR> & {
55
+ export interface ExecPinExtendable<PR extends PinRecord<DataPin> = PinRecord<DataPin>> extends ExecPin<PR> {
56
56
  /**
57
57
  * A function that builds a new pin with the given definition.
58
58
  *
@@ -66,4 +66,5 @@ export type ExecPinBuilder<ParentPR extends PinRecord<DataPin> = PinRecord<DataP
66
66
  * @remarks `ExecPinBuilder`
67
67
  */
68
68
  with: ExecPinBuilder<PR>;
69
- };
69
+ }
70
+ export type ExecPinBuilder<ParentPR extends PinRecord<DataPin> = PinRecord<DataPin>> = <PR extends PinRecord<DataPin> = ParentPR>(definition?: Omit<ExecPin<PR>, 'type' | 'with'>) => ExecPinExtendable<PR>;
@@ -36,6 +36,12 @@ export declare const pins: {
36
36
  * ```
37
37
  *
38
38
  * @example
39
+ * Using a type parameter when no schema is defined:
40
+ * ```ts
41
+ * i.pins.data<string>()
42
+ * ```
43
+ *
44
+ * @example
39
45
  * Customizing the pin with additional properties:
40
46
  * ```ts
41
47
  * i.pins.data({
@@ -11,9 +11,11 @@ export type FlattenExecPinOutputs<R extends PinRecord> = {
11
11
  }[keyof R];
12
12
  export type GetExecPinOutputType<R extends PinRecord, FlatKey extends string> = FlatKey extends `${infer ExecKey}.${infer OutputKey}` ? ExecKey extends keyof R ? R[ExecKey] extends ExecPin<infer O> ? OutputKey extends keyof O ? InferPinOutput<O[OutputKey]> : never : never : never : never;
13
13
  export type InferPinOutput<P extends Pin> = P extends DataPin<infer I, infer O> ? (0 extends 1 & O ? I : O) : never;
14
- export type InferPinRecordInput<R extends PinRecord> = {
14
+ export type InferPinRecordInput<R extends PinRecord> = InferPinRecordInputRequired<R> & InferPinRecordInputOptional<R>;
15
+ export type InferPinRecordInputRequired<R extends PinRecord> = {
15
16
  [K in keyof R as R[K] extends DataPin ? undefined extends InferPinInput<R[K]> ? never : K : never]: InferPinInput<R[K]>;
16
- } & {
17
+ };
18
+ export type InferPinRecordInputOptional<R extends PinRecord> = {
17
19
  [K in keyof R as R[K] extends DataPin ? undefined extends InferPinInput<R[K]> ? K : never : never]?: InferPinInput<R[K]>;
18
20
  };
19
21
  export type InferPinInput<P extends Pin> = P extends DataPin<infer I, infer _> ? I : never;
package/dist/utils.d.ts CHANGED
@@ -8,3 +8,4 @@ export type SerializeObject<T extends object> = {
8
8
  [K in keyof T]: Serialize<T[K]>;
9
9
  };
10
10
  export type ConditionalOptional<C extends boolean, V> = C extends true ? V | undefined : Exclude<V, undefined>;
11
+ export type PartialKeyValues<T, K extends keyof T> = Omit<T, K> & Record<K, Partial<T[K]>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xentom/integration-framework",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -25,11 +25,11 @@
25
25
  "@standard-schema/spec": "^1.0.0"
26
26
  },
27
27
  "devDependencies": {
28
- "@types/bun": "^1.2.19",
28
+ "@types/bun": "^1.3.0",
29
29
  "@xentom/style-guide": "^0.0.0",
30
- "eslint": "^9.32.0",
30
+ "eslint": "^9.37.0",
31
31
  "prettier": "^3.6.2",
32
- "typescript": "^5.9.2"
32
+ "typescript": "^5.9.3"
33
33
  },
34
34
  "prettier": "@xentom/style-guide/prettier"
35
35
  }