@apifuse/provider-sdk 2.2.0-beta.63 → 2.2.0-beta.64
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/AUTHORING.md +99 -0
- package/CHANGELOG.md +4 -0
- package/LICENSE +21 -0
- package/bin/apifuse-submit-check.ts +9 -5
- package/dist/handle-meta.d.ts +36 -1
- package/dist/handle-meta.js +16 -0
- package/dist/handle.d.ts +23 -5
- package/dist/handle.js +54 -8
- package/dist/health-scenario.d.ts +38 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +3 -1
- package/dist/lint.d.ts +5 -0
- package/dist/lint.js +57 -33
- package/dist/provider.d.ts +1 -1
- package/dist/provider.js +1 -1
- package/dist/runtime/handle.js +9 -1
- package/dist/runtime/ocr.js +5 -0
- package/dist/runtime-boundary-lint.d.ts +132 -0
- package/dist/runtime-boundary-lint.js +586 -0
- package/dist/types.d.ts +29 -0
- package/dist/typescript-module.d.ts +39 -0
- package/dist/typescript-module.js +72 -0
- package/package.json +1 -1
- package/src/handle-meta.ts +51 -1
- package/src/handle.ts +92 -10
- package/src/health-scenario.ts +38 -0
- package/src/index.ts +14 -0
- package/src/lint.ts +82 -41
- package/src/provider.ts +2 -0
- package/src/runtime/handle.ts +9 -1
- package/src/runtime/ocr.ts +5 -0
- package/src/runtime-boundary-lint.ts +703 -0
- package/src/types.ts +29 -0
- package/src/typescript-module.ts +100 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
const requireModule = createRequire(import.meta.url);
|
|
3
|
+
/**
|
|
4
|
+
* Package specifiers that may carry the compiler API, in resolution order.
|
|
5
|
+
*
|
|
6
|
+
* `typescript@7` (the Go-native toolchain) resolves but its JavaScript entry
|
|
7
|
+
* exports only `version`/`getExePath` — no `createSourceFile`, so the lint
|
|
8
|
+
* rules cannot parse with it. The compiler API of that generation is published
|
|
9
|
+
* as `@typescript/typescript6`, which is what a `typescript@7` dependency tree
|
|
10
|
+
* (for example the APIFuse monorepo root) installs alongside the shell.
|
|
11
|
+
*/
|
|
12
|
+
export const TYPESCRIPT_COMPILER_MODULE_SPECIFIERS = [
|
|
13
|
+
"typescript",
|
|
14
|
+
"@typescript/typescript6",
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* True when `candidate` is a loaded module that exposes the parser the lint
|
|
18
|
+
* rules need. Internal (narrows to the `typescript` module type, which is not
|
|
19
|
+
* part of the package surface); `lintRuntimeBoundarySources` applies it to the
|
|
20
|
+
* caller-supplied option.
|
|
21
|
+
*/
|
|
22
|
+
export function isTypeScriptCompilerModule(candidate) {
|
|
23
|
+
return (typeof candidate === "object" &&
|
|
24
|
+
candidate !== null &&
|
|
25
|
+
typeof candidate.createSourceFile === "function" &&
|
|
26
|
+
typeof candidate.ScriptTarget === "object");
|
|
27
|
+
}
|
|
28
|
+
function describeCandidate(candidate) {
|
|
29
|
+
if (typeof candidate === "object" && candidate !== null) {
|
|
30
|
+
const version = candidate.version;
|
|
31
|
+
if (typeof version === "string")
|
|
32
|
+
return `resolved ${version} without a compiler API`;
|
|
33
|
+
}
|
|
34
|
+
return "resolved without a compiler API";
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Resolve the compiler API through `load`, trying each specifier in order and
|
|
38
|
+
* accepting the first module that exposes `createSourceFile`. Exposed for the
|
|
39
|
+
* tests; production code uses `getTypeScript()`.
|
|
40
|
+
*/
|
|
41
|
+
export function loadTypeScriptModule(load, specifiers = TYPESCRIPT_COMPILER_MODULE_SPECIFIERS) {
|
|
42
|
+
const attempts = [];
|
|
43
|
+
for (const specifier of specifiers) {
|
|
44
|
+
let candidate;
|
|
45
|
+
try {
|
|
46
|
+
candidate = load(specifier);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
attempts.push(`${specifier}: not resolvable`);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (isTypeScriptCompilerModule(candidate))
|
|
53
|
+
return candidate;
|
|
54
|
+
attempts.push(`${specifier}: ${describeCandidate(candidate)}`);
|
|
55
|
+
}
|
|
56
|
+
throw new Error(`The provider source lint needs the TypeScript compiler API (createSourceFile) and none of the resolvable packages provide it — ${attempts.join("; ")}. Install typescript 6.x as a devDependency, or @typescript/typescript6 next to a typescript 7 toolchain, in the dependency tree that installs @apifuse/provider-sdk.`);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Lazily loaded TypeScript compiler API shared by the source-level lint rules.
|
|
60
|
+
*
|
|
61
|
+
* `typeof import(...)` keeps the type without emitting a static import: the
|
|
62
|
+
* typescript package is a devDependency of the SDK and only the CLI and the
|
|
63
|
+
* lint rules need it at runtime, which the typescript-import-boundary test
|
|
64
|
+
* enforces for production sources. Resolution starts at the installed SDK, so
|
|
65
|
+
* the caller's dependency tree must provide one of
|
|
66
|
+
* `TYPESCRIPT_COMPILER_MODULE_SPECIFIERS`.
|
|
67
|
+
*/
|
|
68
|
+
let typeScriptModule;
|
|
69
|
+
export function getTypeScript() {
|
|
70
|
+
typeScriptModule ??= loadTypeScriptModule(requireModule);
|
|
71
|
+
return typeScriptModule;
|
|
72
|
+
}
|
package/package.json
CHANGED
package/src/handle-meta.ts
CHANGED
|
@@ -19,11 +19,34 @@ export type HandleAccess = "bound" | "public";
|
|
|
19
19
|
*/
|
|
20
20
|
export type HandleIssuedBy = string | readonly string[];
|
|
21
21
|
|
|
22
|
-
/**
|
|
22
|
+
/** The schema side a handle field appears on. */
|
|
23
|
+
export type HandleFieldDirection = "input" | "output";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Direction-specific property keys for one handle kind, for the operations
|
|
27
|
+
* whose caller-facing contract names the same handle differently coming in and
|
|
28
|
+
* going out (the platform canon's `cursor` in, `next_cursor` out). The two keys
|
|
29
|
+
* are two spellings of ONE handle: the value a caller reads from `output` is
|
|
30
|
+
* the value it must send back under `input`, unedited.
|
|
31
|
+
*/
|
|
32
|
+
export interface HandleFieldNames {
|
|
33
|
+
readonly input: string;
|
|
34
|
+
readonly output: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Value stored under {@link APIFUSE_HANDLE_META_KEY} on a handle field.
|
|
39
|
+
*
|
|
40
|
+
* `fieldName` is the INPUT property key — the name a caller sends the handle
|
|
41
|
+
* back under, and the name every runtime error message quotes. `outputFieldName`
|
|
42
|
+
* is present only when the output key differs, so a kind with one name emits
|
|
43
|
+
* exactly the meta it always did.
|
|
44
|
+
*/
|
|
23
45
|
export interface HandleFieldMeta {
|
|
24
46
|
readonly kind: string;
|
|
25
47
|
readonly type: HandleKindType;
|
|
26
48
|
readonly fieldName: string;
|
|
49
|
+
readonly outputFieldName?: string;
|
|
27
50
|
readonly issuedBy?: HandleIssuedBy;
|
|
28
51
|
}
|
|
29
52
|
|
|
@@ -31,15 +54,41 @@ export interface HandleFieldMeta {
|
|
|
31
54
|
* Structural view of a handle kind as seen by the provider declaration
|
|
32
55
|
* (`handle: [WaitingDraft, PageCursor]`), lint, and the registry. The runtime
|
|
33
56
|
* kinds returned by `defineCursor` / `defineDraft` satisfy this shape.
|
|
57
|
+
*
|
|
58
|
+
* `fieldName` / `outputFieldName` follow the {@link HandleFieldMeta} rule:
|
|
59
|
+
* `fieldName` is the input key, `outputFieldName` is set only when the output
|
|
60
|
+
* key differs.
|
|
34
61
|
*/
|
|
35
62
|
export interface HandleKindDeclaration {
|
|
36
63
|
readonly name: string;
|
|
37
64
|
readonly type: HandleKindType;
|
|
38
65
|
readonly fieldName: string;
|
|
66
|
+
readonly outputFieldName?: string;
|
|
39
67
|
readonly access: HandleAccess;
|
|
40
68
|
readonly issuedBy?: HandleIssuedBy;
|
|
41
69
|
}
|
|
42
70
|
|
|
71
|
+
/**
|
|
72
|
+
* The property key a handle kind claims on one side of an operation.
|
|
73
|
+
*
|
|
74
|
+
* This is the single place that resolves a direction to a key, so lint, the
|
|
75
|
+
* runtime, and error text cannot disagree about which name belongs where.
|
|
76
|
+
*/
|
|
77
|
+
export function handleFieldNameFor(
|
|
78
|
+
kind: Pick<HandleKindDeclaration, "fieldName" | "outputFieldName">,
|
|
79
|
+
direction: HandleFieldDirection,
|
|
80
|
+
): string {
|
|
81
|
+
if (direction === "input") return kind.fieldName;
|
|
82
|
+
return kind.outputFieldName ?? kind.fieldName;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** True when a kind names the same handle differently on input and output. */
|
|
86
|
+
export function handleHasDirectionalFieldNames(
|
|
87
|
+
kind: Pick<HandleKindDeclaration, "fieldName" | "outputFieldName">,
|
|
88
|
+
): boolean {
|
|
89
|
+
return kind.outputFieldName !== undefined && kind.outputFieldName !== kind.fieldName;
|
|
90
|
+
}
|
|
91
|
+
|
|
43
92
|
/** Kind names are lowercase letters only so they never fuse with the word body. */
|
|
44
93
|
export const HANDLE_KIND_NAME_PATTERN = /^[a-z]{2,12}$/;
|
|
45
94
|
|
|
@@ -77,6 +126,7 @@ export function isHandleFieldMeta(value: unknown): value is HandleFieldMeta {
|
|
|
77
126
|
typeof record.kind === "string" &&
|
|
78
127
|
(record.type === "cursor" || record.type === "draft") &&
|
|
79
128
|
typeof record.fieldName === "string" &&
|
|
129
|
+
(record.outputFieldName === undefined || typeof record.outputFieldName === "string") &&
|
|
80
130
|
(record.issuedBy === undefined || isHandleIssuedBy(record.issuedBy))
|
|
81
131
|
);
|
|
82
132
|
}
|
package/src/handle.ts
CHANGED
|
@@ -18,9 +18,12 @@ import {
|
|
|
18
18
|
APIFUSE_HANDLE_META_KEY,
|
|
19
19
|
formatHandleIssuers,
|
|
20
20
|
handleFieldDescription,
|
|
21
|
+
handleHasDirectionalFieldNames,
|
|
21
22
|
HANDLE_KIND_NAME_PATTERN,
|
|
22
23
|
type HandleAccess,
|
|
24
|
+
type HandleFieldDirection,
|
|
23
25
|
type HandleFieldMeta,
|
|
26
|
+
type HandleFieldNames,
|
|
24
27
|
type HandleIssuedBy,
|
|
25
28
|
type HandleKindDeclaration,
|
|
26
29
|
type HandleKindType,
|
|
@@ -36,7 +39,9 @@ import type { ProviderStateDurationString } from "./types.js";
|
|
|
36
39
|
|
|
37
40
|
export type {
|
|
38
41
|
HandleAccess,
|
|
42
|
+
HandleFieldDirection,
|
|
39
43
|
HandleFieldMeta,
|
|
44
|
+
HandleFieldNames,
|
|
40
45
|
HandleIssuedBy,
|
|
41
46
|
HandleKindDeclaration,
|
|
42
47
|
HandleKindType,
|
|
@@ -69,6 +74,10 @@ export interface HandleKindBase<TSchema extends ZodType = ZodType> extends Handl
|
|
|
69
74
|
/**
|
|
70
75
|
* Schema for the handle field, usable in output and input positions. Carries
|
|
71
76
|
* the `x-apifuse-handle` meta and an SDK-owned English description.
|
|
77
|
+
*
|
|
78
|
+
* One schema serves both directions even when the kind declares separate
|
|
79
|
+
* `input` / `output` property keys: the meta carries both names and lint
|
|
80
|
+
* checks the key against the side the field actually appears on.
|
|
72
81
|
*/
|
|
73
82
|
field(): ZodString;
|
|
74
83
|
}
|
|
@@ -259,9 +268,20 @@ export function isHandleError(value: unknown): value is HandleError {
|
|
|
259
268
|
*/
|
|
260
269
|
export function handleRecoverySentence(kind: {
|
|
261
270
|
readonly fieldName: string;
|
|
271
|
+
readonly outputFieldName?: string;
|
|
262
272
|
readonly issuedBy?: HandleIssuedBy;
|
|
263
273
|
}): string {
|
|
264
274
|
const issuers = formatHandleIssuers(kind.issuedBy);
|
|
275
|
+
// When the kind is issued under one key and accepted under another, naming
|
|
276
|
+
// only one of them tells the LLM to look for a field that is not there. Name
|
|
277
|
+
// the output key it reads and the input key it writes, in that order.
|
|
278
|
+
if (handleHasDirectionalFieldNames(kind)) {
|
|
279
|
+
const issued = kind.outputFieldName as string;
|
|
280
|
+
if (issuers) {
|
|
281
|
+
return `Call ${issuers} again and pass the new \`${issued}\` back as \`${kind.fieldName}\`, exactly as returned.`;
|
|
282
|
+
}
|
|
283
|
+
return `Request a new \`${issued}\` and pass it back as \`${kind.fieldName}\`, exactly as returned.`;
|
|
284
|
+
}
|
|
265
285
|
if (issuers) {
|
|
266
286
|
return `Call ${issuers} again and pass the new \`${kind.fieldName}\` exactly as returned.`;
|
|
267
287
|
}
|
|
@@ -317,8 +337,16 @@ export function parseHandleDurationMs(value: ProviderStateDurationString, label:
|
|
|
317
337
|
export interface DefineCursorOptions<TSchema extends ZodType> {
|
|
318
338
|
/** Kind name, `/^[a-z]{2,12}$/`; becomes the handle prefix. */
|
|
319
339
|
readonly name: string;
|
|
320
|
-
/**
|
|
321
|
-
|
|
340
|
+
/**
|
|
341
|
+
* Schema property key carrying the handle. Default `${name}_token`.
|
|
342
|
+
*
|
|
343
|
+
* Pass `{ input, output }` when the caller-facing contract names the same
|
|
344
|
+
* handle differently in each direction — the canon's `{ input: "cursor",
|
|
345
|
+
* output: "next_cursor" }`. Both keys must be given; the handle's identity
|
|
346
|
+
* stays the kind, so a value issued under `output` is still only valid when
|
|
347
|
+
* sent back under `input`.
|
|
348
|
+
*/
|
|
349
|
+
readonly fieldName?: string | HandleFieldNames;
|
|
322
350
|
readonly schema: TSchema;
|
|
323
351
|
readonly ttl: ProviderStateDurationString;
|
|
324
352
|
/** `"bound"` (default): connection-scoped, 2 words. `"public"`: provider-scoped, 4-5 words. */
|
|
@@ -338,7 +366,12 @@ export interface DefineDraftOptions<
|
|
|
338
366
|
TResultSchema extends ZodType | undefined,
|
|
339
367
|
> {
|
|
340
368
|
readonly name: string;
|
|
341
|
-
|
|
369
|
+
/**
|
|
370
|
+
* Schema property key carrying the handle. Default `${name}_token`. Accepts
|
|
371
|
+
* `{ input, output }` for a contract that names the handle differently in
|
|
372
|
+
* each direction; see {@link DefineCursorOptions.fieldName}.
|
|
373
|
+
*/
|
|
374
|
+
readonly fieldName?: string | HandleFieldNames;
|
|
342
375
|
readonly schema: TSchema;
|
|
343
376
|
/** Optional schema of the commit result (type-level). */
|
|
344
377
|
readonly result?: TResultSchema;
|
|
@@ -372,12 +405,59 @@ function assertKindName(name: unknown, define: string): asserts name is string {
|
|
|
372
405
|
}
|
|
373
406
|
}
|
|
374
407
|
|
|
375
|
-
function assertFieldName(
|
|
376
|
-
|
|
408
|
+
function assertFieldName(
|
|
409
|
+
fieldName: unknown,
|
|
410
|
+
define: string,
|
|
411
|
+
label: string,
|
|
412
|
+
): asserts fieldName is string {
|
|
413
|
+
if (typeof fieldName !== "string" || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(fieldName)) {
|
|
414
|
+
throw new Error(
|
|
415
|
+
`${define}: ${label} must be a plain identifier usable as a schema property key; received ${JSON.stringify(fieldName)}.`,
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Resolves the `fieldName` option to the input key and, when the two differ,
|
|
422
|
+
* the output key.
|
|
423
|
+
*
|
|
424
|
+
* The string form names one key for both directions and is unchanged. The
|
|
425
|
+
* object form requires BOTH keys: a half-declaration would leave one side
|
|
426
|
+
* silently defaulting to `${name}_token`, which is the kind of mismatch this
|
|
427
|
+
* option exists to make explicit. `outputFieldName` is left undefined when the
|
|
428
|
+
* two keys are equal so a one-name kind emits exactly the meta it always did.
|
|
429
|
+
*/
|
|
430
|
+
function resolveFieldNames(
|
|
431
|
+
value: string | HandleFieldNames | undefined,
|
|
432
|
+
define: string,
|
|
433
|
+
name: string,
|
|
434
|
+
): { fieldName: string; outputFieldName?: string } {
|
|
435
|
+
if (value === undefined) return { fieldName: `${name}_token` };
|
|
436
|
+
if (typeof value === "string") {
|
|
437
|
+
assertFieldName(value, define, "fieldName");
|
|
438
|
+
return { fieldName: value };
|
|
439
|
+
}
|
|
440
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
441
|
+
throw new Error(
|
|
442
|
+
`${define}: "${name}" fieldName must be a property key or { input, output }; received ${JSON.stringify(value)}.`,
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
const record = value as unknown as Record<string, unknown>;
|
|
446
|
+
const extra = Object.keys(record).filter((key) => key !== "input" && key !== "output");
|
|
447
|
+
if (extra.length > 0) {
|
|
448
|
+
throw new Error(
|
|
449
|
+
`${define}: "${name}" fieldName accepts only "input" and "output"; received extra ${extra.map((key) => JSON.stringify(key)).join(", ")}.`,
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
if (record.input === undefined || record.output === undefined) {
|
|
377
453
|
throw new Error(
|
|
378
|
-
`${define}: fieldName must
|
|
454
|
+
`${define}: "${name}" fieldName must declare both "input" and "output" when the two directions differ; declaring one would leave the other defaulting to "${name}_token".`,
|
|
379
455
|
);
|
|
380
456
|
}
|
|
457
|
+
assertFieldName(record.input, define, "fieldName.input");
|
|
458
|
+
assertFieldName(record.output, define, "fieldName.output");
|
|
459
|
+
if (record.input === record.output) return { fieldName: record.input };
|
|
460
|
+
return { fieldName: record.input, outputFieldName: record.output };
|
|
381
461
|
}
|
|
382
462
|
|
|
383
463
|
function assertPositiveInteger(value: number, label: string, define: string): void {
|
|
@@ -442,8 +522,7 @@ export function defineCursor<TSchema extends ZodType>(
|
|
|
442
522
|
): CursorKind<TSchema> {
|
|
443
523
|
const define = "defineCursor";
|
|
444
524
|
assertKindName(options.name, define);
|
|
445
|
-
const fieldName = options.fieldName
|
|
446
|
-
assertFieldName(fieldName, define);
|
|
525
|
+
const { fieldName, outputFieldName } = resolveFieldNames(options.fieldName, define, options.name);
|
|
447
526
|
const access: unknown = options.access ?? "bound";
|
|
448
527
|
assertAccess(access, define);
|
|
449
528
|
const ttlMs = parseHandleDurationMs(options.ttl, `"${options.name}" ttl`);
|
|
@@ -464,12 +543,14 @@ export function defineCursor<TSchema extends ZodType>(
|
|
|
464
543
|
kind: options.name,
|
|
465
544
|
type: "cursor",
|
|
466
545
|
fieldName,
|
|
546
|
+
...(outputFieldName ? { outputFieldName } : {}),
|
|
467
547
|
...(issuedBy ? { issuedBy } : {}),
|
|
468
548
|
};
|
|
469
549
|
return Object.freeze({
|
|
470
550
|
name: options.name,
|
|
471
551
|
type: "cursor",
|
|
472
552
|
fieldName,
|
|
553
|
+
outputFieldName,
|
|
473
554
|
access,
|
|
474
555
|
issuedBy,
|
|
475
556
|
schema: options.schema,
|
|
@@ -494,8 +575,7 @@ export function defineDraft<
|
|
|
494
575
|
>(options: DefineDraftOptions<TSchema, TResultSchema>): DraftKind<TSchema, TResultSchema> {
|
|
495
576
|
const define = "defineDraft";
|
|
496
577
|
assertKindName(options.name, define);
|
|
497
|
-
const fieldName = options.fieldName
|
|
498
|
-
assertFieldName(fieldName, define);
|
|
578
|
+
const { fieldName, outputFieldName } = resolveFieldNames(options.fieldName, define, options.name);
|
|
499
579
|
if (!options.ttl || typeof options.ttl !== "object") {
|
|
500
580
|
throw new Error(`${define}: draft "${options.name}" ttl must be { idle, max }.`);
|
|
501
581
|
}
|
|
@@ -533,12 +613,14 @@ export function defineDraft<
|
|
|
533
613
|
kind: options.name,
|
|
534
614
|
type: "draft",
|
|
535
615
|
fieldName,
|
|
616
|
+
...(outputFieldName ? { outputFieldName } : {}),
|
|
536
617
|
...(issuedBy ? { issuedBy } : {}),
|
|
537
618
|
};
|
|
538
619
|
return Object.freeze({
|
|
539
620
|
name: options.name,
|
|
540
621
|
type: "draft",
|
|
541
622
|
fieldName,
|
|
623
|
+
outputFieldName,
|
|
542
624
|
access,
|
|
543
625
|
strength,
|
|
544
626
|
issuedBy,
|
package/src/health-scenario.ts
CHANGED
|
@@ -545,6 +545,44 @@ export type OperationResult = {
|
|
|
545
545
|
data: JsonValue;
|
|
546
546
|
request_id: string;
|
|
547
547
|
duration_ms: number;
|
|
548
|
+
/**
|
|
549
|
+
* Whether the monitor's operation step was answered from a stale-if-error
|
|
550
|
+
* cache serve — the runtime's projection of the response envelope's
|
|
551
|
+
* `meta.stale`.
|
|
552
|
+
*
|
|
553
|
+
* An operation that reads its upstream through `ctx.cache.getOrSet(...,
|
|
554
|
+
* { staleIfErrorMs })` answers an upstream outage with HTTP 200 and a
|
|
555
|
+
* well-formed, fully schema-valid body for the whole stale window. Every
|
|
556
|
+
* assertion over `status_code` and `data` therefore passes, and the probe
|
|
557
|
+
* rolls up `ok` while the upstream is down. This boolean is the only thing
|
|
558
|
+
* in the step result that separates that from a live read.
|
|
559
|
+
*
|
|
560
|
+
* Always present, so a reference to it resolves to `false` rather than
|
|
561
|
+
* raising `reference_unresolvable`; a response whose `meta` is malformed
|
|
562
|
+
* fails the step rather than projecting as fresh.
|
|
563
|
+
*
|
|
564
|
+
* Providers that serve stale-if-error should guard on it, so an outage the
|
|
565
|
+
* cache is absorbing stops reading as green:
|
|
566
|
+
*
|
|
567
|
+
* ```ts
|
|
568
|
+
* {
|
|
569
|
+
* id: "guard-freshness",
|
|
570
|
+
* result: "freshness-guarded",
|
|
571
|
+
* kind: "guard",
|
|
572
|
+
* condition: {
|
|
573
|
+
* kind: "predicate",
|
|
574
|
+
* operator: "not_equals",
|
|
575
|
+
* actual: { ref: { namespace: "steps", binding: "case", path: ["served_stale_cache"] } },
|
|
576
|
+
* expected: true,
|
|
577
|
+
* },
|
|
578
|
+
* onFail: {
|
|
579
|
+
* attribute: [{ operationId, status: "degraded", reasonCode: "expected_absence", reasonKey }],
|
|
580
|
+
* stop: "scenario",
|
|
581
|
+
* },
|
|
582
|
+
* }
|
|
583
|
+
* ```
|
|
584
|
+
*/
|
|
585
|
+
served_stale_cache: boolean;
|
|
548
586
|
candidate?: { attempts: number; selected_index: number | null };
|
|
549
587
|
};
|
|
550
588
|
export type ExtractResult<T extends JsonValue = JsonValue> = {
|
package/src/index.ts
CHANGED
|
@@ -127,7 +127,19 @@ export {
|
|
|
127
127
|
type LintDiagnostic,
|
|
128
128
|
lintOperation,
|
|
129
129
|
lintProvider,
|
|
130
|
+
type ProviderLintInformation,
|
|
131
|
+
type ProviderLintResult,
|
|
130
132
|
} from "./lint.js";
|
|
133
|
+
export {
|
|
134
|
+
type LintRuntimeBoundarySourcesOptions,
|
|
135
|
+
lintRuntimeBoundarySources,
|
|
136
|
+
RUNTIME_BOUNDARY_RULES,
|
|
137
|
+
type RuntimeBoundaryRule,
|
|
138
|
+
} from "./runtime-boundary-lint.js";
|
|
139
|
+
export {
|
|
140
|
+
TYPESCRIPT_COMPILER_MODULE_SPECIFIERS,
|
|
141
|
+
type TypeScriptCompilerModuleLike,
|
|
142
|
+
} from "./typescript-module.js";
|
|
131
143
|
export * from "./recipes/gov-api.js";
|
|
132
144
|
export * from "./recipes/rest-api.js";
|
|
133
145
|
export { createFlowContext, createScratchpad } from "./runtime/auth-flow.js";
|
|
@@ -143,6 +155,8 @@ export {
|
|
|
143
155
|
APIFUSE_HANDLE_META_KEY,
|
|
144
156
|
HANDLE_KIND_NAME_PATTERN,
|
|
145
157
|
handleFieldDescription,
|
|
158
|
+
handleFieldNameFor,
|
|
159
|
+
handleHasDirectionalFieldNames,
|
|
146
160
|
isHandleFieldMeta,
|
|
147
161
|
} from "./handle-meta.js";
|
|
148
162
|
export {
|