ankka 0.10.0 → 0.11.0

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.
Files changed (74) hide show
  1. package/dist/_proto/ankka/protocol/v1/agent_pb.d.ts +35 -0
  2. package/dist/_proto/ankka/protocol/v1/agent_pb.js +8 -1
  3. package/dist/_proto/ankka/protocol/v1/client_pb.d.ts +542 -2
  4. package/dist/_proto/ankka/protocol/v1/client_pb.js +107 -6
  5. package/dist/_proto/ankka/protocol/v1/discovery_pb.d.ts +330 -0
  6. package/dist/_proto/ankka/protocol/v1/discovery_pb.js +81 -16
  7. package/dist/_proto/ankka/protocol/v1/endpoint_pb.d.ts +184 -0
  8. package/dist/_proto/ankka/protocol/v1/endpoint_pb.js +26 -1
  9. package/dist/_proto/ankka/protocol/v1/view_pb.d.ts +63 -0
  10. package/dist/_proto/ankka/protocol/v1/view_pb.js +11 -1
  11. package/dist/_proto/ankka/protocol/v1/workflow_pb.d.ts +7 -0
  12. package/dist/_proto/ankka/protocol/v1/workflow_pb.js +1 -1
  13. package/dist/agent.d.ts +15 -1
  14. package/dist/agent.js +20 -0
  15. package/dist/approvals.d.ts +47 -0
  16. package/dist/approvals.js +57 -0
  17. package/dist/autonomous.d.ts +23 -1
  18. package/dist/autonomous.js +30 -1
  19. package/dist/client.d.ts +92 -4
  20. package/dist/client.js +262 -15
  21. package/dist/consumer.d.ts +24 -2
  22. package/dist/consumer.js +20 -0
  23. package/dist/context.d.ts +4 -0
  24. package/dist/contract.d.ts +36 -0
  25. package/dist/contract.js +58 -0
  26. package/dist/effects/keyed.d.ts +33 -0
  27. package/dist/effects/keyed.js +41 -0
  28. package/dist/endpoint.d.ts +10 -1
  29. package/dist/endpoint.js +20 -0
  30. package/dist/graph.d.ts +15 -1
  31. package/dist/graph.js +20 -0
  32. package/dist/handlers.d.ts +10 -2
  33. package/dist/handlers.js +6 -2
  34. package/dist/index.d.ts +12 -4
  35. package/dist/index.js +11 -3
  36. package/dist/keyedView.d.ts +56 -0
  37. package/dist/keyedView.js +67 -0
  38. package/dist/mcp.d.ts +57 -0
  39. package/dist/mcp.js +64 -0
  40. package/dist/routes.d.ts +23 -3
  41. package/dist/routes.js +16 -3
  42. package/dist/server/agent.js +12 -4
  43. package/dist/server/discovery.d.ts +12 -1
  44. package/dist/server/discovery.js +95 -1
  45. package/dist/server/http.d.ts +2 -1
  46. package/dist/server/http.js +78 -4
  47. package/dist/server/stateless.js +38 -0
  48. package/dist/server/workflow.js +5 -1
  49. package/dist/service.d.ts +40 -2
  50. package/dist/service.js +209 -14
  51. package/dist/services.d.ts +137 -0
  52. package/dist/services.js +259 -0
  53. package/dist/socket.d.ts +15 -0
  54. package/dist/socket.js +25 -0
  55. package/dist/spec.d.ts +1 -1
  56. package/dist/spec.js +87 -14
  57. package/dist/startFrom.d.ts +32 -0
  58. package/dist/startFrom.js +44 -0
  59. package/dist/testkit/index.d.ts +2 -2
  60. package/dist/testkit/index.js +1 -1
  61. package/dist/testkit/integration.js +5 -0
  62. package/dist/testkit/kinds.d.ts +43 -2
  63. package/dist/testkit/kinds.js +196 -26
  64. package/dist/testkit/unit.d.ts +14 -1
  65. package/dist/testkit/unit.js +40 -3
  66. package/dist/timedAction.d.ts +17 -2
  67. package/dist/timedAction.js +33 -1
  68. package/dist/version.d.ts +1 -1
  69. package/dist/version.js +1 -1
  70. package/dist/view.d.ts +32 -0
  71. package/dist/view.js +8 -0
  72. package/dist/workflow.d.ts +18 -1
  73. package/dist/workflow.js +39 -0
  74. package/package.json +3 -2
package/dist/service.js CHANGED
@@ -3,15 +3,19 @@
3
3
  // time, and reported together before anything listens — the same rule as the Scala builder.
4
4
  //
5
5
  // await Ankka.service().register(ShoppingCartEntity).register(ShoppingCartEndpoint).listen()
6
+ import { isStartFrom } from "./startFrom.js";
7
+ import { Contract } from "./contract.js";
6
8
  import { codecFor, isCodec } from "./codec.js";
7
9
  import { EventSourcedEntity } from "./eventSourcedEntity.js";
8
10
  import { KeyValueEntity } from "./keyValueEntity.js";
9
11
  import { Workflow } from "./workflow.js";
10
12
  import { View } from "./view.js";
13
+ import { KeyedView } from "./keyedView.js";
11
14
  import { Consumer } from "./consumer.js";
12
15
  import { GraphConsumer, graphDeltaCodec } from "./graph.js";
13
16
  import { TimedAction } from "./timedAction.js";
14
17
  import { Agent } from "./agent.js";
18
+ import { serverProblems, TOOL_PREFIX } from "./mcp.js";
15
19
  import { AutonomousAgent, COMPLETE_TASK, FAIL_TASK } from "./autonomous.js";
16
20
  import { Endpoint } from "./endpoint.js";
17
21
  import { isAcl } from "./routes.js";
@@ -96,6 +100,8 @@ export class ServiceBuilder {
96
100
  add(registerWorkflow(cls, problems), cls);
97
101
  else if (extendsBase(cls, View))
98
102
  add(registerView(cls, problems), cls);
103
+ else if (extendsBase(cls, KeyedView))
104
+ add(registerKeyedView(cls, problems), cls);
99
105
  else if (extendsBase(cls, Consumer))
100
106
  add(registerConsumer(cls, problems), cls);
101
107
  else if (extendsBase(cls, GraphConsumer))
@@ -123,7 +129,7 @@ export class ServiceBuilder {
123
129
  }
124
130
  }
125
131
  else {
126
- problems.push(`${nameOf(cls)} is not a component class: it must extend EventSourcedEntity, KeyValueEntity, Workflow, View, Consumer, GraphConsumer, TimedAction, Agent, AutonomousAgent or Endpoint`);
132
+ problems.push(`${nameOf(cls)} is not a component class: it must extend EventSourcedEntity, KeyValueEntity, Workflow, View, KeyedView, Consumer, GraphConsumer, TimedAction, Agent, AutonomousAgent or Endpoint`);
127
133
  }
128
134
  }
129
135
  if (problems.length > 0)
@@ -223,19 +229,59 @@ function registerWorkflow(cls, problems) {
223
229
  return undefined;
224
230
  return Object.freeze({ kind: "workflow", id: cls.componentId, cls, stateCodec: codecFor(cls.state), handlers, steps, settings });
225
231
  }
226
- function sourceOf(cls, fail) {
232
+ /**
233
+ * What a view or consumer says about the topic it reads, checked where its other declarations are:
234
+ * a consumer must say where it starts, and a start position means something only for a topic. A
235
+ * version means something for a view whatever it reads — raising it rebuilds the view, from a topic
236
+ * as far back as the broker retains and from an entity from its first event — and for a consumer only
237
+ * over a topic, whose group it renames.
238
+ */
239
+ function topicProblems(cls, consumer, fail) {
240
+ const readsTopic = cls.source === undefined && cls.topic !== undefined;
241
+ if (cls.startFrom !== undefined && !isStartFrom(cls.startFrom))
242
+ fail("startFrom must be StartFrom.earliest, StartFrom.latest or StartFrom.at(...)");
243
+ if (cls.startFrom !== undefined && !readsTopic)
244
+ fail("declares a start position, which applies to a topic; it reads a component");
245
+ if (consumer && readsTopic && cls.startFrom === undefined) {
246
+ fail(`reads topic ${JSON.stringify(cls.topic)} and declares no start position; declare static startFrom = StartFrom.earliest, StartFrom.latest or StartFrom.at(...)`);
247
+ }
248
+ if (cls.version !== undefined) {
249
+ if (typeof cls.version !== "number" || !Number.isSafeInteger(cls.version) || cls.version < 1) {
250
+ fail(`declares version ${String(cls.version)}; a version is a whole number of 1 or more`);
251
+ }
252
+ else if (consumer && !readsTopic)
253
+ fail("declares a version, which applies to a topic; it reads a component");
254
+ }
255
+ if (cls.contract !== undefined && !(cls.contract instanceof Contract))
256
+ fail("contract must be a Contract: Contract.fromFile(path, name)");
257
+ if (cls.broker !== undefined && (typeof cls.broker !== "string" || cls.broker.trim() === ""))
258
+ fail("broker must be the name of a broker the project declares");
259
+ if (cls.parallel !== undefined && typeof cls.parallel !== "boolean")
260
+ fail("parallel must be true or false");
261
+ if ((cls.contract !== undefined || cls.broker !== undefined || cls.parallel !== undefined) && !readsTopic) {
262
+ fail("declares a contract, a broker or parallel, which apply to a topic; it reads a component");
263
+ }
264
+ }
265
+ function sourceOf(cls, fail, consumer) {
227
266
  const hasSource = cls.source !== undefined;
228
267
  const hasTopic = cls.topic !== undefined;
229
268
  if (hasSource === hasTopic) {
230
269
  fail("needs a static source (a component class) or a static topic, and not both");
231
270
  return undefined;
232
271
  }
272
+ topicProblems(cls, consumer, fail);
233
273
  if (hasTopic) {
234
274
  if (typeof cls.topic !== "string" || cls.topic.trim() === "") {
235
275
  fail("topic must be a non-empty string");
236
276
  return undefined;
237
277
  }
238
- return { topic: cls.topic };
278
+ return {
279
+ topic: cls.topic,
280
+ ...(isStartFrom(cls.startFrom) ? { startFrom: cls.startFrom } : {}),
281
+ ...(cls.contract instanceof Contract ? { contract: cls.contract } : {}),
282
+ ...(typeof cls.broker === "string" ? { broker: cls.broker } : {}),
283
+ ...(cls.parallel === true ? { parallel: true } : {}),
284
+ };
239
285
  }
240
286
  const src = cls.source;
241
287
  const kind = src?.prototype?._kind;
@@ -248,25 +294,112 @@ function sourceOf(cls, fail) {
248
294
  function registerView(cls, problems) {
249
295
  const { fail, ok } = checker(cls, problems);
250
296
  requireId(cls, fail);
251
- const source = sourceOf(cls, fail);
297
+ const source = sourceOf(cls, fail, false);
252
298
  requireShape(cls.events, "events: a schema or codec for the source's events", fail);
253
299
  requireShape(cls.row, "row: a schema or codec for its rows", fail);
254
300
  const queries = cls.queries ?? ["get", "all"];
255
301
  if (!Array.isArray(queries) || queries.some((q) => typeof q !== "string" || q.trim() === ""))
256
302
  fail("queries must be a list of names");
303
+ const declared = cls.declared ?? [];
304
+ if (!Array.isArray(declared) || declared.some((q) => typeof q?.name !== "string" || q.name.trim() === "" || typeof q?.statement !== "string" || q.statement.trim() === "")) {
305
+ fail("declared must be a list of declaredQuery(name, statement)");
306
+ }
307
+ else {
308
+ const names = declared.map((q) => q.name);
309
+ const twice = names.filter((n, i) => names.indexOf(n) !== i);
310
+ if (twice.length > 0)
311
+ fail(`declares the query '${twice[0]}' twice; each query has a name of its own`);
312
+ }
257
313
  if (!ok() || !source)
258
314
  return undefined;
259
- return Object.freeze({ kind: "view", id: cls.componentId, cls, source, eventCodec: codecFor(cls.events), rowCodec: codecFor(cls.row), queries: Object.freeze([...queries]) });
315
+ return Object.freeze({
316
+ kind: "view",
317
+ id: cls.componentId,
318
+ cls,
319
+ source,
320
+ eventCodec: codecFor(cls.events),
321
+ rowCodec: codecFor(cls.row),
322
+ queries: Object.freeze([...queries]),
323
+ version: cls.version,
324
+ declared: Object.freeze([...declared]),
325
+ });
326
+ }
327
+ function checkVersion(version, fail) {
328
+ if (version !== undefined && (typeof version !== "number" || !Number.isSafeInteger(version) || version < 1)) {
329
+ fail(`declares version ${String(version)}; a version is a whole number of 1 or more`);
330
+ }
331
+ }
332
+ function checkDeclared(declared, fail) {
333
+ if (declared === undefined)
334
+ return [];
335
+ if (!Array.isArray(declared) || declared.some((q) => typeof q?.name !== "string" || q.name.trim() === "" || typeof q?.statement !== "string" || q.statement.trim() === "")) {
336
+ fail("declared must be a list of declaredQuery(name, statement)");
337
+ return [];
338
+ }
339
+ const names = declared.map((q) => q.name);
340
+ const twice = names.filter((n, i) => names.indexOf(n) !== i);
341
+ if (twice.length > 0)
342
+ fail(`declares the query '${twice[0]}' twice; each query has a name of its own`);
343
+ return declared;
344
+ }
345
+ /**
346
+ * A keyed view: one or more entities, each through a handler of its own. A topic is not a keyed view's
347
+ * source, and one entity is read once.
348
+ */
349
+ function registerKeyedView(cls, problems) {
350
+ const { fail, ok } = checker(cls, problems);
351
+ requireId(cls, fail);
352
+ requireShape(cls.row, "row: a schema or codec for its rows", fail);
353
+ checkVersion(cls.version, fail);
354
+ const declared = checkDeclared(cls.declared, fail);
355
+ const sources = new Map();
356
+ const declaredSources = cls.sources;
357
+ if (!Array.isArray(declaredSources) || declaredSources.length === 0) {
358
+ fail("declares no source; a keyed view reads one or more entities, each declared with on(...)");
359
+ }
360
+ else {
361
+ for (const [i, s] of declaredSources.entries()) {
362
+ const ref = s?.source;
363
+ const kind = ref?.prototype?._kind;
364
+ if (typeof s?.onChange !== "function" || typeof ref?.componentId !== "string" || typeof kind !== "string") {
365
+ fail(`sources[${i}] is not a source: declare it with on(EntityClass, events, handler)`);
366
+ continue;
367
+ }
368
+ if (kind !== "event-sourced" && kind !== "key-value") {
369
+ fail(`reads ${kind} ${JSON.stringify(ref.componentId)}; a keyed view reads event sourced and key value entities`);
370
+ continue;
371
+ }
372
+ if (!isShape(s.events)) {
373
+ fail(`sources[${i}] needs a schema or codec for ${ref.componentId}'s changes`);
374
+ continue;
375
+ }
376
+ if (sources.has(ref.componentId)) {
377
+ fail(`reads ${JSON.stringify(ref.componentId)} twice; each source is read once`);
378
+ continue;
379
+ }
380
+ sources.set(ref.componentId, Object.freeze({ id: ref.componentId, kind, eventCodec: codecFor(s.events), onChange: s.onChange, deleted: s.deleted }));
381
+ }
382
+ }
383
+ if (!ok())
384
+ return undefined;
385
+ return Object.freeze({
386
+ kind: "keyed-view",
387
+ id: cls.componentId,
388
+ cls,
389
+ sources,
390
+ rowCodec: codecFor(cls.row),
391
+ version: cls.version,
392
+ declared: Object.freeze([...declared]),
393
+ });
260
394
  }
261
395
  function registerConsumer(cls, problems) {
262
396
  const { fail, ok } = checker(cls, problems);
263
397
  requireId(cls, fail);
264
- const source = sourceOf(cls, fail);
398
+ const source = sourceOf(cls, fail, true);
265
399
  requireShape(cls.message, "message: a schema or codec for the source's messages", fail);
266
- if (cls.producesTo !== undefined && (typeof cls.producesTo !== "string" || cls.producesTo.trim() === ""))
267
- fail("producesTo must be a topic name");
268
- if (cls.producesTo !== undefined && !isShape(cls.out))
269
- fail(`produces to ${JSON.stringify(cls.producesTo)} and needs a static out: the shape of what it produces`);
400
+ const produces = publicationOf(cls.producesTo, fail);
401
+ if (produces !== undefined && !isShape(cls.out))
402
+ fail(`produces to ${JSON.stringify(produces.topic)} and needs a static out: the shape of what it produces`);
270
403
  if (!ok() || !source)
271
404
  return undefined;
272
405
  return Object.freeze({
@@ -276,15 +409,44 @@ function registerConsumer(cls, problems) {
276
409
  source,
277
410
  messageCodec: codecFor(cls.message),
278
411
  outCodec: cls.out ? codecFor(cls.out) : undefined,
279
- producesTo: cls.producesTo,
412
+ producesTo: produces?.topic,
413
+ produces,
280
414
  graph: false,
415
+ version: cls.version,
281
416
  });
282
417
  }
418
+ /**
419
+ * `producesTo` as declared: a topic name, or a Publication naming the topic with the contract stated
420
+ * for it and its broker. Undefined when the consumer produces nothing.
421
+ */
422
+ function publicationOf(declared, fail) {
423
+ if (declared === undefined)
424
+ return undefined;
425
+ if (typeof declared === "string") {
426
+ if (declared.trim() === "")
427
+ fail("producesTo must be a topic name");
428
+ return declared.trim() === "" ? undefined : { topic: declared };
429
+ }
430
+ const p = declared;
431
+ if (typeof p !== "object" || p === null || typeof p.topic !== "string" || p.topic.trim() === "") {
432
+ fail("producesTo must be a topic name, or { topic, contract?, broker? }");
433
+ return undefined;
434
+ }
435
+ if (p.contract !== undefined && !(p.contract instanceof Contract))
436
+ fail("producesTo.contract must be a Contract: Contract.fromFile(path, name)");
437
+ if (p.broker !== undefined && (typeof p.broker !== "string" || p.broker.trim() === ""))
438
+ fail("producesTo.broker must be the name of a broker the project declares");
439
+ return {
440
+ topic: p.topic,
441
+ ...(p.contract instanceof Contract ? { contract: p.contract } : {}),
442
+ ...(typeof p.broker === "string" ? { broker: p.broker } : {}),
443
+ };
444
+ }
283
445
  /** A graph consumer is a consumer to discovery and to the runtime: one that produces deltas to its topic. */
284
446
  function registerGraphConsumer(cls, problems) {
285
447
  const { fail, ok } = checker(cls, problems);
286
448
  requireId(cls, fail);
287
- const source = sourceOf(cls, fail);
449
+ const source = sourceOf(cls, fail, true);
288
450
  requireShape(cls.message, "message: a schema or codec for the source's messages", fail);
289
451
  if (typeof cls.producesTo !== "string" || cls.producesTo.trim() === "")
290
452
  fail("needs a static producesTo: the topic its deltas are published to");
@@ -300,7 +462,9 @@ function registerGraphConsumer(cls, problems) {
300
462
  messageCodec: codecFor(cls.message),
301
463
  outCodec: graphDeltaCodec,
302
464
  producesTo: cls.producesTo,
465
+ produces: undefined,
303
466
  graph: true,
467
+ version: cls.version,
304
468
  });
305
469
  }
306
470
  function registerTimedAction(cls, problems) {
@@ -340,9 +504,10 @@ function registerAgent(cls, problems) {
340
504
  const maxToolCallSteps = cls.maxToolCallSteps ?? 100;
341
505
  if (!Number.isInteger(maxToolCallSteps) || maxToolCallSteps <= 0)
342
506
  fail("maxToolCallSteps must be a positive integer");
507
+ const { mcpServers, resultGuardrails } = collectMcp(cls, tools, fail);
343
508
  if (!ok())
344
509
  return undefined;
345
- return Object.freeze({ kind: "agent", id: cls.componentId, cls, handlers, tools, guardrails, role: cls.role ?? "", maxToolCallSteps });
510
+ return Object.freeze({ kind: "agent", id: cls.componentId, cls, handlers, tools, guardrails, mcpServers, resultGuardrails, role: cls.role ?? "", maxToolCallSteps });
346
511
  }
347
512
  function registerAutonomousAgent(cls, problems) {
348
513
  const { fail, ok } = checker(cls, problems);
@@ -378,9 +543,39 @@ function registerAutonomousAgent(cls, problems) {
378
543
  fail(`task type ${JSON.stringify(a.taskType.name)} needs a budget of at least one iteration`);
379
544
  taskTypes.set(a.taskType.name, a.taskType);
380
545
  }
546
+ const { mcpServers, resultGuardrails } = collectMcp(cls, tools, fail);
381
547
  if (!ok())
382
548
  return undefined;
383
- return Object.freeze({ kind: "autonomous-agent", id: cls.componentId, cls, tools, guardrails, taskTypes });
549
+ return Object.freeze({ kind: "autonomous-agent", id: cls.componentId, cls, tools, guardrails, mcpServers, resultGuardrails, taskTypes });
550
+ }
551
+ /** An agent's MCP servers and result guardrails by name, with what is wrong with them in the sidecar's words. */
552
+ function collectMcp(cls, tools, fail) {
553
+ const mcpServers = new Map();
554
+ for (const [property, server] of Object.entries(cls.mcpServers ?? {})) {
555
+ if (typeof server !== "object" || server === null || typeof server.name !== "string") {
556
+ fail(`mcpServers.${property} is not an MCP server: declare it with mcpServer(name, options)`);
557
+ continue;
558
+ }
559
+ serverProblems(server).forEach(fail);
560
+ if (mcpServers.has(server.name))
561
+ fail(`MCP server '${server.name}' is listed twice`);
562
+ mcpServers.set(server.name, server);
563
+ }
564
+ for (const t of tools.values()) {
565
+ if (t.name.startsWith(TOOL_PREFIX))
566
+ fail(`tool '${t.name}' takes the prefix '${TOOL_PREFIX}', which names an MCP server's tools`);
567
+ }
568
+ const resultGuardrails = new Map();
569
+ for (const [property, g] of Object.entries(cls.resultGuardrails ?? {})) {
570
+ if (typeof g !== "object" || g === null || typeof g.name !== "string" || typeof g.check !== "function") {
571
+ fail(`resultGuardrails.${property} is not a result guardrail: declare it with resultGuardrail(name, check)`);
572
+ continue;
573
+ }
574
+ if (resultGuardrails.has(g.name))
575
+ fail(`result guardrail '${g.name}' is declared twice`);
576
+ resultGuardrails.set(g.name, g);
577
+ }
578
+ return { mcpServers, resultGuardrails };
384
579
  }
385
580
  /** Collects a handler table by wire name, reporting a duplicate wire name or a kind the component cannot host. */
386
581
  export function collectHandlers(table, what, allowed, fail) {
@@ -0,0 +1,137 @@
1
+ import { type ComponentClient } from "./client.ts";
2
+ import { type Shape } from "./codec.ts";
3
+ /** The protocol that added calls to other services. */
4
+ export declare const SERVICES_SINCE = "1.8";
5
+ /** The largest body, either way, a call through the sidecar carries: under gRPC's 4 MiB. */
6
+ export declare const MAX_BODY_BYTES = 4000000;
7
+ /** Headers, in order; a name may appear twice. The platform's own are removed before sending. */
8
+ export type ServiceHeaders = ReadonlyArray<readonly [string, string]>;
9
+ /** What the service answered: its status, content type, body and headers, as it sent them. */
10
+ export interface ServiceResponse {
11
+ readonly status: number;
12
+ readonly contentType: string;
13
+ readonly body: Uint8Array;
14
+ readonly headers: ServiceHeaders;
15
+ readonly text: string;
16
+ }
17
+ /** A call to another service that did not end in an answer a typed helper accepts. */
18
+ export declare class ServiceError extends Error {
19
+ readonly service: string;
20
+ constructor(service: string, message: string);
21
+ }
22
+ /** No such service was found; nothing was sent. `reason` says what was tried. */
23
+ export declare class ServiceUnresolvable extends ServiceError {
24
+ readonly reason: string;
25
+ constructor(service: string, reason: string);
26
+ }
27
+ /** What answered for the name is not the service asked for; nothing was sent. */
28
+ export declare class ServiceIdentityMismatch extends ServiceError {
29
+ readonly detail: string;
30
+ constructor(service: string, detail: string);
31
+ }
32
+ /**
33
+ * No answer came: the connection was refused or broke, or the service's timeout passed. The service
34
+ * may have received the request; one that may change something was sent at most once.
35
+ */
36
+ export declare class ServiceUnanswered extends ServiceError {
37
+ readonly reason: string;
38
+ constructor(service: string, reason: string);
39
+ }
40
+ /** The service answered with a status outside 2xx, to a typed helper. `request` returns every answer. */
41
+ export declare class ServiceCallFailed extends ServiceError {
42
+ readonly status: number;
43
+ readonly body: string;
44
+ constructor(service: string, status: number, body: string);
45
+ }
46
+ /** One request, as a client sends it. */
47
+ export interface ServiceRequest {
48
+ readonly service: string;
49
+ readonly project: string | undefined;
50
+ readonly method: string;
51
+ readonly path: string;
52
+ readonly headers: ServiceHeaders;
53
+ readonly contentType: string | undefined;
54
+ readonly body: Uint8Array | undefined;
55
+ }
56
+ type Send = (request: ServiceRequest, target: string) => Promise<ServiceResponse>;
57
+ /** Calls one service. `target` is the service as it was named: `name` or `project/name`. */
58
+ export declare class ServiceClient {
59
+ #private;
60
+ readonly target: string;
61
+ /** @internal */
62
+ constructor(send: Send, service: string, project: string | undefined);
63
+ /** One request, answered whatever its status. Throws only when no answer came. */
64
+ request(method: string, path: string, options?: {
65
+ body?: Uint8Array;
66
+ contentType?: string;
67
+ headers?: ServiceHeaders;
68
+ }): Promise<ServiceResponse>;
69
+ /** A JSON answer, decoded as `returns`; a status outside 2xx is a `ServiceCallFailed`. */
70
+ get<R>(path: string, returns: Shape<R>, options?: {
71
+ headers?: ServiceHeaders;
72
+ }): Promise<R>;
73
+ /** A text answer: what an endpoint returning a string sends. */
74
+ getText(path: string, options?: {
75
+ headers?: ServiceHeaders;
76
+ }): Promise<string>;
77
+ post<B, R>(path: string, body: B, shapes: {
78
+ body: Shape<B>;
79
+ returns: Shape<R>;
80
+ }, options?: {
81
+ headers?: ServiceHeaders;
82
+ }): Promise<R>;
83
+ put<B, R>(path: string, body: B, shapes: {
84
+ body: Shape<B>;
85
+ returns: Shape<R>;
86
+ }, options?: {
87
+ headers?: ServiceHeaders;
88
+ }): Promise<R>;
89
+ /** For a route answering nothing: succeeds on any 2xx. */
90
+ delete(path: string, options?: {
91
+ headers?: ServiceHeaders;
92
+ }): Promise<void>;
93
+ }
94
+ /**
95
+ * How a component obtains clients for other services: `this.services.service("orders")`, or
96
+ * `this.services.service("billing", "invoices")` for a service in another project — whether it admits
97
+ * this one is its own ACL's decision. The call carries the metadata of the handler that is running, so
98
+ * the runtime makes it as that handler: counted from it, and in its trace.
99
+ */
100
+ export declare class Services {
101
+ #private;
102
+ /** Calls through `client`'s sidecar, as the handler `client` is scoped to; with none, every call throws. */
103
+ constructor(client?: ComponentClient);
104
+ service(name: string): ServiceClient;
105
+ service(project: string, name: string): ServiceClient;
106
+ /** @internal */
107
+ _send(request: ServiceRequest, target: string): Promise<ServiceResponse>;
108
+ }
109
+ /**
110
+ * Other services, for a unit test: each answers as the test scripted it, and every request is
111
+ * recorded. A call to a service nothing is scripted for fails the test, naming the service: a test
112
+ * whose call quietly got a default answer is no longer testing what it says.
113
+ */
114
+ export declare class ScriptedServices extends Services {
115
+ #private;
116
+ readonly requests: ServiceRequest[];
117
+ constructor();
118
+ /** `name` answers with what `handler` returns; in this service's own project unless one is given. */
119
+ answer(name: string, handler: (request: ServiceRequest) => ServiceResponse, project?: string): this;
120
+ unresolvable(name: string, project?: string): this;
121
+ unanswered(name: string, project?: string): this;
122
+ mismatch(name: string, project?: string): this;
123
+ /** An answer of `body` as `text/plain`, for a script. */
124
+ static text(body: string, status?: number): ServiceResponse;
125
+ /** An answer of `body` as `application/json`, for a script. */
126
+ static json(body: string, status?: number): ServiceResponse;
127
+ /** @internal */
128
+ _send(request: ServiceRequest, target: string): Promise<ServiceResponse>;
129
+ }
130
+ /** Services for a unit test whose every call throws: the unit kit's default, beside `noClient()`. */
131
+ export declare function noServices(): Services;
132
+ /**
133
+ * @internal Other services, reached through `client`'s sidecar as the handler it is scoped to. Not a
134
+ * member of the client, which an entity holds too: only the kinds that may call one call this.
135
+ */
136
+ export declare function servicesFor(client: ComponentClient): Services;
137
+ export {};