@resonatehq/sdk 0.3.3 → 0.3.4
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/async.d.ts +135 -0
- package/dist/async.d.ts.map +1 -0
- package/dist/async.js +205 -0
- package/dist/core/encoder.d.ts +5 -0
- package/dist/core/encoder.d.ts.map +1 -0
- package/dist/core/encoder.js +1 -0
- package/dist/core/encoders/base64.d.ts +6 -0
- package/dist/core/encoders/base64.d.ts.map +1 -0
- package/dist/core/encoders/base64.js +8 -0
- package/dist/core/encoders/json.d.ts +6 -0
- package/dist/core/encoders/json.d.ts.map +1 -0
- package/dist/core/encoders/json.js +60 -0
- package/dist/core/errors.d.ts +21 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +33 -0
- package/dist/core/execution.d.ts +44 -0
- package/dist/core/execution.d.ts.map +1 -0
- package/dist/core/execution.js +195 -0
- package/dist/core/future.d.ts +54 -0
- package/dist/core/future.d.ts.map +1 -0
- package/dist/core/future.js +95 -0
- package/dist/core/invocation.d.ts +43 -0
- package/dist/core/invocation.d.ts.map +1 -0
- package/dist/core/invocation.js +72 -0
- package/dist/core/logger.d.ts +10 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +1 -0
- package/dist/core/loggers/logger.d.ts +13 -0
- package/dist/core/loggers/logger.d.ts.map +1 -0
- package/dist/core/loggers/logger.js +43 -0
- package/dist/core/options.d.ts +79 -0
- package/dist/core/options.d.ts.map +1 -0
- package/dist/core/options.js +3 -0
- package/dist/core/promises/promises.d.ts +47 -0
- package/dist/core/promises/promises.d.ts.map +1 -0
- package/dist/core/promises/promises.js +128 -0
- package/dist/core/promises/types.d.ts +99 -0
- package/dist/core/promises/types.d.ts.map +1 -0
- package/dist/core/promises/types.js +29 -0
- package/dist/core/retries/retry.d.ts +20 -0
- package/dist/core/retries/retry.d.ts.map +1 -0
- package/dist/core/retries/retry.js +36 -0
- package/dist/core/retry.d.ts +27 -0
- package/dist/core/retry.d.ts.map +1 -0
- package/dist/core/retry.js +17 -0
- package/dist/core/schedules/types.d.ts +19 -0
- package/dist/core/schedules/types.d.ts.map +1 -0
- package/dist/core/schedules/types.js +3 -0
- package/dist/core/storage.d.ts +6 -0
- package/dist/core/storage.d.ts.map +1 -0
- package/dist/core/storage.js +1 -0
- package/dist/core/storages/memory.d.ts +8 -0
- package/dist/core/storages/memory.d.ts.map +1 -0
- package/dist/core/storages/memory.js +22 -0
- package/dist/core/storages/withTimeout.d.ts +10 -0
- package/dist/core/storages/withTimeout.d.ts.map +1 -0
- package/dist/core/storages/withTimeout.js +38 -0
- package/dist/core/store.d.ts +139 -0
- package/dist/core/store.d.ts.map +1 -0
- package/dist/core/store.js +1 -0
- package/dist/core/stores/local.d.ts +54 -0
- package/dist/core/stores/local.d.ts.map +1 -0
- package/dist/core/stores/local.js +368 -0
- package/dist/core/stores/remote.d.ts +48 -0
- package/dist/core/stores/remote.d.ts.map +1 -0
- package/dist/core/stores/remote.js +399 -0
- package/dist/core/utils.d.ts +3 -0
- package/dist/core/utils.d.ts.map +1 -0
- package/dist/core/utils.js +13 -0
- package/dist/generator.d.ts +187 -0
- package/dist/generator.d.ts.map +1 -0
- package/dist/generator.js +396 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/resonate.d.ts +73 -0
- package/dist/resonate.d.ts.map +1 -0
- package/dist/resonate.js +142 -0
- package/package.json +1 -1
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import { DeferredExecution, GeneratorExecution, OrdinaryExecution } from "./core/execution";
|
|
2
|
+
import { Future } from "./core/future";
|
|
3
|
+
import { Invocation } from "./core/invocation";
|
|
4
|
+
import * as utils from "./core/utils";
|
|
5
|
+
import { ResonateBase } from "./resonate";
|
|
6
|
+
/////////////////////////////////////////////////////////////////////
|
|
7
|
+
// Resonate
|
|
8
|
+
/////////////////////////////////////////////////////////////////////
|
|
9
|
+
export class Resonate extends ResonateBase {
|
|
10
|
+
scheduler;
|
|
11
|
+
/**
|
|
12
|
+
* Creates a Resonate instance. This is the starting point for using Resonate.
|
|
13
|
+
*
|
|
14
|
+
* @constructor
|
|
15
|
+
* @param opts - A partial {@link ResonateOptions} object.
|
|
16
|
+
*/
|
|
17
|
+
constructor(opts = {}) {
|
|
18
|
+
super(opts);
|
|
19
|
+
this.scheduler = new Scheduler(this.logger);
|
|
20
|
+
}
|
|
21
|
+
register(name, funcOrVersion, funcOrOpts) {
|
|
22
|
+
return super.register(name, funcOrVersion, funcOrOpts);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Register a module with Resonate. Registered functions can be invoked by calling {@link run}.
|
|
26
|
+
*
|
|
27
|
+
* @template F The type of the generator function.
|
|
28
|
+
* @param module The module to register with Resonate.
|
|
29
|
+
* @param opts Resonate options, can be constructed by calling {@link options}.
|
|
30
|
+
* @returns Resonate function
|
|
31
|
+
*/
|
|
32
|
+
registerModule(module, opts = {}) {
|
|
33
|
+
super.registerModule(module, opts);
|
|
34
|
+
}
|
|
35
|
+
execute(name, version, id, func, args, opts) {
|
|
36
|
+
return this.scheduler.add(name, version, id, func, args, opts);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/////////////////////////////////////////////////////////////////////
|
|
40
|
+
// Context
|
|
41
|
+
/////////////////////////////////////////////////////////////////////
|
|
42
|
+
export class Info {
|
|
43
|
+
invocation;
|
|
44
|
+
constructor(invocation) {
|
|
45
|
+
this.invocation = invocation;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The running count of function execution attempts.
|
|
49
|
+
*/
|
|
50
|
+
get attempt() {
|
|
51
|
+
return this.invocation.attempt;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Uniquely identifies the function invocation.
|
|
55
|
+
*/
|
|
56
|
+
get id() {
|
|
57
|
+
return this.invocation.id;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Deduplicates function invocations with the same id.
|
|
61
|
+
*/
|
|
62
|
+
get idempotencyKey() {
|
|
63
|
+
return this.invocation.idempotencyKey;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The timestamp in ms, once this time elapses the function invocation will timeout.
|
|
67
|
+
*/
|
|
68
|
+
get timeout() {
|
|
69
|
+
return this.invocation.timeout;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The resonate function version.
|
|
73
|
+
*/
|
|
74
|
+
get version() {
|
|
75
|
+
return this.invocation.version;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export class Context {
|
|
79
|
+
invocation;
|
|
80
|
+
constructor(invocation) {
|
|
81
|
+
this.invocation = invocation;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The running count of child function invocations.
|
|
85
|
+
*/
|
|
86
|
+
get counter() {
|
|
87
|
+
return this.invocation.counter;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Uniquely identifies the function invocation.
|
|
91
|
+
*/
|
|
92
|
+
get id() {
|
|
93
|
+
return this.invocation.id;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Deduplicates function invocations with the same id.
|
|
97
|
+
*/
|
|
98
|
+
get idempotencyKey() {
|
|
99
|
+
return this.invocation.idempotencyKey;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The timestamp in ms, once this time elapses the function invocation will timeout.
|
|
103
|
+
*/
|
|
104
|
+
get timeout() {
|
|
105
|
+
return this.invocation.timeout;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The resonate function version.
|
|
109
|
+
*/
|
|
110
|
+
get version() {
|
|
111
|
+
return this.invocation.version;
|
|
112
|
+
}
|
|
113
|
+
run(func, ...args) {
|
|
114
|
+
return this._call(func, args, false);
|
|
115
|
+
}
|
|
116
|
+
call(func, ...args) {
|
|
117
|
+
return this._call(func, args, true);
|
|
118
|
+
}
|
|
119
|
+
_call(func, argsWithOpts, yieldFuture) {
|
|
120
|
+
const { args, opts } = this.invocation.split(argsWithOpts);
|
|
121
|
+
if (typeof func === "string") {
|
|
122
|
+
return { kind: "call", value: { kind: "deferred", func, args: args[0], opts }, yieldFuture };
|
|
123
|
+
}
|
|
124
|
+
else if (func.constructor.name === "GeneratorFunction") {
|
|
125
|
+
return { kind: "call", value: { kind: "resonate", func, args, opts }, yieldFuture };
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
return { kind: "call", value: { kind: "ordinary", func, args, opts }, yieldFuture };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Construct options.
|
|
133
|
+
*
|
|
134
|
+
* @param opts A partial {@link Options} object.
|
|
135
|
+
* @returns Options with the __resonate flag set.
|
|
136
|
+
*/
|
|
137
|
+
options(opts = {}) {
|
|
138
|
+
return { ...opts, __resonate: true };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/////////////////////////////////////////////////////////////////////
|
|
142
|
+
// Scheduler
|
|
143
|
+
/////////////////////////////////////////////////////////////////////
|
|
144
|
+
class Scheduler {
|
|
145
|
+
logger;
|
|
146
|
+
// tick is mutually exclusive
|
|
147
|
+
running = false;
|
|
148
|
+
// all top level executions
|
|
149
|
+
cache = {};
|
|
150
|
+
// executions ready to be run
|
|
151
|
+
runnable = [];
|
|
152
|
+
// executions that are awaiting another execution
|
|
153
|
+
awaiting = [];
|
|
154
|
+
// all executions
|
|
155
|
+
executions = [];
|
|
156
|
+
// executions that have been killed
|
|
157
|
+
killed = [];
|
|
158
|
+
constructor(logger) {
|
|
159
|
+
this.logger = logger;
|
|
160
|
+
}
|
|
161
|
+
add(name, version, id, func, args, opts) {
|
|
162
|
+
// if the execution is already running, and not killed,
|
|
163
|
+
// return the promise
|
|
164
|
+
if (this.cache[id] && !this.cache[id].killed) {
|
|
165
|
+
return this.cache[id].execute();
|
|
166
|
+
}
|
|
167
|
+
// the idempotency key is a hash of the id
|
|
168
|
+
const idempotencyKey = utils.hash(id);
|
|
169
|
+
// params, used for recovery
|
|
170
|
+
const param = {
|
|
171
|
+
func: name,
|
|
172
|
+
version,
|
|
173
|
+
args,
|
|
174
|
+
};
|
|
175
|
+
// create a new invocation
|
|
176
|
+
const invocation = new Invocation(name, version, id, idempotencyKey, undefined, param, opts);
|
|
177
|
+
// create a new execution
|
|
178
|
+
const generator = func(new Context(invocation), ...args);
|
|
179
|
+
const execution = new GeneratorExecution(invocation, generator);
|
|
180
|
+
const promise = execution.execute();
|
|
181
|
+
// once the durable promise has been created,
|
|
182
|
+
// add the execution to runnable
|
|
183
|
+
execution.create().then((promise) => {
|
|
184
|
+
if (promise && promise.pending) {
|
|
185
|
+
this.runnable.push({ execution, promise, next: { kind: "init" } });
|
|
186
|
+
this.tick();
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
// store the invocation, execution, and promise
|
|
190
|
+
// will be used if run is called again with the same id
|
|
191
|
+
this.cache[id] = execution;
|
|
192
|
+
this.executions.push(execution);
|
|
193
|
+
return promise;
|
|
194
|
+
}
|
|
195
|
+
async tick() {
|
|
196
|
+
// need to ensure that tick is mutually exclusive,
|
|
197
|
+
// if tick is already running all continuations will be picked up in the while loop
|
|
198
|
+
if (this.running)
|
|
199
|
+
return;
|
|
200
|
+
this.running = true;
|
|
201
|
+
while (this.runnable.length > 0) {
|
|
202
|
+
// grab the next continuation
|
|
203
|
+
const continuation = this.runnable.shift();
|
|
204
|
+
// step through the generator if in debug mode
|
|
205
|
+
if (this.logger.level === "debug" && (await this.keypress()) === "\u001b") {
|
|
206
|
+
// kill when escape key is pressed
|
|
207
|
+
continuation?.execution.kill("manual kill");
|
|
208
|
+
}
|
|
209
|
+
if (continuation && !continuation.execution.killed) {
|
|
210
|
+
try {
|
|
211
|
+
// apply the next value to the generator
|
|
212
|
+
const result = this.next(continuation);
|
|
213
|
+
// if done, we can resolve the execution
|
|
214
|
+
if (result.done) {
|
|
215
|
+
// need to handle the special case where a generator returns a future
|
|
216
|
+
if (result.value instanceof Future) {
|
|
217
|
+
result.value.promise.then((v) => continuation.execution.resolve(continuation.promise, v), (e) => continuation.execution.reject(continuation.promise, e));
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
// resolve the durable promise
|
|
221
|
+
await continuation.execution.resolve(continuation.promise, result.value);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
// apply the yielded value to the generator
|
|
226
|
+
await this.apply(continuation, result.value);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
// if anything goes wrong, reject the durable promise
|
|
231
|
+
await continuation.execution.reject(continuation.promise, error);
|
|
232
|
+
}
|
|
233
|
+
// housekeeping
|
|
234
|
+
if (continuation.execution.killed) {
|
|
235
|
+
this.kill(continuation.execution);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// print all invocations if in debug mode
|
|
239
|
+
this.print();
|
|
240
|
+
}
|
|
241
|
+
// TODO: suspend
|
|
242
|
+
// set running back to false
|
|
243
|
+
this.running = false;
|
|
244
|
+
}
|
|
245
|
+
next({ execution, next }) {
|
|
246
|
+
// apply the next value to the generator
|
|
247
|
+
switch (next.kind) {
|
|
248
|
+
case "init":
|
|
249
|
+
return execution.generator.next();
|
|
250
|
+
case "value":
|
|
251
|
+
return execution.generator.next(next.value);
|
|
252
|
+
case "error":
|
|
253
|
+
return execution.generator.throw(next.error);
|
|
254
|
+
default:
|
|
255
|
+
this.yeet(`permitted continuation values are (init, value, error), received ${next}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async apply(continuation, yielded) {
|
|
259
|
+
// apply the yielded value to the generator
|
|
260
|
+
switch (yielded.kind) {
|
|
261
|
+
case "call":
|
|
262
|
+
await this.applyCall(continuation, yielded);
|
|
263
|
+
break;
|
|
264
|
+
case "future":
|
|
265
|
+
this.applyFuture(continuation, yielded);
|
|
266
|
+
break;
|
|
267
|
+
default:
|
|
268
|
+
this.yeet(`permitted yielded values are (call, future), received ${yielded}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
async applyCall(continuation, { value, yieldFuture }) {
|
|
272
|
+
// the parent is the current invocation
|
|
273
|
+
const parent = continuation.execution.invocation;
|
|
274
|
+
// the id is either:
|
|
275
|
+
// 1. a provided string in the case of a deferred execution
|
|
276
|
+
// 2. a generated string in the case of an ordinary execution
|
|
277
|
+
const id = value.kind === "deferred" ? value.func : `${parent.id}.${parent.counter}`;
|
|
278
|
+
// the idempotency key is a hash of the id
|
|
279
|
+
const idempotencyKey = utils.hash(id);
|
|
280
|
+
// human readable name of the function
|
|
281
|
+
const name = value.kind === "deferred" ? value.func : value.func.name;
|
|
282
|
+
// version is inherited from the parent
|
|
283
|
+
const version = parent.version;
|
|
284
|
+
// create a new invocation
|
|
285
|
+
const invocation = new Invocation(name, version, id, idempotencyKey, undefined, undefined, value.opts, parent);
|
|
286
|
+
// add child and increment counter
|
|
287
|
+
parent.addChild(invocation);
|
|
288
|
+
parent.counter++;
|
|
289
|
+
let execution;
|
|
290
|
+
if (value.kind === "resonate") {
|
|
291
|
+
// create a generator execution
|
|
292
|
+
const ctx = new Context(invocation);
|
|
293
|
+
execution = new GeneratorExecution(invocation, value.func(ctx, ...value.args));
|
|
294
|
+
const promise = await execution.create();
|
|
295
|
+
if (promise && promise.pending) {
|
|
296
|
+
// if the durable promise is pending, add to runnable
|
|
297
|
+
this.runnable.push({ execution, promise, next: { kind: "init" } });
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
else if (value.kind === "ordinary") {
|
|
301
|
+
// create an ordinary execution
|
|
302
|
+
const info = new Info(invocation);
|
|
303
|
+
execution = new OrdinaryExecution(invocation, () => value.func(info, ...value.args));
|
|
304
|
+
execution.execute().catch(() => { });
|
|
305
|
+
}
|
|
306
|
+
else if (value.kind === "deferred") {
|
|
307
|
+
// create a deferred execution
|
|
308
|
+
execution = new DeferredExecution(invocation);
|
|
309
|
+
execution.execute().catch(() => { });
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
this.yeet(`permitted call values are (resonate, ordinary, deferred), received ${value}`);
|
|
313
|
+
}
|
|
314
|
+
// add to all executions
|
|
315
|
+
this.executions.push(execution);
|
|
316
|
+
if (yieldFuture) {
|
|
317
|
+
// if the call is expected to yield a future, add the future to runnable
|
|
318
|
+
this.runnable.push({
|
|
319
|
+
execution: continuation.execution,
|
|
320
|
+
promise: continuation.promise,
|
|
321
|
+
next: { kind: "value", value: invocation.future },
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
// otherwise, skip ahead to apply future
|
|
326
|
+
this.applyFuture(continuation, invocation.future);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
applyFuture({ execution, promise }, future) {
|
|
330
|
+
if (execution.invocation.future.root !== future.root) {
|
|
331
|
+
this.yeet(`yielded future originates from ${future.root.id}, but this execution originates from ${execution.invocation.future.root.id}`);
|
|
332
|
+
}
|
|
333
|
+
// add to awaiting
|
|
334
|
+
this.awaiting.push(execution);
|
|
335
|
+
execution.invocation.await(future);
|
|
336
|
+
execution.invocation.block(future);
|
|
337
|
+
const apply = (next) => {
|
|
338
|
+
// unblock
|
|
339
|
+
execution.invocation.unblock();
|
|
340
|
+
// remove from awaiting
|
|
341
|
+
this.awaiting = this.awaiting.filter((e) => e !== execution);
|
|
342
|
+
// add to runnable
|
|
343
|
+
this.runnable.push({ execution, promise, next });
|
|
344
|
+
// tick again
|
|
345
|
+
this.tick();
|
|
346
|
+
};
|
|
347
|
+
// add the next value to runnable when the future fulfills
|
|
348
|
+
future.promise.then((value) => apply({ kind: "value", value }), (error) => apply({ kind: "error", error }));
|
|
349
|
+
}
|
|
350
|
+
kill(execution) {
|
|
351
|
+
// add to killed
|
|
352
|
+
const killed = this.executions.filter((e) => e.invocation.root === execution.invocation.root);
|
|
353
|
+
this.killed = this.killed.concat(killed);
|
|
354
|
+
// remove from awaiting and runnable
|
|
355
|
+
this.runnable = this.runnable.filter((c) => c.execution.invocation.root !== execution.invocation.root);
|
|
356
|
+
this.awaiting = this.awaiting.filter((e) => e.invocation.root !== execution.invocation.root);
|
|
357
|
+
}
|
|
358
|
+
async keypress() {
|
|
359
|
+
this.logger.debug("Press any key to continue...");
|
|
360
|
+
return new Promise((resolve) => {
|
|
361
|
+
const onData = (data) => {
|
|
362
|
+
process.stdin.removeListener("data", onData);
|
|
363
|
+
process.stdin.setRawMode(false);
|
|
364
|
+
process.stdin.pause();
|
|
365
|
+
const c = data.toString();
|
|
366
|
+
if (c === "\u0003") {
|
|
367
|
+
this.logger.debug("^C");
|
|
368
|
+
process.exit(1);
|
|
369
|
+
}
|
|
370
|
+
resolve(c);
|
|
371
|
+
};
|
|
372
|
+
process.stdin.resume();
|
|
373
|
+
process.stdin.setRawMode(true);
|
|
374
|
+
process.stdin.once("data", onData);
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
print() {
|
|
378
|
+
this.logger.debug("Executions");
|
|
379
|
+
this.logger.table(this.executions.map((e) => ({
|
|
380
|
+
name: e.invocation.name,
|
|
381
|
+
version: e.invocation.version,
|
|
382
|
+
id: e.invocation.id,
|
|
383
|
+
idempotencyKey: e.invocation.idempotencyKey,
|
|
384
|
+
parent: e.invocation.parent ? e.invocation.parent.id : undefined,
|
|
385
|
+
killed: e.killed,
|
|
386
|
+
pending: e.invocation.future.value.kind === "pending",
|
|
387
|
+
awaited: e.invocation.awaited.map((f) => f.id).join(","),
|
|
388
|
+
blocked: e.invocation.blocked?.id,
|
|
389
|
+
})));
|
|
390
|
+
}
|
|
391
|
+
yeet(msg) {
|
|
392
|
+
// an unrecoverable error has occurred, log and exit
|
|
393
|
+
this.logger.error(msg);
|
|
394
|
+
process.exit(1);
|
|
395
|
+
}
|
|
396
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC;AACjC,OAAO,KAAK,SAAS,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { IEncoder } from "./core/encoder";
|
|
2
|
+
import { ResonatePromise } from "./core/future";
|
|
3
|
+
import { ILogger } from "./core/logger";
|
|
4
|
+
import { ResonateOptions, Options } from "./core/options";
|
|
5
|
+
import { DurablePromise, CreateOptions, CompleteOptions } from "./core/promises/promises";
|
|
6
|
+
import { IRetry } from "./core/retry";
|
|
7
|
+
import { IStore } from "./core/store";
|
|
8
|
+
type Func = (...args: any[]) => any;
|
|
9
|
+
export declare abstract class ResonateBase {
|
|
10
|
+
private readonly functions;
|
|
11
|
+
readonly pid: string;
|
|
12
|
+
readonly timeout: number;
|
|
13
|
+
readonly tags: Record<string, string>;
|
|
14
|
+
protected readonly encoder: IEncoder<unknown, string | undefined>;
|
|
15
|
+
protected readonly logger: ILogger;
|
|
16
|
+
protected readonly retry: IRetry;
|
|
17
|
+
protected readonly store: IStore;
|
|
18
|
+
private interval;
|
|
19
|
+
constructor({ encoder, logger, pid, retry, store, tags, timeout, // 10s
|
|
20
|
+
url, }?: Partial<ResonateOptions>);
|
|
21
|
+
register(name: string, funcOrVersion: Func | number, funcOrOpts: Func | Partial<Options>): (id: string, ...args: any) => ResonatePromise<any>;
|
|
22
|
+
registerModule(module: Record<string, Func>, opts?: Partial<Options>): void;
|
|
23
|
+
/**
|
|
24
|
+
* Run a Resonate function. Functions must first be registered with {@link register}.
|
|
25
|
+
*
|
|
26
|
+
* @template T The return type of the function.
|
|
27
|
+
* @param id A unique id for the function invocation.
|
|
28
|
+
* @param name The function name.
|
|
29
|
+
* @param args The function arguments.
|
|
30
|
+
* @returns A promise that resolve to the function return value.
|
|
31
|
+
*/
|
|
32
|
+
run<T>(name: string, id: string, ...args: any[]): ResonatePromise<T>;
|
|
33
|
+
/**
|
|
34
|
+
* Run a Resonate function. Functions must first be registered with {@link register}.
|
|
35
|
+
*
|
|
36
|
+
* @template T The return type of the function.
|
|
37
|
+
* @param name The function name.
|
|
38
|
+
* @param version The function version.
|
|
39
|
+
* @param id A unique id for the function invocation.
|
|
40
|
+
* @param args The function arguments.
|
|
41
|
+
* @returns A promise that resolve to the function return value.
|
|
42
|
+
*/
|
|
43
|
+
run<T>(name: string, version: number, id: string, ...args: any[]): ResonatePromise<T>;
|
|
44
|
+
protected abstract execute(name: string, version: number, id: string, func: Func, args: any[], opts: Options): ResonatePromise<any>;
|
|
45
|
+
/**
|
|
46
|
+
* Start the resonate service.
|
|
47
|
+
*
|
|
48
|
+
* @param delay Frequency in ms to check for pending promises.
|
|
49
|
+
*/
|
|
50
|
+
start(delay?: number): void;
|
|
51
|
+
/**
|
|
52
|
+
* Stop the resonate service.
|
|
53
|
+
*/
|
|
54
|
+
stop(): void;
|
|
55
|
+
private _start;
|
|
56
|
+
get promises(): {
|
|
57
|
+
create: <T>(id: string, timeout: number, opts?: Partial<CreateOptions>) => Promise<DurablePromise<T>>;
|
|
58
|
+
resolve: <T_1>(id: string, value: T_1, opts?: Partial<CompleteOptions>) => Promise<DurablePromise<T_1>>;
|
|
59
|
+
reject: <T_2>(id: string, error: any, opts?: Partial<CompleteOptions>) => Promise<DurablePromise<T_2>>;
|
|
60
|
+
cancel: <T_3>(id: string, error: any, opts?: Partial<CompleteOptions>) => Promise<DurablePromise<T_3>>;
|
|
61
|
+
get: <T_4>(id: string) => Promise<DurablePromise<T_4>>;
|
|
62
|
+
search: (id: string, state?: string, tags?: Record<string, string>, limit?: number) => AsyncGenerator<DurablePromise<any>[], void, unknown>;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Construct options.
|
|
66
|
+
*
|
|
67
|
+
* @param opts A partial {@link Options} object.
|
|
68
|
+
* @returns Options with the __resonate flag set.
|
|
69
|
+
*/
|
|
70
|
+
options({ encoder, retry, store, tags, timeout, }: Partial<Options>): Options;
|
|
71
|
+
}
|
|
72
|
+
export {};
|
|
73
|
+
//# sourceMappingURL=resonate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resonate.d.ts","sourceRoot":"","sources":["../lib/resonate.ts"],"names":[],"mappings":"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"}
|
package/dist/resonate.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { JSONEncoder } from "./core/encoders/json";
|
|
2
|
+
import { Logger } from "./core/loggers/logger";
|
|
3
|
+
import { DurablePromise } from "./core/promises/promises";
|
|
4
|
+
import { Retry } from "./core/retries/retry";
|
|
5
|
+
import { LocalStore } from "./core/stores/local";
|
|
6
|
+
import { RemoteStore } from "./core/stores/remote";
|
|
7
|
+
import * as utils from "./core/utils";
|
|
8
|
+
/////////////////////////////////////////////////////////////////////
|
|
9
|
+
// Resonate
|
|
10
|
+
/////////////////////////////////////////////////////////////////////
|
|
11
|
+
export class ResonateBase {
|
|
12
|
+
functions = {};
|
|
13
|
+
pid;
|
|
14
|
+
timeout;
|
|
15
|
+
tags;
|
|
16
|
+
encoder;
|
|
17
|
+
logger;
|
|
18
|
+
retry;
|
|
19
|
+
store;
|
|
20
|
+
interval;
|
|
21
|
+
constructor({ encoder = new JSONEncoder(), logger = new Logger(), pid = utils.randomId(), retry = Retry.exponential(), store = undefined, tags = {}, timeout = 10000, // 10s
|
|
22
|
+
url = undefined, } = {}) {
|
|
23
|
+
this.encoder = encoder;
|
|
24
|
+
this.logger = logger;
|
|
25
|
+
this.pid = pid;
|
|
26
|
+
this.retry = retry;
|
|
27
|
+
this.tags = tags;
|
|
28
|
+
this.timeout = timeout;
|
|
29
|
+
if (store) {
|
|
30
|
+
this.store = store;
|
|
31
|
+
}
|
|
32
|
+
else if (url) {
|
|
33
|
+
this.store = new RemoteStore(url, this.pid, this.logger);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
this.store = new LocalStore(this.logger);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
register(name, funcOrVersion, funcOrOpts) {
|
|
40
|
+
const version = typeof funcOrVersion === "number" ? funcOrVersion : 1;
|
|
41
|
+
const func = typeof funcOrVersion === "function" ? funcOrVersion : typeof funcOrOpts === "function" ? funcOrOpts : null;
|
|
42
|
+
const opts = typeof funcOrOpts === "object" ? this.options(funcOrOpts) : this.options({});
|
|
43
|
+
if (!func) {
|
|
44
|
+
throw new Error("Must provide value for func");
|
|
45
|
+
}
|
|
46
|
+
if (version <= 0) {
|
|
47
|
+
throw new Error("Version must be greater than 0");
|
|
48
|
+
}
|
|
49
|
+
if (!this.functions[name]) {
|
|
50
|
+
this.functions[name] = {};
|
|
51
|
+
}
|
|
52
|
+
if (this.functions[name][version]) {
|
|
53
|
+
throw new Error(`Function ${name} version ${version} already registered`);
|
|
54
|
+
}
|
|
55
|
+
// register as latest (0) if version is greatest so far
|
|
56
|
+
if (version > Math.max(...Object.values(this.functions[name]).map((f) => f.version))) {
|
|
57
|
+
this.functions[name][0] = { version, func, opts };
|
|
58
|
+
}
|
|
59
|
+
// register specific version
|
|
60
|
+
this.functions[name][version] = { version, func, opts };
|
|
61
|
+
return (id, ...args) => this.run(name, version, id, ...args);
|
|
62
|
+
}
|
|
63
|
+
registerModule(module, opts = {}) {
|
|
64
|
+
for (const key in module) {
|
|
65
|
+
this.register(key, module[key], opts);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
run(name, idOrVersion, ...args) {
|
|
69
|
+
const id = typeof idOrVersion === "string" ? idOrVersion : args.shift();
|
|
70
|
+
const v = typeof idOrVersion === "number" ? idOrVersion : 0;
|
|
71
|
+
if (!this.functions[name] || !this.functions[name][v]) {
|
|
72
|
+
throw new Error(`Function ${name} version ${v} not registered`);
|
|
73
|
+
}
|
|
74
|
+
const { version, func, opts } = this.functions[name][v];
|
|
75
|
+
return this.execute(name, version, id, func, args, opts);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Start the resonate service.
|
|
79
|
+
*
|
|
80
|
+
* @param delay Frequency in ms to check for pending promises.
|
|
81
|
+
*/
|
|
82
|
+
start(delay = 5000) {
|
|
83
|
+
clearInterval(this.interval);
|
|
84
|
+
this.interval = setInterval(() => this._start(), delay);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Stop the resonate service.
|
|
88
|
+
*/
|
|
89
|
+
stop() {
|
|
90
|
+
clearInterval(this.interval);
|
|
91
|
+
}
|
|
92
|
+
async _start() {
|
|
93
|
+
for await (const promises of this.promises.search("*", "pending", { "resonate:invocation": "true" })) {
|
|
94
|
+
for (const promise of promises) {
|
|
95
|
+
try {
|
|
96
|
+
const param = promise.param();
|
|
97
|
+
if (param &&
|
|
98
|
+
typeof param === "object" &&
|
|
99
|
+
"func" in param &&
|
|
100
|
+
typeof param.func === "string" &&
|
|
101
|
+
"version" in param &&
|
|
102
|
+
typeof param.version === "number" &&
|
|
103
|
+
"args" in param &&
|
|
104
|
+
Array.isArray(param.args)) {
|
|
105
|
+
this.run(param.func, param.version, promise.id, param.args);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
this.logger.warn(e);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
get promises() {
|
|
115
|
+
return {
|
|
116
|
+
create: (id, timeout, opts = {}) => DurablePromise.create(this.store.promises, this.encoder, id, timeout, opts),
|
|
117
|
+
resolve: (id, value, opts = {}) => DurablePromise.resolve(this.store.promises, this.encoder, id, value, opts),
|
|
118
|
+
reject: (id, error, opts = {}) => DurablePromise.reject(this.store.promises, this.encoder, id, error, opts),
|
|
119
|
+
cancel: (id, error, opts = {}) => DurablePromise.cancel(this.store.promises, this.encoder, id, error, opts),
|
|
120
|
+
get: (id) => DurablePromise.get(this.store.promises, this.encoder, id),
|
|
121
|
+
search: (id, state, tags, limit) => DurablePromise.search(this.store.promises, this.encoder, id, state, tags, limit),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Construct options.
|
|
126
|
+
*
|
|
127
|
+
* @param opts A partial {@link Options} object.
|
|
128
|
+
* @returns Options with the __resonate flag set.
|
|
129
|
+
*/
|
|
130
|
+
options({ encoder = this.encoder, retry = this.retry, store = this.store, tags = {}, timeout = this.timeout, }) {
|
|
131
|
+
// merge tags
|
|
132
|
+
tags = { ...this.tags, ...tags };
|
|
133
|
+
return {
|
|
134
|
+
__resonate: true,
|
|
135
|
+
encoder,
|
|
136
|
+
retry,
|
|
137
|
+
store,
|
|
138
|
+
tags,
|
|
139
|
+
timeout,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|