@apifuse/provider-sdk 2.2.0-beta.61 → 2.2.0-beta.62

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 CHANGED
@@ -1,5 +1,9 @@
1
1
  # @apifuse/provider-sdk Changelog
2
2
 
3
+ ## 2.2.0-beta.62
4
+
5
+ - Release candidate for main commit 98c623f799268b6d13ce59844b2b33ed78dcd0c3.
6
+
3
7
  ## 2.2.0-beta.61
4
8
 
5
9
  - Release candidate for main commit 8ba435285f6f448bb1b8d01b1d65d2e1a4b42aaa.
@@ -451,25 +451,29 @@ const NEGATIVE_CONTROLS = [
451
451
  ].join("\n"),
452
452
  },
453
453
  {
454
- filename: "negative-control-native-telemetry-description.ts",
454
+ filename: "negative-control-http-telemetry-free-text.ts",
455
455
  expectedCode: "TS2322",
456
- description: "native gateway telemetry rejects free-string diagnostic fields",
456
+ description: "HTTP gateway telemetry rejects a free-string diagnostic field",
457
457
  source: [
458
- 'import type { GatewayIngestible, NativeTelemetryHeaderPayload } from "@apifuse/provider-sdk";',
459
- "type UnsafeHeader = NativeTelemetryHeaderPayload & { vendorErrorMessage: string };",
460
- "declare const header: NativeTelemetryHeaderPayload;",
461
- 'export const mustNotCompile: GatewayIngestible<UnsafeHeader> = { ...header, vendorErrorMessage: "free text stays log-only" };',
458
+ 'import type { HttpTelemetryHeaderPayload, GatewayIngestible } from "@apifuse/provider-sdk";',
459
+ "type UnsafeHttpHeader = HttpTelemetryHeaderPayload & { message: string };",
460
+ "declare const header: HttpTelemetryHeaderPayload;",
461
+ 'export const mustNotCompile: GatewayIngestible<UnsafeHttpHeader> = { ...header, message: "upstream free text" };',
462
+ "",
462
463
  ].join("\n"),
463
464
  },
464
465
  {
465
- filename: "positive-control-native-telemetry-contributor.ts",
466
+ filename: "positive-control-http-telemetry-contributor.ts",
466
467
  expectedCode: "",
467
- description: "native telemetry contributor satisfies the gateway-ingestible contract",
468
+ description: "HTTP contributor satisfies the gateway and tenant projection contracts",
468
469
  source: [
469
- 'import { NativeTelemetryCollector, type GatewayIngestible, type NativeTelemetryHeaderPayload, type NativeTelemetryLogPayload, type TelemetryContributor } from "@apifuse/provider-sdk";',
470
- "export const native: TelemetryContributor<NativeTelemetryLogPayload, NativeTelemetryHeaderPayload> = new NativeTelemetryCollector();",
471
- "declare const header: NativeTelemetryHeaderPayload;",
472
- "export const ingestible: GatewayIngestible<NativeTelemetryHeaderPayload> = header;",
470
+ 'import { HttpTelemetryCollector, type HttpTelemetryHeaderPayload, type HttpTelemetryLogPayload, type HttpTelemetryRetryPayload, type GatewayIngestible, type TenantNeutral, type TelemetryContributor } from "@apifuse/provider-sdk";',
471
+ 'import { HttpTelemetryCollector as ServerHttpCollector } from "@apifuse/provider-sdk/server";',
472
+ "export const http: TelemetryContributor<HttpTelemetryLogPayload, HttpTelemetryHeaderPayload> = new HttpTelemetryCollector();",
473
+ "declare const header: HttpTelemetryHeaderPayload;",
474
+ "export const ingestible: GatewayIngestible<HttpTelemetryHeaderPayload> = header;",
475
+ "export const retry: TenantNeutral<HttpTelemetryRetryPayload> | undefined = new ServerHttpCollector().toTenantRetryPayload();",
476
+ "",
473
477
  ].join("\n"),
474
478
  },
475
479
  ] as const;
@@ -9,12 +9,18 @@
9
9
  export declare const APIFUSE_HANDLE_META_KEY: "x-apifuse-handle";
10
10
  export type HandleKindType = "cursor" | "draft";
11
11
  export type HandleAccess = "bound" | "public";
12
+ /**
13
+ * Operation key(s) that issue a handle. A single string is the common case; a
14
+ * list names every operation whose output carries the field (for example
15
+ * `search-address` and `reverse-geocode` both issuing `location_token`).
16
+ */
17
+ export type HandleIssuedBy = string | readonly string[];
12
18
  /** Value stored under {@link APIFUSE_HANDLE_META_KEY} on a handle field. */
13
19
  export interface HandleFieldMeta {
14
20
  readonly kind: string;
15
21
  readonly type: HandleKindType;
16
22
  readonly fieldName: string;
17
- readonly issuedBy?: string;
23
+ readonly issuedBy?: HandleIssuedBy;
18
24
  }
19
25
  /**
20
26
  * Structural view of a handle kind as seen by the provider declaration
@@ -26,10 +32,20 @@ export interface HandleKindDeclaration {
26
32
  readonly type: HandleKindType;
27
33
  readonly fieldName: string;
28
34
  readonly access: HandleAccess;
29
- readonly issuedBy?: string;
35
+ readonly issuedBy?: HandleIssuedBy;
30
36
  }
31
37
  /** Kind names are lowercase letters only so they never fuse with the word body. */
32
38
  export declare const HANDLE_KIND_NAME_PATTERN: RegExp;
39
+ /** True for a single operation key or a non-empty list of operation keys. */
40
+ export declare function isHandleIssuedBy(value: unknown): value is HandleIssuedBy;
41
+ /** Normalizes `issuedBy` to a list of operation keys (empty when undefined). */
42
+ export declare function handleIssuerList(issuedBy: HandleIssuedBy | undefined): readonly string[];
43
+ /**
44
+ * Renders the issuing operation(s) as inline code for error and description
45
+ * text: `` `a` ``, `` `a` or `b` ``, `` `a`, `b`, or `c` ``. Undefined when no
46
+ * issuer is declared.
47
+ */
48
+ export declare function formatHandleIssuers(issuedBy: HandleIssuedBy | undefined): string | undefined;
33
49
  export declare function isHandleFieldMeta(value: unknown): value is HandleFieldMeta;
34
50
  /**
35
51
  * The SDK-owned description `kind.field()` emits. Lint exempts a handle field
@@ -9,6 +9,33 @@
9
9
  export const APIFUSE_HANDLE_META_KEY = "x-apifuse-handle";
10
10
  /** Kind names are lowercase letters only so they never fuse with the word body. */
11
11
  export const HANDLE_KIND_NAME_PATTERN = /^[a-z]{2,12}$/;
12
+ /** True for a single operation key or a non-empty list of operation keys. */
13
+ export function isHandleIssuedBy(value) {
14
+ if (typeof value === "string")
15
+ return true;
16
+ return (Array.isArray(value) && value.length > 0 && value.every((entry) => typeof entry === "string"));
17
+ }
18
+ /** Normalizes `issuedBy` to a list of operation keys (empty when undefined). */
19
+ export function handleIssuerList(issuedBy) {
20
+ if (issuedBy === undefined)
21
+ return [];
22
+ return typeof issuedBy === "string" ? [issuedBy] : issuedBy;
23
+ }
24
+ /**
25
+ * Renders the issuing operation(s) as inline code for error and description
26
+ * text: `` `a` ``, `` `a` or `b` ``, `` `a`, `b`, or `c` ``. Undefined when no
27
+ * issuer is declared.
28
+ */
29
+ export function formatHandleIssuers(issuedBy) {
30
+ const issuers = handleIssuerList(issuedBy).map((operation) => `\`${operation}\``);
31
+ if (issuers.length === 0)
32
+ return undefined;
33
+ if (issuers.length === 1)
34
+ return issuers[0];
35
+ if (issuers.length === 2)
36
+ return `${issuers[0]} or ${issuers[1]}`;
37
+ return `${issuers.slice(0, -1).join(", ")}, or ${issuers[issuers.length - 1]}`;
38
+ }
12
39
  export function isHandleFieldMeta(value) {
13
40
  if (typeof value !== "object" || value === null)
14
41
  return false;
@@ -16,7 +43,7 @@ export function isHandleFieldMeta(value) {
16
43
  return (typeof record.kind === "string" &&
17
44
  (record.type === "cursor" || record.type === "draft") &&
18
45
  typeof record.fieldName === "string" &&
19
- (record.issuedBy === undefined || typeof record.issuedBy === "string"));
46
+ (record.issuedBy === undefined || isHandleIssuedBy(record.issuedBy)));
20
47
  }
21
48
  /**
22
49
  * The SDK-owned description `kind.field()` emits. Lint exempts a handle field
@@ -24,6 +51,7 @@ export function isHandleFieldMeta(value) {
24
51
  * provider override re-enters the localization rules.
25
52
  */
26
53
  export function handleFieldDescription(meta) {
27
- const origin = meta.issuedBy ? ` issued by \`${meta.issuedBy}\`` : "";
54
+ const issuers = formatHandleIssuers(meta.issuedBy);
55
+ const origin = issuers ? ` issued by ${issuers}` : "";
28
56
  return `Opaque handle${origin}. Copy it exactly as returned; do not edit or shorten it.`;
29
57
  }
package/dist/handle.d.ts CHANGED
@@ -9,9 +9,9 @@
9
9
  */
10
10
  import { type ZodString, type ZodType, z } from "zod";
11
11
  import { ProviderError, type ProviderErrorOptions } from "./errors.js";
12
- import { type HandleAccess, type HandleFieldMeta, type HandleKindDeclaration, type HandleKindType } from "./handle-meta.js";
12
+ import { type HandleAccess, type HandleFieldMeta, type HandleIssuedBy, type HandleKindDeclaration, type HandleKindType } from "./handle-meta.js";
13
13
  import type { ProviderStateDurationString } from "./types.js";
14
- export type { HandleAccess, HandleFieldMeta, HandleKindDeclaration, HandleKindType };
14
+ export type { HandleAccess, HandleFieldMeta, HandleIssuedBy, HandleKindDeclaration, HandleKindType, };
15
15
  export type HandleStrength = "standard" | "high";
16
16
  /** Words issued per handle: 2 for bound kinds, 4 for public, 5 for high-strength public. */
17
17
  export type HandleWordCount = 2 | 4 | 5;
@@ -41,7 +41,10 @@ export interface CursorKind<TSchema extends ZodType = ZodType> extends HandleKin
41
41
  }
42
42
  export interface DraftKind<TSchema extends ZodType = ZodType, TResultSchema extends ZodType | undefined = ZodType | undefined> extends HandleKindBase<TSchema> {
43
43
  readonly type: "draft";
44
- readonly access: "bound";
44
+ /** `"bound"` (default): connection-scoped, 2 words. `"public"`: provider-scoped, 4-5 words. */
45
+ readonly access: HandleAccess;
46
+ /** Public only; `"high"` forces 5 words. Always `"standard"` for bound drafts. */
47
+ readonly strength: HandleStrength;
45
48
  readonly ttl: {
46
49
  readonly idle: ProviderStateDurationString;
47
50
  readonly max: ProviderStateDurationString;
@@ -52,7 +55,6 @@ export interface DraftKind<TSchema extends ZodType = ZodType, TResultSchema exte
52
55
  readonly resultTtlMs: number;
53
56
  /** Optional schema describing the commit result; type-level only at runtime. */
54
57
  readonly result: TResultSchema | undefined;
55
- readonly wordCount: 2;
56
58
  }
57
59
  export type HandleKind = CursorKind<any> | DraftKind<any, any>;
58
60
  /** Stored record data type (schema output); what `read`, `update`, and `commit` callbacks receive. */
@@ -79,6 +81,12 @@ export interface HandleCommitResult<K extends HandleKind> {
79
81
  export interface HandleContext {
80
82
  /** Validates `data` with `kind.schema`, stores it, and returns the canonical handle. */
81
83
  create<K extends HandleKind>(kind: K, data: InputOf<K>): Promise<string>;
84
+ /**
85
+ * Same as `create` but returns the full record (`handle`, `status`, `data`,
86
+ * `createdAt`, `expiresAt`) so a provider can expose `expires_at` without
87
+ * recomputing ttl math. `create` is the string-returning shortcut.
88
+ */
89
+ createRecord<K extends HandleKind>(kind: K, data: InputOf<K>): Promise<HandleRecord<K>>;
82
90
  /** Tolerantly parses `handle`, loads the record; drafts get their idle TTL touched. */
83
91
  read<K extends HandleKind>(kind: K, handle: string): Promise<HandleRecord<K>>;
84
92
  /** CAS update of an active draft (3 retries) with sliding TTL. */
@@ -123,7 +131,7 @@ export declare function isHandleError(value: unknown): value is HandleError;
123
131
  */
124
132
  export declare function handleRecoverySentence(kind: {
125
133
  readonly fieldName: string;
126
- readonly issuedBy?: string;
134
+ readonly issuedBy?: HandleIssuedBy;
127
135
  }): string;
128
136
  /** Parses a `ProviderStateDurationString` to milliseconds; throws on invalid or non-positive input. */
129
137
  export declare function parseHandleDurationMs(value: ProviderStateDurationString, label: string): number;
@@ -142,8 +150,8 @@ export interface DefineCursorOptions<TSchema extends ZodType> {
142
150
  readonly maxValueBytes?: number;
143
151
  /** Public only; `"high"` forces 5 words. Auto-high when ttl > 1h. */
144
152
  readonly strength?: HandleStrength;
145
- /** Operation key that issues this handle; used in error text and lint. */
146
- readonly issuedBy?: string;
153
+ /** Operation key(s) that issue this handle; used in error text and lint. */
154
+ readonly issuedBy?: HandleIssuedBy;
147
155
  }
148
156
  export interface DefineDraftOptions<TSchema extends ZodType, TResultSchema extends ZodType | undefined> {
149
157
  readonly name: string;
@@ -157,15 +165,28 @@ export interface DefineDraftOptions<TSchema extends ZodType, TResultSchema exten
157
165
  };
158
166
  /** How long a committed record stays for replay. Default "24h". */
159
167
  readonly resultTtl?: ProviderStateDurationString;
160
- /** Default 200 per connection. */
168
+ /**
169
+ * `"bound"` (default): connection-scoped, 2 words. `"public"`: provider-scoped
170
+ * draft for connectionless providers (`auth: none`), 4-5 words, `maxEntries`
171
+ * required; every lookup failure collapses to `HANDLE_INVALID`.
172
+ */
173
+ readonly access?: HandleAccess;
174
+ /** Required when public. Bound default 200 per connection. */
161
175
  readonly maxEntries?: number;
162
176
  /** Default 64,000. */
163
177
  readonly maxValueBytes?: number;
164
- readonly issuedBy?: string;
178
+ /** Public only; `"high"` forces 5 words. Auto-high when ttl.max > 1h. */
179
+ readonly strength?: HandleStrength;
180
+ /** Operation key(s) that issue this handle; used in error text and lint. */
181
+ readonly issuedBy?: HandleIssuedBy;
165
182
  }
166
183
  /** Defines an immutable cursor kind (pagination, offered option lists). */
167
184
  export declare function defineCursor<TSchema extends ZodType>(options: DefineCursorOptions<TSchema>): CursorKind<TSchema>;
168
- /** Defines a mutable, always-bound draft kind (multi-turn forms with one-shot commit). */
185
+ /**
186
+ * Defines a mutable draft kind (multi-turn forms with one-shot commit). Bound
187
+ * by default; `access: "public"` opts a connectionless provider into a
188
+ * provider-scoped draft under the public guessing analysis (ADR-0012 D3).
189
+ */
169
190
  export declare function defineDraft<TSchema extends ZodType, TResultSchema extends ZodType | undefined = undefined>(options: DefineDraftOptions<TSchema, TResultSchema>): DraftKind<TSchema, TResultSchema>;
170
191
  export declare function isDraftKind(kind: HandleKind): kind is DraftKind<any, any>;
171
192
  export declare function isCursorKind(kind: HandleKind): kind is CursorKind<any>;
@@ -174,8 +195,8 @@ export interface PickOptions<T> {
174
195
  readonly field: string;
175
196
  /** Item property compared against `value`. Default = `field`. */
176
197
  readonly by?: keyof T & string;
177
- /** Operation that listed the items; used in the error message when known. */
178
- readonly issuedBy?: string;
198
+ /** Operation(s) that listed the items; used in the error message when known. */
199
+ readonly issuedBy?: HandleIssuedBy;
179
200
  }
180
201
  /**
181
202
  * Chooses one offered item by a plain value the LLM copied from the offer.
package/dist/handle.js CHANGED
@@ -9,9 +9,9 @@
9
9
  */
10
10
  import { z } from "zod";
11
11
  import { brandHandleError, hasHandleErrorBrand, ProviderError, } from "./errors.js";
12
- import { APIFUSE_HANDLE_META_KEY, handleFieldDescription, HANDLE_KIND_NAME_PATTERN, } from "./handle-meta.js";
12
+ import { APIFUSE_HANDLE_META_KEY, formatHandleIssuers, handleFieldDescription, HANDLE_KIND_NAME_PATTERN, isHandleIssuedBy, } from "./handle-meta.js";
13
13
  import { BOUND_HANDLE_WORD_COUNT, HIGH_PUBLIC_HANDLE_WORD_COUNT, PUBLIC_HANDLE_WORD_COUNT, } from "./runtime/handle-wordlist.js";
14
- /** Public cursors whose ttl exceeds this are issued with five words (auto-high). */
14
+ /** Public kinds whose (max) ttl exceeds this are issued with five words (auto-high). */
15
15
  const AUTO_HIGH_TTL_MS = 3_600_000;
16
16
  const DEFAULT_BOUND_MAX_ENTRIES = 200;
17
17
  const DEFAULT_CURSOR_MAX_VALUE_BYTES = 16_000;
@@ -63,8 +63,9 @@ export function isHandleError(value) {
63
63
  * knows the next action without provider-specific wording.
64
64
  */
65
65
  export function handleRecoverySentence(kind) {
66
- if (kind.issuedBy) {
67
- return `Call \`${kind.issuedBy}\` again and pass the new \`${kind.fieldName}\` exactly as returned.`;
66
+ const issuers = formatHandleIssuers(kind.issuedBy);
67
+ if (issuers) {
68
+ return `Call ${issuers} again and pass the new \`${kind.fieldName}\` exactly as returned.`;
68
69
  }
69
70
  return `Request a new \`${kind.fieldName}\` and pass it exactly as returned.`;
70
71
  }
@@ -120,6 +121,38 @@ function assertPositiveInteger(value, label, define) {
120
121
  throw new Error(`${define}: ${label} must be a positive integer; received ${String(value)}.`);
121
122
  }
122
123
  }
124
+ function assertAccess(value, define) {
125
+ if (value !== "bound" && value !== "public") {
126
+ throw new Error(`${define}: access must be "bound" or "public"; received ${String(value)}.`);
127
+ }
128
+ }
129
+ function assertStrength(value, define) {
130
+ if (value !== "standard" && value !== "high") {
131
+ throw new Error(`${define}: strength must be "standard" or "high"; received ${String(value)}.`);
132
+ }
133
+ }
134
+ /** Validates `issuedBy` and returns a frozen copy for lists; `undefined` stays undefined. */
135
+ function normalizeIssuedBy(value, define, name) {
136
+ if (value === undefined)
137
+ return undefined;
138
+ if (!isHandleIssuedBy(value) || (typeof value === "string" ? value.length === 0 : false)) {
139
+ throw new Error(`${define}: "${name}" issuedBy must be an operation key or a non-empty list of operation keys; received ${JSON.stringify(value)}.`);
140
+ }
141
+ if (typeof value === "string")
142
+ return value;
143
+ if (value.some((operation) => operation.length === 0)) {
144
+ throw new Error(`${define}: "${name}" issuedBy must not contain empty operation keys.`);
145
+ }
146
+ return Object.freeze([...value]);
147
+ }
148
+ /** Word count for a kind: 2 bound; public 4, or 5 when high strength or the lifetime exceeds 1h. */
149
+ function wordCountFor(access, strength, lifetimeMs) {
150
+ if (access === "bound")
151
+ return BOUND_HANDLE_WORD_COUNT;
152
+ return strength === "high" || lifetimeMs > AUTO_HIGH_TTL_MS
153
+ ? HIGH_PUBLIC_HANDLE_WORD_COUNT
154
+ : PUBLIC_HANDLE_WORD_COUNT;
155
+ }
123
156
  function createHandleField(meta) {
124
157
  const described = z.string().min(1).describe(handleFieldDescription(meta));
125
158
  // zod v4 `.meta()` replaces the registry entry, so carry the description over.
@@ -133,14 +166,11 @@ export function defineCursor(options) {
133
166
  const fieldName = options.fieldName ?? `${options.name}_token`;
134
167
  assertFieldName(fieldName, define);
135
168
  const access = options.access ?? "bound";
136
- if (access !== "bound" && access !== "public") {
137
- throw new Error(`${define}: access must be "bound" or "public"; received ${String(access)}.`);
138
- }
169
+ assertAccess(access, define);
139
170
  const ttlMs = parseHandleDurationMs(options.ttl, `"${options.name}" ttl`);
140
171
  const strength = options.strength ?? "standard";
141
- if (strength !== "standard" && strength !== "high") {
142
- throw new Error(`${define}: strength must be "standard" or "high"; received ${String(strength)}.`);
143
- }
172
+ assertStrength(strength, define);
173
+ const issuedBy = normalizeIssuedBy(options.issuedBy, define, options.name);
144
174
  if (access === "public" && options.maxEntries === undefined) {
145
175
  throw new Error(`${define}: public cursor "${options.name}" must declare maxEntries (provider-wide live-entry quota).`);
146
176
  }
@@ -148,23 +178,19 @@ export function defineCursor(options) {
148
178
  assertPositiveInteger(maxEntries, "maxEntries", define);
149
179
  const maxValueBytes = options.maxValueBytes ?? DEFAULT_CURSOR_MAX_VALUE_BYTES;
150
180
  assertPositiveInteger(maxValueBytes, "maxValueBytes", define);
151
- const wordCount = access === "bound"
152
- ? BOUND_HANDLE_WORD_COUNT
153
- : strength === "high" || ttlMs > AUTO_HIGH_TTL_MS
154
- ? HIGH_PUBLIC_HANDLE_WORD_COUNT
155
- : PUBLIC_HANDLE_WORD_COUNT;
181
+ const wordCount = wordCountFor(access, strength, ttlMs);
156
182
  const meta = {
157
183
  kind: options.name,
158
184
  type: "cursor",
159
185
  fieldName,
160
- ...(options.issuedBy ? { issuedBy: options.issuedBy } : {}),
186
+ ...(issuedBy ? { issuedBy } : {}),
161
187
  };
162
188
  return Object.freeze({
163
189
  name: options.name,
164
190
  type: "cursor",
165
191
  fieldName,
166
192
  access,
167
- issuedBy: options.issuedBy,
193
+ issuedBy,
168
194
  schema: options.schema,
169
195
  ttl: options.ttl,
170
196
  ttlMs,
@@ -175,7 +201,11 @@ export function defineCursor(options) {
175
201
  field: () => createHandleField(meta),
176
202
  });
177
203
  }
178
- /** Defines a mutable, always-bound draft kind (multi-turn forms with one-shot commit). */
204
+ /**
205
+ * Defines a mutable draft kind (multi-turn forms with one-shot commit). Bound
206
+ * by default; `access: "public"` opts a connectionless provider into a
207
+ * provider-scoped draft under the public guessing analysis (ADR-0012 D3).
208
+ */
179
209
  export function defineDraft(options) {
180
210
  const define = "defineDraft";
181
211
  assertKindName(options.name, define);
@@ -191,22 +221,36 @@ export function defineDraft(options) {
191
221
  }
192
222
  const resultTtl = options.resultTtl ?? DEFAULT_RESULT_TTL;
193
223
  const resultTtlMs = parseHandleDurationMs(resultTtl, `"${options.name}" resultTtl`);
224
+ const access = options.access ?? "bound";
225
+ assertAccess(access, define);
226
+ const strength = options.strength ?? "standard";
227
+ assertStrength(strength, define);
228
+ if (access === "bound" && strength !== "standard") {
229
+ throw new Error(`${define}: draft "${options.name}" strength applies to public drafts only; bound drafts are always two words.`);
230
+ }
231
+ const issuedBy = normalizeIssuedBy(options.issuedBy, define, options.name);
232
+ if (access === "public" && options.maxEntries === undefined) {
233
+ throw new Error(`${define}: public draft "${options.name}" must declare maxEntries (provider-wide live-entry quota).`);
234
+ }
194
235
  const maxEntries = options.maxEntries ?? DEFAULT_BOUND_MAX_ENTRIES;
195
236
  assertPositiveInteger(maxEntries, "maxEntries", define);
196
237
  const maxValueBytes = options.maxValueBytes ?? DEFAULT_DRAFT_MAX_VALUE_BYTES;
197
238
  assertPositiveInteger(maxValueBytes, "maxValueBytes", define);
239
+ // A public draft's guessable lifetime is its hard `max` ttl (idle only shortens it).
240
+ const wordCount = wordCountFor(access, strength, maxTtlMs);
198
241
  const meta = {
199
242
  kind: options.name,
200
243
  type: "draft",
201
244
  fieldName,
202
- ...(options.issuedBy ? { issuedBy: options.issuedBy } : {}),
245
+ ...(issuedBy ? { issuedBy } : {}),
203
246
  };
204
247
  return Object.freeze({
205
248
  name: options.name,
206
249
  type: "draft",
207
250
  fieldName,
208
- access: "bound",
209
- issuedBy: options.issuedBy,
251
+ access,
252
+ strength,
253
+ issuedBy,
210
254
  schema: options.schema,
211
255
  result: options.result,
212
256
  ttl: options.ttl,
@@ -216,7 +260,7 @@ export function defineDraft(options) {
216
260
  resultTtlMs,
217
261
  maxEntries,
218
262
  maxValueBytes,
219
- wordCount: BOUND_HANDLE_WORD_COUNT,
263
+ wordCount,
220
264
  field: () => createHandleField(meta),
221
265
  });
222
266
  }
@@ -247,8 +291,9 @@ export function pick(items, value, options) {
247
291
  if (loose.length === 1)
248
292
  return loose[0];
249
293
  const allowed = items.map(keyOf);
250
- const listed = options.issuedBy
251
- ? ` Use the values exactly as listed by \`${options.issuedBy}\`.`
294
+ const issuers = formatHandleIssuers(options.issuedBy);
295
+ const listed = issuers
296
+ ? ` Use the values exactly as listed by ${issuers}.`
252
297
  : " Use the values exactly as listed.";
253
298
  throw new HandleError("PICK_NOT_OFFERED", `\`${options.field}\` must be one of: ${allowed.join(", ")}.${listed}`, { details: { field: options.field, allowed } });
254
299
  }
package/dist/index.d.ts CHANGED
@@ -20,6 +20,7 @@ export { createFlowContext, createScratchpad } from "./runtime/auth-flow.js";
20
20
  export type { BrowserClientOptions } from "./runtime/browser.js";
21
21
  export { APIFUSE__CACHE__KEY_PEPPER_ENV, createBypassProviderCache, createProviderCache, type ProviderCacheOptions, } from "./runtime/cache.js";
22
22
  export * from "./handle.js";
23
+ export { APIFUSE_HANDLE_META_KEY, HANDLE_KIND_NAME_PATTERN, handleFieldDescription, isHandleFieldMeta, } from "./handle-meta.js";
23
24
  export { type CreateHandleContextOptions, type CreateTestHandleContextOptions, createHandleContext, createTestHandleContext, type HandleNormalizationClass, type NormalizedHandle, normalizeHandle, } from "./runtime/handle.js";
24
25
  export { type CreateCredentialContextOptions, createCredentialContext, } from "./runtime/credential.js";
25
26
  export { createEnvContext } from "./runtime/env.js";
@@ -41,7 +42,7 @@ export { createUnsupportedResolverClient } from "./runtime/resolver-shared.js";
41
42
  export type { ResolverRuntimeOptions } from "./runtime/resolver.js";
42
43
  export type { ResolverPaidUsageContext, ResolverVendorTransport, ResolverVendorUnavailableReason, } from "./runtime/resolver-vendors/types.js";
43
44
  export { assertRequiredSecretsPresent, listMissingRequiredSecrets, MISSING_SECRET_CODE, } from "./runtime/secrets.js";
44
- export { createUnsupportedProviderRuntimeState, UnsupportedProviderStateError, } from "./runtime/state.js";
45
+ export { createMemoryProviderRuntimeState, createUnsupportedProviderRuntimeState, UnsupportedProviderStateError, } from "./runtime/state.js";
45
46
  export { APIFUSE__OCR__API_KEY_ENV, APIFUSE__OCR__BACKEND_ENV, APIFUSE__OCR__BASE_URL_ENV, APIFUSE__OCR__CLOUDFLARE_API_TOKEN_ENV, APIFUSE__OCR__MODEL_ENV, CLOUDFLARE_ACCOUNT_ID_ENV, CLOUDFLARE_WORKERS_AI_OCR_BACKEND, createCloudflareWorkersAiOcrClient, createOcrClientFromEnv, createOpenAiCompatibleOcrClient, createUnsupportedOcrClient, DEFAULT_CLOUDFLARE_WORKERS_AI_OCR_MODEL, extractCaptchaCandidates, OPENAI_COMPATIBLE_OCR_BACKEND, } from "./runtime/ocr.js";
46
47
  export { APIFUSE__STT__BACKEND_ENV, APIFUSE__STT__CLOUDFLARE_API_TOKEN_ENV, APIFUSE__STT__MODEL_ENV, createSttClientFromEnv, createUnsupportedSttClient, extractVerificationCode, resolveSttPrompt, } from "./runtime/stt.js";
47
48
  export { type CreateTraceContextOptions, createTraceContext, type Span, type TraceContext, } from "./runtime/trace.js";
@@ -56,4 +57,5 @@ export * from "./utils/date.js";
56
57
  export * from "./utils/parse.js";
57
58
  export * from "./utils/text.js";
58
59
  export * from "./utils/transform.js";
60
+ export { HttpTelemetryCollector, type HttpTelemetrySink, type HttpTelemetryRequestSink, type HttpAttemptTelemetryEvent, type HttpAttemptSample, type HttpTelemetryErrorCode, type HttpTelemetryRetryPayload, type HttpTelemetryLogPayload, type HttpTelemetryHeaderPayload, } from "./runtime/http-telemetry.js";
59
61
  export { NativeTelemetryCollector, type NativeAttemptSample, type NativeConnectTelemetryEvent, type NativeLifecycleTelemetryEvent, type NativeTelemetryDiagnostics, type NativeTelemetryErrorCode, type NativeTelemetryHeaderPayload, type NativeTelemetryKind, type NativeTelemetryLifecycleKind, type NativeTelemetryLogPayload, type NativeTelemetryOutcome, type NativeTelemetrySink, type NativeTelemetryVendorSkipReason, type NativeVendorSkipTelemetryEvent, } from "./runtime/native-telemetry.js";
package/dist/index.js CHANGED
@@ -17,6 +17,7 @@ export * from "./recipes/rest-api.js";
17
17
  export { createFlowContext, createScratchpad } from "./runtime/auth-flow.js";
18
18
  export { APIFUSE__CACHE__KEY_PEPPER_ENV, createBypassProviderCache, createProviderCache, } from "./runtime/cache.js";
19
19
  export * from "./handle.js";
20
+ export { APIFUSE_HANDLE_META_KEY, HANDLE_KIND_NAME_PATTERN, handleFieldDescription, isHandleFieldMeta, } from "./handle-meta.js";
20
21
  export { createHandleContext, createTestHandleContext, normalizeHandle, } from "./runtime/handle.js";
21
22
  export { createCredentialContext, } from "./runtime/credential.js";
22
23
  export { createEnvContext } from "./runtime/env.js";
@@ -32,7 +33,7 @@ export { ResolverTelemetryCollector, } from "./runtime/resolver-telemetry.js";
32
33
  export { APIFUSE__CDP_POOL__URL, APIFUSE__RESOLVER__2CAPTCHA__API_KEY, APIFUSE__RESOLVER__CAPMONSTER__API_KEY, APIFUSE__RESOLVER__CAPSOLVER__API_KEY, APIFUSE__RESOLVER__HYPERSOLUTIONS__API_KEY, APIFUSE__RESOLVER__TIMEOUT_MS, DEFAULT_RESOLVER_TIMEOUT_MS, } from "./runtime/resolver-config.js";
33
34
  export { createUnsupportedResolverClient } from "./runtime/resolver-shared.js";
34
35
  export { assertRequiredSecretsPresent, listMissingRequiredSecrets, MISSING_SECRET_CODE, } from "./runtime/secrets.js";
35
- export { createUnsupportedProviderRuntimeState, UnsupportedProviderStateError, } from "./runtime/state.js";
36
+ export { createMemoryProviderRuntimeState, createUnsupportedProviderRuntimeState, UnsupportedProviderStateError, } from "./runtime/state.js";
36
37
  export { APIFUSE__OCR__API_KEY_ENV, APIFUSE__OCR__BACKEND_ENV, APIFUSE__OCR__BASE_URL_ENV, APIFUSE__OCR__CLOUDFLARE_API_TOKEN_ENV, APIFUSE__OCR__MODEL_ENV, CLOUDFLARE_ACCOUNT_ID_ENV, CLOUDFLARE_WORKERS_AI_OCR_BACKEND, createCloudflareWorkersAiOcrClient, createOcrClientFromEnv, createOpenAiCompatibleOcrClient, createUnsupportedOcrClient, DEFAULT_CLOUDFLARE_WORKERS_AI_OCR_MODEL, extractCaptchaCandidates, OPENAI_COMPATIBLE_OCR_BACKEND, } from "./runtime/ocr.js";
37
38
  export { APIFUSE__STT__BACKEND_ENV, APIFUSE__STT__CLOUDFLARE_API_TOKEN_ENV, APIFUSE__STT__MODEL_ENV, createSttClientFromEnv, createUnsupportedSttClient, extractVerificationCode, resolveSttPrompt, } from "./runtime/stt.js";
38
39
  export { createTraceContext, } from "./runtime/trace.js";
@@ -45,4 +46,5 @@ export * from "./utils/date.js";
45
46
  export * from "./utils/parse.js";
46
47
  export * from "./utils/text.js";
47
48
  export * from "./utils/transform.js";
49
+ export { HttpTelemetryCollector, } from "./runtime/http-telemetry.js";
48
50
  export { NativeTelemetryCollector, } from "./runtime/native-telemetry.js";
package/dist/lint.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { SDK_RUNTIME_OWNED_ERROR_CODES, SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES, } from "./error-resolution.js";
3
- import { APIFUSE_HANDLE_META_KEY, handleFieldDescription, isHandleFieldMeta, } from "./handle-meta.js";
3
+ import { APIFUSE_HANDLE_META_KEY, handleFieldDescription, handleIssuerList, isHandleIssuedBy, isHandleFieldMeta, } from "./handle-meta.js";
4
4
  import { lintPublicSchemaFieldNames } from "./public-schema-field-lint.js";
5
5
  import { APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_SENSITIVE_META_KEY } from "./schema.js";
6
6
  const requireModule = createRequire(import.meta.url);
@@ -1204,14 +1204,15 @@ function readHandleKindDeclarations(value) {
1204
1204
  return (typeof record.name === "string" &&
1205
1205
  (record.type === "cursor" || record.type === "draft") &&
1206
1206
  typeof record.fieldName === "string" &&
1207
- (record.issuedBy === undefined || typeof record.issuedBy === "string"));
1207
+ (record.issuedBy === undefined || isHandleIssuedBy(record.issuedBy)));
1208
1208
  });
1209
1209
  }
1210
1210
  /**
1211
1211
  * ADR-0012 handle rules: every `x-apifuse-handle` schema field must belong to
1212
1212
  * a declared kind under its declared property key; every declared kind must be
1213
- * both issued (some output) and accepted (some input); `issuedBy` must name an
1214
- * operation whose output carries the field; and `ctx.handle` needs `state`.
1213
+ * both issued (some output) and accepted (some input); every `issuedBy` entry
1214
+ * must be an operation key and at least one of them must output the field; and
1215
+ * `ctx.handle` needs `state`.
1215
1216
  */
1216
1217
  function lintHandleDeclarations(provider) {
1217
1218
  const diagnostics = [];
@@ -1292,20 +1293,39 @@ function lintHandleDeclarations(provider) {
1292
1293
  }
1293
1294
  if (kind.issuedBy === undefined)
1294
1295
  continue;
1295
- if (!Object.hasOwn(operations, kind.issuedBy)) {
1296
+ const issuers = handleIssuerList(kind.issuedBy);
1297
+ const known = issuers.filter((operationKey) => Object.hasOwn(operations, operationKey));
1298
+ for (const operationKey of issuers) {
1299
+ if (known.includes(operationKey))
1300
+ continue;
1301
+ diagnostics.push({
1302
+ rule: "handle-issued-by",
1303
+ level: "error",
1304
+ field,
1305
+ message: `${providerLabel} handle kind "${kind.name}" declares issuedBy "${operationKey}", which is not an operation key.`,
1306
+ });
1307
+ }
1308
+ if (known.length === 0)
1309
+ continue;
1310
+ const issuing = known.filter((operationKey) => outputKindsByOperation.get(operationKey)?.has(kind.name));
1311
+ if (issuing.length > 0)
1312
+ continue;
1313
+ if (known.length === 1) {
1296
1314
  diagnostics.push({
1297
1315
  rule: "handle-issued-by",
1298
1316
  level: "error",
1299
1317
  field,
1300
- message: `${providerLabel} handle kind "${kind.name}" declares issuedBy "${kind.issuedBy}", which is not an operation key.`,
1318
+ message: `${providerLabel} handle kind "${kind.name}" declares issuedBy "${known[0]}", but that operation's output has no "${kind.fieldName}" handle field.`,
1301
1319
  });
1302
1320
  }
1303
- else if (!outputKindsByOperation.get(kind.issuedBy)?.has(kind.name)) {
1321
+ else {
1304
1322
  diagnostics.push({
1305
1323
  rule: "handle-issued-by",
1306
1324
  level: "error",
1307
1325
  field,
1308
- message: `${providerLabel} handle kind "${kind.name}" declares issuedBy "${kind.issuedBy}", but that operation's output has no "${kind.fieldName}" handle field.`,
1326
+ message: `${providerLabel} handle kind "${kind.name}" declares issuedBy ${known
1327
+ .map((operationKey) => `"${operationKey}"`)
1328
+ .join(", ")}, but none of those operations' outputs has a "${kind.fieldName}" handle field. At least one listed issuer must output it.`,
1309
1329
  });
1310
1330
  }
1311
1331
  }
@@ -11,7 +11,9 @@ export { createProviderEnvironment, createInProcessProviderEngine, ENGINE_OWNED_
11
11
  export type { ProviderCapabilityKey, ProviderEngine, ProviderEngineAttachmentInput, ProviderEngineBindingCandidates, ProviderEngineCapabilitySurface, ProviderEngineRequest, ProviderEngineResidentSurface, ProviderEngineSession, ProviderEngineTransport, } from "./engine.js";
12
12
  export { getProviderLocalePath, providerLocaleKey, qualifyProviderLocaleKey, } from "./i18n/index.js";
13
13
  export * from "./handle.js";
14
+ export { APIFUSE_HANDLE_META_KEY, HANDLE_KIND_NAME_PATTERN, handleFieldDescription, isHandleFieldMeta, } from "./handle-meta.js";
14
15
  export { type CreateHandleContextOptions, type CreateTestHandleContextOptions, createHandleContext, createTestHandleContext, type HandleNormalizationClass, type NormalizedHandle, normalizeHandle, } from "./runtime/handle.js";
16
+ export { createMemoryProviderRuntimeState, createUnsupportedProviderRuntimeState, } from "./runtime/state.js";
15
17
  export { APIFUSE_CONTENT_PROVENANCE_META_KEY, APIFUSE_CONTENT_TRUST_META_KEY, APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_REDACTION_MARKER, APIFUSE_SENSITIVE_KIND_META_KEY, APIFUSE_SENSITIVE_META_KEY, collectSensitivePaths, describeKey, field, fields, isSensitiveSchema, publicField, redactPayload, type SensitiveFieldKind, type SensitiveFieldOptions, type SensitivePath, sensitive, untrustedContent, z, } from "./schema.js";
16
18
  export type { AuthAbortData, AuthAbortRetry, AuthFlowTerminalContext, AuthMode, AuthSafeData, AuthSafeJson, FlowContext, HealthCheckAssertionContext, HealthCheckCase, HealthCheckSuite, HealthCheckUnsupported, HealthJourneyDefinition, HealthJourneyEventContext, HealthJourneyManualTriggerPolicy, HealthJourneyRunContext, HealthJourneyRunResult, HealthScheduleRandomization, HttpRedirectFailureReason, HttpRedirectPolicy, HttpRedirectPolicyMode, HttpRetryOptions, HttpRetrySummary, InferSchemaOutput, NativeContext, NativeNetworkClient, NativeNetworkCloseReason, NativeNetworkConnection, NativeNetworkConnectInput, NativeNetworkConnectOptions, NativeNetworkDynamicGrantOptions, NativeNetworkEgressGrant, NativeProviderConfig, NativeProxyDrainHandler, NativeProxyEgressInfo, NativeProxyExpiringEvent, NativeProxyExpiringReason, NativeTcpDynamicEgressRule, NativeTcpEgressGrant, NativeTcpEgressRule, NativeTcpPortRange, NativeTcpTlsMode, NativeTlsConnectOptions, OperationApprovalPolicy, OperationContractMetadata, OperationDefinition, OperationExample, OperationErrorCode, ProviderErrorStatus, OperationLifecycle, OperationObservabilityConfig, OperationObservabilitySensitiveConfig, OperationRelationships, OperationRiskClass, OperationSensitivePath, OperationTransport, ProviderAccessVisibility, ProviderContext, ProviderContextFor, ProviderDefinition, ProviderDeploymentOverrides, ProviderFileRef, ProviderFilesContext, ProviderLocale, ProviderLocaleKey, ProviderLocaleKeyInput, ProviderLogoProfile, ProviderProxyPolicy, ProxiedOAuthConfig, ProviderPublicConnectionMode, ProviderPublicProfile, ProviderResolvedFile, ProviderRuntimeTarget, ProviderRuntimeState, ProviderStealthConfig, ProviderStateDurationString, ProviderStateNamespace, ProviderSupportLevel, RedirectRunReason, SchemaLike, SmsOtpMatcherDefinition, StandardSchemaV1, StateCasResult, StateNamespaceOptions, StateNamespaceScope, StateValue, StateWriteOptions, StealthChallengeClassification, } from "./types.js";
17
19
  export { NativeEgressGrantExpiredError, NativeEgressNotDeclaredError, NativeIdleTimeoutError, NativeNetworkError, NativeProxyExpiredError, } from "./runtime/native-network-errors.js";
package/dist/provider.js CHANGED
@@ -6,7 +6,9 @@ export { AuthError, HttpRedirectError, isProviderError, isSessionExpiredError, i
6
6
  export { createProviderEnvironment, createInProcessProviderEngine, ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES, ENGINE_OWNED_RESOLVER_CREDENTIAL_ENV_NAMES, ENGINE_OWNED_RUNTIME_ENV_NAMES, ENGINE_OWNED_TELEMETRY_ENV_NAMES, isEngineOwnedEnvName, isEngineOwnedProxyCredentialName, isEngineOwnedResolverCredentialName, isEngineOwnedRuntimeEnvName, isEngineOwnedTelemetryEnvName, PROVIDER_CAPABILITY_KEYS, PROVIDER_ENGINE_PROTOCOL_VERSION, readEngineProxyCredentials, } from "./engine.js";
7
7
  export { getProviderLocalePath, providerLocaleKey, qualifyProviderLocaleKey, } from "./i18n/index.js";
8
8
  export * from "./handle.js";
9
+ export { APIFUSE_HANDLE_META_KEY, HANDLE_KIND_NAME_PATTERN, handleFieldDescription, isHandleFieldMeta, } from "./handle-meta.js";
9
10
  export { createHandleContext, createTestHandleContext, normalizeHandle, } from "./runtime/handle.js";
11
+ export { createMemoryProviderRuntimeState, createUnsupportedProviderRuntimeState, } from "./runtime/state.js";
10
12
  export { APIFUSE_CONTENT_PROVENANCE_META_KEY, APIFUSE_CONTENT_TRUST_META_KEY, APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_REDACTION_MARKER, APIFUSE_SENSITIVE_KIND_META_KEY, APIFUSE_SENSITIVE_META_KEY, collectSensitivePaths, describeKey, field, fields, isSensitiveSchema, publicField, redactPayload, sensitive, untrustedContent, z, } from "./schema.js";
11
13
  export { NativeEgressGrantExpiredError, NativeEgressNotDeclaredError, NativeIdleTimeoutError, NativeNetworkError, NativeProxyExpiredError, } from "./runtime/native-network-errors.js";
12
14
  export { HttpRetryAfterPolicy, HttpRetryDelayStrategy, HttpRetryJitter, HttpRetryPreset, HttpRetryUnsafeMethodPolicy, } from "./types.js";