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.
- package/dist/_proto/ankka/protocol/v1/agent_pb.d.ts +35 -0
- package/dist/_proto/ankka/protocol/v1/agent_pb.js +8 -1
- package/dist/_proto/ankka/protocol/v1/client_pb.d.ts +542 -2
- package/dist/_proto/ankka/protocol/v1/client_pb.js +107 -6
- package/dist/_proto/ankka/protocol/v1/discovery_pb.d.ts +330 -0
- package/dist/_proto/ankka/protocol/v1/discovery_pb.js +81 -16
- package/dist/_proto/ankka/protocol/v1/endpoint_pb.d.ts +184 -0
- package/dist/_proto/ankka/protocol/v1/endpoint_pb.js +26 -1
- package/dist/_proto/ankka/protocol/v1/view_pb.d.ts +63 -0
- package/dist/_proto/ankka/protocol/v1/view_pb.js +11 -1
- package/dist/_proto/ankka/protocol/v1/workflow_pb.d.ts +7 -0
- package/dist/_proto/ankka/protocol/v1/workflow_pb.js +1 -1
- package/dist/agent.d.ts +15 -1
- package/dist/agent.js +20 -0
- package/dist/approvals.d.ts +47 -0
- package/dist/approvals.js +57 -0
- package/dist/autonomous.d.ts +23 -1
- package/dist/autonomous.js +30 -1
- package/dist/client.d.ts +92 -4
- package/dist/client.js +262 -15
- package/dist/consumer.d.ts +24 -2
- package/dist/consumer.js +20 -0
- package/dist/context.d.ts +4 -0
- package/dist/contract.d.ts +36 -0
- package/dist/contract.js +58 -0
- package/dist/effects/keyed.d.ts +33 -0
- package/dist/effects/keyed.js +41 -0
- package/dist/endpoint.d.ts +10 -1
- package/dist/endpoint.js +20 -0
- package/dist/graph.d.ts +15 -1
- package/dist/graph.js +20 -0
- package/dist/handlers.d.ts +10 -2
- package/dist/handlers.js +6 -2
- package/dist/index.d.ts +12 -4
- package/dist/index.js +11 -3
- package/dist/keyedView.d.ts +56 -0
- package/dist/keyedView.js +67 -0
- package/dist/mcp.d.ts +57 -0
- package/dist/mcp.js +64 -0
- package/dist/routes.d.ts +23 -3
- package/dist/routes.js +16 -3
- package/dist/server/agent.js +12 -4
- package/dist/server/discovery.d.ts +12 -1
- package/dist/server/discovery.js +95 -1
- package/dist/server/http.d.ts +2 -1
- package/dist/server/http.js +78 -4
- package/dist/server/stateless.js +38 -0
- package/dist/server/workflow.js +5 -1
- package/dist/service.d.ts +40 -2
- package/dist/service.js +209 -14
- package/dist/services.d.ts +137 -0
- package/dist/services.js +259 -0
- package/dist/socket.d.ts +15 -0
- package/dist/socket.js +25 -0
- package/dist/spec.d.ts +1 -1
- package/dist/spec.js +87 -14
- package/dist/startFrom.d.ts +32 -0
- package/dist/startFrom.js +44 -0
- package/dist/testkit/index.d.ts +2 -2
- package/dist/testkit/index.js +1 -1
- package/dist/testkit/integration.js +5 -0
- package/dist/testkit/kinds.d.ts +43 -2
- package/dist/testkit/kinds.js +196 -26
- package/dist/testkit/unit.d.ts +14 -1
- package/dist/testkit/unit.js +40 -3
- package/dist/timedAction.d.ts +17 -2
- package/dist/timedAction.js +33 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/view.d.ts +32 -0
- package/dist/view.js +8 -0
- package/dist/workflow.d.ts +18 -1
- package/dist/workflow.js +39 -0
- package/package.json +3 -2
package/dist/server/discovery.js
CHANGED
|
@@ -1,16 +1,96 @@
|
|
|
1
1
|
// Discovery: the first conversation. The sidecar asks what we host; we answer with the Spec the
|
|
2
2
|
// registry renders. `ReportError` is how the sidecar tells us why it refused to start — logged, and kept
|
|
3
3
|
// so a service can expose it (the conformance reference does, at GET /conformance/problems).
|
|
4
|
+
import { Code, ConnectError } from "@connectrpc/connect";
|
|
4
5
|
import { create } from "@bufbuild/protobuf";
|
|
5
6
|
import { Discovery } from "../_proto/ankka/protocol/v1/discovery_pb.js";
|
|
6
7
|
import { EmptySchema } from "../_proto/ankka/protocol/v1/payload_pb.js";
|
|
8
|
+
import { PROTOCOL_VERSION } from "../spec.js";
|
|
9
|
+
import { olderThanContracts, olderThanDeclaredQueries, olderThanStartPositions } from "../startFrom.js";
|
|
10
|
+
/**
|
|
11
|
+
* A sidecar older than 1.7 would ignore where a topic source starts and its version: a consumer
|
|
12
|
+
* declared `latest` would read everything, and a raised version would rebuild nothing. Refused,
|
|
13
|
+
* naming what declares them, rather than served wrong.
|
|
14
|
+
*/
|
|
15
|
+
export function refusal(spec, sidecarProtocol) {
|
|
16
|
+
return startPositionRefusal(spec, sidecarProtocol) ?? declaredQueryRefusal(spec, sidecarProtocol) ?? contractRefusal(spec, sidecarProtocol);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A sidecar older than 1.14 would ignore a topic's contract, broker and parallel reading and a
|
|
20
|
+
* publication's contract and broker: a contract would go unchecked, a topic on another broker would be
|
|
21
|
+
* read from the installation's. Refused at discovery, naming the components, rather than served wrong.
|
|
22
|
+
*/
|
|
23
|
+
function contractRefusal(spec, sidecarProtocol) {
|
|
24
|
+
if (!olderThanContracts(sidecarProtocol))
|
|
25
|
+
return undefined;
|
|
26
|
+
const declaring = spec.components
|
|
27
|
+
.filter((c) => {
|
|
28
|
+
const d = c.detail;
|
|
29
|
+
if (d.case !== "view" && d.case !== "consumer")
|
|
30
|
+
return false;
|
|
31
|
+
const src = d.value.source;
|
|
32
|
+
const onSource = src !== undefined && (src.contract !== undefined || src.broker !== undefined || src.parallel === true);
|
|
33
|
+
const onPublication = d.case === "consumer" && d.value.produces !== undefined && (d.value.produces.contract !== undefined || d.value.produces.broker !== undefined);
|
|
34
|
+
return onSource || onPublication;
|
|
35
|
+
})
|
|
36
|
+
.map((c) => c.id);
|
|
37
|
+
if (declaring.length === 0)
|
|
38
|
+
return undefined;
|
|
39
|
+
return (`${declaring.join(", ")} declare a topic's contract, broker or parallel reading, which the sidecar ignores: ` +
|
|
40
|
+
`it speaks protocol ${sidecarProtocol}, and this SDK ${PROTOCOL_VERSION}. Run a sidecar speaking 1.14 or later.`);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A sidecar older than 1.13 would ignore a view's declared queries, would read a keyed view as having
|
|
44
|
+
* no source, and would refuse a version on a view that reads an entity as applying only to a topic.
|
|
45
|
+
* Refused at discovery, naming the views, rather than found later or misreported.
|
|
46
|
+
*/
|
|
47
|
+
function declaredQueryRefusal(spec, sidecarProtocol) {
|
|
48
|
+
if (!olderThanDeclaredQueries(sidecarProtocol))
|
|
49
|
+
return undefined;
|
|
50
|
+
const declaring = spec.components
|
|
51
|
+
.filter((c) => {
|
|
52
|
+
if (c.detail.case !== "view")
|
|
53
|
+
return false;
|
|
54
|
+
const v = c.detail.value;
|
|
55
|
+
// A declared query, a keyed view, or a version on a view that reads an entity.
|
|
56
|
+
return v.declaredQueries.length > 0 || v.sources.length > 0 || (v.version !== undefined && v.source?.source.case === "component");
|
|
57
|
+
})
|
|
58
|
+
.map((c) => c.id);
|
|
59
|
+
if (declaring.length === 0)
|
|
60
|
+
return undefined;
|
|
61
|
+
return (`${declaring.join(", ")} declare queries, several sources, or a version on a view that reads an entity, which the sidecar ignores: ` +
|
|
62
|
+
`it speaks protocol ${sidecarProtocol}, and this SDK ${PROTOCOL_VERSION}. Run a sidecar speaking 1.13 or later.`);
|
|
63
|
+
}
|
|
64
|
+
function startPositionRefusal(spec, sidecarProtocol) {
|
|
65
|
+
if (!olderThanStartPositions(sidecarProtocol))
|
|
66
|
+
return undefined;
|
|
67
|
+
const declaring = spec.components
|
|
68
|
+
.filter((c) => {
|
|
69
|
+
const d = c.detail;
|
|
70
|
+
if (d.case !== "view" && d.case !== "consumer")
|
|
71
|
+
return false;
|
|
72
|
+
return d.value.source?.startFrom !== undefined || d.value.version !== undefined;
|
|
73
|
+
})
|
|
74
|
+
.map((c) => c.id);
|
|
75
|
+
if (declaring.length === 0)
|
|
76
|
+
return undefined;
|
|
77
|
+
return (`${declaring.join(", ")} declare where a topic source starts or its version, which the sidecar ignores: ` +
|
|
78
|
+
`it speaks protocol ${sidecarProtocol}, and this SDK ${PROTOCOL_VERSION}. Run a sidecar speaking 1.7 or later.`);
|
|
79
|
+
}
|
|
7
80
|
/** Every problem the sidecar has reported to this process, in order. */
|
|
8
81
|
export const problems = [];
|
|
9
82
|
export function discoveryRoutes(router, spec, log = console.error) {
|
|
10
83
|
router.service(Discovery, {
|
|
11
84
|
async discover(info) {
|
|
12
85
|
log(`ankka: discovery from sidecar (protocol ${info.protocolVersion}, runtime ${info.runtimeVersion})`);
|
|
13
|
-
|
|
86
|
+
const answer = spec();
|
|
87
|
+
const why = refusal(answer, info.protocolVersion) ?? socketRefusal(answer, info.protocolVersion);
|
|
88
|
+
if (why !== undefined) {
|
|
89
|
+
log(`ankka: ${why}`);
|
|
90
|
+
problems.push(why);
|
|
91
|
+
throw new ConnectError(why, Code.FailedPrecondition);
|
|
92
|
+
}
|
|
93
|
+
return answer;
|
|
14
94
|
},
|
|
15
95
|
async reportError(problem) {
|
|
16
96
|
log(`ankka: the sidecar refused to start: ${problem.message}`);
|
|
@@ -19,3 +99,17 @@ export function discoveryRoutes(router, spec, log = console.error) {
|
|
|
19
99
|
},
|
|
20
100
|
});
|
|
21
101
|
}
|
|
102
|
+
/** The protocol version whose runtimes serve socket routes. */
|
|
103
|
+
export const SOCKETS_SINCE = [1, 9];
|
|
104
|
+
/** Why `spec` cannot be declared to a runtime speaking `runtime`, or undefined. A runtime before 1.9
|
|
105
|
+
* does not know a socket route, and would serve it as a plain GET. */
|
|
106
|
+
export function socketRefusal(spec, runtime) {
|
|
107
|
+
const routes = spec.endpoints.flatMap((e) => e.routes.filter((r) => r.socket).map((r) => `${e.id}.${r.id}`));
|
|
108
|
+
if (routes.length === 0)
|
|
109
|
+
return undefined;
|
|
110
|
+
const [major, minor] = runtime.split(".").map((part) => Number.parseInt(part, 10));
|
|
111
|
+
const speaks = Number.isFinite(major) && Number.isFinite(minor) ? [major, minor] : [0, 0];
|
|
112
|
+
if (speaks[0] > SOCKETS_SINCE[0] || (speaks[0] === SOCKETS_SINCE[0] && speaks[1] >= SOCKETS_SINCE[1]))
|
|
113
|
+
return undefined;
|
|
114
|
+
return `this runtime speaks protocol ${runtime || "unknown"}; a socket route needs 1.9 (${routes.join(", ")})`;
|
|
115
|
+
}
|
package/dist/server/http.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { ConnectRouter } from "@connectrpc/connect";
|
|
2
|
-
import { type HttpRequest, type HttpReply, type StreamFrame } from "../_proto/ankka/protocol/v1/endpoint_pb.ts";
|
|
2
|
+
import { type HttpRequest, type HttpReply, type SocketIn, type SocketOut, type StreamFrame } from "../_proto/ankka/protocol/v1/endpoint_pb.ts";
|
|
3
3
|
import type { ServerContext } from "./server.ts";
|
|
4
4
|
export interface HttpDispatcher {
|
|
5
5
|
handle(req: HttpRequest): Promise<HttpReply>;
|
|
6
6
|
handleStream(req: HttpRequest): AsyncIterable<StreamFrame>;
|
|
7
|
+
handleSocket(requests: AsyncIterable<SocketIn>): AsyncIterable<SocketOut>;
|
|
7
8
|
}
|
|
8
9
|
/** The behaviour behind `Http.Handle` and `Http.HandleStream`, over the registry's endpoints. */
|
|
9
10
|
export declare function createHttpDispatcher(ctx: ServerContext): HttpDispatcher;
|
package/dist/server/http.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// `createHttpDispatcher` is the whole behaviour; the Connect servicer and the endpoint unit testkit both
|
|
6
6
|
// call it, so a test cannot pass on a path the sidecar would not take.
|
|
7
7
|
import { create } from "@bufbuild/protobuf";
|
|
8
|
-
import { Http, HttpReplySchema, StreamFrameSchema } from "../_proto/ankka/protocol/v1/endpoint_pb.js";
|
|
8
|
+
import { Http, HttpReplySchema, SocketOutSchema, StreamFrameSchema, } from "../_proto/ankka/protocol/v1/endpoint_pb.js";
|
|
9
9
|
import { codecFor } from "../codec.js";
|
|
10
10
|
import { Headers, Query, metadataFromProto, withRequest } from "../context.js";
|
|
11
11
|
import { CommandError, ErrorCode, httpStatusOf } from "../effects/common.js";
|
|
@@ -13,6 +13,7 @@ import { DecodingError } from "../json.js";
|
|
|
13
13
|
import { errorCodeToProto } from "../kinds.js";
|
|
14
14
|
import { HttpProblem } from "../routes.js";
|
|
15
15
|
import { done, resolve } from "../schema.js";
|
|
16
|
+
import { SocketClosed, socketOf } from "../socket.js";
|
|
16
17
|
import { AsyncQueue } from "./queue.js";
|
|
17
18
|
const utf8 = new TextEncoder();
|
|
18
19
|
function textResponse(status, text) {
|
|
@@ -28,7 +29,15 @@ function principalOf(req) {
|
|
|
28
29
|
const p = req.principal;
|
|
29
30
|
if (!p)
|
|
30
31
|
return null;
|
|
31
|
-
return Object.freeze({
|
|
32
|
+
return Object.freeze({
|
|
33
|
+
subject: p.subject,
|
|
34
|
+
name: p.name ?? null,
|
|
35
|
+
email: p.email ?? null,
|
|
36
|
+
emailVerified: p.emailVerified,
|
|
37
|
+
roles: Object.freeze([...p.roles]),
|
|
38
|
+
claims: Object.freeze({ ...p.claims }),
|
|
39
|
+
issuer: p.issuer ?? null,
|
|
40
|
+
});
|
|
32
41
|
}
|
|
33
42
|
/** The caller the sidecar established; a sidecar that predates protocol 1.1 sends none, which reads as local. */
|
|
34
43
|
function callerOf(req) {
|
|
@@ -69,6 +78,7 @@ function encodeResult(route, result) {
|
|
|
69
78
|
const contentType = codec.contentType === "text/plain" ? "text/plain; charset=utf-8" : codec.contentType;
|
|
70
79
|
return create(HttpReplySchema, { message: { case: "response", value: { status: 200, contentType, body: codec.encode(result), headers: [] } } });
|
|
71
80
|
}
|
|
81
|
+
const NEEDS_SOCKETS = "a socket route needs protocol 1.9; this runtime asked for a request";
|
|
72
82
|
/** The behaviour behind `Http.Handle` and `Http.HandleStream`, over the registry's endpoints. */
|
|
73
83
|
export function createHttpDispatcher(ctx) {
|
|
74
84
|
const instances = new Map();
|
|
@@ -118,6 +128,8 @@ export function createHttpDispatcher(ctx) {
|
|
|
118
128
|
const prep = prepare(req);
|
|
119
129
|
if (!prep.ok)
|
|
120
130
|
return prep.reply;
|
|
131
|
+
if (prep.prepared.route.socket)
|
|
132
|
+
return failureReply(NEEDS_SOCKETS);
|
|
121
133
|
const { route, instance, request, body } = prep.prepared;
|
|
122
134
|
try {
|
|
123
135
|
const result = await withRequest(request, () => route.run(instance, request, body));
|
|
@@ -135,13 +147,21 @@ export function createHttpDispatcher(ctx) {
|
|
|
135
147
|
return;
|
|
136
148
|
}
|
|
137
149
|
const { route, instance, request } = prep.prepared;
|
|
150
|
+
if (route.socket) {
|
|
151
|
+
yield create(StreamFrameSchema, { frame: { case: "failed", value: { message: NEEDS_SOCKETS, code: errorCodeToProto(ErrorCode.Internal) } } });
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
138
154
|
// Produce inside the request's async context, consume here: the handler's `this.request` stays visible.
|
|
139
155
|
const queue = new AsyncQueue();
|
|
140
156
|
void withRequest(request, async () => {
|
|
141
157
|
try {
|
|
142
158
|
const frames = (await route.run(instance, request, undefined));
|
|
143
|
-
for await (const
|
|
144
|
-
|
|
159
|
+
for await (const part of frames) {
|
|
160
|
+
if (typeof part === "string")
|
|
161
|
+
queue.push(create(StreamFrameSchema, { frame: { case: "text", value: part } }));
|
|
162
|
+
else
|
|
163
|
+
queue.push(create(StreamFrameSchema, { frame: { case: "event", value: { name: part.name, data: JSON.stringify(part.value, (_k, v) => (typeof v === "bigint" ? v.toString() : v)) } } }));
|
|
164
|
+
}
|
|
145
165
|
queue.push(create(StreamFrameSchema, { frame: { case: "completed", value: {} } }));
|
|
146
166
|
}
|
|
147
167
|
catch (e) {
|
|
@@ -155,6 +175,59 @@ export function createHttpDispatcher(ctx) {
|
|
|
155
175
|
});
|
|
156
176
|
yield* queue;
|
|
157
177
|
},
|
|
178
|
+
// An open socket: `open` first, then frames until `closed`. The requests are read only when the
|
|
179
|
+
// handler asks for a frame, so a handler that does not read is not read for, and the sidecar's flow
|
|
180
|
+
// control holds the client back.
|
|
181
|
+
async *handleSocket(requests) {
|
|
182
|
+
const incoming = requests[Symbol.asyncIterator]();
|
|
183
|
+
const failed = (message, code = ErrorCode.Internal) => create(SocketOutSchema, { message: { case: "failed", value: { message, code: errorCodeToProto(code) } } });
|
|
184
|
+
const first = await incoming.next();
|
|
185
|
+
if (first.done || first.value.message.case !== "open") {
|
|
186
|
+
yield failed("a socket opens with open");
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const prep = prepare(first.value.message.value);
|
|
190
|
+
if (!prep.ok || !prep.prepared.route.socket) {
|
|
191
|
+
yield failed("unknown socket route", ErrorCode.NotFound);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const { route, instance, request } = prep.prepared;
|
|
195
|
+
const outgoing = new AsyncQueue();
|
|
196
|
+
let closed = false;
|
|
197
|
+
const receive = async () => {
|
|
198
|
+
if (closed)
|
|
199
|
+
return undefined;
|
|
200
|
+
const next = await incoming.next();
|
|
201
|
+
const message = next.done ? undefined : next.value.message;
|
|
202
|
+
if (message?.case === "frame" && message.value.kind.case === "text")
|
|
203
|
+
return message.value.kind.value;
|
|
204
|
+
closed = true; // closed, the stream's end, or a kind this SDK does not know
|
|
205
|
+
return undefined;
|
|
206
|
+
};
|
|
207
|
+
const send = async (text) => {
|
|
208
|
+
if (closed)
|
|
209
|
+
throw new SocketClosed();
|
|
210
|
+
outgoing.push(create(SocketOutSchema, { message: { case: "frame", value: { kind: { case: "text", value: text } } } }));
|
|
211
|
+
};
|
|
212
|
+
void withRequest(request, async () => {
|
|
213
|
+
try {
|
|
214
|
+
await route.run(instance, request, socketOf(receive, send));
|
|
215
|
+
outgoing.push(create(SocketOutSchema, { message: { case: "completed", value: {} } }));
|
|
216
|
+
}
|
|
217
|
+
catch (e) {
|
|
218
|
+
if (e instanceof SocketClosed)
|
|
219
|
+
outgoing.push(create(SocketOutSchema, { message: { case: "completed", value: {} } }));
|
|
220
|
+
else {
|
|
221
|
+
ctx.log(`ankka: SOCKET ${route.template} threw: ${messageOf(e)}`);
|
|
222
|
+
outgoing.push(failed(messageOf(e)));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
finally {
|
|
226
|
+
outgoing.close();
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
yield* outgoing;
|
|
230
|
+
},
|
|
158
231
|
};
|
|
159
232
|
}
|
|
160
233
|
export function httpRoutes(router, ctx) {
|
|
@@ -162,5 +235,6 @@ export function httpRoutes(router, ctx) {
|
|
|
162
235
|
router.service(Http, {
|
|
163
236
|
handle: (req) => dispatcher.handle(req),
|
|
164
237
|
handleStream: (req) => dispatcher.handleStream(req),
|
|
238
|
+
handleSocket: (requests) => dispatcher.handleSocket(requests),
|
|
165
239
|
});
|
|
166
240
|
}
|
package/dist/server/stateless.js
CHANGED
|
@@ -8,6 +8,7 @@ import { TimedAction as TimedActionService, TimedActionEffectSchema } from "../_
|
|
|
8
8
|
import { codecFor } from "../codec.js";
|
|
9
9
|
import { metadataFromProto, metadataToProto } from "../context.js";
|
|
10
10
|
import { ErrorCode } from "../effects/common.js";
|
|
11
|
+
import { clientRows } from "../keyedView.js";
|
|
11
12
|
import { ProtocolVersionError, requireSeveralMessages } from "../consumer.js";
|
|
12
13
|
import { GRAPH_DELTA_SCHEMA, deltaRecords } from "../graph.js";
|
|
13
14
|
import { JSON_CONTENT } from "../codec.js";
|
|
@@ -17,7 +18,44 @@ import { PayloadSchema } from "../_proto/ankka/protocol/v1/payload_pb.js";
|
|
|
17
18
|
function messageOf(e) {
|
|
18
19
|
return e instanceof Error ? `${e.name}: ${e.message}` : String(e);
|
|
19
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* A keyed view's change: sent with the component it came from and no row, answered with the rows the
|
|
23
|
+
* source's handler names. The view reads its own rows through the client, on its own id.
|
|
24
|
+
*/
|
|
25
|
+
async function handleKeyedView(req, sourceId, ctx) {
|
|
26
|
+
const registered = ctx.registry.of("keyed-view", req.componentId);
|
|
27
|
+
if (!registered)
|
|
28
|
+
throw new ConnectError(`no keyed view ${JSON.stringify(req.componentId)} is registered`, Code.NotFound);
|
|
29
|
+
const source = registered.sources.get(sourceId);
|
|
30
|
+
if (!source)
|
|
31
|
+
throw new ConnectError(`keyed view ${registered.id} reads no ${JSON.stringify(sourceId)}`, Code.NotFound);
|
|
32
|
+
const metadata = metadataFromProto(req.metadata);
|
|
33
|
+
const view = new registered.cls();
|
|
34
|
+
try {
|
|
35
|
+
const client = ctx.client.withMetadata(metadata);
|
|
36
|
+
view._bind(metadata, client, clientRows(client, registered.id, registered.rowCodec));
|
|
37
|
+
const effect = (req.deleted
|
|
38
|
+
? source.deleted
|
|
39
|
+
? await source.deleted(view)
|
|
40
|
+
: undefined
|
|
41
|
+
: await source.onChange(view, decodePayload(source.eventCodec, req.event)));
|
|
42
|
+
if (effect !== undefined && effect?.kind !== "rows")
|
|
43
|
+
throw new TypeError(`${registered.id}'s handler for ${sourceId} returned something that is not a keyed view effect`);
|
|
44
|
+
const changes = (effect?.changes ?? []).map((c) => "deleted" in c
|
|
45
|
+
? { key: c.key, change: { case: "delete", value: {} } }
|
|
46
|
+
: { key: c.key, change: { case: "upsert", value: encodePayload(registered.rowCodec, c.row) } });
|
|
47
|
+
return create(ViewEffectSchema, { effect: { case: "rows", value: { changes } } });
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
if (e instanceof ConnectError)
|
|
51
|
+
throw e;
|
|
52
|
+
ctx.log(`ankka: keyed view ${registered.id} on ${sourceId} ${metadata["ce-subject"] ?? "?"} threw: ${messageOf(e)}`);
|
|
53
|
+
throw new ConnectError(messageOf(e), Code.Internal);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
20
56
|
export async function handleView(req, ctx) {
|
|
57
|
+
if (req.sourceId !== undefined)
|
|
58
|
+
return handleKeyedView(req, req.sourceId, ctx);
|
|
21
59
|
const registered = ctx.registry.of("view", req.componentId);
|
|
22
60
|
if (!registered)
|
|
23
61
|
throw new ConnectError(`no view ${JSON.stringify(req.componentId)} is registered`, Code.NotFound);
|
package/dist/server/workflow.js
CHANGED
|
@@ -114,7 +114,11 @@ class WorkflowStream {
|
|
|
114
114
|
return failure(run.id, { message: `${registered.id}/${run.step}: the input could not be decoded: ${messageOf(e)}`, code: ErrorCode.BadRequest });
|
|
115
115
|
}
|
|
116
116
|
let effect;
|
|
117
|
-
|
|
117
|
+
// What the sidecar says about this step: its trace, and the step as the caller of whatever it calls.
|
|
118
|
+
// Carried on the step's calls as a command's is; a sidecar before 1.3 sends none.
|
|
119
|
+
const metadata = metadataFromProto(run.metadata);
|
|
120
|
+
fresh._bindCommand(this.state, commandContext(registered.id, this.entityId, 0n, metadata), ctx.client.withMetadata(metadata));
|
|
121
|
+
fresh._enterStep();
|
|
118
122
|
try {
|
|
119
123
|
effect = (await step.run(fresh, input));
|
|
120
124
|
}
|
package/dist/service.d.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
import { type StartFrom } from "./startFrom.ts";
|
|
2
|
+
import { Contract, type Publication } from "./contract.ts";
|
|
1
3
|
import { type Codec } from "./codec.ts";
|
|
2
4
|
import { EventSourcedEntity, type EventSourcedEntityClass } from "./eventSourcedEntity.ts";
|
|
3
5
|
import { KeyValueEntity, type KeyValueEntityClass } from "./keyValueEntity.ts";
|
|
4
6
|
import { Workflow, type WorkflowClass } from "./workflow.ts";
|
|
5
|
-
import { View, type ViewClass } from "./view.ts";
|
|
7
|
+
import { View, type DeclaredQuery, type ViewClass } from "./view.ts";
|
|
8
|
+
import { KeyedView, type KeyedViewClass } from "./keyedView.ts";
|
|
6
9
|
import { Consumer, type ConsumerClass } from "./consumer.ts";
|
|
7
10
|
import { GraphConsumer, type GraphConsumerClass } from "./graph.ts";
|
|
8
11
|
import { TimedAction, type TimedActionClass } from "./timedAction.ts";
|
|
9
12
|
import { Agent, type AgentClass } from "./agent.ts";
|
|
13
|
+
import { type McpServerRef, type ResultGuardrailRef } from "./mcp.ts";
|
|
10
14
|
import { AutonomousAgent, type AutonomousAgentClass, type TaskType } from "./autonomous.ts";
|
|
11
15
|
import { Endpoint, type EndpointClass } from "./endpoint.ts";
|
|
12
16
|
import type { GuardrailRef, HandlerRef, ToolRef } from "./handlers.ts";
|
|
@@ -49,6 +53,13 @@ export type Source = {
|
|
|
49
53
|
};
|
|
50
54
|
} | {
|
|
51
55
|
readonly topic: string;
|
|
56
|
+
readonly startFrom?: StartFrom;
|
|
57
|
+
/** The contract the topic is expected to carry. */
|
|
58
|
+
readonly contract?: Contract;
|
|
59
|
+
/** The declared broker the topic is on; absent is the installation's. */
|
|
60
|
+
readonly broker?: string;
|
|
61
|
+
/** Partitions handled at once, each in order. */
|
|
62
|
+
readonly parallel?: boolean;
|
|
52
63
|
};
|
|
53
64
|
export interface RegisteredView {
|
|
54
65
|
readonly kind: "view";
|
|
@@ -58,6 +69,25 @@ export interface RegisteredView {
|
|
|
58
69
|
readonly eventCodec: Codec<any>;
|
|
59
70
|
readonly rowCodec: Codec<any>;
|
|
60
71
|
readonly queries: readonly string[];
|
|
72
|
+
readonly version: number | undefined;
|
|
73
|
+
readonly declared: readonly DeclaredQuery[];
|
|
74
|
+
}
|
|
75
|
+
/** One source of a keyed view: the entity it reads, how its changes decode, and its handlers. */
|
|
76
|
+
export interface RegisteredKeyedSource {
|
|
77
|
+
readonly id: string;
|
|
78
|
+
readonly kind: ComponentKind;
|
|
79
|
+
readonly eventCodec: Codec<any>;
|
|
80
|
+
readonly onChange: (view: any, event: any) => unknown;
|
|
81
|
+
readonly deleted: ((view: any) => unknown) | undefined;
|
|
82
|
+
}
|
|
83
|
+
export interface RegisteredKeyedView {
|
|
84
|
+
readonly kind: "keyed-view";
|
|
85
|
+
readonly id: string;
|
|
86
|
+
readonly cls: KeyedViewClass<any, any>;
|
|
87
|
+
readonly sources: ReadonlyMap<string, RegisteredKeyedSource>;
|
|
88
|
+
readonly rowCodec: Codec<any>;
|
|
89
|
+
readonly version: number | undefined;
|
|
90
|
+
readonly declared: readonly DeclaredQuery[];
|
|
61
91
|
}
|
|
62
92
|
export interface RegisteredConsumer {
|
|
63
93
|
readonly kind: "consumer";
|
|
@@ -67,8 +97,11 @@ export interface RegisteredConsumer {
|
|
|
67
97
|
readonly messageCodec: Codec<any>;
|
|
68
98
|
readonly outCodec: Codec<any> | undefined;
|
|
69
99
|
readonly producesTo: string | undefined;
|
|
100
|
+
/** The publication as declared: the topic with the contract stated for it and its broker. */
|
|
101
|
+
readonly produces: Publication | undefined;
|
|
70
102
|
/** A graph consumer: its handlers return elements, each published as a delta under its element key. */
|
|
71
103
|
readonly graph: boolean;
|
|
104
|
+
readonly version: number | undefined;
|
|
72
105
|
}
|
|
73
106
|
export interface RegisteredTimedAction {
|
|
74
107
|
readonly kind: "timed-action";
|
|
@@ -83,6 +116,8 @@ export interface RegisteredAgent {
|
|
|
83
116
|
readonly handlers: ReadonlyMap<string, HandlerRef<any, any, any, any>>;
|
|
84
117
|
readonly tools: ReadonlyMap<string, ToolRef<any, any>>;
|
|
85
118
|
readonly guardrails: ReadonlyMap<string, GuardrailRef>;
|
|
119
|
+
readonly mcpServers: ReadonlyMap<string, McpServerRef>;
|
|
120
|
+
readonly resultGuardrails: ReadonlyMap<string, ResultGuardrailRef>;
|
|
86
121
|
readonly role: string;
|
|
87
122
|
readonly maxToolCallSteps: number;
|
|
88
123
|
}
|
|
@@ -92,9 +127,11 @@ export interface RegisteredAutonomousAgent {
|
|
|
92
127
|
readonly cls: AutonomousAgentClass<any>;
|
|
93
128
|
readonly tools: ReadonlyMap<string, ToolRef<any, any>>;
|
|
94
129
|
readonly guardrails: ReadonlyMap<string, GuardrailRef>;
|
|
130
|
+
readonly mcpServers: ReadonlyMap<string, McpServerRef>;
|
|
131
|
+
readonly resultGuardrails: ReadonlyMap<string, ResultGuardrailRef>;
|
|
95
132
|
readonly taskTypes: ReadonlyMap<string, TaskType<any>>;
|
|
96
133
|
}
|
|
97
|
-
export type RegisteredComponent = RegisteredEventSourced | RegisteredKeyValue | RegisteredWorkflow | RegisteredView | RegisteredConsumer | RegisteredTimedAction | RegisteredAgent | RegisteredAutonomousAgent;
|
|
134
|
+
export type RegisteredComponent = RegisteredEventSourced | RegisteredKeyValue | RegisteredWorkflow | RegisteredView | RegisteredKeyedView | RegisteredConsumer | RegisteredTimedAction | RegisteredAgent | RegisteredAutonomousAgent;
|
|
98
135
|
export interface RegisteredEndpoint {
|
|
99
136
|
readonly id: string;
|
|
100
137
|
readonly cls: EndpointClass<any>;
|
|
@@ -136,6 +173,7 @@ export declare class ServiceBuilder {
|
|
|
136
173
|
register<S, C extends KeyValueEntity<S>>(cls: KeyValueEntityClass<S, C>): this;
|
|
137
174
|
register<S, C extends Workflow<S>>(cls: WorkflowClass<S, C>): this;
|
|
138
175
|
register<E, Row, C extends View<E, Row>>(cls: ViewClass<E, Row, C>): this;
|
|
176
|
+
register<Row, C extends KeyedView<Row>>(cls: KeyedViewClass<Row, C>): this;
|
|
139
177
|
register<M, Out, C extends Consumer<M, Out>>(cls: ConsumerClass<M, Out, C>): this;
|
|
140
178
|
register<M, C extends GraphConsumer<M>>(cls: GraphConsumerClass<M, C>): this;
|
|
141
179
|
register<C extends TimedAction>(cls: TimedActionClass<C>): this;
|