@newhomestar/sdk 0.5.2 → 0.6.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.
package/dist/index.d.ts CHANGED
@@ -13,6 +13,18 @@ export interface ActionDef<I extends ZodTypeAny, O extends ZodTypeAny> {
13
13
  };
14
14
  method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
15
15
  path?: string;
16
+ /**
17
+ * Per-field parameter metadata — tells the platform where each input field
18
+ * goes (path, query, body, header) and what UI widget the admin dashboard
19
+ * should render (text, number, date, select, boolean, etc.).
20
+ *
21
+ * If not provided, the system falls back to convention:
22
+ * - Fields matching `:param` in path → path params
23
+ * - Remaining fields for GET → query params
24
+ * - Remaining fields for POST/PUT/PATCH → body params
25
+ * - UI type inferred from Zod type (string→text, number→number, boolean→boolean, enum→select)
26
+ */
27
+ params?: Record<string, import("./integration.js").ParamMeta>;
16
28
  capabilities?: Array<{
17
29
  type: 'webhook';
18
30
  eventTypes: string[];
@@ -40,12 +52,61 @@ export interface ActionDef<I extends ZodTypeAny, O extends ZodTypeAny> {
40
52
  consumerGroup?: string;
41
53
  }>;
42
54
  }
55
+ /** Validated JWT claims from express-oauth2-jwt-bearer */
56
+ export interface JWTPayload {
57
+ /** Issuer (iss claim) */
58
+ iss?: string;
59
+ /** Subject (sub claim) — typically the user ID */
60
+ sub?: string;
61
+ /** Audience (aud claim) */
62
+ aud?: string | string[];
63
+ /** Expiration time (exp claim) */
64
+ exp?: number;
65
+ /** Issued at (iat claim) */
66
+ iat?: number;
67
+ /** JWT ID (jti claim) */
68
+ jti?: string;
69
+ /** Client ID (azp / client_id claim) */
70
+ azp?: string;
71
+ client_id?: string;
72
+ /** Any additional custom claims */
73
+ [key: string]: unknown;
74
+ }
43
75
  export interface ActionCtx {
44
76
  jobId: string;
45
77
  progress: (percent: number, meta?: unknown) => void;
78
+ /** Raw HTTP headers from the inbound request (HTTP mode only) */
79
+ headers?: Record<string, string | string[] | undefined>;
80
+ /** Bearer token extracted from the Authorization header (HTTP mode only) */
81
+ authToken?: string;
82
+ /** Validated JWT payload from JWKS verification (HTTP mode only) */
83
+ auth?: JWTPayload;
84
+ /**
85
+ * Resolve OAuth credentials for the current integration (or a named slug).
86
+ * Handles all auth modes (mtls, client_credentials, standard) with
87
+ * 3-tier caching: in-memory → DB (vault-encrypted) → fresh token exchange.
88
+ *
89
+ * @param slug - Integration slug (defaults to the current worker's name)
90
+ * @param userId - User ID for standard OAuth; omit for server flows
91
+ */
92
+ resolveCredentials: (slug?: string, userId?: string) => Promise<import("./credentials.js").ResolvedCredentials>;
93
+ /**
94
+ * mTLS-aware fetch wrapper. Resolves credentials automatically (or accepts
95
+ * pre-resolved ones) and handles node:https for mTLS integrations.
96
+ *
97
+ * @param url - Full URL to fetch
98
+ * @param options - Standard fetch options (method, headers, body)
99
+ * @param credentials - Pre-resolved credentials; if omitted, calls resolveCredentials()
100
+ */
101
+ fetch: (url: string, options?: {
102
+ method?: string;
103
+ headers?: Record<string, string>;
104
+ body?: string;
105
+ }, credentials?: import("./credentials.js").ResolvedCredentials) => Promise<Response>;
46
106
  }
47
107
  export declare function action<I extends ZodTypeAny, O extends ZodTypeAny>(cfg: {
48
108
  name?: string;
109
+ description?: string;
49
110
  input: I;
50
111
  output: O;
51
112
  fga?: {
@@ -56,6 +117,14 @@ export declare function action<I extends ZodTypeAny, O extends ZodTypeAny>(cfg:
56
117
  };
57
118
  method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
58
119
  path?: string;
120
+ scopes?: string[];
121
+ category?: string;
122
+ /**
123
+ * Per-field parameter metadata — tells the platform where each input field
124
+ * goes (path, query, body, header) and what UI widget to render.
125
+ * @see ParamMeta from ./integration.js
126
+ */
127
+ params?: Record<string, import("./integration.js").ParamMeta>;
59
128
  capabilities?: Array<{
60
129
  type: 'webhook';
61
130
  eventTypes: string[];
@@ -121,11 +190,40 @@ export declare function generateOpenAPISpec<T extends WorkerDef>(def: T): Promis
121
190
  paths: any;
122
191
  }>;
123
192
  /**
124
- * Enhanced HTTP server exposing each action under configurable routes
193
+ * Options for runHttpServer, including optional JWKS auth configuration.
125
194
  */
126
- export declare function runHttpServer<T extends WorkerDef>(def: T, opts?: {
195
+ export interface HttpServerOptions {
127
196
  port?: number;
128
- }): void;
197
+ /**
198
+ * Auth issuer base URL (OIDC provider). The library fetches
199
+ * `{issuerBaseURL}/.well-known/jwks.json` automatically.
200
+ * Falls back to AUTH_ISSUER_BASE_URL env var, then to the production default.
201
+ */
202
+ issuerBaseURL?: string;
203
+ /**
204
+ * Expected JWT audience claim.
205
+ * Falls back to AUTH_AUDIENCE env var, then "starfleet".
206
+ */
207
+ audience?: string;
208
+ /**
209
+ * Paths that should NOT require a valid JWT (e.g. health checks).
210
+ * `/health` and `/healthcheck` are always exempt.
211
+ */
212
+ publicPaths?: string[];
213
+ /**
214
+ * Set to true to completely disable JWKS auth (e.g. local dev without an auth server).
215
+ * Falls back to NOVA_SKIP_AUTH env var ("true" / "1").
216
+ */
217
+ skipAuth?: boolean;
218
+ }
219
+ /**
220
+ * Enhanced HTTP server exposing each action under configurable routes.
221
+ * All routes are protected by JWKS-based JWT verification (RS256) via
222
+ * express-oauth2-jwt-bearer, matching the project-starfleet-backend pattern.
223
+ *
224
+ * Health / liveness endpoints are automatically exempted from auth.
225
+ */
226
+ export declare function runHttpServer<T extends WorkerDef>(def: T, opts?: HttpServerOptions): void;
129
227
  /**
130
228
  * Run both HTTP server and queue consumer concurrently
131
229
  * This gives you the best of both worlds: direct API access AND event processing
@@ -136,6 +234,10 @@ export declare function runDualMode<T extends WorkerDef>(def: T, opts?: {
136
234
  export type { ZodTypeAny as SchemaAny, ZodTypeAny };
137
235
  export { parseNovaSpec } from "./parseSpec.js";
138
236
  export type { NovaSpec } from "./parseSpec.js";
237
+ export { defineIntegration, validateIntegration, integrationSchema, integrationEvent, integrationFunction, schema, event, IntegrationDefSchema, } from "./integration.js";
238
+ export type { IntegrationDef, IntegrationSchemaDef, IntegrationEventDef, IntegrationFunctionDef, ValidationResult, SchemaType, ParamMeta, ParamIn, ParamUiType, } from "./integration.js";
239
+ export { parseIntegrationSpec, IntegrationSpecSchema } from "./integrationSpec.js";
240
+ export type { IntegrationSpec } from "./integrationSpec.js";
139
241
  export type WebhookCapability = {
140
242
  type: 'webhook';
141
243
  eventTypes: string[];
@@ -166,3 +268,5 @@ export type StreamCapability = {
166
268
  consumerGroup?: string;
167
269
  };
168
270
  export type Capability = WebhookCapability | ScheduledCapability | QueueCapability | StreamCapability;
271
+ export { createPlatformClient, resolveCredentials, resolveCredentialsViaHttp, detectCredentialStrategy, integrationFetch, emitPlatformEvent, IntegrationNotFoundError, IntegrationDisabledError, CredentialsNotConfiguredError, ConnectionNotFoundError, TokenExchangeError, } from "./credentials.js";
272
+ export type { ResolvedCredentials, IntegrationConfig, AuthMode, } from "./credentials.js";
package/dist/index.js CHANGED
@@ -1,12 +1,3 @@
1
- // nova-sdk-esm – Modern ESM Nova SDK with full oRPC integration (v0.4.2)
2
- // =====================================================
3
- // 1. Public API – action(), defineWorker(), enqueue() - SAME AS BEFORE
4
- // 2. Enhanced HTTP server with REST endpoints and custom routing
5
- // 3. Full oRPC integration with OpenAPI spec generation
6
- // 4. Runtime harness for *worker* pipelines using Supabase RPC
7
- // 5. Modern ESM architecture for future compatibility
8
- // -----------------------------------------------------------
9
- import { z } from "zod";
10
1
  import dotenv from "dotenv";
11
2
  import { createClient } from "@supabase/supabase-js";
12
3
  import { OpenFgaClient } from "@openfga/sdk";
@@ -15,7 +6,7 @@ import { createServer } from "node:http";
15
6
  import { os } from "@orpc/server";
16
7
  import { RPCHandler } from "@orpc/server/node";
17
8
  import { CORSPlugin } from "@orpc/server/plugins";
18
- import { OpenAPIHandler } from "@orpc/openapi/fetch";
9
+ import { createPlatformClient, resolveCredentials as _resolveCredentials, resolveCredentialsViaHttp as _resolveCredentialsViaHttp, detectCredentialStrategy, integrationFetch as _integrationFetch, } from "./credentials.js";
19
10
  if (!process.env.RUNTIME_SUPABASE_URL) {
20
11
  // local dev – read .env.local
21
12
  dotenv.config({ path: ".env.local", override: true });
@@ -71,11 +62,13 @@ export function createORPCRouter(def) {
71
62
  .input(actionDef.input)
72
63
  .output(actionDef.output)
73
64
  .handler(async ({ input, context }) => {
65
+ const credCtx = buildCredentialCtx(def.name);
74
66
  const ctx = {
75
67
  jobId: context?.jobId || `orpc-${Date.now()}`,
76
68
  progress: (percent, meta) => {
77
69
  console.log(`[${actionName}] Progress: ${percent}%`, meta);
78
- }
70
+ },
71
+ ...credCtx,
79
72
  };
80
73
  return await actionDef.handler(input, ctx);
81
74
  })
@@ -258,9 +251,11 @@ export async function runWorker(def) {
258
251
  }
259
252
  try {
260
253
  const parsedInput = act.input.parse(payload);
254
+ const credCtx = buildCredentialCtx(def.name);
261
255
  const ctx = {
262
256
  jobId,
263
257
  progress: (percent, meta) => runtime.from("job_events").insert({ job_id: jobId, percent, meta }),
258
+ ...credCtx,
264
259
  };
265
260
  const result = await act.handler(parsedInput, ctx);
266
261
  act.output.parse(result);
@@ -323,29 +318,245 @@ export async function generateOpenAPISpec(def) {
323
318
  }, {})
324
319
  };
325
320
  }
321
+ /*──────────────── Credential Context Builder ───────────────*/
322
+ /**
323
+ * Build resolveCredentials() and fetch() methods bound to this worker's slug.
324
+ *
325
+ * Automatically detects the credential resolution strategy:
326
+ * - **Strategy A (DB)**: PLATFORM_SUPABASE_* env vars → direct Supabase Vault access
327
+ * - **Strategy B (HTTP)**: AUTH_ISSUER_BASE_URL env var → call auth server with JWT
328
+ *
329
+ * @param defaultSlug - Integration slug (e.g., "adp")
330
+ * @param authToken - JWT Bearer token from the inbound request (for HTTP strategy)
331
+ */
332
+ function buildCredentialCtx(defaultSlug, authToken) {
333
+ // Cache the resolved credentials per request to avoid duplicate round-trips
334
+ let _lastCreds = null;
335
+ const strategy = detectCredentialStrategy();
336
+ const ctxResolveCredentials = async (slug, userId) => {
337
+ const targetSlug = slug ?? defaultSlug;
338
+ if (strategy === "db") {
339
+ // Strategy A: Direct DB access via PLATFORM_SUPABASE_*
340
+ const platformDB = createPlatformClient();
341
+ const creds = await _resolveCredentials(platformDB, targetSlug, userId);
342
+ _lastCreds = creds;
343
+ return creds;
344
+ }
345
+ if (strategy === "http") {
346
+ // Strategy B: HTTP callback to auth server with JWT
347
+ const authBaseUrl = process.env.AUTH_ISSUER_BASE_URL;
348
+ if (!authToken) {
349
+ throw new Error(`[nova-sdk] HTTP credential resolution requires a JWT bearer token, ` +
350
+ `but no authToken was provided in the request context. ` +
351
+ `Ensure the request includes an Authorization: Bearer header.`);
352
+ }
353
+ const creds = await _resolveCredentialsViaHttp(authBaseUrl, targetSlug, authToken, userId);
354
+ _lastCreds = creds;
355
+ return creds;
356
+ }
357
+ // strategy === "none"
358
+ throw new Error(`[nova-sdk] No credential resolution strategy available. ` +
359
+ `Set either PLATFORM_SUPABASE_URL + PLATFORM_SUPABASE_SERVICE_ROLE_KEY (direct DB) ` +
360
+ `or AUTH_ISSUER_BASE_URL (HTTP callback) to enable credential resolution.`);
361
+ };
362
+ const ctxFetch = async (url, options, credentials) => {
363
+ const creds = credentials ?? _lastCreds ?? await ctxResolveCredentials();
364
+ return _integrationFetch(url, creds, options);
365
+ };
366
+ return { resolveCredentials: ctxResolveCredentials, fetch: ctxFetch };
367
+ }
326
368
  /*──────────────── HTTP Server Harness (Enhanced) ───────────────*/
327
369
  import express from "express";
328
370
  import bodyParser from "body-parser";
371
+ import { auth } from "express-oauth2-jwt-bearer";
329
372
  /**
330
- * Enhanced HTTP server exposing each action under configurable routes
373
+ * Enhanced HTTP server exposing each action under configurable routes.
374
+ * All routes are protected by JWKS-based JWT verification (RS256) via
375
+ * express-oauth2-jwt-bearer, matching the project-starfleet-backend pattern.
376
+ *
377
+ * Health / liveness endpoints are automatically exempted from auth.
331
378
  */
332
379
  export function runHttpServer(def, opts = {}) {
333
380
  const app = express();
334
381
  app.use(bodyParser.json());
382
+ // ── Determine whether auth is enabled ──
383
+ const skipAuth = opts.skipAuth ??
384
+ ['true', '1'].includes((process.env.NOVA_SKIP_AUTH ?? '').toLowerCase());
385
+ // ── Build the set of public (unauthenticated) paths ──
386
+ const publicPaths = new Set([
387
+ '/health',
388
+ '/healthcheck',
389
+ '/healthcheck/',
390
+ ...(opts.publicPaths ?? []),
391
+ ]);
392
+ // Also exempt any action named "health"
393
+ for (const [actionName, act] of Object.entries(def.actions)) {
394
+ if (actionName === 'health') {
395
+ const route = act.path || `/${def.name}/${actionName}`;
396
+ publicPaths.add(route);
397
+ }
398
+ }
399
+ if (!skipAuth) {
400
+ // ── JWKS middleware (same pattern as project-starfleet-backend) ──
401
+ const issuerBaseURL = opts.issuerBaseURL ??
402
+ process.env.AUTH_ISSUER_BASE_URL ??
403
+ 'https://auth.newhomeconnect.dev';
404
+ const audience = opts.audience ??
405
+ process.env.AUTH_AUDIENCE ??
406
+ 'starfleet';
407
+ console.log(`[nova] 🔐 JWKS auth enabled — issuer: ${issuerBaseURL}, audience: ${audience}`);
408
+ console.log(`[nova] 🔓 Public (unauthenticated) paths: ${[...publicPaths].join(', ')}`);
409
+ const jwtCheck = auth({
410
+ audience,
411
+ issuerBaseURL,
412
+ tokenSigningAlg: 'RS256',
413
+ });
414
+ // Apply JWKS middleware to all routes EXCEPT public paths
415
+ app.use((req, res, next) => {
416
+ if (publicPaths.has(req.path)) {
417
+ return next();
418
+ }
419
+ return jwtCheck(req, res, next);
420
+ });
421
+ // ── InvalidTokenError handler (matches project-starfleet-backend) ──
422
+ app.use((err, _req, res, next) => {
423
+ if (err && (err.name === 'InvalidTokenError' || err.code === 'invalid_token')) {
424
+ console.warn(`[nova] 🚫 JWT rejected: ${err.message}`);
425
+ return res.status(401).json({
426
+ error: 'Unauthorized',
427
+ message: 'Invalid or expired token',
428
+ code: 'invalid_token',
429
+ });
430
+ }
431
+ if (err && err.status === 401) {
432
+ console.warn(`[nova] 🚫 Auth failed: ${err.message}`);
433
+ return res.status(401).json({
434
+ error: 'Unauthorized',
435
+ message: err.message || 'Authentication failed',
436
+ });
437
+ }
438
+ next(err);
439
+ });
440
+ }
441
+ else {
442
+ console.warn(`[nova] ⚠️ JWKS auth DISABLED (skipAuth=true). All routes are public.`);
443
+ }
444
+ // ── Register action routes ──
335
445
  for (const [actionName, act] of Object.entries(def.actions)) {
336
446
  const method = (act.method || 'POST').toLowerCase();
337
447
  const route = act.path || `/${def.name}/${actionName}`;
338
- // unified handler: parse JSON body or default to empty object
339
448
  const handler = async (req, res) => {
340
449
  try {
341
- const payload = req.body && typeof req.body === 'object' ? req.body : {};
342
- const input = act.input.parse(payload);
343
- const ctx = { jobId: `http-${Date.now()}`, progress: () => { } };
450
+ // ── Smart input extraction using params metadata ──
451
+ // Merges path params, query params, and body based on explicit
452
+ // `params` metadata OR convention (GET→query, POST→body).
453
+ const rawInput = {};
454
+ const paramsMeta = act.params ?? {};
455
+ const hasExplicitParams = Object.keys(paramsMeta).length > 0;
456
+ if (hasExplicitParams) {
457
+ // ── Explicit params mode: use metadata to route fields ──
458
+ for (const [key, meta] of Object.entries(paramsMeta)) {
459
+ if (meta.in === 'path' && req.params?.[key] !== undefined) {
460
+ rawInput[key] = req.params[key];
461
+ }
462
+ else if (meta.in === 'query' && req.query?.[key] !== undefined) {
463
+ rawInput[key] = req.query[key];
464
+ }
465
+ else if (meta.in === 'header') {
466
+ const headerVal = req.headers?.[key.toLowerCase()];
467
+ if (headerVal !== undefined)
468
+ rawInput[key] = headerVal;
469
+ }
470
+ else if (meta.in === 'body' && req.body?.[key] !== undefined) {
471
+ rawInput[key] = req.body[key];
472
+ }
473
+ }
474
+ // Also include any body fields not in params (backward compat)
475
+ if (req.body && typeof req.body === 'object') {
476
+ for (const [k, v] of Object.entries(req.body)) {
477
+ if (!(k in rawInput))
478
+ rawInput[k] = v;
479
+ }
480
+ }
481
+ }
482
+ else {
483
+ // ── Convention mode: derive from HTTP method + path ──
484
+ // 1. Path params from Express :param matching
485
+ if (req.params && typeof req.params === 'object') {
486
+ Object.assign(rawInput, req.params);
487
+ }
488
+ // 2. For GET requests: all remaining input comes from query params
489
+ if (method === 'get' && req.query && typeof req.query === 'object') {
490
+ Object.assign(rawInput, req.query);
491
+ }
492
+ // 3. Body params (for all methods — Express won't have body for GET usually)
493
+ if (req.body && typeof req.body === 'object') {
494
+ Object.assign(rawInput, req.body);
495
+ }
496
+ }
497
+ // ── Type coercion for query/path string values ──
498
+ // Express delivers query and path params as strings, but Zod expects
499
+ // numbers/booleans. Coerce based on params metadata or Zod schema shape.
500
+ for (const [key, val] of Object.entries(rawInput)) {
501
+ if (typeof val !== 'string')
502
+ continue;
503
+ const meta = paramsMeta[key];
504
+ const uiType = meta?.uiType;
505
+ // Coerce numbers
506
+ if (uiType === 'number' || uiType === 'integer') {
507
+ const parsed = Number(val);
508
+ if (!isNaN(parsed))
509
+ rawInput[key] = parsed;
510
+ }
511
+ // Coerce booleans
512
+ else if (uiType === 'boolean') {
513
+ rawInput[key] = val === 'true' || val === '1';
514
+ }
515
+ // Auto-detect from Zod schema shape if no explicit uiType
516
+ else if (!uiType && act.input?._def?.shape) {
517
+ const fieldDef = act.input._def.shape()?.[key];
518
+ const innerType = fieldDef?._def?.innerType?._def?.typeName ?? fieldDef?._def?.typeName;
519
+ if (innerType === 'ZodNumber') {
520
+ const parsed = Number(val);
521
+ if (!isNaN(parsed))
522
+ rawInput[key] = parsed;
523
+ }
524
+ else if (innerType === 'ZodBoolean') {
525
+ rawInput[key] = val === 'true' || val === '1';
526
+ }
527
+ }
528
+ }
529
+ const input = act.input.parse(rawInput);
530
+ const jobId = `http-${Date.now()}`;
531
+ const authHeader = req.headers['authorization'];
532
+ const authToken = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : undefined;
533
+ // Extract validated JWT payload from express-oauth2-jwt-bearer
534
+ // The library attaches it as req.auth after successful verification
535
+ const jwtPayload = req.auth?.payload ?? req.auth;
536
+ const credCtx = buildCredentialCtx(def.name, authToken);
537
+ const ctx = {
538
+ jobId,
539
+ progress: (percent, meta) => {
540
+ console.log(`[nova][${actionName}] Progress: ${percent}%`, meta ?? '');
541
+ },
542
+ headers: req.headers,
543
+ authToken,
544
+ auth: jwtPayload,
545
+ ...credCtx,
546
+ };
547
+ const authLabel = jwtPayload?.sub
548
+ ? ` [auth ✓ sub=${jwtPayload.sub}]`
549
+ : authToken
550
+ ? ' [auth ✓]'
551
+ : ' [no auth]';
552
+ console.log(`[nova] ▶ ${method.toUpperCase()} ${route} → ${actionName} (${jobId})${authLabel}`);
344
553
  const out = await act.handler(input, ctx);
345
554
  act.output.parse(out);
555
+ console.log(`[nova] ✅ ${actionName} completed (${jobId})`);
346
556
  res.json(out);
347
557
  }
348
558
  catch (err) {
559
+ console.error(`[nova] ❌ ${actionName} failed:`, err.message);
349
560
  res.status(400).json({ error: err.message });
350
561
  }
351
562
  };
@@ -356,13 +567,22 @@ export function runHttpServer(def, opts = {}) {
356
567
  app.get('/health', handler);
357
568
  }
358
569
  }
570
+ // ── Log credential resolution strategy ──
571
+ const credStrategy = detectCredentialStrategy();
572
+ const strategyLabels = {
573
+ db: '🗄️ Direct DB (PLATFORM_SUPABASE_*)',
574
+ http: '🌐 HTTP callback (AUTH_ISSUER_BASE_URL → auth server)',
575
+ none: '⚠️ NONE — ctx.resolveCredentials() will throw if called',
576
+ };
577
+ console.log(`[nova] 🔑 Credential strategy: ${strategyLabels[credStrategy]}`);
359
578
  const port = opts.port ?? (process.env.PORT ? parseInt(process.env.PORT) : 8000);
360
579
  app.listen(port, () => {
361
580
  console.log(`[nova] HTTP server listening on http://localhost:${port}`);
362
581
  Object.entries(def.actions).forEach(([actionName, actionDef]) => {
363
582
  const method = (actionDef.method || 'POST').toUpperCase();
364
583
  const path = actionDef.path || `/${def.name}/${actionName}`;
365
- console.log(`[nova] ${method} ${path} -> ${actionName}`);
584
+ const isPublic = publicPaths.has(path) ? ' 🔓' : ' 🔐';
585
+ console.log(`[nova] ${method} ${path} -> ${actionName}${isPublic}`);
366
586
  });
367
587
  });
368
588
  }
@@ -392,3 +612,30 @@ export function runDualMode(def, opts = {}) {
392
612
  }
393
613
  // YAML spec parsing utility
394
614
  export { parseNovaSpec } from "./parseSpec.js";
615
+ // Integration definition API
616
+ export { defineIntegration, validateIntegration,
617
+ // Verbose helpers (backward-compatible)
618
+ integrationSchema, integrationEvent, integrationFunction,
619
+ // Lean helpers (recommended)
620
+ schema, event, IntegrationDefSchema, } from "./integration.js";
621
+ // Integration spec parsing utility
622
+ export { parseIntegrationSpec, IntegrationSpecSchema } from "./integrationSpec.js";
623
+ /*──────────────── Credential Resolution (re-exports) ───────────────*/
624
+ // These are the first-class SDK exports for integration credential management.
625
+ // Every integration gets vault-backed token caching, mTLS, and OAuth for free.
626
+ export { createPlatformClient, resolveCredentials, resolveCredentialsViaHttp, detectCredentialStrategy, integrationFetch, emitPlatformEvent,
627
+ // Error classes
628
+ IntegrationNotFoundError, IntegrationDisabledError, CredentialsNotConfiguredError, ConnectionNotFoundError, TokenExchangeError, } from "./credentials.js";
629
+ // // Default export for compatibility
630
+ // import { parseNovaSpec as parseNovaSpecFunction } from "./parseSpec.js";
631
+ // export default {
632
+ // defineWorker,
633
+ // action,
634
+ // enqueue,
635
+ // runHttpServer,
636
+ // runORPCServer,
637
+ // runWorker,
638
+ // generateOpenAPISpec,
639
+ // createORPCRouter,
640
+ // parseNovaSpec: parseNovaSpecFunction
641
+ // };