ankka 0.9.1 → 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/consumer_pb.d.ts +52 -1
- package/dist/_proto/ankka/protocol/v1/consumer_pb.js +11 -1
- 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 +40 -2
- package/dist/consumer.js +50 -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/effects/stateless.d.ts +21 -0
- package/dist/effects/stateless.js +19 -0
- package/dist/endpoint.d.ts +10 -1
- package/dist/endpoint.js +20 -0
- package/dist/graph.d.ts +171 -0
- package/dist/graph.js +446 -0
- package/dist/handlers.d.ts +10 -2
- package/dist/handlers.js +6 -2
- package/dist/index.d.ts +15 -6
- package/dist/index.js +13 -4
- 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 +104 -16
- package/dist/server/workflow.js +5 -1
- package/dist/service.d.ts +45 -3
- package/dist/service.js +234 -12
- 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 +75 -5
- package/dist/testkit/kinds.js +264 -34
- 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/graph.js
ADDED
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
// Publishing a graph: a consumer that says which elements a change leaves in which state, and has each
|
|
2
|
+
// published as a graph delta — one element's whole state at a version, or a tombstone marking it
|
|
3
|
+
// deleted — under the contract `ankka.graph-delta.v1`. The record's key is the element's
|
|
4
|
+
// (`node:<id>` or `edge:<id>`), never the author's to set, and its version is the change's sequence
|
|
5
|
+
// number unless one is stated. A graph database kept in step with the topic applies a delta when its
|
|
6
|
+
// version is newer than what it holds, so a change handled twice does no harm.
|
|
7
|
+
//
|
|
8
|
+
// export class CartGraph extends GraphConsumer<ShoppingCartEvent> {
|
|
9
|
+
// static readonly componentId = "cart-graph"
|
|
10
|
+
// static readonly source = ShoppingCartEntity
|
|
11
|
+
// static readonly message = ShoppingCartEntity.events
|
|
12
|
+
// static readonly producesTo = "cart-graph"
|
|
13
|
+
// onMessage(event) {
|
|
14
|
+
// return this.effects.publish([this.graph.node(`cart:${this.subject}`, { labels: ["Cart"] })])
|
|
15
|
+
// }
|
|
16
|
+
// }
|
|
17
|
+
import { JSON_CONTENT } from "./codec.js";
|
|
18
|
+
import { secretsFor } from "./client.js";
|
|
19
|
+
import { sequenceNumberOf } from "./consumer.js";
|
|
20
|
+
import { renderDouble, reviver } from "./json.js";
|
|
21
|
+
import { servicesFor } from "./services.js";
|
|
22
|
+
/** The contract's name: the manifest and the `ce-type` of every delta. */
|
|
23
|
+
export const GRAPH_DELTA_SCHEMA = "ankka.graph-delta.v1";
|
|
24
|
+
/** The record key of every delta for the node `id`. */
|
|
25
|
+
export function nodeKey(id) {
|
|
26
|
+
return `node:${id}`;
|
|
27
|
+
}
|
|
28
|
+
/** The record key of every delta for the edge `id`. Nodes and edges are separate id spaces. */
|
|
29
|
+
export function edgeKey(id) {
|
|
30
|
+
return `edge:${id}`;
|
|
31
|
+
}
|
|
32
|
+
/** Thrown where an element is built, or when a result is returned, for something the merge sink would refuse. */
|
|
33
|
+
export class GraphError extends Error {
|
|
34
|
+
why;
|
|
35
|
+
constructor(why, message) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = "GraphError";
|
|
38
|
+
this.why = why;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
42
|
+
const RESERVED = new Set(["id", "_version", "_deleted"]);
|
|
43
|
+
const INT64_MIN = -(2n ** 63n);
|
|
44
|
+
const INT64_MAX = 2n ** 63n - 1n;
|
|
45
|
+
function show(value) {
|
|
46
|
+
if (typeof value === "bigint")
|
|
47
|
+
return `${value}n`;
|
|
48
|
+
if (typeof value === "number")
|
|
49
|
+
return String(value);
|
|
50
|
+
try {
|
|
51
|
+
return JSON.stringify(value) ?? String(value);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return String(value);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function spaceOf(element) {
|
|
58
|
+
return element.kind === "edge" || (element.kind === "tombstone" && element.element === "edge") ? "edge" : "node";
|
|
59
|
+
}
|
|
60
|
+
function describe(element) {
|
|
61
|
+
const what = element.kind === "tombstone" ? `tombstone of ${spaceOf(element)}` : element.kind;
|
|
62
|
+
return `${what} ${show(element.id)}`;
|
|
63
|
+
}
|
|
64
|
+
/** The element key: `node:<id>` or `edge:<id>`; a tombstone has the key of the element it marks. */
|
|
65
|
+
export function elementKey(element) {
|
|
66
|
+
return spaceOf(element) === "edge" ? edgeKey(element.id) : nodeKey(element.id);
|
|
67
|
+
}
|
|
68
|
+
/** The kind of one scalar. A float with a whole value is an integer, as the sink reads it. */
|
|
69
|
+
function scalarKind(who, name, value) {
|
|
70
|
+
switch (typeof value) {
|
|
71
|
+
case "string":
|
|
72
|
+
return "string";
|
|
73
|
+
case "boolean":
|
|
74
|
+
return "boolean";
|
|
75
|
+
case "bigint":
|
|
76
|
+
if (value < INT64_MIN || value > INT64_MAX)
|
|
77
|
+
throw new GraphError("integer-range", `${who}: property ${show(name)} is ${value}, which does not fit 64 bits`);
|
|
78
|
+
return "integer";
|
|
79
|
+
case "number":
|
|
80
|
+
if (!Number.isFinite(value))
|
|
81
|
+
throw new GraphError("property-value", `${who}: property ${show(name)} is ${value}, which is not a finite number`);
|
|
82
|
+
if (!Number.isInteger(value))
|
|
83
|
+
return "float";
|
|
84
|
+
if (Number.isSafeInteger(value))
|
|
85
|
+
return "integer";
|
|
86
|
+
if (value < Number(INT64_MIN) || value >= 2 ** 63)
|
|
87
|
+
throw new GraphError("integer-range", `${who}: property ${show(name)} is ${value}, a whole number that does not fit 64 bits`);
|
|
88
|
+
throw new GraphError("integer-range", `${who}: property ${show(name)} is ${value}, a whole number past 2^53 that a number cannot hold exactly; pass it as a bigint`);
|
|
89
|
+
default:
|
|
90
|
+
throw new GraphError("property-value", `${who}: property ${show(name)} is ${show(value)}; a property is a string, a number, a boolean, or a non-empty list of one of those`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function checkProperties(who, properties) {
|
|
94
|
+
if (properties === undefined)
|
|
95
|
+
return Object.freeze({});
|
|
96
|
+
if (typeof properties !== "object" || properties === null || Array.isArray(properties)) {
|
|
97
|
+
throw new GraphError("property-value", `${who}: properties must be an object of property values, not ${show(properties)}`);
|
|
98
|
+
}
|
|
99
|
+
const out = {};
|
|
100
|
+
for (const [name, value] of Object.entries(properties)) {
|
|
101
|
+
if (RESERVED.has(name))
|
|
102
|
+
throw new GraphError("reserved", `${who}: property ${show(name)} is reserved`);
|
|
103
|
+
if (Array.isArray(value)) {
|
|
104
|
+
if (value.length === 0)
|
|
105
|
+
throw new GraphError("property-value", `${who}: property ${show(name)} is an empty list; leave the property out instead`);
|
|
106
|
+
const kinds = new Set(value.map((v) => scalarKind(who, name, v)));
|
|
107
|
+
if (kinds.size > 1)
|
|
108
|
+
throw new GraphError("property-value", `${who}: property ${show(name)} is a list of more than one kind (${[...kinds].join(", ")}); a whole-number float counts as an integer`);
|
|
109
|
+
out[name] = Object.freeze([...value]);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
scalarKind(who, name, value);
|
|
113
|
+
out[name] = value;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return Object.freeze(out);
|
|
117
|
+
}
|
|
118
|
+
function checkVersion(who, version) {
|
|
119
|
+
if (typeof version === "bigint") {
|
|
120
|
+
if (version < 1n || version > INT64_MAX)
|
|
121
|
+
throw new GraphError("version", `${who}: version must be a whole number of at least 1 within 64 bits, not ${version}`);
|
|
122
|
+
return version <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(version) : version;
|
|
123
|
+
}
|
|
124
|
+
if (typeof version !== "number" || !Number.isSafeInteger(version) || version < 1) {
|
|
125
|
+
throw new GraphError("version", `${who}: version must be a whole number of at least 1 (a safe integer, or a bigint within 64 bits), not ${show(version)}`);
|
|
126
|
+
}
|
|
127
|
+
return version;
|
|
128
|
+
}
|
|
129
|
+
function checkId(who, id) {
|
|
130
|
+
if (typeof id !== "string" || id === "")
|
|
131
|
+
throw new GraphError("id", `${who}: id must be a non-empty string`);
|
|
132
|
+
return id;
|
|
133
|
+
}
|
|
134
|
+
function checkEndpoints(who, e) {
|
|
135
|
+
for (const field of ["type", "from", "to"]) {
|
|
136
|
+
if (typeof e[field] !== "string" || e[field] === "")
|
|
137
|
+
throw new GraphError("endpoints", `${who}: an edge needs a type, a from and a to; ${field} is missing or empty`);
|
|
138
|
+
}
|
|
139
|
+
if (!IDENTIFIER.test(e.type))
|
|
140
|
+
throw new GraphError("identifier", `${who}: type ${show(e.type)} is not an identifier ([A-Za-z_][A-Za-z0-9_]*)`);
|
|
141
|
+
return { type: e.type, from: e.from, to: e.to };
|
|
142
|
+
}
|
|
143
|
+
function checkLabels(who, labels) {
|
|
144
|
+
if (labels === undefined)
|
|
145
|
+
return Object.freeze([]);
|
|
146
|
+
if (!Array.isArray(labels))
|
|
147
|
+
throw new GraphError("identifier", `${who}: labels must be a list of identifiers, not ${show(labels)}`);
|
|
148
|
+
for (const label of labels) {
|
|
149
|
+
if (typeof label !== "string" || !IDENTIFIER.test(label))
|
|
150
|
+
throw new GraphError("identifier", `${who}: label ${show(label)} is not an identifier ([A-Za-z_][A-Za-z0-9_]*)`);
|
|
151
|
+
}
|
|
152
|
+
return Object.freeze([...labels]);
|
|
153
|
+
}
|
|
154
|
+
/** The element, checked against everything the merge sink would refuse of it, as a frozen value. */
|
|
155
|
+
function checked(element) {
|
|
156
|
+
const raw = element;
|
|
157
|
+
if (typeof raw !== "object" || raw === null)
|
|
158
|
+
throw new TypeError(`not a graph element: ${show(raw)}`);
|
|
159
|
+
if (raw.kind !== "node" && raw.kind !== "edge" && raw.kind !== "tombstone")
|
|
160
|
+
throw new TypeError(`not a graph element: kind is ${show(raw.kind)}`);
|
|
161
|
+
if (raw.kind === "tombstone" && raw.element !== "node" && raw.element !== "edge")
|
|
162
|
+
throw new TypeError(`not a graph element: a tombstone marks a node or an edge, not ${show(raw.element)}`);
|
|
163
|
+
const who = describe(raw);
|
|
164
|
+
const id = checkId(who, raw.id);
|
|
165
|
+
const version = raw.version === undefined ? {} : { version: checkVersion(who, raw.version) };
|
|
166
|
+
if (raw.kind === "node") {
|
|
167
|
+
return Object.freeze({ kind: "node", id, ...version, labels: checkLabels(who, raw.labels), properties: checkProperties(who, raw.properties) });
|
|
168
|
+
}
|
|
169
|
+
if (raw.kind === "edge") {
|
|
170
|
+
return Object.freeze({ kind: "edge", id, ...version, ...checkEndpoints(who, raw), properties: checkProperties(who, raw.properties) });
|
|
171
|
+
}
|
|
172
|
+
if (raw.element === "edge")
|
|
173
|
+
return Object.freeze({ kind: "tombstone", element: "edge", id, ...version, ...checkEndpoints(who, raw) });
|
|
174
|
+
return Object.freeze({ kind: "tombstone", element: "node", id, ...version });
|
|
175
|
+
}
|
|
176
|
+
/** `this.graph` in a graph consumer: builds elements, refusing where they are built what the sink would refuse. */
|
|
177
|
+
export class Graph {
|
|
178
|
+
node(id, options = {}) {
|
|
179
|
+
return checked({ kind: "node", id, labels: options.labels, properties: options.properties, ...(options.version !== undefined ? { version: options.version } : {}) });
|
|
180
|
+
}
|
|
181
|
+
edge(id, options) {
|
|
182
|
+
return checked({ kind: "edge", id, type: options?.type, from: options?.from, to: options?.to, properties: options?.properties, ...(options?.version !== undefined ? { version: options.version } : {}) });
|
|
183
|
+
}
|
|
184
|
+
/** Marks a node deleted. The node stays in the graph, marked, so a straggling older delta cannot bring it back. */
|
|
185
|
+
tombstoneNode(id, options = {}) {
|
|
186
|
+
return checked({ kind: "tombstone", element: "node", id, ...(options.version !== undefined ? { version: options.version } : {}) });
|
|
187
|
+
}
|
|
188
|
+
/** Marks an edge deleted; it names the edge's type and endpoints so the edge is found without a scan. */
|
|
189
|
+
tombstoneEdge(id, options) {
|
|
190
|
+
return checked({ kind: "tombstone", element: "edge", id, type: options?.type, from: options?.from, to: options?.to, ...(options?.version !== undefined ? { version: options.version } : {}) });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/** `this.effects` in a graph consumer: there is nothing here that publishes anything but elements. */
|
|
194
|
+
export class GraphEffects {
|
|
195
|
+
/** Publish these elements, each as a delta under its own key. An empty list publishes nothing. */
|
|
196
|
+
publish(elements) {
|
|
197
|
+
if (!Array.isArray(elements))
|
|
198
|
+
throw new TypeError("publish takes a list of elements");
|
|
199
|
+
return Object.freeze({ kind: "publish", elements: Object.freeze(elements.map(checked)) });
|
|
200
|
+
}
|
|
201
|
+
done() {
|
|
202
|
+
return Object.freeze({ kind: "done" });
|
|
203
|
+
}
|
|
204
|
+
ignore() {
|
|
205
|
+
return Object.freeze({ kind: "ignore" });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// ── writing ──
|
|
209
|
+
function writeScalar(value) {
|
|
210
|
+
switch (typeof value) {
|
|
211
|
+
case "string":
|
|
212
|
+
return JSON.stringify(value);
|
|
213
|
+
case "boolean":
|
|
214
|
+
return value ? "true" : "false";
|
|
215
|
+
case "bigint":
|
|
216
|
+
return value.toString();
|
|
217
|
+
default:
|
|
218
|
+
return Number.isInteger(value) ? String(value) : renderDouble(value);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function writeProperties(properties) {
|
|
222
|
+
const fields = Object.entries(properties).map(([name, value]) => `${JSON.stringify(name)}:${Array.isArray(value) ? `[${value.map(writeScalar).join(",")}]` : writeScalar(value)}`);
|
|
223
|
+
return `{${fields.join(",")}}`;
|
|
224
|
+
}
|
|
225
|
+
/** The delta as the contract writes it. `labels` and `properties` are always written. */
|
|
226
|
+
function writeDelta(delta) {
|
|
227
|
+
const id = JSON.stringify(delta.id);
|
|
228
|
+
const version = delta.version.toString();
|
|
229
|
+
switch (delta.kind) {
|
|
230
|
+
case "node":
|
|
231
|
+
return `{"kind":"node","id":${id},"version":${version},"labels":[${delta.labels.map((l) => JSON.stringify(l)).join(",")}],"properties":${writeProperties(delta.properties)}}`;
|
|
232
|
+
case "edge":
|
|
233
|
+
return `{"kind":"edge","id":${id},"version":${version},"type":${JSON.stringify(delta.type)},"from":${JSON.stringify(delta.from)},"to":${JSON.stringify(delta.to)},"properties":${writeProperties(delta.properties)}}`;
|
|
234
|
+
default:
|
|
235
|
+
return delta.element === "edge"
|
|
236
|
+
? `{"kind":"tombstone","element":"edge","id":${id},"version":${version},"type":${JSON.stringify(delta.type)},"from":${JSON.stringify(delta.from)},"to":${JSON.stringify(delta.to)}}`
|
|
237
|
+
: `{"kind":"tombstone","element":"node","id":${id},"version":${version}}`;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// ── reading ──
|
|
241
|
+
const utf8 = new TextEncoder();
|
|
242
|
+
const utf8Decoder = new TextDecoder("utf-8", { fatal: true });
|
|
243
|
+
function readScalar(name, raw) {
|
|
244
|
+
const refuse = () => {
|
|
245
|
+
throw new Error(`property '${name}' is not a scalar or array of scalars`);
|
|
246
|
+
};
|
|
247
|
+
switch (typeof raw) {
|
|
248
|
+
case "string":
|
|
249
|
+
return { kind: "string", value: raw };
|
|
250
|
+
case "boolean":
|
|
251
|
+
return { kind: "boolean", value: raw };
|
|
252
|
+
case "bigint":
|
|
253
|
+
return raw < INT64_MIN || raw > INT64_MAX ? refuse() : { kind: "integer", value: raw };
|
|
254
|
+
case "number":
|
|
255
|
+
if (!Number.isInteger(raw))
|
|
256
|
+
return { kind: "float", value: raw };
|
|
257
|
+
if (Number.isSafeInteger(raw))
|
|
258
|
+
return { kind: "integer", value: raw };
|
|
259
|
+
// A whole number written with a fraction or an exponent: an integer if it fits 64 bits.
|
|
260
|
+
return raw < Number(INT64_MIN) || raw >= 2 ** 63 ? refuse() : { kind: "integer", value: BigInt(raw) };
|
|
261
|
+
default:
|
|
262
|
+
return refuse();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
function readProperties(raw) {
|
|
266
|
+
if (raw === undefined)
|
|
267
|
+
return Object.freeze({});
|
|
268
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
269
|
+
throw new Error("properties must be an object");
|
|
270
|
+
const out = {};
|
|
271
|
+
for (const [name, value] of Object.entries(raw)) {
|
|
272
|
+
if (RESERVED.has(name))
|
|
273
|
+
throw new Error(`property '${name}' is reserved`);
|
|
274
|
+
if (Array.isArray(value)) {
|
|
275
|
+
const read = value.map((v) => readScalar(name, v));
|
|
276
|
+
if (read.length === 0 || new Set(read.map((r) => r.kind)).size > 1)
|
|
277
|
+
throw new Error(`property '${name}' is not a scalar or array of scalars`);
|
|
278
|
+
out[name] = Object.freeze(read.map((r) => r.value));
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
out[name] = readScalar(name, value).value;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return Object.freeze(out);
|
|
285
|
+
}
|
|
286
|
+
function readVersion(raw) {
|
|
287
|
+
if (typeof raw === "bigint" && raw >= 0n && raw <= INT64_MAX)
|
|
288
|
+
return raw;
|
|
289
|
+
if (typeof raw === "number" && Number.isInteger(raw) && raw >= 0) {
|
|
290
|
+
if (Number.isSafeInteger(raw))
|
|
291
|
+
return raw;
|
|
292
|
+
if (raw < 2 ** 63)
|
|
293
|
+
return BigInt(raw);
|
|
294
|
+
}
|
|
295
|
+
throw new Error("version is not a non-negative integer");
|
|
296
|
+
}
|
|
297
|
+
function readEndpoints(o, problem) {
|
|
298
|
+
const text = (v) => typeof v === "string" && v !== "";
|
|
299
|
+
if (!text(o.type) || !IDENTIFIER.test(o.type) || !text(o.from) || !text(o.to))
|
|
300
|
+
throw new Error(problem);
|
|
301
|
+
return { type: o.type, from: o.from, to: o.to };
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Reads a delta's value back into an element, for tests and for a consumer of a delta topic. Given a
|
|
305
|
+
* `key` it also holds the contract's rule that the key is the delta's element key. It accepts a
|
|
306
|
+
* version of 0, which the contract allows and the builder does not write. An integer past 2⁵³ comes
|
|
307
|
+
* back as a `bigint`.
|
|
308
|
+
*/
|
|
309
|
+
export function readDelta(value, key) {
|
|
310
|
+
let raw;
|
|
311
|
+
try {
|
|
312
|
+
raw = JSON.parse(utf8Decoder.decode(value), reviver);
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
throw new Error("not a JSON object");
|
|
316
|
+
}
|
|
317
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
318
|
+
throw new Error("not a JSON object");
|
|
319
|
+
const o = raw;
|
|
320
|
+
if (o.kind === undefined)
|
|
321
|
+
throw new Error("kind missing");
|
|
322
|
+
if (o.kind !== "node" && o.kind !== "edge" && o.kind !== "tombstone")
|
|
323
|
+
throw new Error(`unknown kind '${String(o.kind)}'`);
|
|
324
|
+
if (typeof o.id !== "string" || o.id === "")
|
|
325
|
+
throw new Error("id missing or empty");
|
|
326
|
+
const id = o.id;
|
|
327
|
+
const version = readVersion(o.version);
|
|
328
|
+
let delta;
|
|
329
|
+
if (o.kind === "node") {
|
|
330
|
+
const labels = o.labels ?? [];
|
|
331
|
+
if (!Array.isArray(labels) || labels.some((l) => typeof l !== "string" || !IDENTIFIER.test(l)))
|
|
332
|
+
throw new Error("labels must be an array of identifiers");
|
|
333
|
+
delta = Object.freeze({ kind: "node", id, version, labels: Object.freeze([...labels]), properties: readProperties(o.properties) });
|
|
334
|
+
}
|
|
335
|
+
else if (o.kind === "edge") {
|
|
336
|
+
delta = Object.freeze({ kind: "edge", id, version, ...readEndpoints(o, "edge needs type, from and to"), properties: readProperties(o.properties) });
|
|
337
|
+
}
|
|
338
|
+
else if (o.element === "node") {
|
|
339
|
+
delta = Object.freeze({ kind: "tombstone", element: "node", id, version });
|
|
340
|
+
}
|
|
341
|
+
else if (o.element === "edge") {
|
|
342
|
+
delta = Object.freeze({ kind: "tombstone", element: "edge", id, version, ...readEndpoints(o, "tombstone of an edge needs type, from and to") });
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
throw new Error("tombstone needs element 'node' or 'edge'");
|
|
346
|
+
}
|
|
347
|
+
if (key !== undefined && key !== elementKey(delta))
|
|
348
|
+
throw new Error(`key '${key}' is not this delta's element key '${elementKey(delta)}'`);
|
|
349
|
+
return delta;
|
|
350
|
+
}
|
|
351
|
+
/** The codec of a delta: JSON under the manifest `ankka.graph-delta.v1`. Hand-written, since a delta's properties have no fixed shape. */
|
|
352
|
+
export const graphDeltaCodec = Object.freeze({
|
|
353
|
+
manifest: GRAPH_DELTA_SCHEMA,
|
|
354
|
+
contentType: JSON_CONTENT,
|
|
355
|
+
encode: (delta) => utf8.encode(writeDelta(checked(delta))),
|
|
356
|
+
decode: (bytes) => readDelta(bytes),
|
|
357
|
+
});
|
|
358
|
+
/**
|
|
359
|
+
* What a graph consumer's effect publishes for the change `metadata` describes: every element at its
|
|
360
|
+
* stated version, else the change's sequence number, refusing a result that names an element twice or
|
|
361
|
+
* leaves a version to a change that has no sequence number. The server and the test kit both read an
|
|
362
|
+
* effect through this, so they cannot disagree about what a handler published.
|
|
363
|
+
*
|
|
364
|
+
* @internal
|
|
365
|
+
*/
|
|
366
|
+
export function deltaRecords(effect, metadata) {
|
|
367
|
+
if (effect.kind !== "publish")
|
|
368
|
+
return [];
|
|
369
|
+
const sequence = sequenceNumberOf(metadata);
|
|
370
|
+
const seen = new Set();
|
|
371
|
+
return effect.elements.map((given) => {
|
|
372
|
+
const element = checked(given);
|
|
373
|
+
const key = elementKey(element);
|
|
374
|
+
if (seen.has(key))
|
|
375
|
+
throw new GraphError("duplicate", `${describe(element)} is in this result twice (${key}); an element is published once for a change`);
|
|
376
|
+
seen.add(key);
|
|
377
|
+
let version = element.version;
|
|
378
|
+
if (version === undefined) {
|
|
379
|
+
if (sequence === undefined || sequence < 1n) {
|
|
380
|
+
throw new GraphError("no-sequence", `${describe(element)}: this source has no sequence number; state a version`);
|
|
381
|
+
}
|
|
382
|
+
version = checkVersion(describe(element), sequence);
|
|
383
|
+
}
|
|
384
|
+
const delta = Object.freeze({ ...element, version });
|
|
385
|
+
return Object.freeze({ key, value: utf8.encode(writeDelta(delta)), delta });
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
// ── the consumer ──
|
|
389
|
+
/**
|
|
390
|
+
* A consumer that publishes a graph. Its handlers return elements; each is published to `producesTo`
|
|
391
|
+
* as a delta under its element key. It has no `produce` and no way to set a key, and it is discovered
|
|
392
|
+
* and hosted as an ordinary consumer.
|
|
393
|
+
*/
|
|
394
|
+
export class GraphConsumer {
|
|
395
|
+
graph = new Graph();
|
|
396
|
+
effects = new GraphEffects();
|
|
397
|
+
#metadata = {};
|
|
398
|
+
#client;
|
|
399
|
+
/** The change's metadata: `ce-subject` is the source instance's id, `ankka.sequence` its sequence number. */
|
|
400
|
+
get metadata() {
|
|
401
|
+
return this.#metadata;
|
|
402
|
+
}
|
|
403
|
+
get subject() {
|
|
404
|
+
return this.#metadata["ce-subject"] ?? "";
|
|
405
|
+
}
|
|
406
|
+
/** The change's sequence number, which is the version of every element that states none. */
|
|
407
|
+
get sequenceNumber() {
|
|
408
|
+
return sequenceNumberOf(this.#metadata);
|
|
409
|
+
}
|
|
410
|
+
get client() {
|
|
411
|
+
if (!this.#client)
|
|
412
|
+
throw new Error("client is only available inside onMessage or onDelete");
|
|
413
|
+
return this.#client;
|
|
414
|
+
}
|
|
415
|
+
#secrets;
|
|
416
|
+
/** The service's secret store: values kept encrypted in the service's own database, never in a journal or a view. */
|
|
417
|
+
get secrets() {
|
|
418
|
+
return this.#secrets ?? secretsFor(this.client);
|
|
419
|
+
}
|
|
420
|
+
/** A unit test's store in place of the runtime's: `component.secrets = new InMemorySecrets()`. */
|
|
421
|
+
set secrets(store) {
|
|
422
|
+
this.#secrets = store;
|
|
423
|
+
}
|
|
424
|
+
#services;
|
|
425
|
+
/** Other services, called as this one, through the runtime: `this.services.service("orders")`. */
|
|
426
|
+
get services() {
|
|
427
|
+
return this.#services ?? servicesFor(this.client);
|
|
428
|
+
}
|
|
429
|
+
/** A unit test's in place of the runtime's: `component.services = new ScriptedServices()`. */
|
|
430
|
+
set services(services) {
|
|
431
|
+
this.#services = services;
|
|
432
|
+
}
|
|
433
|
+
/** @internal */
|
|
434
|
+
get _kind() {
|
|
435
|
+
return "consumer";
|
|
436
|
+
}
|
|
437
|
+
/** The source was deleted. Ignored unless this says otherwise; publish tombstones here. */
|
|
438
|
+
onDelete() {
|
|
439
|
+
return this.effects.ignore();
|
|
440
|
+
}
|
|
441
|
+
/** @internal */
|
|
442
|
+
_bind(metadata, client) {
|
|
443
|
+
this.#metadata = metadata;
|
|
444
|
+
this.#client = client;
|
|
445
|
+
}
|
|
446
|
+
}
|
package/dist/handlers.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Approval } from "./approvals.ts";
|
|
1
2
|
import type { Shape } from "./codec.ts";
|
|
2
3
|
import type { EffectLike, ReadOnlyLike } from "./effects/common.ts";
|
|
3
4
|
export type HandlerKind = "command" | "query" | "stream" | "step" | "action";
|
|
@@ -35,14 +36,21 @@ export declare function step<C, E extends EffectLike>(name: string, run: (self:
|
|
|
35
36
|
/** A timed action: `action("remind", s.string, (t, id) => t.remind(id))`. */
|
|
36
37
|
export declare function action<C, I, E extends EffectLike>(name: string, input: Shape<I>, run: (self: C, input: I) => MaybePromise<E>): HandlerRef<C, I, undefined, E>;
|
|
37
38
|
export declare function action<C, E extends EffectLike>(name: string, run: (self: C) => MaybePromise<E>): HandlerRef<C, undefined, undefined, E>;
|
|
38
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* An agent tool: the model sees `description` and the JSON Schema of `input`; the process runs `run`.
|
|
41
|
+
* With `approval`, the tool waits for a person's decision before it runs: the caller is answered with an
|
|
42
|
+
* approval request, and the turn goes on when someone decides it.
|
|
43
|
+
*/
|
|
39
44
|
export interface ToolRef<C = unknown, I = unknown> {
|
|
40
45
|
readonly name: string;
|
|
41
46
|
readonly description: string;
|
|
42
47
|
readonly input: Shape<I>;
|
|
43
48
|
readonly run: (self: C, input: I) => MaybePromise<unknown>;
|
|
49
|
+
readonly approval?: Approval;
|
|
44
50
|
}
|
|
45
|
-
export declare function tool<C, I>(name: string, description: string, input: Shape<I>, run: (self: C, input: I) => MaybePromise<unknown
|
|
51
|
+
export declare function tool<C, I>(name: string, description: string, input: Shape<I>, run: (self: C, input: I) => MaybePromise<unknown>, options?: {
|
|
52
|
+
readonly approval?: Approval;
|
|
53
|
+
}): ToolRef<C, I>;
|
|
46
54
|
export type GuardrailStage = "input" | "output";
|
|
47
55
|
/** An agent guardrail: returns a reason to block, or `null` to let the text through. */
|
|
48
56
|
export interface GuardrailRef {
|
package/dist/handlers.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// and `guardrails` tables. Each carries the wire name, the input and reply schemas, and the function
|
|
3
3
|
// that runs it. The wire name is declared here and nowhere else; renaming the method it calls changes
|
|
4
4
|
// nothing on the wire. These are plain values, so the registry can read them without reflection.
|
|
5
|
+
import { approvalProblem } from "./approvals.js";
|
|
5
6
|
function ref(kind, name, input, reply, run, flags) {
|
|
6
7
|
if (typeof name !== "string" || name.trim() === "")
|
|
7
8
|
throw new TypeError(`a ${kind} needs a wire name`);
|
|
@@ -49,13 +50,16 @@ export function action(name, ...rest) {
|
|
|
49
50
|
const [run] = rest;
|
|
50
51
|
return ref("action", name, undefined, undefined, run, { readOnly: false, streaming: false });
|
|
51
52
|
}
|
|
52
|
-
export function tool(name, description, input, run) {
|
|
53
|
+
export function tool(name, description, input, run, options = {}) {
|
|
53
54
|
if (typeof name !== "string" || name.trim() === "")
|
|
54
55
|
throw new TypeError("a tool needs a name");
|
|
55
56
|
if (typeof description !== "string" || description.trim() === "") {
|
|
56
57
|
throw new TypeError(`tool ${name}: a tool needs a description, or the model cannot choose it`);
|
|
57
58
|
}
|
|
58
|
-
|
|
59
|
+
const problem = approvalProblem(options.approval);
|
|
60
|
+
if (problem)
|
|
61
|
+
throw new TypeError(`tool ${name}: ${problem}`);
|
|
62
|
+
return Object.freeze({ name, description, input, run, ...(options.approval !== undefined ? { approval: options.approval } : {}) });
|
|
59
63
|
}
|
|
60
64
|
export function guardrail(name, check) {
|
|
61
65
|
if (typeof name !== "string" || name.trim() === "")
|
package/dist/index.d.ts
CHANGED
|
@@ -11,20 +11,29 @@ export { UpdateBuilder, KeyValueEffects, type UpdateEffect, type KeyValueReadOnl
|
|
|
11
11
|
export { KeyValueEntity, type KeyValueEntityClass } from "./keyValueEntity.ts";
|
|
12
12
|
export { WorkflowEffects, StepEffects, WorkflowUpdateBuilder, StepUpdateBuilder, workflowSettings, type WorkflowEffect, type WorkflowReadOnlyEffect, type WorkflowCommandEffect, type StepEffect, type StepOutcome, type StepRef, type PauseOptions, type WorkflowSettings, type StepSettings, type Recovery, } from "./effects/workflow.ts";
|
|
13
13
|
export { Workflow, type WorkflowClass } from "./workflow.ts";
|
|
14
|
-
export { ViewEffects, ConsumerEffects, TimedActionEffects, type ViewEffect, type ConsumerEffect, type TimedActionEffect } from "./effects/stateless.ts";
|
|
15
|
-
export { View, type ViewClass } from "./view.ts";
|
|
16
|
-
export {
|
|
14
|
+
export { ViewEffects, ConsumerEffects, TimedActionEffects, type ViewEffect, type ConsumerEffect, type OutgoingMessage, type TimedActionEffect } from "./effects/stateless.ts";
|
|
15
|
+
export { View, declaredQuery, tableOf, type DeclaredQuery, type ViewClass } from "./view.ts";
|
|
16
|
+
export { KeyedView, on, type KeyedSource, type KeyedViewClass, type ViewRows } from "./keyedView.ts";
|
|
17
|
+
export { KeyedViewEffects, type KeyedViewEffect, type RowChange } from "./effects/keyed.ts";
|
|
18
|
+
export { Consumer, ProtocolVersionError, type ConsumerClass } from "./consumer.ts";
|
|
19
|
+
export { Contract, CONTRACT_NAME_RULE, fingerprintOf, type Publication } from "./contract.ts";
|
|
20
|
+
export { GRAPH_DELTA_SCHEMA, Graph, GraphConsumer, GraphEffects, GraphError, edgeKey, elementKey, graphDeltaCodec, nodeKey, readDelta, type Delta, type Element, type GraphConsumerClass, type GraphEffect, type GraphFault, type Properties, type PropertyValue, type Scalar, type Version, } from "./graph.ts";
|
|
17
21
|
export { TimedAction, type TimedActionClass } from "./timedAction.ts";
|
|
18
22
|
export { AgentEffects, type AgentEffect } from "./effects/agent.ts";
|
|
19
23
|
export { Agent, type AgentClass } from "./agent.ts";
|
|
24
|
+
export { ApprovalAwaited, APPROVALS_SINCE, type AgentOutcome, type Approval, type ApprovalRequest, type DecisionInput } from "./approvals.ts";
|
|
25
|
+
export { mcpServer, resultGuardrail, toolName, type McpServerOptions, type McpServerRef, type ResultGuardrailRef } from "./mcp.ts";
|
|
20
26
|
export { AutonomousAgent, AutonomousAgentCalls, TaskCalls, Tasks, accepted, rejected, taskAcceptance, taskRule, taskType, type Accepted, type AgentState, type Attachment, type AutonomousAgentClass, type AutonomousSettings, type Notification, type Rejected, type TaskAcceptance, type TaskRule, type TaskSnapshot, type TaskType, } from "./autonomous.ts";
|
|
21
27
|
export { Endpoint, type EndpointClass } from "./endpoint.ts";
|
|
22
|
-
export {
|
|
28
|
+
export { type Socket, SocketClosed } from "./socket.ts";
|
|
29
|
+
export { Acl, Callers, type CallerMatcher, type CallersAcl, HttpProblem, get, post, put, patch, del, sse, sseEvent, type SseEvent, socket, type RouteRef, type RouteOptions, type RouteTable, type Params, type ParamNames, type HttpMethod } from "./routes.ts";
|
|
23
30
|
export { type CommandContext, type RequestContext, type Principal, type Caller, Query, Headers } from "./context.ts";
|
|
24
|
-
export { ComponentClient, Calls, TypedCalls, Invocation, Views, Timers, type TimerTarget, type ComponentRef } from "./client.ts";
|
|
25
|
-
export {
|
|
31
|
+
export { ComponentClient, Calls, TypedCalls, Invocation, Views, Timers, Secrets, InMemorySecrets, noSecrets, type TimerTarget, type TimedActionTarget, type ComponentRef } from "./client.ts";
|
|
32
|
+
export { Services, ServiceClient, ScriptedServices, noServices, ServiceError, ServiceUnresolvable, ServiceIdentityMismatch, ServiceUnanswered, ServiceCallFailed, type ServiceResponse, type ServiceRequest, type ServiceHeaders, } from "./services.ts";
|
|
33
|
+
export { Ankka, ServiceBuilder, Registry, RegistrationError, type ServiceOptions, type RegisteredComponent, type RegisteredEndpoint, type Source, type RegisteredEventSourced, type RegisteredKeyValue, type RegisteredWorkflow, type RegisteredView, type RegisteredKeyedView, type RegisteredKeyedSource, type RegisteredConsumer, type RegisteredTimedAction, type RegisteredAgent, } from "./service.ts";
|
|
26
34
|
export { Server, type ServerOptions } from "./server/server.ts";
|
|
27
35
|
export { problems as sidecarProblems } from "./server/discovery.ts";
|
|
28
36
|
export { PROTOCOL_VERSION, SDK_NAME } from "./spec.ts";
|
|
37
|
+
export { StartFrom } from "./startFrom.ts";
|
|
29
38
|
export { materialiseEventSourced, materialiseKeyValue, materialiseWorkflowCommand, materialiseStep, type Materialised, type MaterialisedKeyValue, type MaterialisedWorkflowCommand, type MaterialisedStep, } from "./materialise.ts";
|
|
30
39
|
export type { ComponentKind } from "./kinds.ts";
|
package/dist/index.js
CHANGED
|
@@ -23,18 +23,27 @@ export { KeyValueEntity } from "./keyValueEntity.js";
|
|
|
23
23
|
export { WorkflowEffects, StepEffects, WorkflowUpdateBuilder, StepUpdateBuilder, workflowSettings, } from "./effects/workflow.js";
|
|
24
24
|
export { Workflow } from "./workflow.js";
|
|
25
25
|
export { ViewEffects, ConsumerEffects, TimedActionEffects } from "./effects/stateless.js";
|
|
26
|
-
export { View } from "./view.js";
|
|
27
|
-
export {
|
|
26
|
+
export { View, declaredQuery, tableOf } from "./view.js";
|
|
27
|
+
export { KeyedView, on } from "./keyedView.js";
|
|
28
|
+
export { KeyedViewEffects } from "./effects/keyed.js";
|
|
29
|
+
export { Consumer, ProtocolVersionError } from "./consumer.js";
|
|
30
|
+
export { Contract, CONTRACT_NAME_RULE, fingerprintOf } from "./contract.js";
|
|
31
|
+
export { GRAPH_DELTA_SCHEMA, Graph, GraphConsumer, GraphEffects, GraphError, edgeKey, elementKey, graphDeltaCodec, nodeKey, readDelta, } from "./graph.js";
|
|
28
32
|
export { TimedAction } from "./timedAction.js";
|
|
29
33
|
export { AgentEffects } from "./effects/agent.js";
|
|
30
34
|
export { Agent } from "./agent.js";
|
|
35
|
+
export { ApprovalAwaited, APPROVALS_SINCE } from "./approvals.js";
|
|
36
|
+
export { mcpServer, resultGuardrail, toolName } from "./mcp.js";
|
|
31
37
|
export { AutonomousAgent, AutonomousAgentCalls, TaskCalls, Tasks, accepted, rejected, taskAcceptance, taskRule, taskType, } from "./autonomous.js";
|
|
32
38
|
export { Endpoint } from "./endpoint.js";
|
|
33
|
-
export {
|
|
39
|
+
export { SocketClosed } from "./socket.js";
|
|
40
|
+
export { Acl, Callers, HttpProblem, get, post, put, patch, del, sse, sseEvent, socket } from "./routes.js";
|
|
34
41
|
export { Query, Headers } from "./context.js";
|
|
35
|
-
export { ComponentClient, Calls, TypedCalls, Invocation, Views, Timers } from "./client.js";
|
|
42
|
+
export { ComponentClient, Calls, TypedCalls, Invocation, Views, Timers, Secrets, InMemorySecrets, noSecrets } from "./client.js";
|
|
43
|
+
export { Services, ServiceClient, ScriptedServices, noServices, ServiceError, ServiceUnresolvable, ServiceIdentityMismatch, ServiceUnanswered, ServiceCallFailed, } from "./services.js";
|
|
36
44
|
export { Ankka, ServiceBuilder, Registry, RegistrationError, } from "./service.js";
|
|
37
45
|
export { Server } from "./server/server.js";
|
|
38
46
|
export { problems as sidecarProblems } from "./server/discovery.js";
|
|
39
47
|
export { PROTOCOL_VERSION, SDK_NAME } from "./spec.js";
|
|
48
|
+
export { StartFrom } from "./startFrom.js";
|
|
40
49
|
export { materialiseEventSourced, materialiseKeyValue, materialiseWorkflowCommand, materialiseStep, } from "./materialise.js";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Shape } from "./codec.ts";
|
|
2
|
+
import type { Metadata } from "./effects/common.ts";
|
|
3
|
+
import type { ComponentClient, ComponentRef } from "./client.ts";
|
|
4
|
+
import { KeyedViewEffects, type KeyedViewEffect } from "./effects/keyed.ts";
|
|
5
|
+
import type { DeclaredQuery } from "./view.ts";
|
|
6
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
7
|
+
/** A keyed view's own rows, for the length of one change. It reaches no other view. */
|
|
8
|
+
export interface ViewRows<Row> {
|
|
9
|
+
/** The row under `key`, or `null`. */
|
|
10
|
+
get(key: string): Promise<Row | null>;
|
|
11
|
+
/** The rows of one of this view's declared queries, asked with its values. */
|
|
12
|
+
ask(name: string, values?: Readonly<Record<string, string>>): Promise<Row[]>;
|
|
13
|
+
}
|
|
14
|
+
/** One source of a keyed view, and what the view does with each of its changes and with its entity's deletion. */
|
|
15
|
+
export interface KeyedSource<V = any, E = any> {
|
|
16
|
+
readonly source: ComponentRef;
|
|
17
|
+
readonly events: Shape<E>;
|
|
18
|
+
readonly onChange: (view: V, event: E) => MaybePromise<KeyedViewEffect<any>>;
|
|
19
|
+
readonly deleted?: (view: V) => MaybePromise<KeyedViewEffect<any>>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Declares a source of a keyed view: the entity whose changes it reads, the shape of those changes, and
|
|
23
|
+
* the handler for each. When the entity is deleted the view does nothing unless `deleted` names rows.
|
|
24
|
+
*/
|
|
25
|
+
export declare function on<V, E>(source: ComponentRef, events: Shape<E>, onChange: (view: V, event: E) => MaybePromise<KeyedViewEffect<any>>, options?: {
|
|
26
|
+
readonly deleted?: (view: V) => MaybePromise<KeyedViewEffect<any>>;
|
|
27
|
+
}): KeyedSource<V, E>;
|
|
28
|
+
export declare abstract class KeyedView<Row> {
|
|
29
|
+
#private;
|
|
30
|
+
readonly effects: KeyedViewEffects<Row>;
|
|
31
|
+
/** The change's metadata: `ce-subject` is the source entity's id, `ankka.sequence` its sequence number. */
|
|
32
|
+
get metadata(): Metadata;
|
|
33
|
+
/** The id of the entity the change came from. */
|
|
34
|
+
get subject(): string;
|
|
35
|
+
/** This view's own rows, by key or by one of its declared queries. */
|
|
36
|
+
get rows(): ViewRows<Row>;
|
|
37
|
+
get client(): ComponentClient;
|
|
38
|
+
/** @internal */
|
|
39
|
+
get _kind(): "view";
|
|
40
|
+
/** @internal */
|
|
41
|
+
_bind(metadata: Metadata, client: ComponentClient | undefined, rows: ViewRows<Row>): void;
|
|
42
|
+
}
|
|
43
|
+
export interface KeyedViewClass<Row = unknown, C extends KeyedView<Row> = KeyedView<Row>> {
|
|
44
|
+
new (): C;
|
|
45
|
+
readonly componentId: string;
|
|
46
|
+
readonly row: Shape<Row>;
|
|
47
|
+
/** One per entity the view reads, declared with `on(...)`. */
|
|
48
|
+
readonly sources: readonly KeyedSource<C, any>[];
|
|
49
|
+
/** Raised to have the view emptied and every source read again from its beginning. Absent is 1. */
|
|
50
|
+
readonly version?: number;
|
|
51
|
+
/** The queries the view can be asked by name, by its own handlers among others. */
|
|
52
|
+
readonly declared?: readonly DeclaredQuery[];
|
|
53
|
+
}
|
|
54
|
+
/** The rows a keyed view reads through the component client, on its own id. */
|
|
55
|
+
export declare function clientRows<Row>(client: ComponentClient, viewId: string, row: Shape<Row>): ViewRows<Row>;
|
|
56
|
+
export {};
|