@newhomestar/sdk 0.8.3 → 0.8.5

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.
@@ -12,6 +12,18 @@ export interface ResolvedCredentials {
12
12
  authMode: AuthMode;
13
13
  /** mTLS agent (if auth_mode = 'mtls') — use for ALL API requests to that provider */
14
14
  httpsAgent?: https.Agent;
15
+ /**
16
+ * Per-connection configuration values filled in by the user during setup.
17
+ * Populated from `app_integration_configs.config` via the auth server's
18
+ * credential resolution response. Empty `{}` if no config exists.
19
+ */
20
+ config?: Record<string, unknown>;
21
+ /**
22
+ * Whether the user has completed all required configuration fields.
23
+ * `true` when every required configField has a value; `false` otherwise.
24
+ * `undefined` if the integration has no configFields.
25
+ */
26
+ configComplete?: boolean;
15
27
  }
16
28
  export declare class IntegrationNotFoundError extends Error {
17
29
  constructor(slug: string);
@@ -262,6 +262,8 @@ export async function resolveCredentialsViaHttp(authBaseUrl, slug, bearerToken,
262
262
  expiresAt,
263
263
  integrationId: `http:${slug}`,
264
264
  authMode: "standard",
265
+ config: creds.config ?? {},
266
+ configComplete: creds.configComplete ?? undefined,
265
267
  };
266
268
  }
267
269
  // Standard auth but no access token — user hasn't authorized yet
@@ -303,6 +305,8 @@ export async function resolveCredentialsViaHttp(authBaseUrl, slug, bearerToken,
303
305
  integrationId: `http:${slug}`,
304
306
  authMode: creds.authMode,
305
307
  httpsAgent,
308
+ config: creds.config ?? {},
309
+ configComplete: creds.configComplete ?? undefined,
306
310
  };
307
311
  }
308
312
  /**
@@ -459,6 +463,8 @@ export async function resolveCredentialsViaServiceToken(authBaseUrl, slug, servi
459
463
  expiresAt,
460
464
  integrationId: `service:${slug}`,
461
465
  authMode: "standard",
466
+ config: creds.config ?? {},
467
+ configComplete: creds.configComplete ?? undefined,
462
468
  };
463
469
  }
464
470
  throw new Error(`[nova-sdk] Standard auth integration "${slug}" requires user context (JWT). ` +
@@ -498,6 +504,8 @@ export async function resolveCredentialsViaServiceToken(authBaseUrl, slug, servi
498
504
  integrationId: `service:${slug}`,
499
505
  authMode: creds.authMode,
500
506
  httpsAgent,
507
+ config: creds.config ?? {},
508
+ configComplete: creds.configComplete ?? undefined,
501
509
  };
502
510
  }
503
511
  /**
package/dist/index.d.ts CHANGED
@@ -168,6 +168,25 @@ export interface ActionCtx {
168
168
  authToken?: string;
169
169
  /** Validated JWT payload from JWKS verification (HTTP mode only) */
170
170
  auth?: JWTPayload;
171
+ /**
172
+ * Per-connection configuration values filled in by the user during setup.
173
+ * Available in every action handler. Values come from the auth server's
174
+ * credential resolution response (`app_integration_configs` table).
175
+ *
176
+ * Empty object `{}` if the integration has no `configFields` or the
177
+ * connection has no saved configuration.
178
+ *
179
+ * @example
180
+ * ```ts
181
+ * handler: async (input, ctx) => {
182
+ * const domain = ctx.config.companyDomain as string;
183
+ * const baseUrl = `https://api.bamboohr.com/api/gateway.php/${domain}`;
184
+ * const res = await ctx.fetch(`${baseUrl}/v1/employees/directory`);
185
+ * // ...
186
+ * }
187
+ * ```
188
+ */
189
+ config: Record<string, unknown>;
171
190
  /**
172
191
  * Resolve OAuth credentials for the current integration (or a named slug).
173
192
  * Handles all auth modes (mtls, client_credentials, standard).
@@ -362,8 +381,8 @@ export type { QueueEventPayload, LogEventPayload, OutboxRelayOptions, ServiceEmi
362
381
  export type { ZodTypeAny as SchemaAny, ZodTypeAny };
363
382
  export { parseNovaSpec } from "./parseSpec.js";
364
383
  export type { NovaSpec } from "./parseSpec.js";
365
- export { defineIntegration, validateIntegration, integrationSchema, integrationEvent, integrationFunction, schema, event, IntegrationDefSchema, } from "./integration.js";
366
- export type { IntegrationDef, IntegrationSchemaDef, IntegrationEventDef, IntegrationFunctionDef, ValidationResult, SchemaType, ParamMeta, ParamIn, ParamUiType, SyncMappingDef, SyncMappingFieldDef, WebhookConfig, WebhookTypeDef, WebhookFieldDef, } from "./integration.js";
384
+ export { defineIntegration, validateIntegration, integrationSchema, integrationEvent, integrationFunction, schema, event, IntegrationDefSchema, ConfigPhase, ConfigPhaseSchema, ConfigWidget, ConfigWidgetSchema, } from "./integration.js";
385
+ export type { IntegrationDef, IntegrationSchemaDef, IntegrationEventDef, IntegrationFunctionDef, ValidationResult, SchemaType, ParamMeta, ParamIn, ParamUiType, SyncMappingDef, SyncMappingFieldDef, WebhookConfig, WebhookTypeDef, WebhookFieldDef, ConfigFieldDef, OptionsFetcher, OptionsContext, } from "./integration.js";
367
386
  export { parseIntegrationSpec, IntegrationSpecSchema } from "./integrationSpec.js";
368
387
  export type { IntegrationSpec } from "./integrationSpec.js";
369
388
  export type WebhookCapability = {
package/dist/index.js CHANGED
@@ -65,10 +65,12 @@ export function createORPCRouter(def) {
65
65
  const credCtx = buildCredentialCtx(def.name);
66
66
  const ctx = {
67
67
  jobId: context?.jobId || `orpc-${Date.now()}`,
68
+ config: credCtx._config,
68
69
  progress: (percent, meta) => {
69
70
  console.log(`[${actionName}] Progress: ${percent}%`, meta);
70
71
  },
71
- ...credCtx,
72
+ resolveCredentials: credCtx.resolveCredentials,
73
+ fetch: credCtx.fetch,
72
74
  };
73
75
  return await actionDef.handler(input, ctx);
74
76
  })
@@ -302,8 +304,10 @@ export async function runWorker(def) {
302
304
  const credCtx = buildCredentialCtx(def.name);
303
305
  const ctx = {
304
306
  jobId,
307
+ config: credCtx._config,
305
308
  progress: (percent, meta) => runtime.from("job_events").insert({ job_id: jobId, percent, meta }),
306
- ...credCtx,
309
+ resolveCredentials: credCtx.resolveCredentials,
310
+ fetch: credCtx.fetch,
307
311
  };
308
312
  const result = await act.handler(parsedInput, ctx);
309
313
  act.output.parse(result);
@@ -461,12 +465,14 @@ async function runWorkerSSE(def) {
461
465
  const credCtx = buildCredentialCtx(def.name);
462
466
  const ctx = {
463
467
  jobId: `sse-${msg_id}`,
468
+ config: credCtx._config,
464
469
  read_ct: read_ct ?? 0,
465
470
  progress: (percent, meta) => {
466
471
  console.log(`[nova] progress ${percent}%`, meta ?? '');
467
472
  },
468
473
  heartbeat: (extendBy = 30) => _heartbeatSSE(def.queue, msg_id, extendBy),
469
- ...credCtx,
474
+ resolveCredentials: credCtx.resolveCredentials,
475
+ fetch: credCtx.fetch,
470
476
  };
471
477
  const result = await actionDef.handler(parsedInput, ctx);
472
478
  actionDef.output.parse(result);
@@ -550,12 +556,20 @@ export async function generateOpenAPISpec(def) {
550
556
  * within the same handler invocation.
551
557
  * On 401, ctx.fetch() retries once with forceRefresh=true (X-Nova-Token-Invalid).
552
558
  *
559
+ * Returns `_config` — a shared mutable object that gets populated with
560
+ * config values from the auth server response when resolveCredentials() is
561
+ * called. Assign `ctx.config = credCtx._config` so that config values
562
+ * become available to the handler after credential resolution.
563
+ *
553
564
  * @param defaultSlug - Integration slug (e.g., "jira", "bamboohr")
554
565
  * @param authToken - JWT Bearer token from the inbound request (forwarded to auth server)
555
566
  */
556
567
  function buildCredentialCtx(defaultSlug, authToken) {
557
568
  // Per-request credentials cache — avoids duplicate round-trips within a single handler
558
569
  let _lastCreds = null;
570
+ // Shared config ref — populated when resolveCredentials() returns config
571
+ // from the auth server's credential resolution response.
572
+ const _config = {};
559
573
  const strategy = detectCredentialStrategy();
560
574
  const ctxResolveCredentials = async (slug, userId) => {
561
575
  const targetSlug = slug ?? defaultSlug;
@@ -569,6 +583,13 @@ function buildCredentialCtx(defaultSlug, authToken) {
569
583
  }
570
584
  const creds = await _resolveCredentialsViaHttp(authBaseUrl, targetSlug, authToken, userId);
571
585
  _lastCreds = creds;
586
+ // ── Populate shared config ref from credential resolution response ──
587
+ // The auth server includes config + configComplete in its response.
588
+ // We merge into _config (mutates in place) so ctx.config is updated.
589
+ if (creds.config && Object.keys(creds.config).length > 0) {
590
+ Object.assign(_config, creds.config);
591
+ console.log(`[nova-sdk] 📋 ctx.config populated with ${Object.keys(creds.config).length} field(s) from credential resolution`);
592
+ }
572
593
  return creds;
573
594
  }
574
595
  // strategy === "none"
@@ -612,7 +633,7 @@ function buildCredentialCtx(defaultSlug, authToken) {
612
633
  }
613
634
  return response;
614
635
  };
615
- return { resolveCredentials: ctxResolveCredentials, fetch: ctxFetch };
636
+ return { resolveCredentials: ctxResolveCredentials, fetch: ctxFetch, _config };
616
637
  }
617
638
  /*──────────────── HTTP Server Harness (Enhanced) ───────────────*/
618
639
  import express from "express";
@@ -788,13 +809,15 @@ export function runHttpServer(def, opts = {}) {
788
809
  const credCtx = buildCredentialCtx(def.name, authToken);
789
810
  const ctx = {
790
811
  jobId,
812
+ config: credCtx._config, // populated from credential resolution when resolveCredentials() is called
791
813
  progress: (percent, meta) => {
792
814
  console.log(`[nova][${actionName}] Progress: ${percent}%`, meta ?? '');
793
815
  },
794
816
  headers: req.headers,
795
817
  authToken,
796
818
  auth: jwtPayload,
797
- ...credCtx,
819
+ resolveCredentials: credCtx.resolveCredentials,
820
+ fetch: credCtx.fetch,
798
821
  };
799
822
  const authLabel = jwtPayload?.sub
800
823
  ? ` [auth ✓ sub=${jwtPayload.sub}]`
@@ -847,6 +870,59 @@ export function runHttpServer(def, opts = {}) {
847
870
  app.get('/health', handler);
848
871
  }
849
872
  }
873
+ // ── Auto-register optionsFetcher routes for configFields ──
874
+ // When an integration has configFields with optionsFetcher handlers,
875
+ // the SDK automatically registers POST /config-options/:fieldKey routes.
876
+ // This is transparent to the integration author — they just define the handler.
877
+ const integrationDef = def;
878
+ if (integrationDef.configFields) {
879
+ for (const field of integrationDef.configFields) {
880
+ if (field.optionsFetcher) {
881
+ const optionsRoute = `/config-options/${field.key}`;
882
+ app.post(optionsRoute, async (req, res) => {
883
+ try {
884
+ const authHeader = req.headers['authorization'];
885
+ const authToken = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : undefined;
886
+ if (!authToken) {
887
+ return res.status(401).json({
888
+ error: 'Authorization required',
889
+ message: 'optionsFetcher requires a valid Bearer token',
890
+ });
891
+ }
892
+ // Resolve credentials (same flow as action handlers)
893
+ const credCtx = buildCredentialCtx(def.name, authToken);
894
+ const credentials = await credCtx.resolveCredentials();
895
+ // Build OptionsContext for the handler
896
+ const optionsCtx = {
897
+ fetch: credCtx.fetch,
898
+ config: req.body?.config ?? {},
899
+ credentials,
900
+ tenantId: req.auth?.sub ?? 'unknown',
901
+ };
902
+ // Run the optionsFetcher handler
903
+ console.log(`[nova] 🔧 Running optionsFetcher for config field "${field.key}"`);
904
+ const rawOptions = await field.optionsFetcher.handler(optionsCtx);
905
+ // Validate each option against the declared schema
906
+ const validated = rawOptions.map((item, i) => {
907
+ const result = field.optionsFetcher.schema.safeParse(item);
908
+ if (!result.success) {
909
+ throw new Error(`optionsFetcher "${field.key}" returned invalid item at index ${i}: ` +
910
+ JSON.stringify(result.error.issues ?? result.error.message));
911
+ }
912
+ return result.data;
913
+ });
914
+ console.log(`[nova] ✅ optionsFetcher "${field.key}" returned ${validated.length} options`);
915
+ res.json({ options: validated });
916
+ }
917
+ catch (err) {
918
+ console.error(`[nova] ❌ optionsFetcher "${field.key}" failed:`, err.message);
919
+ res.status(500).json({ error: err.message });
920
+ }
921
+ });
922
+ console.log(`[nova] 🔧 POST ${optionsRoute} -> optionsFetcher("${field.key}") 🔐`);
923
+ }
924
+ }
925
+ }
850
926
  // ── Log credential resolution strategy ──
851
927
  const credStrategy = detectCredentialStrategy();
852
928
  const strategyLabels = {
@@ -905,7 +981,9 @@ export { defineIntegration, validateIntegration,
905
981
  // Verbose helpers (backward-compatible)
906
982
  integrationSchema, integrationEvent, integrationFunction,
907
983
  // Lean helpers (recommended)
908
- schema, event, IntegrationDefSchema, } from "./integration.js";
984
+ schema, event, IntegrationDefSchema,
985
+ // Config field enums + Zod schemas (Phase 1: SDK Foundation)
986
+ ConfigPhase, ConfigPhaseSchema, ConfigWidget, ConfigWidgetSchema, } from "./integration.js";
909
987
  // Integration spec parsing utility
910
988
  export { parseIntegrationSpec, IntegrationSpecSchema } from "./integrationSpec.js";
911
989
  /*──────────────── Credential Resolution (re-exports) ───────────────*/
@@ -1,4 +1,5 @@
1
1
  import { z, type ZodTypeAny } from "zod";
2
+ import type { ResolvedCredentials } from "./credentials.js";
2
3
  /** Where the parameter is sent in the HTTP request */
3
4
  export type ParamIn = 'path' | 'query' | 'body' | 'header';
4
5
  /** UI input widget type hint for the admin dashboard form builder */
@@ -54,6 +55,217 @@ export interface ParamMeta {
54
55
  /** Group name for visual grouping in the form (e.g., "Identity", "Options") */
55
56
  group?: string;
56
57
  }
58
+ /** Zod schema for config field lifecycle phase validation */
59
+ export declare const ConfigPhaseSchema: z.ZodEnum<{
60
+ before_auth: "before_auth";
61
+ after_auth: "after_auth";
62
+ }>;
63
+ /** Config field lifecycle phase type — `"before_auth" | "after_auth"` */
64
+ export type ConfigPhase = z.infer<typeof ConfigPhaseSchema>;
65
+ /**
66
+ * Named constants for config field lifecycle phases.
67
+ * Use these instead of raw strings for IDE autocomplete and compile-time safety.
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * import { ConfigPhase } from "@newhomestar/sdk";
72
+ *
73
+ * { when: ConfigPhase.BeforeAuth } // ✅ autocomplete + type-safe
74
+ * { when: "before_auth" } // ✅ also works (same type)
75
+ * { when: "beforeAuth" } // ❌ TypeScript error
76
+ * ```
77
+ */
78
+ export declare const ConfigPhase: {
79
+ /** Collected before OAuth redirect (e.g., company domain needed to build auth URL) */
80
+ readonly BeforeAuth: "before_auth";
81
+ /** Collected after OAuth completes (e.g., preferences that need API access) */
82
+ readonly AfterAuth: "after_auth";
83
+ };
84
+ /** Zod schema for config field widget type validation */
85
+ export declare const ConfigWidgetSchema: z.ZodEnum<{
86
+ number: "number";
87
+ boolean: "boolean";
88
+ date: "date";
89
+ text: "text";
90
+ textarea: "textarea";
91
+ select: "select";
92
+ password: "password";
93
+ }>;
94
+ /** Config field widget type — UI input hint */
95
+ export type ConfigWidget = z.infer<typeof ConfigWidgetSchema>;
96
+ /**
97
+ * Named constants for config field widget types.
98
+ *
99
+ * @example
100
+ * ```ts
101
+ * import { ConfigWidget } from "@newhomestar/sdk";
102
+ *
103
+ * { widget: ConfigWidget.Select } // ✅
104
+ * { widget: "select" } // ✅ also works
105
+ * ```
106
+ */
107
+ export declare const ConfigWidget: {
108
+ readonly Text: "text";
109
+ readonly Select: "select";
110
+ readonly Boolean: "boolean";
111
+ readonly Number: "number";
112
+ readonly Textarea: "textarea";
113
+ readonly Password: "password";
114
+ readonly Date: "date";
115
+ };
116
+ /**
117
+ * Context available inside `optionsFetcher.handler`.
118
+ * Same authenticated environment as action handlers — token-injected fetch,
119
+ * previously-saved config values, and full resolved credentials.
120
+ *
121
+ * @example
122
+ * ```ts
123
+ * optionsFetcher: {
124
+ * schema: z.object({ label: z.string(), value: z.string() }),
125
+ * handler: async (ctx) => {
126
+ * // ctx.fetch has the OAuth token injected automatically
127
+ * const res = await ctx.fetch("https://api.atlassian.com/oauth/token/accessible-resources");
128
+ * const sites = await res.json();
129
+ * return sites.map(s => ({ label: s.name, value: s.id }));
130
+ * },
131
+ * }
132
+ * ```
133
+ */
134
+ export interface OptionsContext {
135
+ /** Token-injected fetch — same as ctx.fetch in action handlers */
136
+ fetch: (url: string, options?: {
137
+ method?: string;
138
+ headers?: Record<string, string>;
139
+ body?: string;
140
+ }, credentials?: ResolvedCredentials) => Promise<Response>;
141
+ /** Previously-saved config values for this connection (so field B can depend on field A) */
142
+ config: Record<string, unknown>;
143
+ /** Full resolved credentials (accessToken, refreshToken, etc.) */
144
+ credentials: ResolvedCredentials;
145
+ /** Tenant ID for calling internal Nova platform services */
146
+ tenantId: string;
147
+ }
148
+ /**
149
+ * A handler function that fetches select options dynamically.
150
+ * Runs inside the integration container (not in the UI or auth server).
151
+ *
152
+ * Can make multiple API calls, call internal services, transform data, etc.
153
+ * Output is validated against the provided Zod schema at runtime.
154
+ *
155
+ * @example
156
+ * ```ts
157
+ * optionsFetcher: {
158
+ * schema: z.object({ label: z.string(), value: z.string() }),
159
+ * handler: async (ctx) => {
160
+ * const res = await ctx.fetch("https://api.atlassian.com/oauth/token/accessible-resources");
161
+ * const sites = await res.json();
162
+ * return sites.map(s => ({ label: s.name, value: s.id }));
163
+ * },
164
+ * }
165
+ * ```
166
+ */
167
+ export interface OptionsFetcher<TItem extends {
168
+ label: string;
169
+ value: string;
170
+ } = {
171
+ label: string;
172
+ value: string;
173
+ }> {
174
+ /** Zod schema for each returned option item — enforces shape at runtime */
175
+ schema: z.ZodType<TItem>;
176
+ /**
177
+ * Handler function that fetches and returns the options array.
178
+ * Has access to authenticated fetch, saved config, and credentials.
179
+ */
180
+ handler: (ctx: OptionsContext) => Promise<TItem[]>;
181
+ }
182
+ /**
183
+ * A single configurable field declared by an integration.
184
+ * Reuses the same ParamMeta rendering system used for action parameters.
185
+ *
186
+ * Values are stored per connection and available at runtime via `ctx.config`.
187
+ *
188
+ * @example
189
+ * ```ts
190
+ * import { ConfigPhase, ConfigWidget } from "@newhomestar/sdk";
191
+ *
192
+ * configFields: [
193
+ * {
194
+ * key: "companyDomain",
195
+ * label: "Company Domain",
196
+ * schema: z.string().min(1).regex(/^[a-z0-9-]+$/i),
197
+ * when: ConfigPhase.BeforeAuth,
198
+ * required: true,
199
+ * helpText: "Your BambooHR subdomain (the 'acme' part of acme.bamboohr.com)",
200
+ * placeholder: "acme",
201
+ * widget: ConfigWidget.Text,
202
+ * },
203
+ * {
204
+ * key: "cloudId",
205
+ * label: "Jira Site",
206
+ * schema: z.string(),
207
+ * when: ConfigPhase.AfterAuth,
208
+ * widget: ConfigWidget.Select,
209
+ * optionsFetcher: {
210
+ * schema: z.object({ label: z.string(), value: z.string() }),
211
+ * handler: async (ctx) => {
212
+ * const res = await ctx.fetch("https://api.atlassian.com/...");
213
+ * return (await res.json()).map(s => ({ label: s.name, value: s.id }));
214
+ * },
215
+ * },
216
+ * },
217
+ * ]
218
+ * ```
219
+ */
220
+ export interface ConfigFieldDef<T extends z.ZodType = z.ZodType> {
221
+ /** Unique key for this field (used in ctx.config[key]) — must be a valid JS identifier */
222
+ key: string;
223
+ /** Human-readable label for the UI */
224
+ label: string;
225
+ /** Zod schema for validation (converted to JSON Schema at build time) */
226
+ schema: T;
227
+ /** When to collect this field in the connect flow */
228
+ when: ConfigPhase;
229
+ /** Whether this field is required (default: true) */
230
+ required?: boolean;
231
+ /** Default value if not provided by user */
232
+ defaultValue?: z.infer<T>;
233
+ /** Help text shown below the field in the UI */
234
+ helpText?: string;
235
+ /** Placeholder text for input fields */
236
+ placeholder?: string;
237
+ /** UI widget type hint (reuses ParamMeta widgets) */
238
+ widget?: ConfigWidget;
239
+ /** Static options for select widgets */
240
+ options?: Array<{
241
+ label: string;
242
+ value: string;
243
+ }>;
244
+ /**
245
+ * Dynamic options handler — fetches options from APIs at runtime.
246
+ * Runs inside the integration container with full auth context.
247
+ * Only valid for `when: ConfigPhase.AfterAuth` (needs credentials).
248
+ *
249
+ * The SDK automatically registers a `POST /config-options/:key` route
250
+ * for each field with an optionsFetcher — transparent to the integration author.
251
+ */
252
+ optionsFetcher?: OptionsFetcher;
253
+ /** Group label for organizing related fields in the UI */
254
+ group?: string;
255
+ /**
256
+ * Conditional visibility: only show this field when another field
257
+ * matches a specific value. Enables progressive disclosure.
258
+ *
259
+ * @example
260
+ * ```ts
261
+ * { visibleWhen: { field: "syncTimeOff", equals: true } }
262
+ * ```
263
+ */
264
+ visibleWhen?: {
265
+ field: string;
266
+ equals: unknown;
267
+ };
268
+ }
57
269
  export type SchemaType = 'request' | 'response' | 'entity' | 'webhook_payload' | 'configuration';
58
270
  export interface IntegrationSchemaDef<T extends ZodTypeAny = ZodTypeAny> {
59
271
  /** Human-readable name for this schema */
@@ -426,6 +638,44 @@ export interface IntegrationDef {
426
638
  * The integration's handler action does the actual processing.
427
639
  */
428
640
  webhooks?: WebhookConfig;
641
+ /**
642
+ * Configuration fields that users fill in during the connect flow.
643
+ * Values are stored per connection and available at runtime via `ctx.config`.
644
+ *
645
+ * Fields are split into two lifecycle phases:
646
+ * - `ConfigPhase.BeforeAuth` — collected before OAuth (e.g., company domain for auth URL)
647
+ * - `ConfigPhase.AfterAuth` — collected after OAuth (e.g., sync preferences, dynamic selects)
648
+ *
649
+ * @example
650
+ * ```ts
651
+ * import { ConfigPhase } from "@newhomestar/sdk";
652
+ *
653
+ * configFields: [
654
+ * {
655
+ * key: "companyDomain",
656
+ * label: "Company Domain",
657
+ * schema: z.string().min(1),
658
+ * when: ConfigPhase.BeforeAuth,
659
+ * helpText: "Your subdomain (e.g., 'acme' from acme.bamboohr.com)",
660
+ * widget: "text",
661
+ * },
662
+ * {
663
+ * key: "syncInterval",
664
+ * label: "Sync Interval",
665
+ * schema: z.enum(["1h", "6h", "24h"]),
666
+ * when: ConfigPhase.AfterAuth,
667
+ * defaultValue: "6h",
668
+ * widget: "select",
669
+ * options: [
670
+ * { label: "Every hour", value: "1h" },
671
+ * { label: "Every 6 hours", value: "6h" },
672
+ * { label: "Every 24 hours", value: "24h" },
673
+ * ],
674
+ * },
675
+ * ]
676
+ * ```
677
+ */
678
+ configFields?: ConfigFieldDef[];
429
679
  }
430
680
  export declare const IntegrationDefSchema: z.ZodObject<{
431
681
  slug: z.ZodString;
@@ -570,6 +820,38 @@ export declare const IntegrationDefSchema: z.ZodObject<{
570
820
  }, z.core.$strip>>>;
571
821
  }, z.core.$strip>>;
572
822
  }, z.core.$strip>>;
823
+ configFields: z.ZodOptional<z.ZodArray<z.ZodObject<{
824
+ key: z.ZodString;
825
+ label: z.ZodString;
826
+ schema: z.ZodAny;
827
+ when: z.ZodEnum<{
828
+ before_auth: "before_auth";
829
+ after_auth: "after_auth";
830
+ }>;
831
+ required: z.ZodOptional<z.ZodBoolean>;
832
+ defaultValue: z.ZodOptional<z.ZodUnknown>;
833
+ helpText: z.ZodOptional<z.ZodString>;
834
+ placeholder: z.ZodOptional<z.ZodString>;
835
+ widget: z.ZodOptional<z.ZodEnum<{
836
+ number: "number";
837
+ boolean: "boolean";
838
+ date: "date";
839
+ text: "text";
840
+ textarea: "textarea";
841
+ select: "select";
842
+ password: "password";
843
+ }>>;
844
+ options: z.ZodOptional<z.ZodArray<z.ZodObject<{
845
+ label: z.ZodString;
846
+ value: z.ZodString;
847
+ }, z.core.$strip>>>;
848
+ optionsFetcher: z.ZodOptional<z.ZodAny>;
849
+ group: z.ZodOptional<z.ZodString>;
850
+ visibleWhen: z.ZodOptional<z.ZodObject<{
851
+ field: z.ZodString;
852
+ equals: z.ZodUnknown;
853
+ }, z.core.$strip>>;
854
+ }, z.core.$strip>>>;
573
855
  }, z.core.$strip>;
574
856
  /**
575
857
  * Result of validating an integration definition before build/push.