@apifuse/provider-sdk 2.2.0-beta.41 → 2.2.0-beta.43

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.
@@ -1,4 +1,17 @@
1
- import ts from "typescript";
1
+ import type TS from "typescript";
2
+
3
+ const ts: typeof import("typescript") = await loadTypeScript();
4
+
5
+ async function loadTypeScript(): Promise<typeof import("typescript")> {
6
+ try {
7
+ return await import("typescript");
8
+ } catch {
9
+ console.error(
10
+ "apifuse migrate-shape requires typescript; install it in the workspace running the CLI (bun add -d typescript)",
11
+ );
12
+ process.exit(1);
13
+ }
14
+ }
2
15
 
3
16
  /**
4
17
  * Provider authoring shape migration.
@@ -207,16 +220,16 @@ type ShapeClassification =
207
220
  readonly kind: Exclude<ProviderShapeKind, "two-phase">;
208
221
  /** Variable the declaration is currently bound to, when it is bound. */
209
222
  readonly variableName?: string;
210
- readonly variableStatement?: ts.VariableStatement;
223
+ readonly variableStatement?: TS.VariableStatement;
211
224
  /** Extra properties on a `{ ...provider, deployment }` default export. */
212
225
  readonly spreadExportExtras?: string;
213
226
  }
214
227
  | { readonly status: "skipped"; readonly reason: string };
215
228
 
216
229
  function classifyShape(
217
- source: ts.SourceFile,
218
- call: ts.CallExpression,
219
- exportAssignment: ts.ExportAssignment,
230
+ source: TS.SourceFile,
231
+ call: TS.CallExpression,
232
+ exportAssignment: TS.ExportAssignment,
220
233
  ): ShapeClassification {
221
234
  const variableStatement = enclosingVariableStatement(call);
222
235
 
@@ -307,9 +320,9 @@ function classifyShape(
307
320
  };
308
321
  }
309
322
 
310
- function collectDefineProviderCalls(source: ts.SourceFile): ts.CallExpression[] {
311
- const calls: ts.CallExpression[] = [];
312
- const visit = (node: ts.Node): void => {
323
+ function collectDefineProviderCalls(source: TS.SourceFile): TS.CallExpression[] {
324
+ const calls: TS.CallExpression[] = [];
325
+ const visit = (node: TS.Node): void => {
313
326
  if (
314
327
  ts.isCallExpression(node) &&
315
328
  ts.isIdentifier(node.expression) &&
@@ -323,7 +336,7 @@ function collectDefineProviderCalls(source: ts.SourceFile): ts.CallExpression[]
323
336
  return calls;
324
337
  }
325
338
 
326
- function findDefaultExport(source: ts.SourceFile): ts.ExportAssignment | undefined {
339
+ function findDefaultExport(source: TS.SourceFile): TS.ExportAssignment | undefined {
327
340
  for (const statement of source.statements) {
328
341
  if (ts.isExportAssignment(statement) && statement.isExportEquals !== true) {
329
342
  return statement;
@@ -333,8 +346,8 @@ function findDefaultExport(source: ts.SourceFile): ts.ExportAssignment | undefin
333
346
  }
334
347
 
335
348
  function defaultExportCallsBuilder(
336
- exportAssignment: ts.ExportAssignment,
337
- declarationCall: ts.CallExpression,
349
+ exportAssignment: TS.ExportAssignment,
350
+ declarationCall: TS.CallExpression,
338
351
  ): boolean {
339
352
  const expression = exportAssignment.expression;
340
353
  if (!ts.isCallExpression(expression)) return false;
@@ -353,9 +366,9 @@ function defaultExportCallsBuilder(
353
366
  * breaks idempotency for repeated fan-out runs.
354
367
  */
355
368
  function isAlreadyTwoPhase(
356
- source: ts.SourceFile,
357
- exportAssignment: ts.ExportAssignment,
358
- declarationCall: ts.CallExpression,
369
+ source: TS.SourceFile,
370
+ exportAssignment: TS.ExportAssignment,
371
+ declarationCall: TS.CallExpression,
359
372
  ): boolean {
360
373
  if (defaultExportCallsBuilder(exportAssignment, declarationCall)) return true;
361
374
 
@@ -375,7 +388,7 @@ function isAlreadyTwoPhase(
375
388
  // Does any spread/exported identifier bind a call to an identifier other
376
389
  // than defineProvider — i.e. a builder call?
377
390
  let found = false;
378
- const visit = (node: ts.Node): void => {
391
+ const visit = (node: TS.Node): void => {
379
392
  if (found) return;
380
393
  if (
381
394
  ts.isVariableDeclaration(node) &&
@@ -397,8 +410,8 @@ function isAlreadyTwoPhase(
397
410
  }
398
411
 
399
412
  function findOperationsProperty(
400
- declaration: ts.ObjectLiteralExpression,
401
- ): ts.ObjectLiteralElementLike | undefined {
413
+ declaration: TS.ObjectLiteralExpression,
414
+ ): TS.ObjectLiteralElementLike | undefined {
402
415
  for (const property of declaration.properties) {
403
416
  if (ts.isSpreadAssignment(property)) continue;
404
417
  const name = property.name;
@@ -419,8 +432,8 @@ function findOperationsProperty(
419
432
  * its initializer verbatim, including a multi-line inline map.
420
433
  */
421
434
  function operationsPropertyValueText(
422
- property: ts.ObjectLiteralElementLike,
423
- source: ts.SourceFile,
435
+ property: TS.ObjectLiteralElementLike,
436
+ source: TS.SourceFile,
424
437
  ): string | undefined {
425
438
  if (ts.isShorthandPropertyAssignment(property)) {
426
439
  return property.name.text;
@@ -432,9 +445,9 @@ function operationsPropertyValueText(
432
445
  }
433
446
 
434
447
  function removeOperationsProperty(
435
- property: ts.ObjectLiteralElementLike,
436
- declaration: ts.ObjectLiteralExpression,
437
- source: ts.SourceFile,
448
+ property: TS.ObjectLiteralElementLike,
449
+ declaration: TS.ObjectLiteralExpression,
450
+ source: TS.SourceFile,
438
451
  ): TextEdit[] {
439
452
  const properties = declaration.properties;
440
453
  const index = properties.indexOf(property);
@@ -464,8 +477,8 @@ function removeOperationsProperty(
464
477
  }
465
478
 
466
479
  function introduceBuilder(
467
- source: ts.SourceFile,
468
- call: ts.CallExpression,
480
+ source: TS.SourceFile,
481
+ call: TS.CallExpression,
469
482
  shape: Extract<ShapeClassification, { status: "ok" }>,
470
483
  builderName: string,
471
484
  ): TextEdit[] {
@@ -501,8 +514,8 @@ function introduceBuilder(
501
514
  }
502
515
 
503
516
  function rewriteDefaultExport(
504
- source: ts.SourceFile,
505
- exportAssignment: ts.ExportAssignment,
517
+ source: TS.SourceFile,
518
+ exportAssignment: TS.ExportAssignment,
506
519
  shape: Extract<ShapeClassification, { status: "ok" }>,
507
520
  builderName: string,
508
521
  operationsText: string,
@@ -564,8 +577,8 @@ function rewriteDefaultExport(
564
577
  * type the two-phase shape exists to provide.
565
578
  */
566
579
  function ensureProviderContextType(
567
- source: ts.SourceFile,
568
- call: ts.CallExpression,
580
+ source: TS.SourceFile,
581
+ call: TS.CallExpression,
569
582
  shape: Extract<ShapeClassification, { status: "ok" }>,
570
583
  builderName: string,
571
584
  ): TextEdit[] {
@@ -577,6 +590,15 @@ function ensureProviderContextType(
577
590
  ts.isTypeAliasDeclaration(statement) &&
578
591
  statement.name.text === PROVIDER_CONTEXT_TYPE_NAME,
579
592
  );
593
+ // A named import of `ProviderContext` (the deprecated SDK-root context
594
+ // type, imported by legacy sources) occupies the same name: adding the
595
+ // alias alongside it is TS2440. The import must yield to the derived
596
+ // alias — the whole point of the two-phase shape — so drop it and let the
597
+ // alias own the name.
598
+ const conflictingImportSpecifier = findNamedImportSpecifier(
599
+ source,
600
+ PROVIDER_CONTEXT_TYPE_NAME,
601
+ );
580
602
 
581
603
  if (!hasContextTypeAlias) {
582
604
  // After the builder statement — which is the variable statement when the
@@ -591,6 +613,9 @@ function ensureProviderContextType(
591
613
  end: insertAt,
592
614
  text: `\n\nexport type ${PROVIDER_CONTEXT_TYPE_NAME} = ${PROVIDER_CONTEXT_OF_TYPE_NAME}<typeof ${builderName}>;`,
593
615
  });
616
+ if (conflictingImportSpecifier !== undefined) {
617
+ edits.push(removeImportSpecifier(source, conflictingImportSpecifier));
618
+ }
594
619
  }
595
620
  }
596
621
 
@@ -622,8 +647,8 @@ function ensureProviderContextType(
622
647
  }
623
648
 
624
649
  function findProviderSdkImport(
625
- source: ts.SourceFile,
626
- ): ts.ImportDeclaration | undefined {
650
+ source: TS.SourceFile,
651
+ ): TS.ImportDeclaration | undefined {
627
652
  for (const statement of source.statements) {
628
653
  if (!ts.isImportDeclaration(statement)) continue;
629
654
  const moduleSpecifier = statement.moduleSpecifier;
@@ -640,13 +665,59 @@ function findProviderSdkImport(
640
665
  return undefined;
641
666
  }
642
667
 
668
+ /** Named import specifier binding `localName` in any import declaration. */
669
+ function findNamedImportSpecifier(
670
+ source: TS.SourceFile,
671
+ localName: string,
672
+ ): TS.ImportSpecifier | undefined {
673
+ for (const statement of source.statements) {
674
+ if (!ts.isImportDeclaration(statement)) continue;
675
+ const named = statement.importClause?.namedBindings;
676
+ if (named === undefined || !ts.isNamedImports(named)) continue;
677
+ for (const element of named.elements) {
678
+ if (element.name.text === localName) return element;
679
+ }
680
+ }
681
+ return undefined;
682
+ }
683
+
684
+ /**
685
+ * Edit removing one specifier from its named-import list, absorbing one
686
+ * neighboring comma so the list stays well-formed. Callers guarantee the
687
+ * list has at least one other specifier (legacy sources always import
688
+ * defineProvider alongside the context type).
689
+ */
690
+ function removeImportSpecifier(
691
+ source: TS.SourceFile,
692
+ specifier: TS.ImportSpecifier,
693
+ ): TextEdit {
694
+ const list = specifier.parent;
695
+ const index = list.elements.indexOf(specifier);
696
+ const text = source.getFullText();
697
+ let start = specifier.getFullStart();
698
+ let end = specifier.getEnd();
699
+ let cursor = end;
700
+ while (cursor < text.length && /\s/.test(text.charAt(cursor))) cursor += 1;
701
+ if (text.charAt(cursor) === ",") {
702
+ end = cursor + 1;
703
+ } else if (index > 0) {
704
+ const previous = list.elements[index - 1];
705
+ if (previous !== undefined) {
706
+ let back = previous.getEnd();
707
+ while (back < text.length && /\s/.test(text.charAt(back))) back += 1;
708
+ if (text.charAt(back) === ",") start = back;
709
+ }
710
+ }
711
+ return { start, end, text: "" };
712
+ }
713
+
643
714
  /**
644
715
  * `buildProvider` unless the module already binds that name, in which case a
645
716
  * numbered suffix keeps the transform from shadowing an existing binding.
646
717
  */
647
- function pickBuilderName(source: ts.SourceFile): string {
718
+ function pickBuilderName(source: TS.SourceFile): string {
648
719
  const taken = new Set<string>();
649
- const visit = (node: ts.Node): void => {
720
+ const visit = (node: TS.Node): void => {
650
721
  if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) {
651
722
  taken.add(node.name.text);
652
723
  }
@@ -668,8 +739,8 @@ function pickBuilderName(source: ts.SourceFile): string {
668
739
  return `${DECLARATION_BUILDER_NAME}Migrated`;
669
740
  }
670
741
 
671
- function enclosingVariableStatement(node: ts.Node): ts.VariableStatement | undefined {
672
- let current: ts.Node | undefined = node.parent;
742
+ function enclosingVariableStatement(node: TS.Node): TS.VariableStatement | undefined {
743
+ let current: TS.Node | undefined = node.parent;
673
744
  while (current !== undefined) {
674
745
  if (ts.isVariableStatement(current)) return current;
675
746
  if (ts.isSourceFile(current)) return undefined;
@@ -678,9 +749,9 @@ function enclosingVariableStatement(node: ts.Node): ts.VariableStatement | undef
678
749
  return undefined;
679
750
  }
680
751
 
681
- function firstSyntaxError(source: ts.SourceFile): string | undefined {
752
+ function firstSyntaxError(source: TS.SourceFile): string | undefined {
682
753
  const diagnostics = (
683
- source as ts.SourceFile & { parseDiagnostics?: ts.DiagnosticWithLocation[] }
754
+ source as TS.SourceFile & { parseDiagnostics?: TS.DiagnosticWithLocation[] }
684
755
  ).parseDiagnostics;
685
756
  if (diagnostics === undefined || diagnostics.length === 0) return undefined;
686
757
  const first = diagnostics[0];
package/src/define.ts CHANGED
@@ -2892,6 +2892,16 @@ export type ProviderContextOf<TBuilder> = TBuilder extends ProviderBuilder<infer
2892
2892
  ? ProviderContextFor<TDeclaration>
2893
2893
  : never;
2894
2894
 
2895
+ /** Annotate an operation while preserving the declaration-derived context. */
2896
+ export type OperationDefinitionFor<
2897
+ TBuilder,
2898
+ TInput extends SchemaLike = SchemaLike,
2899
+ TOutput extends SchemaLike = SchemaLike,
2900
+ > = OperationDefinition<TInput, TOutput, ProviderContextOf<TBuilder>>;
2901
+
2902
+ /** Annotate a built provider while preserving the declaration-derived context. */
2903
+ export type ProviderDefinitionFor<TBuilder> = ProviderDefinition<ProviderContextOf<TBuilder>>;
2904
+
2895
2905
  /** Establish a provider declaration before its operations are contextually typed. */
2896
2906
  export function defineProvider<const TDeclaration extends ProviderDeclaration>(
2897
2907
  declaration: TDeclaration &
package/src/index.ts CHANGED
@@ -37,9 +37,11 @@ export {
37
37
  defineStreamOperation,
38
38
  every,
39
39
  type AuthStartNoInputGuard,
40
+ type OperationDefinitionFor,
40
41
  type ProviderBuilder,
41
42
  type ProviderContextOf,
42
43
  type ProviderDeclaration,
44
+ type ProviderDefinitionFor,
43
45
  } from "./define.js";
44
46
  export {
45
47
  AssertionExpressionSchema,
package/src/provider.ts CHANGED
@@ -36,9 +36,11 @@ export {
36
36
  every,
37
37
  } from "./define.js";
38
38
  export type {
39
+ OperationDefinitionFor,
39
40
  ProviderBuilder,
40
41
  ProviderContextOf,
41
42
  ProviderDeclaration,
43
+ ProviderDefinitionFor,
42
44
  } from "./define.js";
43
45
  export type { JsonPrimitive, JsonValue } from "./contract-json.js";
44
46
  export {
@@ -23,7 +23,10 @@ import {
23
23
  type ProviderErrorObservability,
24
24
  type ProviderErrorOptions,
25
25
  } from "../errors.js";
26
- import { sanitizeDiagnosticText } from "../fixture-sanitization.js";
26
+ import {
27
+ REDACTED_FIXTURE_VALUE,
28
+ sanitizeDiagnosticText,
29
+ } from "../fixture-sanitization.js";
27
30
  import {
28
31
  loadProviderLocaleCatalogs,
29
32
  localizeAuthTurn,
@@ -231,17 +234,21 @@ export type ProviderServerStatefulOwnerFenceValidator = (
231
234
  signal: AbortSignal,
232
235
  ) => boolean | Promise<boolean>;
233
236
 
234
- export type ProviderServerOperationExecutorInput = {
235
- readonly provider: ProviderDefinition;
237
+ export type ProviderServerOperationExecutorInput<
238
+ TContext extends Partial<ProviderContext> = ProviderContext,
239
+ > = {
240
+ readonly provider: ProviderDefinition<TContext>;
236
241
  readonly operationId: string;
237
- readonly ctx: ProviderContext;
242
+ readonly ctx: TContext;
238
243
  readonly request: OperationRequest & { readonly deadlineAt?: string };
239
244
  readonly signal?: AbortSignal;
240
245
  readonly internalStatefulForward?: ProviderServerStatefulForwardEnvelope;
241
246
  };
242
247
 
243
- export type ProviderServerOperationExecutor = (
244
- input: ProviderServerOperationExecutorInput,
248
+ export type ProviderServerOperationExecutor<
249
+ TContext extends Partial<ProviderContext> = ProviderContext,
250
+ > = (
251
+ input: ProviderServerOperationExecutorInput<TContext>,
245
252
  ) => Promise<unknown>;
246
253
 
247
254
  type RequestCleanup = () => void | Promise<void>;
@@ -1069,12 +1076,12 @@ export type ProviderServerLogEvent =
1069
1076
 
1070
1077
  export type ProviderServerLogger = (event: ProviderServerLogEvent) => void;
1071
1078
 
1072
- export type ProviderServerOptions = {
1079
+ export type ProviderServerOptions<TContext extends Partial<ProviderContext> = ProviderContext> = {
1073
1080
  logger?: ProviderServerLogger;
1074
1081
  /** Optional provider-specific operation executor. Stateful providers use this to preserve provider-local runtime semantics. */
1075
- operationExecutor?: ProviderServerOperationExecutor;
1082
+ operationExecutor?: ProviderServerOperationExecutor<TContext>;
1076
1083
  /** Optional signed internal executor for stateful owner forwarding. */
1077
- internalOperationExecutor?: ProviderServerOperationExecutor;
1084
+ internalOperationExecutor?: ProviderServerOperationExecutor<TContext>;
1078
1085
  statefulForwarding?: {
1079
1086
  readonly secret: string;
1080
1087
  readonly maxSkewMs?: number;
@@ -1519,9 +1526,48 @@ export type ProviderErrorCauseFrame = {
1519
1526
 
1520
1527
  const MAX_PROVIDER_ERROR_CAUSE_FRAMES = 5;
1521
1528
  const MAX_PROVIDER_ERROR_CAUSE_MESSAGE_LENGTH = 300;
1529
+ const UNSTRUCTURED_PROVIDER_ERROR_CAUSE_MESSAGE = "[UNSTRUCTURED_UPSTREAM_TEXT]";
1530
+ const PROVIDER_ERROR_CAUSE_RETAINED_URL_RUN = /https?:\/\/[^\s"'<>]+/giu;
1531
+ const PROVIDER_ERROR_CAUSE_TOKEN_RUN = /\S+/gu;
1532
+ const PROVIDER_ERROR_CAUSE_TOKEN_EDGE_PUNCTUATION =
1533
+ /^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu;
1534
+ const STRUCTURALLY_SAFE_PROVIDER_ERROR_CAUSE_WORDS = new Set([
1535
+ "completion",
1536
+ "diagnostic",
1537
+ "provider",
1538
+ "rejected",
1539
+ "returned",
1540
+ "upstream",
1541
+ ]);
1542
+
1543
+ /**
1544
+ * Cause frames fail closed when sanitization leaves a plausible credential-shaped free-text run.
1545
+ * Redaction sentinels and retained URLs are ignored. Every other whitespace token (or each side
1546
+ * of a structured key=value token) with at least eight Unicode characters must reduce to this
1547
+ * small vocabulary drawn from SDK diagnostics; counting punctuation keeps bare passwords opaque.
1548
+ */
1549
+ function isStructurallySafeProviderErrorCauseMessage(message: string): boolean {
1550
+ const classifiableMessage = message
1551
+ .replaceAll(REDACTED_FIXTURE_VALUE, " ")
1552
+ .replace(PROVIDER_ERROR_CAUSE_RETAINED_URL_RUN, " ");
1553
+ for (const match of classifiableMessage.matchAll(PROVIDER_ERROR_CAUSE_TOKEN_RUN)) {
1554
+ const token = match[0];
1555
+ for (const run of token.split("=")) {
1556
+ const diagnosticWord = run
1557
+ .replace(PROVIDER_ERROR_CAUSE_TOKEN_EDGE_PUNCTUATION, "")
1558
+ .toLowerCase();
1559
+ if (STRUCTURALLY_SAFE_PROVIDER_ERROR_CAUSE_WORDS.has(diagnosticWord)) continue;
1560
+ if ([...run].length >= 8) return false;
1561
+ }
1562
+ }
1563
+ return true;
1564
+ }
1522
1565
 
1523
1566
  function providerErrorCauseMessage(message: string): string {
1524
1567
  const sanitized = sanitizeDiagnosticText(message);
1568
+ if (!isStructurallySafeProviderErrorCauseMessage(sanitized)) {
1569
+ return UNSTRUCTURED_PROVIDER_ERROR_CAUSE_MESSAGE;
1570
+ }
1525
1571
  return sanitized.length > MAX_PROVIDER_ERROR_CAUSE_MESSAGE_LENGTH
1526
1572
  ? `${sanitized.slice(0, MAX_PROVIDER_ERROR_CAUSE_MESSAGE_LENGTH)}… [truncated]`
1527
1573
  : sanitized;
@@ -2371,16 +2417,18 @@ function parseStatefulForwardingEnvelope(rawBody: unknown): ProviderServerStatef
2371
2417
  * Primary, cross-runtime app factory. Declared capability ESM is preloaded
2372
2418
  * asynchronously, so this path works on Bun and every supported Node release.
2373
2419
  */
2374
- export async function createServerAppAsync(
2375
- provider: ProviderDefinition,
2376
- options: ProviderServerOptions = {},
2420
+ export async function createServerAppAsync<TContext extends Partial<ProviderContext> = ProviderContext>(
2421
+ provider: ProviderDefinition<TContext>,
2422
+ options: ProviderServerOptions<TContext> = {},
2377
2423
  ): Promise<Hono> {
2378
- validateFailClosedDeclaration(provider);
2379
- validateStatefulServerConfig(options);
2424
+ const runtimeProvider = provider as unknown as ProviderDefinition;
2425
+ const runtimeOptions = options as unknown as ProviderServerOptions;
2426
+ validateFailClosedDeclaration(runtimeProvider);
2427
+ validateStatefulServerConfig(runtimeOptions);
2380
2428
  return createServerAppWithCapabilityModules(
2381
- provider,
2382
- options,
2383
- await loadProviderCapabilityModules(provider),
2429
+ runtimeProvider,
2430
+ runtimeOptions,
2431
+ await loadProviderCapabilityModules(runtimeProvider),
2384
2432
  );
2385
2433
  }
2386
2434
 
@@ -2390,16 +2438,18 @@ export async function createServerAppAsync(
2390
2438
  * capability require Bun or Node >=22.12; older Node releases receive an
2391
2439
  * actionable error directing them to createServerAppAsync().
2392
2440
  */
2393
- export function createServerApp(
2394
- provider: ProviderDefinition,
2395
- options: ProviderServerOptions = {},
2441
+ export function createServerApp<TContext extends Partial<ProviderContext> = ProviderContext>(
2442
+ provider: ProviderDefinition<TContext>,
2443
+ options: ProviderServerOptions<TContext> = {},
2396
2444
  ): Hono {
2397
- validateFailClosedDeclaration(provider);
2398
- validateStatefulServerConfig(options);
2445
+ const runtimeProvider = provider as unknown as ProviderDefinition;
2446
+ const runtimeOptions = options as unknown as ProviderServerOptions;
2447
+ validateFailClosedDeclaration(runtimeProvider);
2448
+ validateStatefulServerConfig(runtimeOptions);
2399
2449
  return createServerAppWithCapabilityModules(
2400
- provider,
2401
- options,
2402
- loadProviderCapabilityModulesSync(provider),
2450
+ runtimeProvider,
2451
+ runtimeOptions,
2452
+ loadProviderCapabilityModulesSync(runtimeProvider),
2403
2453
  );
2404
2454
  }
2405
2455
 
@@ -3089,7 +3139,8 @@ export type ProviderServerHandle = {
3089
3139
  close(options?: ProviderServerCloseOptions): Promise<void>;
3090
3140
  };
3091
3141
 
3092
- export interface ServeOptions extends ProviderServerOptions {
3142
+ export interface ServeOptions<TContext extends Partial<ProviderContext> = ProviderContext>
3143
+ extends ProviderServerOptions<TContext> {
3093
3144
  host?: string;
3094
3145
  port?: number;
3095
3146
  /**
@@ -3116,9 +3167,9 @@ type ProcessSignalCoordinator = {
3116
3167
 
3117
3168
  const processSignalCoordinators = new Map<NodeJS.Signals, ProcessSignalCoordinator>();
3118
3169
 
3119
- export async function serve(
3120
- provider: ProviderDefinition,
3121
- options: ServeOptions = {},
3170
+ export async function serve<TContext extends Partial<ProviderContext> = ProviderContext>(
3171
+ provider: ProviderDefinition<TContext>,
3172
+ options: ServeOptions<TContext> = {},
3122
3173
  ): Promise<ProviderServerHandle> {
3123
3174
  const bunRuntime = getBunServeRuntime();
3124
3175
 
@@ -3133,7 +3184,7 @@ export async function serve(
3133
3184
  );
3134
3185
  const configuredSignals = resolveShutdownSignals(options.shutdown?.signals ?? true);
3135
3186
  const selfTestSecrets = resolveSelfTestMasterSecrets();
3136
- const serverAppOptions: ProviderServerOptions = {
3187
+ const serverAppOptions: ProviderServerOptions<TContext> = {
3137
3188
  logger: options.logger,
3138
3189
  ocr: options.ocr,
3139
3190
  stt: options.stt,
@@ -3163,12 +3214,15 @@ export async function serve(
3163
3214
  // socket the tenant-facing gateway never dials. Off by default — it only
3164
3215
  // starts when the shared self-test master secret env is present.
3165
3216
  if (selfTestSecrets && selfTestModule) {
3166
- const selfTestApp = selfTestModule.createSelfTestApp(provider, {
3167
- secrets: selfTestSecrets,
3168
- invoke: selfTestModule.createSelfTestInvoke(app),
3169
- authFlow: selfTestModule.createSelfTestAuthFlowInvoke(app),
3170
- logger,
3171
- });
3217
+ const selfTestApp = selfTestModule.createSelfTestApp(
3218
+ provider as unknown as ProviderDefinition,
3219
+ {
3220
+ secrets: selfTestSecrets,
3221
+ invoke: selfTestModule.createSelfTestInvoke(app),
3222
+ authFlow: selfTestModule.createSelfTestAuthFlowInvoke(app),
3223
+ logger,
3224
+ },
3225
+ );
3172
3226
  servers.push(
3173
3227
  bunRuntime.serve({
3174
3228
  port: options.selfTestPort ?? selfTestModule.resolveSelfTestPort(),
package/src/types.ts CHANGED
@@ -2479,7 +2479,7 @@ export interface ProviderDeploymentOverrides {
2479
2479
  buildContext?: string;
2480
2480
  }
2481
2481
 
2482
- export interface ProviderDefinition {
2482
+ export interface ProviderDefinition<TContext = ProviderContext> {
2483
2483
  id: string;
2484
2484
  version: string;
2485
2485
  runtime: "standard" | "shared" | "browser";
@@ -2509,7 +2509,7 @@ export interface ProviderDefinition {
2509
2509
  credential?: CredentialDeclaration;
2510
2510
  context?: ContextDeclaration;
2511
2511
  meta: ProviderMeta;
2512
- operations: Record<string, OperationDefinition<SchemaLike, SchemaLike>>;
2512
+ operations: Record<string, OperationDefinition<SchemaLike, SchemaLike, TContext>>;
2513
2513
  healthMonitor?: ProviderHealthMonitorConfig;
2514
2514
  /** Transitional alias for `healthMonitor`; `defineProvider` mirrors both. */
2515
2515
  healthProbe?: ProviderHealthProbeConfig;