ankka 0.11.0 → 0.12.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/README.md +1 -0
- package/dist/_proto/ankka/protocol/v1/client_pb.d.ts +316 -0
- package/dist/_proto/ankka/protocol/v1/client_pb.js +97 -23
- package/dist/_proto/ankka/protocol/v1/consumer_pb.d.ts +10 -1
- package/dist/_proto/ankka/protocol/v1/consumer_pb.js +1 -1
- package/dist/_proto/ankka/protocol/v1/discovery_pb.d.ts +43 -0
- package/dist/_proto/ankka/protocol/v1/discovery_pb.js +1 -1
- package/dist/_proto/ankka/protocol/v1/endpoint_pb.d.ts +26 -0
- package/dist/_proto/ankka/protocol/v1/endpoint_pb.js +15 -10
- package/dist/_proto/ankka/protocol/v1/event_sourced_pb.d.ts +86 -0
- package/dist/_proto/ankka/protocol/v1/event_sourced_pb.js +11 -1
- package/dist/_proto/ankka/protocol/v1/payload_pb.d.ts +61 -0
- package/dist/_proto/ankka/protocol/v1/payload_pb.js +18 -1
- package/dist/_proto/ankka/protocol/v1/view_pb.d.ts +7 -1
- package/dist/_proto/ankka/protocol/v1/view_pb.js +1 -1
- package/dist/_proto/ankka/protocol/v1/wasm_pb.d.ts +12 -0
- package/dist/_proto/ankka/protocol/v1/wasm_pb.js +1 -1
- package/dist/client.d.ts +96 -3
- package/dist/client.js +228 -6
- package/dist/consumer.d.ts +18 -1
- package/dist/consumer.js +15 -1
- package/dist/context.d.ts +25 -1
- package/dist/context.js +5 -2
- package/dist/contract.d.ts +4 -0
- package/dist/contract.js +2 -0
- package/dist/effects/common.d.ts +7 -0
- package/dist/effects/common.js +9 -1
- package/dist/effects/eventSourced.d.ts +26 -1
- package/dist/effects/eventSourced.js +58 -5
- package/dist/eventSourcedEntity.d.ts +8 -3
- package/dist/graph.d.ts +9 -1
- package/dist/graph.js +7 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +3 -2
- package/dist/keyedView.d.ts +4 -1
- package/dist/keyedView.js +7 -1
- package/dist/kinds.js +2 -0
- package/dist/routes.d.ts +21 -1
- package/dist/routes.js +27 -1
- package/dist/server/discovery.d.ts +5 -0
- package/dist/server/discovery.js +90 -3
- package/dist/server/eventSourced.d.ts +4 -1
- package/dist/server/eventSourced.js +24 -6
- package/dist/server/http.js +2 -0
- package/dist/server/stateless.js +5 -4
- package/dist/service.d.ts +6 -0
- package/dist/service.js +26 -1
- package/dist/spec.d.ts +1 -1
- package/dist/spec.js +10 -4
- package/dist/standing.d.ts +18 -0
- package/dist/standing.js +26 -0
- package/dist/startFrom.d.ts +4 -0
- package/dist/startFrom.js +8 -1
- package/dist/testkit/index.d.ts +1 -1
- package/dist/testkit/kinds.d.ts +9 -5
- package/dist/testkit/kinds.js +17 -14
- package/dist/testkit/unit.d.ts +27 -2
- package/dist/testkit/unit.js +47 -7
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/view.d.ts +18 -3
- package/dist/view.js +13 -4
- package/package.json +1 -1
package/dist/testkit/unit.js
CHANGED
|
@@ -7,7 +7,8 @@ import { create } from "@bufbuild/protobuf";
|
|
|
7
7
|
import { HttpRequestSchema, SocketInSchema } from "../_proto/ankka/protocol/v1/endpoint_pb.js";
|
|
8
8
|
import { ComponentClient, noClient } from "../client.js";
|
|
9
9
|
import { binaryCodecs, codecFor } from "../codec.js";
|
|
10
|
-
import { commandContext } from "../context.js";
|
|
10
|
+
import { commandContext, eventContext } from "../context.js";
|
|
11
|
+
import { ErrorCode } from "../effects/common.js";
|
|
11
12
|
import { EventSourcedEntity } from "../eventSourcedEntity.js";
|
|
12
13
|
import { materialiseEventSourced } from "../materialise.js";
|
|
13
14
|
import { Ankka } from "../service.js";
|
|
@@ -22,19 +23,24 @@ function findHandler(handlers, handler, who) {
|
|
|
22
23
|
throw new Error(`${who} has no handler ${JSON.stringify(name)}; declared: ${[...handlers.keys()].sort().join(", ")}`);
|
|
23
24
|
return found;
|
|
24
25
|
}
|
|
25
|
-
/** Drives one event sourced entity instance through its handlers, folding events as it goes. */
|
|
26
26
|
export class EventSourcedTestKit {
|
|
27
27
|
#registered;
|
|
28
28
|
#entity;
|
|
29
29
|
#client;
|
|
30
|
+
#mode;
|
|
31
|
+
#here;
|
|
30
32
|
entityId;
|
|
33
|
+
/** The handover or change to the replication set the last call asked for, if any. */
|
|
34
|
+
lastReplication = undefined;
|
|
31
35
|
/** The state now, after every call so far. */
|
|
32
36
|
state;
|
|
33
37
|
/** The sequence number of the last event, `0n` before any. */
|
|
34
38
|
sequence = 0n;
|
|
35
39
|
/** Every event persisted so far, in order. */
|
|
36
40
|
allEvents = [];
|
|
37
|
-
constructor(cls, entityId, client) {
|
|
41
|
+
constructor(cls, entityId, client, mode = { kind: "unreplicated" }) {
|
|
42
|
+
this.#mode = mode;
|
|
43
|
+
this.#here = mode.kind === "unreplicated" ? null : (mode.installation ?? "test");
|
|
38
44
|
const registry = Ankka.service({ client, log: () => { } })
|
|
39
45
|
.register(cls)
|
|
40
46
|
.validate();
|
|
@@ -46,15 +52,36 @@ export class EventSourcedTestKit {
|
|
|
46
52
|
this.state = this.#entity.emptyState();
|
|
47
53
|
}
|
|
48
54
|
/** A kit for `cls` as instance `entityId`. Calls to other components reach `client`, which by default refuses them. */
|
|
49
|
-
static of(cls, entityId = "test", client = noClient()
|
|
50
|
-
|
|
55
|
+
static of(cls, entityId = "test", client = noClient(),
|
|
56
|
+
/**
|
|
57
|
+
* How a replicated entity's copy stands (1.19): `{ kind: "home" }`, where every command runs, or
|
|
58
|
+
* `{ kind: "follower", home }`, where a command that may persist is refused `NOT_HOME` and a query is
|
|
59
|
+
* answered from what `replay` gave it. Not replicated by default.
|
|
60
|
+
*/
|
|
61
|
+
mode = { kind: "unreplicated" }) {
|
|
62
|
+
return new EventSourcedTestKit(cls, entityId, client, mode);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Applies `events` as history the copy already holds, written at `writtenAt` (this installation by
|
|
66
|
+
* default): for a follower, what it has mirrored from its home.
|
|
67
|
+
*/
|
|
68
|
+
replay(events, writtenAt = this.#here) {
|
|
69
|
+
for (const event of events) {
|
|
70
|
+
this.sequence += 1n;
|
|
71
|
+
this.state = this.#entity.applyEvent(this.state, event, eventContext(writtenAt, writtenAt === this.#here, this.sequence));
|
|
72
|
+
}
|
|
51
73
|
}
|
|
52
74
|
/** Sends a command or query and returns what its effect amounts to. */
|
|
53
75
|
async call(handler, input, metadata = {}) {
|
|
54
76
|
const ref = findHandler(this.#registered.handlers, handler, this.#registered.cls.name);
|
|
77
|
+
if (this.#mode.kind === "follower" && !ref.readOnly) {
|
|
78
|
+
// The runtime forwards such a command to the home; here there is no home to forward to.
|
|
79
|
+
const error = { message: `the home of '${this.entityId}' is '${this.#mode.home}'; a follower handles no command that may persist`, code: ErrorCode.NotHome };
|
|
80
|
+
return Object.freeze({ events: [], newState: this.state, retention: null, reply: undefined, error, noReply: false });
|
|
81
|
+
}
|
|
55
82
|
const inputCodec = ref.input ? codecFor(ref.input) : undefined;
|
|
56
83
|
const wireInput = inputCodec ? roundTrip(inputCodec, input) : undefined;
|
|
57
|
-
const context = commandContext(this.#registered.id, this.entityId, this.sequence, metadata);
|
|
84
|
+
const context = commandContext(this.#registered.id, this.entityId, this.sequence, metadata, undefined, this.#here, this.#mode.kind !== "follower");
|
|
58
85
|
this.#entity._bindCommand(this.state, context, this.#client.withMetadata(metadata));
|
|
59
86
|
let effect;
|
|
60
87
|
try {
|
|
@@ -65,7 +92,20 @@ export class EventSourcedTestKit {
|
|
|
65
92
|
}
|
|
66
93
|
if (ref.readOnly && effect.kind !== "read-only")
|
|
67
94
|
throw new Error(`${this.#registered.id}/${ref.name} is a query and returned a persisting effect`);
|
|
68
|
-
|
|
95
|
+
let applied = this.sequence;
|
|
96
|
+
const m = materialiseEventSourced(effect, this.state, (s, e) => {
|
|
97
|
+
applied += 1n;
|
|
98
|
+
return this.#entity.applyEvent(s, e, eventContext(this.#here, true, applied));
|
|
99
|
+
});
|
|
100
|
+
const replication = effect.kind === "persist" ? effect.replication : undefined;
|
|
101
|
+
if (!m.error && replication && this.#mode.kind === "unreplicated") {
|
|
102
|
+
const error = {
|
|
103
|
+
message: `${this.#registered.id} is not replicated in this test kit; drive it with { kind: "home" } to see the handover or the change to the replication set it asks for`,
|
|
104
|
+
code: ErrorCode.BadRequest,
|
|
105
|
+
};
|
|
106
|
+
return Object.freeze({ events: [], newState: this.state, retention: null, reply: undefined, error, noReply: false });
|
|
107
|
+
}
|
|
108
|
+
this.lastReplication = m.error ? undefined : replication;
|
|
69
109
|
const eventCodec = this.#registered.eventCodec;
|
|
70
110
|
const stateCodec = this.#registered.stateCodec;
|
|
71
111
|
const events = m.events.map((e) => roundTrip(eventCodec, e));
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.
|
|
1
|
+
export declare const VERSION = "0.12.0";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Written by scripts/proto.ts from package.json; do not edit. 0.0.0 in the tree, the tag's version in a release.
|
|
2
|
-
export const VERSION = "0.
|
|
2
|
+
export const VERSION = "0.12.0";
|
package/dist/view.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { Metadata } from "./effects/common.ts";
|
|
|
5
5
|
import type { ComponentClient } from "./client.ts";
|
|
6
6
|
import type { ComponentRef } from "./client.ts";
|
|
7
7
|
import { ViewEffects, type ViewEffect } from "./effects/stateless.ts";
|
|
8
|
+
import type { Standing } from "./standing.ts";
|
|
8
9
|
type MaybePromise<T> = T | Promise<T>;
|
|
9
10
|
/**
|
|
10
11
|
* A question a view can be asked by name: one SQL statement over the view's own table, whose values
|
|
@@ -16,9 +17,16 @@ type MaybePromise<T> = T | Promise<T>;
|
|
|
16
17
|
export interface DeclaredQuery {
|
|
17
18
|
readonly name: string;
|
|
18
19
|
readonly statement: string;
|
|
20
|
+
/** Whether it may be watched: its statement selects `row_key` beside `payload`, with no limit and no aggregate. */
|
|
21
|
+
readonly watched?: boolean;
|
|
19
22
|
}
|
|
20
|
-
/**
|
|
21
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Declares a query for a view's `static declared`. The SDK sends the statement as written.
|
|
25
|
+
* `{ watched: true }` declares that it may also be watched, which the platform checks at start.
|
|
26
|
+
*/
|
|
27
|
+
export declare function declaredQuery(name: string, statement: string, options?: {
|
|
28
|
+
readonly watched?: boolean;
|
|
29
|
+
}): DeclaredQuery;
|
|
22
30
|
/** The table a view's rows are kept in, for a declared query's statement to name. */
|
|
23
31
|
export declare function tableOf(componentId: string): string;
|
|
24
32
|
export declare abstract class View<E, Row> {
|
|
@@ -30,6 +38,8 @@ export declare abstract class View<E, Row> {
|
|
|
30
38
|
get metadata(): Metadata;
|
|
31
39
|
/** The id of the source instance the change came from. */
|
|
32
40
|
get subject(): string;
|
|
41
|
+
/** Of a change from a workflow: where it stood once the effect that recorded the state was applied. `undefined` for a change from an entity or a topic. */
|
|
42
|
+
get standing(): Standing | undefined;
|
|
33
43
|
get client(): ComponentClient;
|
|
34
44
|
/** @internal */
|
|
35
45
|
get _kind(): "view";
|
|
@@ -38,7 +48,7 @@ export declare abstract class View<E, Row> {
|
|
|
38
48
|
/** The source was deleted. The row goes with it unless this says otherwise — an order history keeps a checked-out cart's row. */
|
|
39
49
|
onDelete(): MaybePromise<ViewEffect<Row>>;
|
|
40
50
|
/** @internal */
|
|
41
|
-
_bind(row: Row | null, metadata: Metadata, client: ComponentClient): void;
|
|
51
|
+
_bind(row: Row | null, metadata: Metadata, client: ComponentClient, standing?: Standing): void;
|
|
42
52
|
}
|
|
43
53
|
export interface ViewClass<E = unknown, Row = unknown, C extends View<E, Row> = View<E, Row>> {
|
|
44
54
|
new (): C;
|
|
@@ -57,6 +67,11 @@ export interface ViewClass<E = unknown, Row = unknown, C extends View<E, Row> =
|
|
|
57
67
|
readonly contract?: Contract;
|
|
58
68
|
/** The declared broker the topic read is on; absent is the installation's. */
|
|
59
69
|
readonly broker?: string;
|
|
70
|
+
/**
|
|
71
|
+
* Another project's topic (1.15): the id of the project whose topic `topic` is. The broker serves it
|
|
72
|
+
* while that project grants this service consume on it.
|
|
73
|
+
*/
|
|
74
|
+
readonly project?: string;
|
|
60
75
|
/** Whether the partitions an instance holds are handled at once, each in order. */
|
|
61
76
|
readonly parallel?: boolean;
|
|
62
77
|
readonly events: Shape<E>;
|
package/dist/view.js
CHANGED
|
@@ -9,9 +9,12 @@
|
|
|
9
9
|
// onChange(event) { ... return this.effects.updateRow(...) }
|
|
10
10
|
// }
|
|
11
11
|
import { ViewEffects } from "./effects/stateless.js";
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Declares a query for a view's `static declared`. The SDK sends the statement as written.
|
|
14
|
+
* `{ watched: true }` declares that it may also be watched, which the platform checks at start.
|
|
15
|
+
*/
|
|
16
|
+
export function declaredQuery(name, statement, options = {}) {
|
|
17
|
+
return Object.freeze({ name, statement, watched: options.watched ?? false });
|
|
15
18
|
}
|
|
16
19
|
/** The table a view's rows are kept in, for a declared query's statement to name. */
|
|
17
20
|
export function tableOf(componentId) {
|
|
@@ -21,6 +24,7 @@ export class View {
|
|
|
21
24
|
effects = new ViewEffects();
|
|
22
25
|
#row = null;
|
|
23
26
|
#metadata = {};
|
|
27
|
+
#standing = undefined;
|
|
24
28
|
#client;
|
|
25
29
|
#bound = false;
|
|
26
30
|
/** The current row, or `null` when there is none yet. */
|
|
@@ -37,6 +41,10 @@ export class View {
|
|
|
37
41
|
get subject() {
|
|
38
42
|
return this.#metadata["ce-subject"] ?? "";
|
|
39
43
|
}
|
|
44
|
+
/** Of a change from a workflow: where it stood once the effect that recorded the state was applied. `undefined` for a change from an entity or a topic. */
|
|
45
|
+
get standing() {
|
|
46
|
+
return this.#standing;
|
|
47
|
+
}
|
|
40
48
|
get client() {
|
|
41
49
|
if (!this.#client)
|
|
42
50
|
throw new Error("client is only available inside onChange or onDelete");
|
|
@@ -51,9 +59,10 @@ export class View {
|
|
|
51
59
|
return this.effects.deleteRow();
|
|
52
60
|
}
|
|
53
61
|
/** @internal */
|
|
54
|
-
_bind(row, metadata, client) {
|
|
62
|
+
_bind(row, metadata, client, standing) {
|
|
55
63
|
this.#row = row;
|
|
56
64
|
this.#metadata = metadata;
|
|
65
|
+
this.#standing = standing;
|
|
57
66
|
this.#client = client;
|
|
58
67
|
this.#bound = true;
|
|
59
68
|
}
|
package/package.json
CHANGED