@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.
- package/CHANGELOG.md +8 -0
- package/bin/apifuse-check.ts +1 -1
- package/bin/apifuse-dev.ts +1 -1
- package/bin/apifuse-migrate-shape.ts +141 -23
- package/bin/apifuse-submit-check.ts +99 -86
- package/dist/cli/commands.js +1 -1
- package/dist/cli/create.d.ts +1 -1
- package/dist/cli/create.js +2 -2
- package/dist/cli/migrate-operation-shape.d.ts +44 -0
- package/dist/cli/migrate-operation-shape.js +113 -0
- package/dist/cli/migrate-provider-shape.js +64 -1
- package/dist/define.d.ts +4 -0
- package/dist/index.d.ts +1 -1
- package/dist/provider.d.ts +1 -1
- package/dist/server/serve-implementation.d.ts +11 -11
- package/dist/server/serve-implementation.js +50 -7
- package/dist/types.d.ts +2 -2
- package/package.json +3 -3
- package/src/cli/commands.ts +1 -1
- package/src/cli/create.ts +2 -2
- package/src/cli/migrate-operation-shape.ts +184 -0
- package/src/cli/migrate-provider-shape.ts +107 -36
- package/src/define.ts +10 -0
- package/src/index.ts +2 -0
- package/src/provider.ts +2 -0
- package/src/server/serve-implementation.ts +90 -36
- package/src/types.ts +2 -2
|
@@ -1,4 +1,17 @@
|
|
|
1
|
-
import
|
|
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?:
|
|
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:
|
|
218
|
-
call:
|
|
219
|
-
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:
|
|
311
|
-
const calls:
|
|
312
|
-
const visit = (node:
|
|
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:
|
|
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:
|
|
337
|
-
declarationCall:
|
|
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:
|
|
357
|
-
exportAssignment:
|
|
358
|
-
declarationCall:
|
|
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:
|
|
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:
|
|
401
|
-
):
|
|
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:
|
|
423
|
-
source:
|
|
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:
|
|
436
|
-
declaration:
|
|
437
|
-
source:
|
|
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:
|
|
468
|
-
call:
|
|
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:
|
|
505
|
-
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:
|
|
568
|
-
call:
|
|
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:
|
|
626
|
-
):
|
|
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:
|
|
718
|
+
function pickBuilderName(source: TS.SourceFile): string {
|
|
648
719
|
const taken = new Set<string>();
|
|
649
|
-
const visit = (node:
|
|
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:
|
|
672
|
-
let current:
|
|
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:
|
|
752
|
+
function firstSyntaxError(source: TS.SourceFile): string | undefined {
|
|
682
753
|
const diagnostics = (
|
|
683
|
-
source as
|
|
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 {
|
|
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
|
-
|
|
237
|
+
export type ProviderServerOperationExecutorInput<
|
|
238
|
+
TContext extends Partial<ProviderContext> = ProviderContext,
|
|
239
|
+
> = {
|
|
240
|
+
readonly provider: ProviderDefinition<TContext>;
|
|
236
241
|
readonly operationId: string;
|
|
237
|
-
readonly ctx:
|
|
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
|
-
|
|
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
|
-
|
|
2379
|
-
|
|
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
|
-
|
|
2382
|
-
|
|
2383
|
-
await loadProviderCapabilityModules(
|
|
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
|
-
|
|
2398
|
-
|
|
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
|
-
|
|
2401
|
-
|
|
2402
|
-
loadProviderCapabilityModulesSync(
|
|
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
|
|
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(
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
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;
|