@resonatehq/sdk 0.11.4 → 0.11.5
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/src/async/context.d.ts +5 -6
- package/dist/src/async/context.js +39 -30
- package/dist/src/async/core.js +47 -8
- package/dist/src/async/resonate.d.ts +1 -3
- package/dist/src/async/resonate.js +40 -17
- package/dist/src/computation.js +8 -7
- package/dist/src/context.d.ts +3 -5
- package/dist/src/context.js +46 -38
- package/dist/src/core.js +40 -1
- package/dist/src/exceptions.d.ts +1 -0
- package/dist/src/exceptions.js +5 -0
- package/dist/src/heartbeat.d.ts +15 -12
- package/dist/src/heartbeat.js +30 -25
- package/dist/src/ids.d.ts +46 -0
- package/dist/src/ids.js +85 -0
- package/dist/src/network/local.js +26 -2
- package/dist/src/network/nats.js +5 -5
- package/dist/src/network/types.d.ts +4 -0
- package/dist/src/options.d.ts +1 -3
- package/dist/src/options.js +5 -4
- package/dist/src/resonate.d.ts +1 -5
- package/dist/src/resonate.js +40 -19
- package/dist/src/util.d.ts +1 -1
- package/dist/src/util.js +10 -6
- package/package.json +1 -1
package/dist/src/resonate.d.ts
CHANGED
|
@@ -24,7 +24,6 @@ export declare class Resonate {
|
|
|
24
24
|
private clock;
|
|
25
25
|
private pid;
|
|
26
26
|
private ttl;
|
|
27
|
-
private idPrefix;
|
|
28
27
|
private core;
|
|
29
28
|
private codec;
|
|
30
29
|
private network;
|
|
@@ -57,10 +56,8 @@ export declare class Resonate {
|
|
|
57
56
|
* @param options.logger - Custom logger implementation. Defaults to {@link ConsoleLogger}.
|
|
58
57
|
* @param options.encryptor - Payload encryptor. Defaults to {@link NoopEncryptor}.
|
|
59
58
|
* @param options.network - Custom network implementation. Defaults to `undefined`.
|
|
60
|
-
* @param options.prefix - ID prefix applied to generated IDs. Defaults to
|
|
61
|
-
* `process.env.RESONATE_PREFIX` when set.
|
|
62
59
|
*/
|
|
63
|
-
constructor({ url, group, pid, ttl, token, timeout, verbose, logLevel, logger, encryptor, network,
|
|
60
|
+
constructor({ url, group, pid, ttl, token, timeout, verbose, logLevel, logger, encryptor, network, }?: {
|
|
64
61
|
url?: string;
|
|
65
62
|
group?: string;
|
|
66
63
|
pid?: string;
|
|
@@ -72,7 +69,6 @@ export declare class Resonate {
|
|
|
72
69
|
logger?: Logger;
|
|
73
70
|
encryptor?: Encryptor;
|
|
74
71
|
network?: Network;
|
|
75
|
-
prefix?: string;
|
|
76
72
|
});
|
|
77
73
|
/**
|
|
78
74
|
* Registers a function with Resonate for execution and version control.
|
package/dist/src/resonate.js
CHANGED
|
@@ -4,6 +4,7 @@ import { Core } from "./core.js";
|
|
|
4
4
|
import { NoopEncryptor } from "./encryptor.js";
|
|
5
5
|
import exceptions, { ResonateTimeoutException } from "./exceptions.js";
|
|
6
6
|
import { AsyncHeartbeat, NoopHeartbeat } from "./heartbeat.js";
|
|
7
|
+
import { validateRootId } from "./ids.js";
|
|
7
8
|
import { ConsoleLogger } from "./logger.js";
|
|
8
9
|
import { HttpNetwork, PollMessageSource } from "./network/http.js";
|
|
9
10
|
import { LocalNetwork } from "./network/local.js";
|
|
@@ -18,7 +19,6 @@ export class Resonate {
|
|
|
18
19
|
clock;
|
|
19
20
|
pid;
|
|
20
21
|
ttl;
|
|
21
|
-
idPrefix;
|
|
22
22
|
core;
|
|
23
23
|
codec;
|
|
24
24
|
network;
|
|
@@ -51,15 +51,11 @@ export class Resonate {
|
|
|
51
51
|
* @param options.logger - Custom logger implementation. Defaults to {@link ConsoleLogger}.
|
|
52
52
|
* @param options.encryptor - Payload encryptor. Defaults to {@link NoopEncryptor}.
|
|
53
53
|
* @param options.network - Custom network implementation. Defaults to `undefined`.
|
|
54
|
-
* @param options.prefix - ID prefix applied to generated IDs. Defaults to
|
|
55
|
-
* `process.env.RESONATE_PREFIX` when set.
|
|
56
54
|
*/
|
|
57
|
-
constructor({ url = undefined, group = "default", pid = undefined, ttl = 1 * util.MIN, token = undefined, timeout = undefined, verbose = false, logLevel = undefined, logger = undefined, encryptor = undefined, network = undefined,
|
|
55
|
+
constructor({ url = undefined, group = "default", pid = undefined, ttl = 1 * util.MIN, token = undefined, timeout = undefined, verbose = false, logLevel = undefined, logger = undefined, encryptor = undefined, network = undefined, } = {}) {
|
|
58
56
|
this.clock = new WallClock();
|
|
59
57
|
this.ttl = ttl;
|
|
60
58
|
this.codec = new Codec(encryptor ?? new NoopEncryptor());
|
|
61
|
-
const resolvedPrefix = prefix ?? getEnv("RESONATE_PREFIX");
|
|
62
|
-
this.idPrefix = resolvedPrefix ? `${resolvedPrefix}:` : "";
|
|
63
59
|
// Resolve logger: explicit logger > ConsoleLogger with resolved level
|
|
64
60
|
// logLevel takes precedence over verbose; verbose: true -> "debug"
|
|
65
61
|
const resolvedLogLevel = logLevel ?? (verbose ? "debug" : "warn");
|
|
@@ -106,7 +102,7 @@ export class Resonate {
|
|
|
106
102
|
}
|
|
107
103
|
this.registry = new Registry();
|
|
108
104
|
this.dependencies = new Map();
|
|
109
|
-
this.optsBuilder = new OptionsBuilder({ match: this.network.match.bind(this.network)
|
|
105
|
+
this.optsBuilder = new OptionsBuilder({ match: this.network.match.bind(this.network) });
|
|
110
106
|
this.core = new Core({
|
|
111
107
|
pid: this.pid,
|
|
112
108
|
ttl: this.ttl,
|
|
@@ -179,7 +175,11 @@ export class Resonate {
|
|
|
179
175
|
if (!registered) {
|
|
180
176
|
throw exceptions.REGISTRY_FUNCTION_NOT_REGISTERED(typeof funcOrName === "string" ? funcOrName : funcOrName.name, opts.version);
|
|
181
177
|
}
|
|
182
|
-
|
|
178
|
+
// Validated at the call site that named the workflow, rather than
|
|
179
|
+
// surfacing later as an opaque 400 from the server: the id becomes the
|
|
180
|
+
// origin of its whole lineage, so ':' is rejected outright ('.' is only
|
|
181
|
+
// read below the origin).
|
|
182
|
+
validateRootId(id);
|
|
183
183
|
util.assert(registered.version > 0, "function version must be greater than zero");
|
|
184
184
|
const { promise, task } = await this.taskCreate({
|
|
185
185
|
kind: "task.create",
|
|
@@ -204,10 +204,10 @@ export class Resonate {
|
|
|
204
204
|
},
|
|
205
205
|
tags: {
|
|
206
206
|
...opts.tags,
|
|
207
|
+
// A genuine top-level root is its own lineage origin, so
|
|
208
|
+
// origin == branch == parent == id here. Every descendant id
|
|
209
|
+
// extends it as `{id}:{lineage}`.
|
|
207
210
|
"resonate:origin": id,
|
|
208
|
-
// A genuine top-level root is its own lineage origin AND its own
|
|
209
|
-
// id-generation prefix; the prefix then propagates down unchanged.
|
|
210
|
-
"resonate:prefix": id,
|
|
211
211
|
"resonate:branch": id,
|
|
212
212
|
"resonate:parent": id,
|
|
213
213
|
"resonate:scope": "global",
|
|
@@ -234,7 +234,7 @@ export class Resonate {
|
|
|
234
234
|
if (typeof funcOrName === "function" && !registered) {
|
|
235
235
|
throw exceptions.REGISTRY_FUNCTION_NOT_REGISTERED(funcOrName.name, opts.version);
|
|
236
236
|
}
|
|
237
|
-
id
|
|
237
|
+
validateRootId(id);
|
|
238
238
|
const func = registered ? registered.name : funcOrName;
|
|
239
239
|
const version = registered ? registered.version : opts.version || 1;
|
|
240
240
|
const promise = await this.promiseCreate({
|
|
@@ -254,10 +254,10 @@ export class Resonate {
|
|
|
254
254
|
},
|
|
255
255
|
tags: {
|
|
256
256
|
...opts.tags,
|
|
257
|
+
// A genuine top-level root is its own lineage origin, so
|
|
258
|
+
// origin == branch == parent == id here. Every descendant id
|
|
259
|
+
// extends it as `{id}:{lineage}`.
|
|
257
260
|
"resonate:origin": id,
|
|
258
|
-
// A genuine top-level root is its own lineage origin AND its own
|
|
259
|
-
// id-generation prefix; the prefix then propagates down unchanged.
|
|
260
|
-
"resonate:prefix": id,
|
|
261
261
|
"resonate:branch": id,
|
|
262
262
|
"resonate:parent": id,
|
|
263
263
|
"resonate:scope": "global",
|
|
@@ -280,7 +280,15 @@ export class Resonate {
|
|
|
280
280
|
args: args,
|
|
281
281
|
version: registered ? registered.version : opts.version || 1,
|
|
282
282
|
});
|
|
283
|
-
|
|
283
|
+
// Each firing creates a root promise named from this id, so the schedule
|
|
284
|
+
// name is bound by the same rules as a run/rpc id. The server stamps the
|
|
285
|
+
// *whole* templated id onto the fired promise's resonate:origin tag, so
|
|
286
|
+
// the template must join with a plain '-': a ':' would hide the timestamp
|
|
287
|
+
// below the origin, collapsing every firing onto one lineage. A '.' is
|
|
288
|
+
// fine now — it is only read below the origin — but '-' keeps the
|
|
289
|
+
// timestamp clearly distinct from any dots in the schedule name.
|
|
290
|
+
validateRootId(name);
|
|
291
|
+
await this.schedules.create(name, cron, "{{.id}}-{{.timestamp}}", opts.timeout, {
|
|
284
292
|
promiseHeaders: headers,
|
|
285
293
|
promiseData: data,
|
|
286
294
|
promiseTags: { ...opts.tags, "resonate:target": opts.target },
|
|
@@ -290,7 +298,8 @@ export class Resonate {
|
|
|
290
298
|
};
|
|
291
299
|
}
|
|
292
300
|
async get(id) {
|
|
293
|
-
id
|
|
301
|
+
// get is a lookup, not a create: it takes any id, including a child's
|
|
302
|
+
// (e.g. "wf:1.2"), so it deliberately does NOT validate.
|
|
294
303
|
const promise = await this.promiseGet({
|
|
295
304
|
kind: "promise.get",
|
|
296
305
|
head: { corrId: randomUUID(), version: util.VERSION },
|
|
@@ -384,6 +393,14 @@ export class Resonate {
|
|
|
384
393
|
try {
|
|
385
394
|
const res = await this.send(req);
|
|
386
395
|
if (!isSuccess(res)) {
|
|
396
|
+
// Listener registration can be retried for transient server pressure,
|
|
397
|
+
// but semantic client errors cannot become valid by waiting.
|
|
398
|
+
if (res.head.status !== 429 && res.head.status !== 500) {
|
|
399
|
+
throw exceptions.SERVER_ERROR(res.data, false, {
|
|
400
|
+
code: res.head.status,
|
|
401
|
+
message: res.data,
|
|
402
|
+
});
|
|
403
|
+
}
|
|
387
404
|
await delay(retryDelay);
|
|
388
405
|
continue;
|
|
389
406
|
}
|
|
@@ -419,8 +436,12 @@ export class Resonate {
|
|
|
419
436
|
};
|
|
420
437
|
return {
|
|
421
438
|
id: promise.id,
|
|
422
|
-
done: () =>
|
|
423
|
-
|
|
439
|
+
done: () => promise.state === "pending"
|
|
440
|
+
? this.promiseRegisterListener(registerListenerReq).then((res) => res.state !== "pending")
|
|
441
|
+
: Promise.resolve(true),
|
|
442
|
+
result: () => promise.state === "pending"
|
|
443
|
+
? this.promiseRegisterListener(registerListenerReq).then((res) => this.subscribe(promise.id, res))
|
|
444
|
+
: this.subscribe(promise.id, promise),
|
|
424
445
|
};
|
|
425
446
|
}
|
|
426
447
|
onMessage(msg) {
|
package/dist/src/util.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export declare function isUrl(str: string): boolean;
|
|
|
18
18
|
export declare function base64Encode(str: string): string;
|
|
19
19
|
export declare function base64Decode(str: string): string;
|
|
20
20
|
export declare function semverLessThan(a: string, b: string): boolean;
|
|
21
|
-
export declare function detachedId(
|
|
21
|
+
export declare function detachedId(origin: string, seqid: string): string;
|
|
22
22
|
export declare function getCallerInfo(): string;
|
|
23
23
|
export declare function executeWithRetry(ctx: InnerContext, func: Func, args: any[], logger: Logger): Promise<Result<any, any>>;
|
|
24
24
|
export declare function buildEffects(send: Send, codec: Codec, fence: {
|
package/dist/src/util.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import exceptions from "./exceptions.js";
|
|
2
|
+
import { joinId } from "./ids.js";
|
|
2
3
|
import { isSuccess, } from "./network/types.js";
|
|
3
4
|
import { RESONATE_OPTIONS } from "./options.js";
|
|
4
5
|
import { randomUUID } from "./platform.js";
|
|
@@ -82,12 +83,15 @@ function cyrb53(input, seed = 0) {
|
|
|
82
83
|
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
|
83
84
|
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
|
84
85
|
}
|
|
85
|
-
export function detachedId(
|
|
86
|
-
// `${
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
|
|
86
|
+
export function detachedId(origin, seqid) {
|
|
87
|
+
// `${origin}:d${hash}` -- the `d` marks the segment as a detached child (vs a
|
|
88
|
+
// child's numeric `.${seq}`). `origin` is the lineage origin, set once at the
|
|
89
|
+
// top and propagated down unchanged forever, so recursive detached ids stay
|
|
90
|
+
// bounded at one segment past the origin instead of growing a segment per
|
|
91
|
+
// level. The child keeps the parent's origin rather than re-rooting onto its
|
|
92
|
+
// own id: the server derives the origin as everything before the first `:`
|
|
93
|
+
// and requires every id in a lineage to start with `{origin}:`.
|
|
94
|
+
return joinId(origin, `d${cyrb53(seqid).toString(16).padStart(14, "0")}`);
|
|
91
95
|
}
|
|
92
96
|
export function getCallerInfo() {
|
|
93
97
|
const err = new Error();
|