ankka 0.6.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 (108) hide show
  1. package/README.md +30 -0
  2. package/dist/_proto/ankka/protocol/v1/agent_pb.d.ts +350 -0
  3. package/dist/_proto/ankka/protocol/v1/agent_pb.js +90 -0
  4. package/dist/_proto/ankka/protocol/v1/client_pb.d.ts +254 -0
  5. package/dist/_proto/ankka/protocol/v1/client_pb.js +47 -0
  6. package/dist/_proto/ankka/protocol/v1/consumer_pb.d.ts +105 -0
  7. package/dist/_proto/ankka/protocol/v1/consumer_pb.js +28 -0
  8. package/dist/_proto/ankka/protocol/v1/discovery_pb.d.ts +598 -0
  9. package/dist/_proto/ankka/protocol/v1/discovery_pb.js +175 -0
  10. package/dist/_proto/ankka/protocol/v1/endpoint_pb.d.ts +224 -0
  11. package/dist/_proto/ankka/protocol/v1/endpoint_pb.js +44 -0
  12. package/dist/_proto/ankka/protocol/v1/event_sourced_pb.d.ts +212 -0
  13. package/dist/_proto/ankka/protocol/v1/event_sourced_pb.js +47 -0
  14. package/dist/_proto/ankka/protocol/v1/key_value_pb.d.ts +154 -0
  15. package/dist/_proto/ankka/protocol/v1/key_value_pb.js +36 -0
  16. package/dist/_proto/ankka/protocol/v1/payload_pb.d.ts +276 -0
  17. package/dist/_proto/ankka/protocol/v1/payload_pb.js +110 -0
  18. package/dist/_proto/ankka/protocol/v1/timed_action_pb.d.ts +75 -0
  19. package/dist/_proto/ankka/protocol/v1/timed_action_pb.js +23 -0
  20. package/dist/_proto/ankka/protocol/v1/view_pb.d.ts +93 -0
  21. package/dist/_proto/ankka/protocol/v1/view_pb.js +23 -0
  22. package/dist/_proto/ankka/protocol/v1/workflow_pb.d.ts +298 -0
  23. package/dist/_proto/ankka/protocol/v1/workflow_pb.js +67 -0
  24. package/dist/agent.d.ts +27 -0
  25. package/dist/agent.js +36 -0
  26. package/dist/client.d.ts +92 -0
  27. package/dist/client.js +239 -0
  28. package/dist/codec.d.ts +44 -0
  29. package/dist/codec.js +215 -0
  30. package/dist/consumer.d.ts +31 -0
  31. package/dist/consumer.js +32 -0
  32. package/dist/context.d.ts +61 -0
  33. package/dist/context.js +75 -0
  34. package/dist/effects/agent.d.ts +37 -0
  35. package/dist/effects/agent.js +48 -0
  36. package/dist/effects/common.d.ts +68 -0
  37. package/dist/effects/common.js +55 -0
  38. package/dist/effects/eventSourced.d.ts +50 -0
  39. package/dist/effects/eventSourced.js +66 -0
  40. package/dist/effects/keyValue.d.ts +36 -0
  41. package/dist/effects/keyValue.js +47 -0
  42. package/dist/effects/stateless.d.ts +40 -0
  43. package/dist/effects/stateless.js +35 -0
  44. package/dist/effects/workflow.d.ts +99 -0
  45. package/dist/effects/workflow.js +83 -0
  46. package/dist/endpoint.d.ts +21 -0
  47. package/dist/endpoint.js +29 -0
  48. package/dist/eventSourcedEntity.d.ts +40 -0
  49. package/dist/eventSourcedEntity.js +66 -0
  50. package/dist/handlers.d.ts +55 -0
  51. package/dist/handlers.js +64 -0
  52. package/dist/index.d.ts +29 -0
  53. package/dist/index.js +39 -0
  54. package/dist/json.d.ts +21 -0
  55. package/dist/json.js +362 -0
  56. package/dist/keyValueEntity.d.ts +29 -0
  57. package/dist/keyValueEntity.js +52 -0
  58. package/dist/kinds.d.ts +8 -0
  59. package/dist/kinds.js +41 -0
  60. package/dist/materialise.d.ts +33 -0
  61. package/dist/materialise.js +55 -0
  62. package/dist/routes.d.ts +71 -0
  63. package/dist/routes.js +79 -0
  64. package/dist/schema.d.ts +112 -0
  65. package/dist/schema.js +200 -0
  66. package/dist/server/agent.d.ts +14 -0
  67. package/dist/server/agent.js +113 -0
  68. package/dist/server/discovery.d.ts +5 -0
  69. package/dist/server/discovery.js +21 -0
  70. package/dist/server/eventSourced.d.ts +12 -0
  71. package/dist/server/eventSourced.js +168 -0
  72. package/dist/server/http.d.ts +10 -0
  73. package/dist/server/http.js +153 -0
  74. package/dist/server/keyValue.d.ts +5 -0
  75. package/dist/server/keyValue.js +122 -0
  76. package/dist/server/payloads.d.ts +6 -0
  77. package/dist/server/payloads.js +11 -0
  78. package/dist/server/queue.d.ts +10 -0
  79. package/dist/server/queue.js +67 -0
  80. package/dist/server/server.d.ts +34 -0
  81. package/dist/server/server.js +101 -0
  82. package/dist/server/stateless.d.ts +9 -0
  83. package/dist/server/stateless.js +108 -0
  84. package/dist/server/workflow.d.ts +5 -0
  85. package/dist/server/workflow.js +244 -0
  86. package/dist/service.d.ts +147 -0
  87. package/dist/service.js +384 -0
  88. package/dist/spec.d.ts +7 -0
  89. package/dist/spec.js +106 -0
  90. package/dist/testkit/index.d.ts +3 -0
  91. package/dist/testkit/index.js +6 -0
  92. package/dist/testkit/integration.d.ts +60 -0
  93. package/dist/testkit/integration.js +284 -0
  94. package/dist/testkit/kinds.d.ts +133 -0
  95. package/dist/testkit/kinds.js +398 -0
  96. package/dist/testkit/unit.d.ts +54 -0
  97. package/dist/testkit/unit.js +224 -0
  98. package/dist/time.d.ts +73 -0
  99. package/dist/time.js +275 -0
  100. package/dist/timedAction.d.ts +20 -0
  101. package/dist/timedAction.js +28 -0
  102. package/dist/version.d.ts +1 -0
  103. package/dist/version.js +2 -0
  104. package/dist/view.d.ts +37 -0
  105. package/dist/view.js +52 -0
  106. package/dist/workflow.d.ts +33 -0
  107. package/dist/workflow.js +61 -0
  108. package/package.json +75 -0
package/dist/time.d.ts ADDED
@@ -0,0 +1,73 @@
1
+ /** A point on the time line, to the nanosecond, in UTC. */
2
+ export declare class Instant {
3
+ /** Seconds since 1970-01-01T00:00:00Z, negative before it. */
4
+ readonly epochSecond: number;
5
+ /** Nanoseconds within the second, 0 to 999_999_999. */
6
+ readonly nano: number;
7
+ private constructor();
8
+ static ofEpochSecond(epochSecond: number, nanoAdjustment?: number): Instant;
9
+ static ofEpochMilli(epochMilli: number): Instant;
10
+ static now(): Instant;
11
+ static fromDate(date: Date): Instant;
12
+ /** Parses ISO-8601 with `Z` or an offset and up to nine fractional digits. */
13
+ static parse(text: string): Instant;
14
+ toEpochMilli(): number;
15
+ /** Millisecond precision; sub-millisecond nanoseconds are dropped. */
16
+ toDate(): Date;
17
+ plusMillis(millis: number): Instant;
18
+ plus(duration: Duration): Instant;
19
+ isBefore(other: Instant): boolean;
20
+ isAfter(other: Instant): boolean;
21
+ equals(other: unknown): boolean;
22
+ static compare(a: Instant, b: Instant): number;
23
+ /** ISO-8601 in UTC with `Z` and 0, 3, 6 or 9 fractional digits, as the Scala codecs write it. */
24
+ toString(): string;
25
+ toJSON(): string;
26
+ }
27
+ /** The fraction text for a nanosecond count: none, or 3, 6 or 9 digits. */
28
+ export declare function fractionOf(nano: number): string;
29
+ /** An amount of time, to the nanosecond, as `java.time.Duration` holds and prints it. */
30
+ export declare class Duration {
31
+ /** Whole seconds, negative for a negative duration. */
32
+ readonly seconds: number;
33
+ /** Nanoseconds within the second, 0 to 999_999_999, always non-negative (the Java convention). */
34
+ readonly nano: number;
35
+ private constructor();
36
+ static readonly ZERO: Duration;
37
+ static ofSeconds(seconds: number, nanoAdjustment?: number): Duration;
38
+ static ofMillis(millis: number): Duration;
39
+ static ofNanos(nanos: number): Duration;
40
+ static ofMinutes(minutes: number): Duration;
41
+ static ofHours(hours: number): Duration;
42
+ /** Parses the ISO-8601 form `java.time.Duration` reads: `PT1.5S`, `PT2H30M`, `P1DT12H`, `-PT1S`. */
43
+ static parse(text: string): Duration;
44
+ toMillis(): number;
45
+ toNanos(): number;
46
+ isNegative(): boolean;
47
+ isZero(): boolean;
48
+ plus(other: Duration): Duration;
49
+ equals(other: unknown): boolean;
50
+ static compare(a: Duration, b: Duration): number;
51
+ /** `java.time.Duration.toString`: `PT0S`, `PT1.5S`, `PT2H30M`, `PT-0.5S`. */
52
+ toString(): string;
53
+ toJSON(): string;
54
+ }
55
+ /** A calendar date with no zone, as its ISO-8601 text. */
56
+ export declare class LocalDate {
57
+ readonly value: string;
58
+ private constructor();
59
+ static parse(text: string): LocalDate;
60
+ static of(year: number, month: number, day: number): LocalDate;
61
+ equals(other: unknown): boolean;
62
+ toString(): string;
63
+ toJSON(): string;
64
+ }
65
+ /** A date and time with no zone, as its ISO-8601 text. */
66
+ export declare class LocalDateTime {
67
+ readonly value: string;
68
+ private constructor();
69
+ static parse(text: string): LocalDateTime;
70
+ equals(other: unknown): boolean;
71
+ toString(): string;
72
+ toJSON(): string;
73
+ }
package/dist/time.js ADDED
@@ -0,0 +1,275 @@
1
+ // Time values with the precision the encoding needs. `Date` holds milliseconds; an `Instant` in a
2
+ // journal may carry nanoseconds, and `ENCODING.md` writes 0, 3, 6 or 9 fractional digits according to
3
+ // the value. `Temporal` would do, but it is a global only from Node 26. These are the four small
4
+ // value classes in between: immutable, comparable, convertible to and from `Date` where that makes sense.
5
+ const NANOS_PER_SECOND = 1_000_000_000;
6
+ const NANOS_PER_MILLI = 1_000_000;
7
+ const INSTANT = /^(-?\d{4,9})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,9}))?(Z|[+-]\d{2}:\d{2})$/;
8
+ /** A point on the time line, to the nanosecond, in UTC. */
9
+ export class Instant {
10
+ /** Seconds since 1970-01-01T00:00:00Z, negative before it. */
11
+ epochSecond;
12
+ /** Nanoseconds within the second, 0 to 999_999_999. */
13
+ nano;
14
+ constructor(epochSecond, nano) {
15
+ this.epochSecond = epochSecond;
16
+ this.nano = nano;
17
+ Object.freeze(this);
18
+ }
19
+ static ofEpochSecond(epochSecond, nanoAdjustment = 0) {
20
+ if (!Number.isInteger(epochSecond) || !Number.isInteger(nanoAdjustment)) {
21
+ throw new RangeError("Instant.ofEpochSecond takes integers");
22
+ }
23
+ const seconds = epochSecond + Math.floor(nanoAdjustment / NANOS_PER_SECOND);
24
+ const nano = ((nanoAdjustment % NANOS_PER_SECOND) + NANOS_PER_SECOND) % NANOS_PER_SECOND;
25
+ return new Instant(seconds, nano);
26
+ }
27
+ static ofEpochMilli(epochMilli) {
28
+ if (!Number.isInteger(epochMilli))
29
+ throw new RangeError("Instant.ofEpochMilli takes an integer");
30
+ return Instant.ofEpochSecond(Math.floor(epochMilli / 1000), (((epochMilli % 1000) + 1000) % 1000) * NANOS_PER_MILLI);
31
+ }
32
+ static now() {
33
+ return Instant.ofEpochMilli(Date.now());
34
+ }
35
+ static fromDate(date) {
36
+ const millis = date.getTime();
37
+ if (Number.isNaN(millis))
38
+ throw new RangeError("Instant.fromDate: invalid Date");
39
+ return Instant.ofEpochMilli(millis);
40
+ }
41
+ /** Parses ISO-8601 with `Z` or an offset and up to nine fractional digits. */
42
+ static parse(text) {
43
+ const m = INSTANT.exec(text);
44
+ if (!m)
45
+ throw new RangeError(`not an ISO-8601 instant: ${JSON.stringify(text)}`);
46
+ const [, year, month, day, hour, minute, second, fraction, zone] = m;
47
+ const millis = Date.UTC(Number(year), Number(month) - 1, Number(day), Number(hour), Number(minute), Number(second));
48
+ const check = new Date(millis);
49
+ // Date.UTC rolls an out-of-range field over (month 13 becomes January); a real instant reads back as itself.
50
+ if (Number.isNaN(millis) ||
51
+ check.getUTCMonth() !== Number(month) - 1 ||
52
+ check.getUTCDate() !== Number(day) ||
53
+ check.getUTCHours() !== Number(hour) ||
54
+ check.getUTCMinutes() !== Number(minute) ||
55
+ check.getUTCSeconds() !== Number(second)) {
56
+ throw new RangeError(`not an ISO-8601 instant: ${JSON.stringify(text)}`);
57
+ }
58
+ let seconds = Math.floor(millis / 1000);
59
+ if (zone !== "Z") {
60
+ const sign = zone.startsWith("-") ? -1 : 1;
61
+ const offsetMinutes = sign * (Number(zone.slice(1, 3)) * 60 + Number(zone.slice(4, 6)));
62
+ seconds -= offsetMinutes * 60;
63
+ }
64
+ const nano = fraction ? Number(fraction.padEnd(9, "0")) : 0;
65
+ return new Instant(seconds, nano);
66
+ }
67
+ toEpochMilli() {
68
+ return this.epochSecond * 1000 + Math.floor(this.nano / NANOS_PER_MILLI);
69
+ }
70
+ /** Millisecond precision; sub-millisecond nanoseconds are dropped. */
71
+ toDate() {
72
+ return new Date(this.toEpochMilli());
73
+ }
74
+ plusMillis(millis) {
75
+ return Instant.ofEpochSecond(this.epochSecond, this.nano + millis * NANOS_PER_MILLI);
76
+ }
77
+ plus(duration) {
78
+ return Instant.ofEpochSecond(this.epochSecond + duration.seconds, this.nano + duration.nano);
79
+ }
80
+ isBefore(other) {
81
+ return Instant.compare(this, other) < 0;
82
+ }
83
+ isAfter(other) {
84
+ return Instant.compare(this, other) > 0;
85
+ }
86
+ equals(other) {
87
+ return other instanceof Instant && other.epochSecond === this.epochSecond && other.nano === this.nano;
88
+ }
89
+ static compare(a, b) {
90
+ return a.epochSecond !== b.epochSecond ? a.epochSecond - b.epochSecond : a.nano - b.nano;
91
+ }
92
+ /** ISO-8601 in UTC with `Z` and 0, 3, 6 or 9 fractional digits, as the Scala codecs write it. */
93
+ toString() {
94
+ const date = new Date(this.epochSecond * 1000);
95
+ const base = date.toISOString().slice(0, 19); // "YYYY-MM-DDTHH:MM:SS"
96
+ return base + fractionOf(this.nano) + "Z";
97
+ }
98
+ toJSON() {
99
+ return this.toString();
100
+ }
101
+ }
102
+ /** The fraction text for a nanosecond count: none, or 3, 6 or 9 digits. */
103
+ export function fractionOf(nano) {
104
+ if (nano === 0)
105
+ return "";
106
+ const digits = String(nano).padStart(9, "0");
107
+ if (nano % NANOS_PER_MILLI === 0)
108
+ return "." + digits.slice(0, 3);
109
+ if (nano % 1000 === 0)
110
+ return "." + digits.slice(0, 6);
111
+ return "." + digits;
112
+ }
113
+ const DURATION = /^([+-]?)P(?:([+-]?\d+)D)?(?:T(?:([+-]?\d+)H)?(?:([+-]?\d+)M)?(?:([+-]?\d+)(?:[.,](\d{1,9}))?S)?)?$/i;
114
+ /** An amount of time, to the nanosecond, as `java.time.Duration` holds and prints it. */
115
+ export class Duration {
116
+ /** Whole seconds, negative for a negative duration. */
117
+ seconds;
118
+ /** Nanoseconds within the second, 0 to 999_999_999, always non-negative (the Java convention). */
119
+ nano;
120
+ constructor(seconds, nano) {
121
+ this.seconds = seconds;
122
+ this.nano = nano;
123
+ Object.freeze(this);
124
+ }
125
+ static ZERO = new Duration(0, 0);
126
+ static ofSeconds(seconds, nanoAdjustment = 0) {
127
+ if (!Number.isInteger(seconds) || !Number.isInteger(nanoAdjustment)) {
128
+ throw new RangeError("Duration.ofSeconds takes integers");
129
+ }
130
+ const s = seconds + Math.floor(nanoAdjustment / NANOS_PER_SECOND);
131
+ const n = ((nanoAdjustment % NANOS_PER_SECOND) + NANOS_PER_SECOND) % NANOS_PER_SECOND;
132
+ return new Duration(s, n);
133
+ }
134
+ static ofMillis(millis) {
135
+ if (!Number.isInteger(millis))
136
+ throw new RangeError("Duration.ofMillis takes an integer");
137
+ return Duration.ofSeconds(Math.floor(millis / 1000), (((millis % 1000) + 1000) % 1000) * NANOS_PER_MILLI);
138
+ }
139
+ static ofNanos(nanos) {
140
+ if (!Number.isInteger(nanos))
141
+ throw new RangeError("Duration.ofNanos takes an integer");
142
+ return Duration.ofSeconds(0, nanos);
143
+ }
144
+ static ofMinutes(minutes) {
145
+ return Duration.ofSeconds(minutes * 60);
146
+ }
147
+ static ofHours(hours) {
148
+ return Duration.ofSeconds(hours * 3600);
149
+ }
150
+ /** Parses the ISO-8601 form `java.time.Duration` reads: `PT1.5S`, `PT2H30M`, `P1DT12H`, `-PT1S`. */
151
+ static parse(text) {
152
+ const m = DURATION.exec(text);
153
+ if (!m || text.length <= 2)
154
+ throw new RangeError(`not an ISO-8601 duration: ${JSON.stringify(text)}`);
155
+ const [, sign, days, hours, minutes, secs, fraction] = m;
156
+ if (days === undefined && hours === undefined && minutes === undefined && secs === undefined) {
157
+ throw new RangeError(`not an ISO-8601 duration: ${JSON.stringify(text)}`);
158
+ }
159
+ let seconds = Number(days ?? 0) * 86400 + Number(hours ?? 0) * 3600 + Number(minutes ?? 0) * 60 + Number(secs ?? 0);
160
+ let nanos = fraction ? Number(fraction.padEnd(9, "0")) : 0;
161
+ if (secs?.startsWith("-"))
162
+ nanos = -nanos;
163
+ if (sign === "-") {
164
+ seconds = -seconds;
165
+ nanos = -nanos;
166
+ }
167
+ return Duration.ofSeconds(seconds, nanos);
168
+ }
169
+ toMillis() {
170
+ return this.seconds * 1000 + Math.floor(this.nano / NANOS_PER_MILLI);
171
+ }
172
+ toNanos() {
173
+ return this.seconds * NANOS_PER_SECOND + this.nano;
174
+ }
175
+ isNegative() {
176
+ return this.seconds < 0;
177
+ }
178
+ isZero() {
179
+ return this.seconds === 0 && this.nano === 0;
180
+ }
181
+ plus(other) {
182
+ return Duration.ofSeconds(this.seconds + other.seconds, this.nano + other.nano);
183
+ }
184
+ equals(other) {
185
+ return other instanceof Duration && other.seconds === this.seconds && other.nano === this.nano;
186
+ }
187
+ static compare(a, b) {
188
+ return a.seconds !== b.seconds ? a.seconds - b.seconds : a.nano - b.nano;
189
+ }
190
+ /** `java.time.Duration.toString`: `PT0S`, `PT1.5S`, `PT2H30M`, `PT-0.5S`. */
191
+ toString() {
192
+ if (this.isZero())
193
+ return "PT0S";
194
+ const total = this.seconds;
195
+ const hours = Math.trunc(total / 3600);
196
+ const minutes = Math.trunc((total % 3600) / 60);
197
+ let secs = total % 60;
198
+ let out = "PT";
199
+ if (hours !== 0)
200
+ out += `${hours}H`;
201
+ if (minutes !== 0)
202
+ out += `${minutes}M`;
203
+ if (secs === 0 && this.nano === 0 && out.length > 2)
204
+ return out;
205
+ // Java prints a negative duration with a fraction as e.g. PT-0.5S: seconds -1 and nanos 500_000_000 → "-0.5".
206
+ if (secs < 0 && this.nano > 0) {
207
+ if (secs === -1)
208
+ out += "-0";
209
+ else
210
+ out += String(secs + 1);
211
+ }
212
+ else {
213
+ out += String(secs);
214
+ }
215
+ if (this.nano > 0) {
216
+ const n = secs < 0 ? NANOS_PER_SECOND - this.nano : this.nano;
217
+ out += "." + String(n).padStart(9, "0").replace(/0+$/, "");
218
+ }
219
+ return out + "S";
220
+ }
221
+ toJSON() {
222
+ return this.toString();
223
+ }
224
+ }
225
+ const LOCAL_DATE = /^-?\d{4,9}-\d{2}-\d{2}$/;
226
+ const LOCAL_DATE_TIME = /^-?\d{4,9}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?$/;
227
+ /** A calendar date with no zone, as its ISO-8601 text. */
228
+ export class LocalDate {
229
+ value;
230
+ constructor(value) {
231
+ this.value = value;
232
+ Object.freeze(this);
233
+ }
234
+ static parse(text) {
235
+ if (!LOCAL_DATE.test(text) || Number.isNaN(Date.parse(text + "T00:00:00Z"))) {
236
+ throw new RangeError(`not an ISO-8601 date: ${JSON.stringify(text)}`);
237
+ }
238
+ return new LocalDate(text);
239
+ }
240
+ static of(year, month, day) {
241
+ return LocalDate.parse(`${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`);
242
+ }
243
+ equals(other) {
244
+ return other instanceof LocalDate && other.value === this.value;
245
+ }
246
+ toString() {
247
+ return this.value;
248
+ }
249
+ toJSON() {
250
+ return this.value;
251
+ }
252
+ }
253
+ /** A date and time with no zone, as its ISO-8601 text. */
254
+ export class LocalDateTime {
255
+ value;
256
+ constructor(value) {
257
+ this.value = value;
258
+ Object.freeze(this);
259
+ }
260
+ static parse(text) {
261
+ if (!LOCAL_DATE_TIME.test(text) || Number.isNaN(Date.parse(text + "Z"))) {
262
+ throw new RangeError(`not an ISO-8601 date-time: ${JSON.stringify(text)}`);
263
+ }
264
+ return new LocalDateTime(text);
265
+ }
266
+ equals(other) {
267
+ return other instanceof LocalDateTime && other.value === this.value;
268
+ }
269
+ toString() {
270
+ return this.value;
271
+ }
272
+ toJSON() {
273
+ return this.value;
274
+ }
275
+ }
@@ -0,0 +1,20 @@
1
+ import type { Metadata } from "./effects/common.ts";
2
+ import type { ComponentClient } from "./client.ts";
3
+ import type { HandlerTable } from "./handlers.ts";
4
+ import { TimedActionEffects } from "./effects/stateless.ts";
5
+ export declare abstract class TimedAction {
6
+ #private;
7
+ readonly effects: TimedActionEffects;
8
+ /** `ankka.timer` is the timer's id, `ankka.attempts` how many times it has fired. */
9
+ get metadata(): Metadata;
10
+ get client(): ComponentClient;
11
+ /** @internal */
12
+ get _kind(): "timed-action";
13
+ /** @internal */
14
+ _bind(metadata: Metadata, client: ComponentClient): void;
15
+ }
16
+ export interface TimedActionClass<C extends TimedAction = TimedAction> {
17
+ new (): C;
18
+ readonly componentId: string;
19
+ readonly actions: HandlerTable<C>;
20
+ }
@@ -0,0 +1,28 @@
1
+ // A timed action: a call the platform makes later. Scheduled through `client.timers`; the sidecar's
2
+ // sweeper delivers it here, retrying a failure with backoff.
3
+ //
4
+ // static readonly actions = { remind: action("remind", s.string, (t: Reminder, id) => t.remind(id)) }
5
+ import { TimedActionEffects } from "./effects/stateless.js";
6
+ export class TimedAction {
7
+ effects = new TimedActionEffects();
8
+ #metadata = {};
9
+ #client;
10
+ /** `ankka.timer` is the timer's id, `ankka.attempts` how many times it has fired. */
11
+ get metadata() {
12
+ return this.#metadata;
13
+ }
14
+ get client() {
15
+ if (!this.#client)
16
+ throw new Error("client is only available inside an action");
17
+ return this.#client;
18
+ }
19
+ /** @internal */
20
+ get _kind() {
21
+ return "timed-action";
22
+ }
23
+ /** @internal */
24
+ _bind(metadata, client) {
25
+ this.#metadata = metadata;
26
+ this.#client = client;
27
+ }
28
+ }
@@ -0,0 +1 @@
1
+ export declare const VERSION = "0.6.0";
@@ -0,0 +1,2 @@
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.6.0";
package/dist/view.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ import type { Shape } from "./codec.ts";
2
+ import type { Metadata } from "./effects/common.ts";
3
+ import type { ComponentClient } from "./client.ts";
4
+ import type { ComponentRef } from "./client.ts";
5
+ import { ViewEffects, type ViewEffect } from "./effects/stateless.ts";
6
+ type MaybePromise<T> = T | Promise<T>;
7
+ export declare abstract class View<E, Row> {
8
+ #private;
9
+ readonly effects: ViewEffects<Row>;
10
+ /** The current row, or `null` when there is none yet. */
11
+ get row(): Row | null;
12
+ /** The change's metadata: `ce-subject` is the source instance's id, `ankka.sequence` its sequence number. */
13
+ get metadata(): Metadata;
14
+ /** The id of the source instance the change came from. */
15
+ get subject(): string;
16
+ get client(): ComponentClient;
17
+ /** @internal */
18
+ get _kind(): "view";
19
+ /** What `event` does to the row. */
20
+ abstract onChange(event: E): MaybePromise<ViewEffect<Row>>;
21
+ /** The source was deleted. The row goes with it unless this says otherwise — an order history keeps a checked-out cart's row. */
22
+ onDelete(): MaybePromise<ViewEffect<Row>>;
23
+ /** @internal */
24
+ _bind(row: Row | null, metadata: Metadata, client: ComponentClient): void;
25
+ }
26
+ export interface ViewClass<E = unknown, Row = unknown, C extends View<E, Row> = View<E, Row>> {
27
+ new (): C;
28
+ readonly componentId: string;
29
+ /** The component whose changes feed the view, or `topic`. */
30
+ readonly source?: ComponentRef;
31
+ readonly topic?: string;
32
+ readonly events: Shape<E>;
33
+ readonly row: Shape<Row>;
34
+ /** The query names the view answers; `get` and `all` by default. */
35
+ readonly queries?: readonly string[];
36
+ }
37
+ export {};
package/dist/view.js ADDED
@@ -0,0 +1,52 @@
1
+ // A view: a queryable projection of a source's changes. The sidecar runs the projection and stores the
2
+ // rows; this process only says what each event does to a row.
3
+ //
4
+ // export class CartRows extends View<ShoppingCartEvent, CartRow> {
5
+ // static readonly componentId = "cart-rows"
6
+ // static readonly source = ShoppingCartEntity
7
+ // static readonly events = ShoppingCartEntity.events
8
+ // static readonly row = jsonCodec(CartRow, "cart-row")
9
+ // onChange(event) { ... return this.effects.updateRow(...) }
10
+ // }
11
+ import { ViewEffects } from "./effects/stateless.js";
12
+ export class View {
13
+ effects = new ViewEffects();
14
+ #row = null;
15
+ #metadata = {};
16
+ #client;
17
+ #bound = false;
18
+ /** The current row, or `null` when there is none yet. */
19
+ get row() {
20
+ if (!this.#bound)
21
+ throw new Error("row is only available inside onChange or onDelete");
22
+ return this.#row;
23
+ }
24
+ /** The change's metadata: `ce-subject` is the source instance's id, `ankka.sequence` its sequence number. */
25
+ get metadata() {
26
+ return this.#metadata;
27
+ }
28
+ /** The id of the source instance the change came from. */
29
+ get subject() {
30
+ return this.#metadata["ce-subject"] ?? "";
31
+ }
32
+ get client() {
33
+ if (!this.#client)
34
+ throw new Error("client is only available inside onChange or onDelete");
35
+ return this.#client;
36
+ }
37
+ /** @internal */
38
+ get _kind() {
39
+ return "view";
40
+ }
41
+ /** The source was deleted. The row goes with it unless this says otherwise — an order history keeps a checked-out cart's row. */
42
+ onDelete() {
43
+ return this.effects.deleteRow();
44
+ }
45
+ /** @internal */
46
+ _bind(row, metadata, client) {
47
+ this.#row = row;
48
+ this.#metadata = metadata;
49
+ this.#client = client;
50
+ this.#bound = true;
51
+ }
52
+ }
@@ -0,0 +1,33 @@
1
+ import type { Shape } from "./codec.ts";
2
+ import type { CommandContext } from "./context.ts";
3
+ import type { HandlerTable } from "./handlers.ts";
4
+ import type { ComponentClient } from "./client.ts";
5
+ import { StepEffects, WorkflowEffects, type WorkflowSettings } from "./effects/workflow.ts";
6
+ export declare abstract class Workflow<S> {
7
+ #private;
8
+ /** Inside a command handler. */
9
+ readonly effects: WorkflowEffects<S>;
10
+ /** Inside a step. */
11
+ readonly stepEffects: StepEffects<S>;
12
+ get state(): S;
13
+ get entityId(): string;
14
+ get context(): CommandContext;
15
+ get client(): ComponentClient;
16
+ /** @internal */
17
+ get _kind(): "workflow";
18
+ abstract emptyState(): S;
19
+ /** @internal */
20
+ _bindInstance(entityId: string): void;
21
+ /** @internal */
22
+ _bindCommand(state: S, context: CommandContext, client: ComponentClient): void;
23
+ /** @internal */
24
+ _unbindCommand(): void;
25
+ }
26
+ export interface WorkflowClass<S = unknown, C extends Workflow<S> = Workflow<S>> {
27
+ new (): C;
28
+ readonly componentId: string;
29
+ readonly state: Shape<S>;
30
+ readonly handlers: HandlerTable<C>;
31
+ readonly steps: HandlerTable<C>;
32
+ readonly settings?: WorkflowSettings;
33
+ }
@@ -0,0 +1,61 @@
1
+ // A workflow: a durable multi-step process. Commands change state and start steps; steps run in this
2
+ // process, one at a time per instance, and say what happens next. The sidecar journals every
3
+ // transition and drives the steps, retries and timeouts — a step that throws is retried and eventually
4
+ // compensated as `settings` declares.
5
+ //
6
+ // static readonly steps = { reserve: step("reserve", (w: CheckoutWorkflow) => w.reserve()), charge: step("charge", s.int, (w, q) => w.charge(q)) }
7
+ // static readonly settings = workflowSettings({ steps: { charge: { recovery: { maxRetries: 1, failoverTo: "compensate" } } } })
8
+ import { StepEffects, WorkflowEffects } from "./effects/workflow.js";
9
+ export class Workflow {
10
+ /** Inside a command handler. */
11
+ effects = new WorkflowEffects();
12
+ /** Inside a step. */
13
+ stepEffects = new StepEffects();
14
+ #state;
15
+ #entityId;
16
+ #context;
17
+ #client;
18
+ #bound = false;
19
+ get state() {
20
+ if (!this.#bound)
21
+ throw new Error("state is only available inside a handler or a step");
22
+ return this.#state;
23
+ }
24
+ get entityId() {
25
+ if (this.#entityId === undefined)
26
+ throw new Error("entityId is only available once the workflow is bound to an instance");
27
+ return this.#entityId;
28
+ }
29
+ get context() {
30
+ if (!this.#context)
31
+ throw new Error("context is only available inside a handler or a step");
32
+ return this.#context;
33
+ }
34
+ get client() {
35
+ if (!this.#client)
36
+ throw new Error("client is only available inside a handler or a step");
37
+ return this.#client;
38
+ }
39
+ /** @internal */
40
+ get _kind() {
41
+ return "workflow";
42
+ }
43
+ /** @internal */
44
+ _bindInstance(entityId) {
45
+ this.#entityId = entityId;
46
+ }
47
+ /** @internal */
48
+ _bindCommand(state, context, client) {
49
+ this.#state = state;
50
+ this.#context = context;
51
+ this.#client = client;
52
+ this.#bound = true;
53
+ }
54
+ /** @internal */
55
+ _unbindCommand() {
56
+ this.#state = undefined;
57
+ this.#context = undefined;
58
+ this.#client = undefined;
59
+ this.#bound = false;
60
+ }
61
+ }
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "ankka",
3
+ "version": "0.6.0",
4
+ "description": "Build services on ankka in TypeScript: entities, views, workflows, agents and endpoints hosted by the ankka sidecar.",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "engines": {
8
+ "node": ">=22.22.0"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "main": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "ankka-source": "./src/index.ts",
18
+ "types": "./dist/index.d.ts",
19
+ "default": "./dist/index.js"
20
+ },
21
+ "./testkit": {
22
+ "ankka-source": "./src/testkit/index.ts",
23
+ "types": "./dist/testkit/index.d.ts",
24
+ "default": "./dist/testkit/index.js"
25
+ }
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/thinkmorestupidless/ankka.git",
30
+ "directory": "sdks/typescript"
31
+ },
32
+ "homepage": "https://docs.ankka.cloud/",
33
+ "keywords": [
34
+ "ankka",
35
+ "event-sourcing",
36
+ "actors",
37
+ "agents",
38
+ "workflows",
39
+ "sidecar"
40
+ ],
41
+ "scripts": {
42
+ "proto": "node scripts/proto.ts",
43
+ "typecheck": "tsc --noEmit -p tsconfig.json",
44
+ "build": "tsc -p tsconfig.build.json",
45
+ "test": "node --conditions=ankka-source --test 'test/**/*.test.ts' 'examples/**/*.test.ts'",
46
+ "test:slow": "ANKKA_SLOW=1 node --conditions=ankka-source --test 'test/**/*.test.ts' 'examples/**/*.test.ts'",
47
+ "conformance": "node --conditions=ankka-source bin/conformance.ts",
48
+ "example": "node --conditions=ankka-source examples/shopping-cart/main.ts"
49
+ },
50
+ "dependencies": {
51
+ "@bufbuild/protobuf": "^2.15.0",
52
+ "@connectrpc/connect": "^2.2.0",
53
+ "@connectrpc/connect-node": "^2.2.0"
54
+ },
55
+ "peerDependencies": {
56
+ "@testcontainers/postgresql": "^12.1.0",
57
+ "testcontainers": "^12.1.0"
58
+ },
59
+ "peerDependenciesMeta": {
60
+ "@testcontainers/postgresql": {
61
+ "optional": true
62
+ },
63
+ "testcontainers": {
64
+ "optional": true
65
+ }
66
+ },
67
+ "devDependencies": {
68
+ "@bufbuild/buf": "^1.73.0",
69
+ "@bufbuild/protoc-gen-es": "^2.15.0",
70
+ "@testcontainers/postgresql": "^12.1.0",
71
+ "@types/node": "^24.19.0",
72
+ "testcontainers": "^12.1.0",
73
+ "typescript": "^7.0.2"
74
+ }
75
+ }