@apifuse/provider-sdk 2.2.0-beta.35 → 2.2.0-beta.37
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 +22 -8
- package/CHANGELOG.md +8 -0
- package/README.md +20 -8
- package/bin/apifuse-dev.ts +1 -1
- package/bin/apifuse-pack-types.ts +6 -5
- package/bin/apifuse-record.ts +1 -1
- package/bin/apifuse-submit-check.ts +23 -10
- package/dist/ceremonies/index.d.ts +8 -0
- package/dist/ceremonies/index.js +32 -25
- package/dist/cli/templates/provider/Dockerfile.tpl +1 -1
- package/dist/cli/templates/provider/index.ts.tpl +6 -3
- package/dist/cli/templates/provider/operations/ping.ts.tpl +2 -1
- package/dist/define.d.ts +46 -25
- package/dist/define.js +381 -9
- package/dist/index.d.ts +2 -2
- package/dist/provider.d.ts +2 -1
- package/dist/runtime/browser.js +19 -11
- package/dist/runtime/choice.js +24 -7
- package/dist/runtime/resolver-public.d.ts +1 -1
- package/dist/runtime/resolver-public.js +1 -1
- package/dist/runtime/resolver-vendors/browser.js +57 -14
- package/dist/runtime/resolver-vendors/types.d.ts +9 -1
- package/dist/runtime/resolver-vendors/types.js +15 -0
- package/dist/runtime/resolver.d.ts +1 -0
- package/dist/runtime/resolver.js +13 -7
- package/dist/server/serve-implementation.js +25 -0
- package/dist/types.d.ts +25 -17
- package/package.json +6 -1
- package/src/ceremonies/index.ts +45 -31
- package/src/cli/templates/provider/Dockerfile.tpl +1 -1
- package/src/cli/templates/provider/index.ts.tpl +6 -3
- package/src/cli/templates/provider/operations/ping.ts.tpl +2 -1
- package/src/define.ts +462 -59
- package/src/index.ts +5 -2
- package/src/provider.ts +6 -1
- package/src/runtime/browser.ts +34 -11
- package/src/runtime/choice.ts +25 -8
- package/src/runtime/resolver-public.ts +2 -0
- package/src/runtime/resolver-vendors/browser.ts +69 -11
- package/src/runtime/resolver-vendors/types.ts +21 -0
- package/src/runtime/resolver.ts +17 -5
- package/src/server/serve-implementation.ts +39 -1
- package/src/testing/run.ts +3 -3
- package/src/types.ts +41 -17
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { defineProvider } from "@apifuse/provider-sdk/provider";
|
|
1
|
+
import { defineProvider, type ProviderContextOf } from "@apifuse/provider-sdk/provider";
|
|
2
2
|
|
|
3
3
|
import { providerMeta } from "./meta";
|
|
4
4
|
import { operations } from "./operations";
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
const buildProvider = defineProvider({
|
|
7
7
|
id: "{{PROVIDER_ID}}",
|
|
8
8
|
version: "1.0.0",
|
|
9
9
|
runtime: "{{RUNTIME}}"{{BROWSER_BLOCK}},
|
|
@@ -11,5 +11,8 @@ export default defineProvider({
|
|
|
11
11
|
reviewed: "community",
|
|
12
12
|
{{SECRETS_BLOCK}}{{CREDENTIAL_BLOCK}}auth: {{AUTH_BLOCK}},
|
|
13
13
|
meta: providerMeta,
|
|
14
|
-
operations: operations,
|
|
15
14
|
});
|
|
15
|
+
|
|
16
|
+
export type ProviderContext = ProviderContextOf<typeof buildProvider>;
|
|
17
|
+
|
|
18
|
+
export default buildProvider({ operations });
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { defineOperation } from "@apifuse/provider-sdk/provider";
|
|
2
|
+
import type { ProviderContext } from "../index";
|
|
2
3
|
|
|
3
4
|
import { pingInputSchema, pingOutputSchema } from "../schemas/ping";
|
|
4
5
|
|
|
5
|
-
export const pingOperation = defineOperation({
|
|
6
|
+
export const pingOperation = defineOperation<ProviderContext>()({
|
|
6
7
|
descriptionKey: "operations.ping.description",
|
|
7
8
|
input: pingInputSchema,
|
|
8
9
|
output: pingOutputSchema,
|
package/src/define.ts
CHANGED
|
@@ -31,6 +31,8 @@ import type {
|
|
|
31
31
|
ProviderAccessConfig,
|
|
32
32
|
ProviderChallengeKind,
|
|
33
33
|
ProviderDefinition,
|
|
34
|
+
ProviderContext,
|
|
35
|
+
ProviderContextFor,
|
|
34
36
|
ProviderOcrConfig,
|
|
35
37
|
ProviderDeploymentOverrides,
|
|
36
38
|
ProviderHealthMonitorConfig,
|
|
@@ -197,71 +199,383 @@ function parsePositiveMsDuration(value: string): number | undefined {
|
|
|
197
199
|
return parsed;
|
|
198
200
|
}
|
|
199
201
|
|
|
200
|
-
type ProviderOperation = OperationDefinition<
|
|
201
|
-
type OperationConfig<
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
202
|
+
type ProviderOperation = OperationDefinition<any, any, any>;
|
|
203
|
+
type OperationConfig<
|
|
204
|
+
TInput extends SchemaLike,
|
|
205
|
+
TOutput extends SchemaLike,
|
|
206
|
+
TContext = ProviderContext,
|
|
207
|
+
> = Omit<OperationDefinition<TInput, TOutput, TContext>, "handler"> & {
|
|
205
208
|
handler(
|
|
206
|
-
ctx:
|
|
209
|
+
ctx: TContext,
|
|
207
210
|
input: InferSchemaOutput<TInput>,
|
|
208
211
|
):
|
|
209
212
|
| OperationHandlerResult<InferSchemaOutput<TOutput>>
|
|
210
213
|
| Promise<OperationHandlerResult<InferSchemaOutput<TOutput>>>;
|
|
211
214
|
};
|
|
212
|
-
type OperationMapConfig<
|
|
215
|
+
type OperationMapConfig<
|
|
216
|
+
TOperations extends Record<string, ProviderOperation>,
|
|
217
|
+
TContext = ProviderContext,
|
|
218
|
+
> = {
|
|
213
219
|
[K in keyof TOperations]: TOperations[K] extends OperationDefinition<infer TInput, infer TOutput>
|
|
214
|
-
? OperationConfig<TInput, TOutput> | OperationDefinition<TInput, TOutput>
|
|
220
|
+
? OperationConfig<TInput, TOutput, TContext> | OperationDefinition<TInput, TOutput, TContext>
|
|
215
221
|
: never;
|
|
216
222
|
};
|
|
217
|
-
type StreamOperationConfig<
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
223
|
+
type StreamOperationConfig<
|
|
224
|
+
TInput extends SchemaLike,
|
|
225
|
+
TOutput extends SchemaLike,
|
|
226
|
+
TContext = ProviderContext,
|
|
227
|
+
> =
|
|
228
|
+
| SseOperationConfig<TInput, TOutput, TContext>
|
|
229
|
+
| HttpStreamOperationConfig<TInput, TOutput, TContext>
|
|
230
|
+
| WebSocketOperationConfig<TInput, TOutput, TContext>;
|
|
231
|
+
type SseOperationConfig<
|
|
232
|
+
TInput extends SchemaLike,
|
|
233
|
+
TOutput extends SchemaLike,
|
|
234
|
+
TContext = ProviderContext,
|
|
235
|
+
> = Omit<OperationConfig<TInput, TOutput, TContext>, "handler" | "transport"> & {
|
|
225
236
|
transport: OperationSseTransport;
|
|
226
237
|
handler(
|
|
227
|
-
ctx:
|
|
238
|
+
ctx: TContext,
|
|
228
239
|
input: InferSchemaOutput<TInput>,
|
|
229
240
|
): AsyncIterable<ProviderStreamEvent> | Promise<AsyncIterable<ProviderStreamEvent>>;
|
|
230
241
|
};
|
|
231
|
-
type HttpStreamOperationConfig<
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
242
|
+
type HttpStreamOperationConfig<
|
|
243
|
+
TInput extends SchemaLike,
|
|
244
|
+
TOutput extends SchemaLike,
|
|
245
|
+
TContext = ProviderContext,
|
|
246
|
+
> = Omit<OperationConfig<TInput, TOutput, TContext>, "handler" | "transport"> & {
|
|
235
247
|
transport: OperationHttpStreamTransport;
|
|
236
248
|
handler(
|
|
237
|
-
ctx:
|
|
249
|
+
ctx: TContext,
|
|
238
250
|
input: InferSchemaOutput<TInput>,
|
|
239
251
|
): Response | ReadableStream<Uint8Array> | Promise<Response | ReadableStream<Uint8Array>>;
|
|
240
252
|
};
|
|
241
|
-
type WebSocketOperationConfig<
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
253
|
+
type WebSocketOperationConfig<
|
|
254
|
+
TInput extends SchemaLike,
|
|
255
|
+
TOutput extends SchemaLike,
|
|
256
|
+
TContext = ProviderContext,
|
|
257
|
+
> = Omit<OperationConfig<TInput, TOutput, TContext>, "handler" | "transport"> & {
|
|
245
258
|
transport: OperationWebSocketTransport;
|
|
246
259
|
handler(
|
|
247
|
-
ctx:
|
|
260
|
+
ctx: TContext,
|
|
248
261
|
input: InferSchemaOutput<TInput>,
|
|
249
262
|
): Response | ReadableStream<Uint8Array> | Promise<Response | ReadableStream<Uint8Array>>;
|
|
250
263
|
};
|
|
251
264
|
|
|
252
|
-
type
|
|
265
|
+
type AuthStartHandlerNoInputGuard<TStart> = TStart extends (...args: infer TArgs) => unknown
|
|
266
|
+
? TArgs["length"] extends 0 | 1
|
|
267
|
+
? unknown
|
|
268
|
+
: {
|
|
269
|
+
"auth start handlers must not declare input parameters; return a form turn from start and receive user input in continue": never;
|
|
270
|
+
}
|
|
271
|
+
: unknown;
|
|
272
|
+
|
|
273
|
+
export type AuthStartNoInputGuard<TConfig> = TConfig extends {
|
|
253
274
|
auth?: { flow?: { start: infer TStart } };
|
|
254
275
|
}
|
|
255
|
-
? TStart
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
276
|
+
? AuthStartHandlerNoInputGuard<TStart>
|
|
277
|
+
: TConfig extends { start: infer TStart }
|
|
278
|
+
? AuthStartHandlerNoInputGuard<TStart>
|
|
279
|
+
: unknown;
|
|
280
|
+
|
|
281
|
+
function splitAuthStartParameters(parameters: string): string[] | undefined {
|
|
282
|
+
const parts: string[] = [];
|
|
283
|
+
let start = 0;
|
|
284
|
+
let round = 0;
|
|
285
|
+
let square = 0;
|
|
286
|
+
let curly = 0;
|
|
287
|
+
let quote: "'" | '"' | "`" | undefined;
|
|
288
|
+
let escaped = false;
|
|
289
|
+
let lineComment = false;
|
|
290
|
+
let blockComment = false;
|
|
291
|
+
const templateDepths = [0];
|
|
292
|
+
templateDepths.length = 0;
|
|
293
|
+
|
|
294
|
+
for (let index = 0; index < parameters.length; index++) {
|
|
295
|
+
const character = parameters[index];
|
|
296
|
+
const nextCharacter = parameters[index + 1];
|
|
297
|
+
if (lineComment) {
|
|
298
|
+
if (character === "\n" || character === "\r") lineComment = false;
|
|
299
|
+
else continue;
|
|
300
|
+
}
|
|
301
|
+
if (blockComment) {
|
|
302
|
+
if (character === "*" && nextCharacter === "/") {
|
|
303
|
+
blockComment = false;
|
|
304
|
+
index++;
|
|
305
|
+
}
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
if (quote) {
|
|
309
|
+
if (escaped) {
|
|
310
|
+
escaped = false;
|
|
311
|
+
} else if (character === "\\") {
|
|
312
|
+
escaped = true;
|
|
313
|
+
} else if (quote === "`" && character === "$" && nextCharacter === "{") {
|
|
314
|
+
curly++;
|
|
315
|
+
templateDepths.push(curly);
|
|
316
|
+
quote = undefined;
|
|
317
|
+
index++;
|
|
318
|
+
} else if (character === quote) {
|
|
319
|
+
quote = undefined;
|
|
320
|
+
}
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
if (character === "'" || character === '"' || character === "`") {
|
|
324
|
+
quote = character;
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
if (character === "/" && nextCharacter === "/") {
|
|
328
|
+
lineComment = true;
|
|
329
|
+
index++;
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
if (character === "/" && nextCharacter === "*") {
|
|
333
|
+
blockComment = true;
|
|
334
|
+
index++;
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
if (character === "/") return undefined;
|
|
338
|
+
// Annex B HTML-like comments are not lexed here; give up rather than
|
|
339
|
+
// risk misreading the parameter list.
|
|
340
|
+
if (character === "<" && parameters.startsWith("!--", index + 1)) return undefined;
|
|
341
|
+
if (character === "-" && parameters.startsWith("->", index + 1)) return undefined;
|
|
342
|
+
if (character === "(") round++;
|
|
343
|
+
else if (character === ")") round--;
|
|
344
|
+
else if (character === "[") square++;
|
|
345
|
+
else if (character === "]") square--;
|
|
346
|
+
else if (character === "{") curly++;
|
|
347
|
+
else if (character === "}") {
|
|
348
|
+
if (templateDepths.at(-1) === curly) {
|
|
349
|
+
templateDepths.pop();
|
|
350
|
+
curly--;
|
|
351
|
+
quote = "`";
|
|
352
|
+
} else curly--;
|
|
353
|
+
} else if (character === "," && round === 0 && square === 0 && curly === 0) {
|
|
354
|
+
parts.push(parameters.slice(start, index));
|
|
355
|
+
start = index + 1;
|
|
356
|
+
}
|
|
357
|
+
if (round < 0 || square < 0 || curly < 0) return undefined;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (
|
|
361
|
+
quote ||
|
|
362
|
+
blockComment ||
|
|
363
|
+
templateDepths.length > 0 ||
|
|
364
|
+
round !== 0 ||
|
|
365
|
+
square !== 0 ||
|
|
366
|
+
curly !== 0
|
|
367
|
+
)
|
|
368
|
+
return undefined;
|
|
369
|
+
parts.push(parameters.slice(start));
|
|
370
|
+
return parts;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function authStartParameterList(source: string): string | undefined {
|
|
374
|
+
let index = 0;
|
|
375
|
+
while (index < source.length && /\s/.test(source[index] ?? "")) index++;
|
|
376
|
+
if (index >= source.length) return undefined;
|
|
377
|
+
|
|
378
|
+
let openIndex = -1;
|
|
379
|
+
let parenthesizedArrow = false;
|
|
380
|
+
let asyncMethodOrArrow = false;
|
|
381
|
+
const skipTrivia = () => {
|
|
382
|
+
while (index < source.length) {
|
|
383
|
+
if (/\s/.test(source[index] ?? "")) {
|
|
384
|
+
index++;
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
if (source[index] === "/" && source[index + 1] === "/") {
|
|
388
|
+
index += 2;
|
|
389
|
+
while (index < source.length && source[index] !== "\n" && source[index] !== "\r") index++;
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
if (source[index] === "/" && source[index + 1] === "*") {
|
|
393
|
+
const end = source.indexOf("*/", index + 2);
|
|
394
|
+
if (end < 0) {
|
|
395
|
+
index = source.length;
|
|
396
|
+
return;
|
|
260
397
|
}
|
|
261
|
-
|
|
262
|
-
|
|
398
|
+
index = end + 2;
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
const initial = source.slice(index);
|
|
406
|
+
const isAsync = initial.startsWith("async") && !/[\w$]/.test(initial[5] ?? "");
|
|
407
|
+
if (isAsync) {
|
|
408
|
+
index += 5;
|
|
409
|
+
skipTrivia();
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const afterAsync = source.slice(index);
|
|
413
|
+
const isFunction = afterAsync.startsWith("function") && !/[\w$]/.test(afterAsync[8] ?? "");
|
|
414
|
+
if (isFunction) {
|
|
415
|
+
index += 8;
|
|
416
|
+
skipTrivia();
|
|
417
|
+
if (source[index] === "*") {
|
|
418
|
+
index++;
|
|
419
|
+
skipTrivia();
|
|
420
|
+
}
|
|
421
|
+
} else if (source[index] === "*") {
|
|
422
|
+
index++;
|
|
423
|
+
skipTrivia();
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (source[index] === "(") {
|
|
427
|
+
openIndex = index;
|
|
428
|
+
parenthesizedArrow = !isFunction;
|
|
429
|
+
asyncMethodOrArrow = isAsync && !isFunction;
|
|
430
|
+
} else if (isFunction) {
|
|
431
|
+
if (!/[A-Za-z_$]/.test(source[index] ?? "")) return undefined;
|
|
432
|
+
index++;
|
|
433
|
+
while (index < source.length && /[A-Za-z0-9_$]/.test(source[index] ?? "")) index++;
|
|
434
|
+
skipTrivia();
|
|
435
|
+
if (source[index] !== "(") return undefined;
|
|
436
|
+
openIndex = index;
|
|
437
|
+
} else {
|
|
438
|
+
const identifierStart = index;
|
|
439
|
+
if (!/[A-Za-z_$]/.test(source[index] ?? "")) return undefined;
|
|
440
|
+
index++;
|
|
441
|
+
while (index < source.length && /[A-Za-z0-9_$]/.test(source[index] ?? "")) index++;
|
|
442
|
+
const firstIdentifier = source.slice(identifierStart, index);
|
|
443
|
+
skipTrivia();
|
|
444
|
+
if (source[index] === "=" && source[index + 1] === ">") return undefined;
|
|
445
|
+
if (source[index] !== "(") {
|
|
446
|
+
if (firstIdentifier !== "get" && firstIdentifier !== "set") return undefined;
|
|
447
|
+
if (!/[A-Za-z_$]/.test(source[index] ?? "")) return undefined;
|
|
448
|
+
index++;
|
|
449
|
+
while (index < source.length && /[A-Za-z0-9_$]/.test(source[index] ?? "")) index++;
|
|
450
|
+
skipTrivia();
|
|
451
|
+
}
|
|
452
|
+
if (source[index] !== "(") return undefined;
|
|
453
|
+
openIndex = index;
|
|
454
|
+
}
|
|
455
|
+
if (openIndex < 0) return undefined;
|
|
456
|
+
|
|
457
|
+
let depth = 1;
|
|
458
|
+
let square = 0;
|
|
459
|
+
let curly = 0;
|
|
460
|
+
let quote: "'" | '"' | "`" | undefined;
|
|
461
|
+
let escaped = false;
|
|
462
|
+
let lineComment = false;
|
|
463
|
+
let blockComment = false;
|
|
464
|
+
const templateDepths = [0];
|
|
465
|
+
templateDepths.length = 0;
|
|
466
|
+
for (index = openIndex + 1; index < source.length; index++) {
|
|
467
|
+
const character = source[index];
|
|
468
|
+
const nextCharacter = source[index + 1];
|
|
469
|
+
if (lineComment) {
|
|
470
|
+
if (character === "\n" || character === "\r") lineComment = false;
|
|
471
|
+
else continue;
|
|
472
|
+
}
|
|
473
|
+
if (blockComment) {
|
|
474
|
+
if (character === "*" && nextCharacter === "/") {
|
|
475
|
+
blockComment = false;
|
|
476
|
+
index++;
|
|
477
|
+
}
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
if (quote) {
|
|
481
|
+
if (escaped) escaped = false;
|
|
482
|
+
else if (character === "\\") escaped = true;
|
|
483
|
+
else if (quote === "`" && character === "$" && nextCharacter === "{") {
|
|
484
|
+
curly++;
|
|
485
|
+
templateDepths.push(curly);
|
|
486
|
+
quote = undefined;
|
|
487
|
+
index++;
|
|
488
|
+
} else if (character === quote) quote = undefined;
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
if (character === "'" || character === '"' || character === "`") {
|
|
492
|
+
quote = character;
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
if (character === "/" && nextCharacter === "/") {
|
|
496
|
+
lineComment = true;
|
|
497
|
+
index++;
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
if (character === "/" && nextCharacter === "*") {
|
|
501
|
+
blockComment = true;
|
|
502
|
+
index++;
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
if (character === "/") return undefined;
|
|
506
|
+
// Annex B HTML-like comments are not lexed here; give up rather than
|
|
507
|
+
// risk misreading the parameter list.
|
|
508
|
+
if (character === "<" && source.startsWith("!--", index + 1)) return undefined;
|
|
509
|
+
if (character === "-" && source.startsWith("->", index + 1)) return undefined;
|
|
510
|
+
if (character === "(") depth++;
|
|
511
|
+
else if (character === ")") {
|
|
512
|
+
depth--;
|
|
513
|
+
if (depth === 0 && square === 0 && curly === 0) {
|
|
514
|
+
const closeIndex = index;
|
|
515
|
+
if (parenthesizedArrow) {
|
|
516
|
+
index++;
|
|
517
|
+
skipTrivia();
|
|
518
|
+
const hasArrow = source[index] === "=" && source[index + 1] === ">";
|
|
519
|
+
if (!hasArrow && (!asyncMethodOrArrow || source[index] !== "{")) return undefined;
|
|
520
|
+
}
|
|
521
|
+
return source.slice(openIndex + 1, closeIndex);
|
|
522
|
+
}
|
|
523
|
+
if (depth < 0) return undefined;
|
|
524
|
+
} else if (character === "[") square++;
|
|
525
|
+
else if (character === "]") {
|
|
526
|
+
square--;
|
|
527
|
+
if (square < 0) return undefined;
|
|
528
|
+
} else if (character === "{") curly++;
|
|
529
|
+
else if (character === "}") {
|
|
530
|
+
if (templateDepths.at(-1) === curly) {
|
|
531
|
+
templateDepths.pop();
|
|
532
|
+
curly--;
|
|
533
|
+
quote = "`";
|
|
534
|
+
} else curly--;
|
|
535
|
+
if (curly < 0) return undefined;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return undefined;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Conservative defense in depth for defaulted second parameters, which
|
|
543
|
+
* JavaScript intentionally omits from Function.length. Ambiguous source is
|
|
544
|
+
* ignored so this check can never reject a valid provider on weak evidence.
|
|
545
|
+
*/
|
|
546
|
+
function authStartHasHiddenInput(start: unknown): boolean {
|
|
547
|
+
let source: string;
|
|
548
|
+
try {
|
|
549
|
+
source = Function.prototype.toString.call(start);
|
|
550
|
+
} catch {
|
|
551
|
+
return false;
|
|
552
|
+
}
|
|
553
|
+
if (
|
|
554
|
+
!source ||
|
|
555
|
+
source.includes("[native code]") ||
|
|
556
|
+
/^\s*(?:async\s+)?function\s+bound\b/.test(source)
|
|
557
|
+
)
|
|
558
|
+
return false;
|
|
559
|
+
|
|
560
|
+
const parameters = authStartParameterList(source);
|
|
561
|
+
if (!parameters) return false;
|
|
562
|
+
const parts = splitAuthStartParameters(parameters);
|
|
563
|
+
if (!parts || parts.length < 2) return false;
|
|
564
|
+
|
|
565
|
+
// Require ordinary, readable source formatting. This intentionally fails
|
|
566
|
+
// open for minified output and for transpilers that rewrite defaults.
|
|
567
|
+
const commaIndex = parameters.indexOf(",");
|
|
568
|
+
if (commaIndex < 0 || !/\s/.test(parameters[commaIndex + 1] ?? "")) return false;
|
|
569
|
+
const first = parts[0].trim();
|
|
570
|
+
const second = parts[1].trim();
|
|
571
|
+
const identifier = /^[_$A-Za-z][_$A-Za-z0-9]*/;
|
|
572
|
+
const firstName = first.match(identifier)?.[0];
|
|
573
|
+
const secondName = second.match(identifier)?.[0];
|
|
574
|
+
if (!firstName || !secondName || firstName.length < 3 || secondName.length < 3) return false;
|
|
575
|
+
return /\s=\s/.test(second);
|
|
576
|
+
}
|
|
263
577
|
|
|
264
|
-
export interface
|
|
578
|
+
export interface ProviderDeclaration {
|
|
265
579
|
id: string;
|
|
266
580
|
version: string;
|
|
267
581
|
runtime: "standard" | "shared" | "browser";
|
|
@@ -272,6 +586,8 @@ export interface ProviderConfig<TOperations extends Record<string, ProviderOpera
|
|
|
272
586
|
* resolves omitted fields against the runtime deployment profiles.
|
|
273
587
|
*/
|
|
274
588
|
deployment?: ProviderDeploymentOverrides;
|
|
589
|
+
/** Declares that provider operations use the SDK HTTP client. */
|
|
590
|
+
http?: true;
|
|
275
591
|
allowedHosts?: string[];
|
|
276
592
|
native?: NativeProviderConfig;
|
|
277
593
|
stealth?: {
|
|
@@ -284,11 +600,21 @@ export interface ProviderConfig<TOperations extends Record<string, ProviderOpera
|
|
|
284
600
|
resolver?: ProviderResolverConfig;
|
|
285
601
|
browser?: { engine: BrowserEngine };
|
|
286
602
|
auth?: AuthConfig;
|
|
603
|
+
/** Declares that provider operations issue and consume SDK choice tokens. */
|
|
604
|
+
choice?: true;
|
|
287
605
|
reviewed?: ProviderReviewed;
|
|
288
606
|
access?: ProviderAccessConfig;
|
|
289
607
|
secrets?: ProviderSecretDeclaration[];
|
|
608
|
+
/** Declares that provider operations read SDK-managed environment values. */
|
|
609
|
+
env?: true;
|
|
290
610
|
credential?: CredentialDeclaration;
|
|
291
611
|
context?: ContextDeclaration;
|
|
612
|
+
/** Declares that provider operations use SDK-managed persistent state. */
|
|
613
|
+
state?: true;
|
|
614
|
+
/** Declares that provider operations use the SDK provider cache. */
|
|
615
|
+
cache?: true;
|
|
616
|
+
/** Declares that provider operations access runtime-resolvable files. */
|
|
617
|
+
files?: true;
|
|
292
618
|
meta: {
|
|
293
619
|
displayName: string;
|
|
294
620
|
displayNameKey?: string;
|
|
@@ -310,25 +636,35 @@ export interface ProviderConfig<TOperations extends Record<string, ProviderOpera
|
|
|
310
636
|
publicSchemaFieldNames?: "normalized";
|
|
311
637
|
};
|
|
312
638
|
};
|
|
313
|
-
operations: OperationMapConfig<TOperations>;
|
|
314
639
|
healthMonitor?: ProviderHealthMonitorConfig;
|
|
315
640
|
/** New name for `healthMonitor` (transitional alias); declaring both is a ValidationError. */
|
|
316
641
|
healthProbe?: ProviderHealthMonitorConfig;
|
|
317
642
|
healthJourneys?: readonly HealthJourneyDefinition[];
|
|
318
643
|
}
|
|
319
644
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
645
|
+
interface ProviderConfig<
|
|
646
|
+
TOperations extends Record<string, ProviderOperation>,
|
|
647
|
+
TContext = ProviderContext,
|
|
648
|
+
> extends ProviderDeclaration {
|
|
649
|
+
operations: OperationMapConfig<TOperations, TContext>;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/** Define one factored provider operation with schema-driven handler inference. */
|
|
653
|
+
export function defineOperation<TContext>() {
|
|
654
|
+
return function operation<TInput extends SchemaLike, TOutput extends SchemaLike>(
|
|
655
|
+
config: OperationConfig<TInput, TOutput, TContext>,
|
|
656
|
+
): OperationDefinition<TInput, TOutput, TContext> {
|
|
657
|
+
return config;
|
|
658
|
+
};
|
|
325
659
|
}
|
|
326
660
|
|
|
327
|
-
/** Define a non-JSON
|
|
328
|
-
export function defineStreamOperation<
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
661
|
+
/** Define a factored non-JSON operation with explicit transport metadata. */
|
|
662
|
+
export function defineStreamOperation<TContext>() {
|
|
663
|
+
return function streamOperation<TInput extends SchemaLike, TOutput extends SchemaLike>(
|
|
664
|
+
config: StreamOperationConfig<TInput, TOutput, TContext>,
|
|
665
|
+
): OperationDefinition<TInput, TOutput, TContext> {
|
|
666
|
+
return config;
|
|
667
|
+
};
|
|
332
668
|
}
|
|
333
669
|
|
|
334
670
|
function assertObjectConfig(value: unknown): asserts value is Record<string, unknown> {
|
|
@@ -479,6 +815,14 @@ function validateProviderShape(config: unknown): void {
|
|
|
479
815
|
assertRequiredField(config, "operations", String(config.id));
|
|
480
816
|
if (typeof config.runtime === "string")
|
|
481
817
|
assertLiteralField(config.runtime, "runtime", VALID_RUNTIMES, String(config.id));
|
|
818
|
+
if (config.native !== undefined && config.runtime === "browser") {
|
|
819
|
+
throw new ValidationError(
|
|
820
|
+
`Provider "${String(config.id)}" cannot declare capability "native" with runtime "browser"`,
|
|
821
|
+
{
|
|
822
|
+
fix: 'Use runtime: "standard" or runtime: "shared", or remove the native declaration.',
|
|
823
|
+
},
|
|
824
|
+
);
|
|
825
|
+
}
|
|
482
826
|
const auth = config.auth;
|
|
483
827
|
if (auth && typeof auth === "object" && "mode" in auth && typeof auth.mode === "string")
|
|
484
828
|
assertLiteralField(auth.mode, "auth.mode", VALID_AUTH_MODES, String(config.id));
|
|
@@ -510,6 +854,24 @@ function validateProviderShape(config: unknown): void {
|
|
|
510
854
|
},
|
|
511
855
|
);
|
|
512
856
|
}
|
|
857
|
+
if (
|
|
858
|
+
auth &&
|
|
859
|
+
typeof auth === "object" &&
|
|
860
|
+
"flow" in auth &&
|
|
861
|
+
auth.flow &&
|
|
862
|
+
typeof auth.flow === "object" &&
|
|
863
|
+
"start" in auth.flow &&
|
|
864
|
+
typeof auth.flow.start === "function" &&
|
|
865
|
+
auth.flow.start.length <= 1 &&
|
|
866
|
+
authStartHasHiddenInput(auth.flow.start)
|
|
867
|
+
) {
|
|
868
|
+
throw new ProviderError(
|
|
869
|
+
`Provider "${String(config.id)}" auth.flow.start must not declare an input parameter`,
|
|
870
|
+
{
|
|
871
|
+
fix: "Return a form turn from start(ctx), then receive user input in continue(ctx, input).",
|
|
872
|
+
},
|
|
873
|
+
);
|
|
874
|
+
}
|
|
513
875
|
const access = config.access;
|
|
514
876
|
if (access !== undefined) {
|
|
515
877
|
if (!access || typeof access !== "object" || Array.isArray(access)) {
|
|
@@ -768,12 +1130,14 @@ function validateProviderResolver(config: { id: string; resolver?: ProviderResol
|
|
|
768
1130
|
"resolver",
|
|
769
1131
|
config.id,
|
|
770
1132
|
);
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
1133
|
+
if (resolver.vendors !== undefined) {
|
|
1134
|
+
validateResolverLiteralArray(
|
|
1135
|
+
resolver.vendors,
|
|
1136
|
+
"resolver.vendors",
|
|
1137
|
+
VALID_PROVIDER_RESOLVER_VENDORS,
|
|
1138
|
+
config.id,
|
|
1139
|
+
);
|
|
1140
|
+
}
|
|
777
1141
|
validateResolverLiteralArray(
|
|
778
1142
|
resolver.kinds,
|
|
779
1143
|
"resolver.kinds",
|
|
@@ -2464,12 +2828,48 @@ function validateProviderDeployment(providerId: string, deployment: unknown): vo
|
|
|
2464
2828
|
});
|
|
2465
2829
|
}
|
|
2466
2830
|
|
|
2467
|
-
|
|
2831
|
+
/** The second authoring phase for a declaration established by defineProvider. */
|
|
2832
|
+
export type ProviderBuilder<TDeclaration extends ProviderDeclaration> = <
|
|
2833
|
+
TOperations extends Record<string, ProviderOperation>,
|
|
2834
|
+
>(
|
|
2835
|
+
implementation: {
|
|
2836
|
+
operations: OperationMapConfig<TOperations, ProviderContextFor<TDeclaration>>;
|
|
2837
|
+
},
|
|
2838
|
+
) => ProviderDefinition & {
|
|
2839
|
+
operations: OperationMapConfig<TOperations, ProviderContextFor<TDeclaration>>;
|
|
2840
|
+
};
|
|
2841
|
+
|
|
2842
|
+
/** Extract the declaration-derived operation context from a provider builder. */
|
|
2843
|
+
export type ProviderContextOf<TBuilder> = TBuilder extends ProviderBuilder<infer TDeclaration>
|
|
2844
|
+
? ProviderContextFor<TDeclaration>
|
|
2845
|
+
: never;
|
|
2846
|
+
|
|
2847
|
+
/** Establish a provider declaration before its operations are contextually typed. */
|
|
2848
|
+
export function defineProvider<const TDeclaration extends ProviderDeclaration>(
|
|
2849
|
+
declaration: TDeclaration &
|
|
2850
|
+
Record<Exclude<keyof TDeclaration, keyof ProviderDeclaration>, never> &
|
|
2851
|
+
AuthStartNoInputGuard<TDeclaration>,
|
|
2852
|
+
): ProviderBuilder<TDeclaration> {
|
|
2853
|
+
const buildProvider = <TOperations extends Record<string, ProviderOperation>>(
|
|
2854
|
+
implementation: {
|
|
2855
|
+
operations: OperationMapConfig<TOperations, ProviderContextFor<TDeclaration>>;
|
|
2856
|
+
},
|
|
2857
|
+
) =>
|
|
2858
|
+
finalizeProvider({
|
|
2859
|
+
...declaration,
|
|
2860
|
+
...implementation,
|
|
2861
|
+
} as ProviderConfig<TOperations, ProviderContextFor<TDeclaration>>);
|
|
2862
|
+
return buildProvider as ProviderBuilder<TDeclaration>;
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2865
|
+
function finalizeProvider<
|
|
2468
2866
|
TOperations extends Record<string, ProviderOperation>,
|
|
2469
|
-
|
|
2867
|
+
TContext,
|
|
2470
2868
|
>(
|
|
2471
|
-
config:
|
|
2472
|
-
): ProviderDefinition & {
|
|
2869
|
+
config: ProviderConfig<TOperations, TContext>,
|
|
2870
|
+
): ProviderDefinition & {
|
|
2871
|
+
operations: OperationMapConfig<TOperations, TContext>;
|
|
2872
|
+
} {
|
|
2473
2873
|
validateProviderShape(config);
|
|
2474
2874
|
const operations = resolveOperationFixtureRequests(config.operations);
|
|
2475
2875
|
if (!CONNECTOR_ID_REGEX.test(config.id))
|
|
@@ -2530,7 +2930,9 @@ export function defineProvider<
|
|
|
2530
2930
|
`Provider "${config.id}" cannot define browser config unless runtime is "browser"`,
|
|
2531
2931
|
{ fix: 'Set runtime: "browser" or remove the browser config' },
|
|
2532
2932
|
);
|
|
2533
|
-
const provider: ProviderDefinition & {
|
|
2933
|
+
const provider: ProviderDefinition & {
|
|
2934
|
+
operations: OperationMapConfig<TOperations, TContext>;
|
|
2935
|
+
} = {
|
|
2534
2936
|
id: config.id,
|
|
2535
2937
|
version: config.version,
|
|
2536
2938
|
runtime: config.runtime,
|
|
@@ -2552,7 +2954,8 @@ export function defineProvider<
|
|
|
2552
2954
|
credential: config.credential,
|
|
2553
2955
|
context: config.context,
|
|
2554
2956
|
meta: config.meta,
|
|
2555
|
-
operations
|
|
2957
|
+
operations: operations as ProviderDefinition["operations"] &
|
|
2958
|
+
OperationMapConfig<TOperations, TContext>,
|
|
2556
2959
|
// Transitional healthMonitor → healthProbe alias: mirror whichever field
|
|
2557
2960
|
// was declared onto both so old and new consumers keep working.
|
|
2558
2961
|
healthMonitor: config.healthMonitor ?? config.healthProbe,
|