@resonatehq/sdk 0.6.2 → 0.7.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 (202) hide show
  1. package/README.md +24 -45
  2. package/dist/dev/network.d.ts +16 -0
  3. package/dist/dev/network.d.ts.map +1 -0
  4. package/dist/dev/network.js +89 -0
  5. package/dist/dev/network.js.map +1 -0
  6. package/dist/dev/server.d.ts +87 -0
  7. package/dist/dev/server.d.ts.map +1 -0
  8. package/dist/dev/server.js +891 -0
  9. package/dist/dev/server.js.map +1 -0
  10. package/dist/sim/example-1.d.ts +2 -0
  11. package/dist/sim/example-1.d.ts.map +1 -0
  12. package/dist/sim/example-1.js +94 -0
  13. package/dist/sim/example-1.js.map +1 -0
  14. package/dist/sim/example-2.d.ts +2 -0
  15. package/dist/sim/example-2.d.ts.map +1 -0
  16. package/dist/sim/example-2.js +95 -0
  17. package/dist/sim/example-2.js.map +1 -0
  18. package/dist/sim/example-3.d.ts +2 -0
  19. package/dist/sim/example-3.d.ts.map +1 -0
  20. package/dist/sim/example-3.js +95 -0
  21. package/dist/sim/example-3.js.map +1 -0
  22. package/dist/sim/main.d.ts +14 -0
  23. package/dist/sim/main.d.ts.map +1 -0
  24. package/dist/sim/main.js +206 -0
  25. package/dist/sim/main.js.map +1 -0
  26. package/dist/sim/src/server.d.ts +15 -0
  27. package/dist/sim/src/server.d.ts.map +1 -0
  28. package/dist/sim/src/server.js +85 -0
  29. package/dist/sim/src/server.js.map +1 -0
  30. package/dist/sim/src/simulator.d.ts +65 -0
  31. package/dist/sim/src/simulator.d.ts.map +1 -0
  32. package/dist/sim/src/simulator.js +235 -0
  33. package/dist/sim/src/simulator.js.map +1 -0
  34. package/dist/sim/src/worker.d.ts +22 -0
  35. package/dist/sim/src/worker.d.ts.map +1 -0
  36. package/dist/sim/src/worker.js +175 -0
  37. package/dist/sim/src/worker.js.map +1 -0
  38. package/dist/src/clock.d.ts +11 -0
  39. package/dist/src/clock.d.ts.map +1 -0
  40. package/dist/src/clock.js +17 -0
  41. package/dist/src/clock.js.map +1 -0
  42. package/dist/src/computation.d.ts +40 -0
  43. package/dist/src/computation.d.ts.map +1 -0
  44. package/dist/src/computation.js +225 -0
  45. package/dist/src/computation.js.map +1 -0
  46. package/dist/src/context.d.ts +120 -0
  47. package/dist/src/context.d.ts.map +1 -0
  48. package/dist/src/context.js +288 -0
  49. package/dist/src/context.js.map +1 -0
  50. package/dist/src/coroutine.d.ts +35 -0
  51. package/dist/src/coroutine.d.ts.map +1 -0
  52. package/dist/src/coroutine.js +251 -0
  53. package/dist/src/coroutine.js.map +1 -0
  54. package/dist/src/decorator.d.ts +14 -0
  55. package/dist/src/decorator.d.ts.map +1 -0
  56. package/dist/src/decorator.js +208 -0
  57. package/dist/src/decorator.js.map +1 -0
  58. package/dist/src/handler.d.ts +40 -0
  59. package/dist/src/handler.d.ts.map +1 -0
  60. package/dist/src/handler.js +198 -0
  61. package/dist/src/handler.js.map +1 -0
  62. package/dist/src/heartbeat.d.ts +22 -0
  63. package/dist/src/heartbeat.d.ts.map +1 -0
  64. package/dist/src/heartbeat.js +87 -0
  65. package/dist/src/heartbeat.js.map +1 -0
  66. package/dist/src/index.d.ts +8 -0
  67. package/dist/src/index.d.ts.map +1 -0
  68. package/dist/src/index.js +25 -0
  69. package/dist/src/index.js.map +1 -0
  70. package/dist/src/network/network.d.ts +216 -0
  71. package/dist/src/network/network.d.ts.map +1 -0
  72. package/dist/{core/logger.js → src/network/network.js} +2 -1
  73. package/dist/src/network/network.js.map +1 -0
  74. package/dist/src/network/remote.d.ts +67 -0
  75. package/dist/src/network/remote.d.ts.map +1 -0
  76. package/dist/src/network/remote.js +514 -0
  77. package/dist/src/network/remote.js.map +1 -0
  78. package/dist/src/nursery.d.ts +19 -0
  79. package/dist/src/nursery.d.ts.map +1 -0
  80. package/dist/src/nursery.js +102 -0
  81. package/dist/src/nursery.js.map +1 -0
  82. package/dist/src/processor/processor.d.ts +11 -0
  83. package/dist/src/processor/processor.d.ts.map +1 -0
  84. package/dist/src/processor/processor.js +24 -0
  85. package/dist/src/processor/processor.js.map +1 -0
  86. package/dist/src/promises.d.ts +23 -0
  87. package/dist/src/promises.d.ts.map +1 -0
  88. package/dist/src/promises.js +157 -0
  89. package/dist/src/promises.js.map +1 -0
  90. package/dist/src/registry.d.ts +15 -0
  91. package/dist/src/registry.d.ts.map +1 -0
  92. package/dist/src/registry.js +26 -0
  93. package/dist/src/registry.js.map +1 -0
  94. package/dist/src/resonate-inner.d.ts +45 -0
  95. package/dist/src/resonate-inner.d.ts.map +1 -0
  96. package/dist/src/resonate-inner.js +157 -0
  97. package/dist/src/resonate-inner.js.map +1 -0
  98. package/dist/src/resonate.d.ts +92 -0
  99. package/dist/src/resonate.d.ts.map +1 -0
  100. package/dist/src/resonate.js +240 -0
  101. package/dist/src/resonate.js.map +1 -0
  102. package/dist/src/schedules.d.ts +9 -0
  103. package/dist/src/schedules.d.ts.map +1 -0
  104. package/dist/src/schedules.js +65 -0
  105. package/dist/src/schedules.js.map +1 -0
  106. package/dist/src/tasks.d.ts +9 -0
  107. package/dist/src/tasks.d.ts.map +1 -0
  108. package/dist/src/tasks.js +61 -0
  109. package/dist/src/tasks.js.map +1 -0
  110. package/dist/src/types.d.ts +86 -0
  111. package/dist/src/types.d.ts.map +1 -0
  112. package/dist/src/types.js +14 -0
  113. package/dist/src/types.js.map +1 -0
  114. package/dist/src/util.d.ts +14 -0
  115. package/dist/src/util.d.ts.map +1 -0
  116. package/dist/src/util.js +69 -0
  117. package/dist/src/util.js.map +1 -0
  118. package/dist/tsconfig.build.tsbuildinfo +1 -0
  119. package/package.json +51 -52
  120. package/dist/core/encoder.d.ts +0 -5
  121. package/dist/core/encoder.d.ts.map +0 -1
  122. package/dist/core/encoder.js +0 -3
  123. package/dist/core/encoder.js.map +0 -1
  124. package/dist/core/encoders/base64.d.ts +0 -6
  125. package/dist/core/encoders/base64.d.ts.map +0 -1
  126. package/dist/core/encoders/base64.js +0 -13
  127. package/dist/core/encoders/base64.js.map +0 -1
  128. package/dist/core/encoders/json.d.ts +0 -6
  129. package/dist/core/encoders/json.d.ts.map +0 -1
  130. package/dist/core/encoders/json.js +0 -77
  131. package/dist/core/encoders/json.js.map +0 -1
  132. package/dist/core/errors.d.ts +0 -24
  133. package/dist/core/errors.d.ts.map +0 -1
  134. package/dist/core/errors.js +0 -42
  135. package/dist/core/errors.js.map +0 -1
  136. package/dist/core/logger.d.ts +0 -10
  137. package/dist/core/logger.d.ts.map +0 -1
  138. package/dist/core/logger.js.map +0 -1
  139. package/dist/core/loggers/logger.d.ts +0 -13
  140. package/dist/core/loggers/logger.d.ts.map +0 -1
  141. package/dist/core/loggers/logger.js +0 -48
  142. package/dist/core/loggers/logger.js.map +0 -1
  143. package/dist/core/options.d.ts +0 -160
  144. package/dist/core/options.d.ts.map +0 -1
  145. package/dist/core/options.js +0 -18
  146. package/dist/core/options.js.map +0 -1
  147. package/dist/core/promises/promises.d.ts +0 -210
  148. package/dist/core/promises/promises.d.ts.map +0 -1
  149. package/dist/core/promises/promises.js +0 -300
  150. package/dist/core/promises/promises.js.map +0 -1
  151. package/dist/core/promises/types.d.ts +0 -26
  152. package/dist/core/promises/types.d.ts.map +0 -1
  153. package/dist/core/promises/types.js +0 -37
  154. package/dist/core/promises/types.js.map +0 -1
  155. package/dist/core/retry.d.ts +0 -37
  156. package/dist/core/retry.d.ts.map +0 -1
  157. package/dist/core/retry.js +0 -141
  158. package/dist/core/retry.js.map +0 -1
  159. package/dist/core/schedules/schedules.d.ts +0 -22
  160. package/dist/core/schedules/schedules.d.ts.map +0 -1
  161. package/dist/core/schedules/schedules.js +0 -29
  162. package/dist/core/schedules/schedules.js.map +0 -1
  163. package/dist/core/schedules/types.d.ts +0 -19
  164. package/dist/core/schedules/types.d.ts.map +0 -1
  165. package/dist/core/schedules/types.js +0 -8
  166. package/dist/core/schedules/types.js.map +0 -1
  167. package/dist/core/storage.d.ts +0 -6
  168. package/dist/core/storage.d.ts.map +0 -1
  169. package/dist/core/storage.js +0 -3
  170. package/dist/core/storage.js.map +0 -1
  171. package/dist/core/storages/memory.d.ts +0 -8
  172. package/dist/core/storages/memory.d.ts.map +0 -1
  173. package/dist/core/storages/memory.js +0 -29
  174. package/dist/core/storages/memory.js.map +0 -1
  175. package/dist/core/storages/withTimeout.d.ts +0 -10
  176. package/dist/core/storages/withTimeout.d.ts.map +0 -1
  177. package/dist/core/storages/withTimeout.js +0 -44
  178. package/dist/core/storages/withTimeout.js.map +0 -1
  179. package/dist/core/store.d.ts +0 -142
  180. package/dist/core/store.d.ts.map +0 -1
  181. package/dist/core/store.js +0 -3
  182. package/dist/core/store.js.map +0 -1
  183. package/dist/core/stores/local.d.ts +0 -57
  184. package/dist/core/stores/local.d.ts.map +0 -1
  185. package/dist/core/stores/local.js +0 -405
  186. package/dist/core/stores/local.js.map +0 -1
  187. package/dist/core/stores/remote.d.ts +0 -51
  188. package/dist/core/stores/remote.d.ts.map +0 -1
  189. package/dist/core/stores/remote.js +0 -431
  190. package/dist/core/stores/remote.js.map +0 -1
  191. package/dist/core/utils.d.ts +0 -85
  192. package/dist/core/utils.d.ts.map +0 -1
  193. package/dist/core/utils.js +0 -121
  194. package/dist/core/utils.js.map +0 -1
  195. package/dist/index.d.ts +0 -19
  196. package/dist/index.d.ts.map +0 -1
  197. package/dist/index.js +0 -57
  198. package/dist/index.js.map +0 -1
  199. package/dist/resonate.d.ts +0 -312
  200. package/dist/resonate.d.ts.map +0 -1
  201. package/dist/resonate.js +0 -718
  202. package/dist/resonate.js.map +0 -1
package/dist/resonate.js DELETED
@@ -1,718 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
- Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.InvocationHandle = exports.Context = exports.Resonate = void 0;
27
- const json_1 = require("./core/encoders/json");
28
- const errors_1 = require("./core/errors");
29
- const logger_1 = require("./core/loggers/logger");
30
- const options_1 = require("./core/options");
31
- const durablePromises = __importStar(require("./core/promises/promises"));
32
- const retryPolicies = __importStar(require("./core/retry"));
33
- const retry_1 = require("./core/retry");
34
- const schedules = __importStar(require("./core/schedules/schedules"));
35
- const local_1 = require("./core/stores/local");
36
- const remote_1 = require("./core/stores/remote");
37
- const utils = __importStar(require("./core/utils"));
38
- const utils_1 = require("./core/utils");
39
- //////////////////////////////////////////////////////////////////////
40
- class Resonate {
41
- #registeredFunctions = {};
42
- #invocationHandles;
43
- #interval;
44
- store;
45
- logger;
46
- defaultInvocationOptions;
47
- promises;
48
- schedules;
49
- constructor({ auth = undefined, encoder = new json_1.JSONEncoder(), heartbeat = 15000, // 15s
50
- logger = new logger_1.Logger(), pid = utils.randomId(), pollFrequency = 5000, // 5s
51
- retryPolicy = retryPolicies.exponential(), store = undefined, tags = {}, timeout = 10000, // 10s
52
- url = undefined, } = {}) {
53
- if (store) {
54
- this.store = store;
55
- }
56
- else if (url) {
57
- this.store = new remote_1.RemoteStore(url, {
58
- auth,
59
- heartbeat,
60
- logger,
61
- pid,
62
- });
63
- }
64
- else {
65
- this.store = new local_1.LocalStore({
66
- auth,
67
- heartbeat,
68
- logger,
69
- pid,
70
- });
71
- }
72
- this.logger = logger;
73
- this.#invocationHandles = new Map();
74
- this.defaultInvocationOptions = {
75
- __resonate: true,
76
- durable: true,
77
- eidFn: utils.randomId,
78
- encoder: encoder,
79
- idempotencyKeyFn: utils.hash,
80
- shouldLock: undefined,
81
- pollFrequency,
82
- retryPolicy,
83
- tags,
84
- timeout,
85
- version: 0,
86
- };
87
- // promises
88
- this.promises = {
89
- create: (id, timeout, opts = {}) => durablePromises.DurablePromise.create(this.promisesStore, encoder, id, timeout, opts),
90
- resolve: (id, value, opts = {}) => durablePromises.DurablePromise.resolve(this.promisesStore, encoder, id, value, opts),
91
- reject: (id, error, opts = {}) => durablePromises.DurablePromise.reject(this.promisesStore, encoder, id, error, opts),
92
- cancel: (id, error, opts = {}) => durablePromises.DurablePromise.cancel(this.promisesStore, encoder, id, error, opts),
93
- get: (id) => durablePromises.DurablePromise.get(this.promisesStore, encoder, id),
94
- search: (id, state, tags, limit) => durablePromises.DurablePromise.search(this.promisesStore, encoder, id, state, tags, limit),
95
- };
96
- // schedules
97
- this.schedules = {
98
- create: (id, cron, promiseId, promiseTimeout, opts = {}) => schedules.Schedule.create(this.store.schedules, encoder, id, cron, promiseId, promiseTimeout, opts),
99
- get: (id) => schedules.Schedule.get(this.store.schedules, encoder, id),
100
- search: (id, tags, limit) => schedules.Schedule.search(this.store.schedules, encoder, id, tags, limit),
101
- };
102
- }
103
- get promisesStore() {
104
- return this.store.promises;
105
- }
106
- get locksStore() {
107
- return this.store.locks;
108
- }
109
- options(opts) {
110
- return (0, options_1.options)(opts);
111
- }
112
- registeredFunction(funcName, version) {
113
- if (!this.#registeredFunctions[funcName]?.[version]) {
114
- throw new Error(`Function ${funcName} version ${version} not registered`);
115
- }
116
- return this.#registeredFunctions[funcName][version];
117
- }
118
- register(name, func, opts = {}) {
119
- // set default version
120
- opts.version = opts.version ?? 1;
121
- // set default values for the options options
122
- const options = this.withDefaultOpts(opts);
123
- if (options.version <= 0) {
124
- throw new Error("Version must be greater than 0");
125
- }
126
- if (!this.#registeredFunctions[name]) {
127
- this.#registeredFunctions[name] = {};
128
- }
129
- if (this.#registeredFunctions[name][options.version]) {
130
- throw new Error(`Function ${name} version ${options.version} already registered`);
131
- }
132
- // Get the highest version number of existing functions with this name
133
- const latestVersion = Math.max(...Object.values(this.#registeredFunctions[name]).map((f) => f.opts.version));
134
- // If the new function's version is higher, register it as the latest (index 0)
135
- if (options.version > latestVersion) {
136
- this.#registeredFunctions[name][0] = { func, opts: options };
137
- }
138
- // register specific version
139
- this.#registeredFunctions[name][options.version] = { func, opts: options };
140
- }
141
- registerModule(module, opts = {}) {
142
- for (const key in module) {
143
- this.register(key, module[key], opts);
144
- }
145
- }
146
- /**
147
- * Start the resonate service which continually checks for pending promises
148
- * every `delay` ms.
149
- *
150
- * @param delay Frequency in ms to check for pending promises.
151
- */
152
- async start(delay = 5000) {
153
- clearInterval(this.#interval);
154
- // await the first run of the recovery path to avoid races with the normal flow of the program
155
- await this.#_start();
156
- this.#interval = setInterval(this.#_start.bind(this), delay);
157
- }
158
- /**
159
- * Stop the resonate service.
160
- */
161
- async stop() {
162
- clearInterval(this.#interval);
163
- }
164
- async #_start() {
165
- try {
166
- for await (const promises of this.promisesStore.search("*", "pending", { "resonate:invocation": "true" })) {
167
- for (const promiseRecord of promises) {
168
- const param = this.defaultInvocationOptions.encoder.decode(promiseRecord.param.data);
169
- if (param &&
170
- typeof param === "object" &&
171
- "func" in param &&
172
- typeof param.func === "string" &&
173
- "version" in param &&
174
- typeof param.version === "number" &&
175
- "args" in param &&
176
- Array.isArray(param.args) &&
177
- "retryPolicy" in param &&
178
- retryPolicies.isRetryPolicy(param.retryPolicy)) {
179
- // Since the promise is already created on the server, we should use that idempotencyKey.
180
- // If for whatever reason it is not, we should recalculate it using our defaults.
181
- const idempotencyKeyFn = (_) => {
182
- return (promiseRecord.idempotencyKeyForCreate ??
183
- this.defaultInvocationOptions.idempotencyKeyFn(promiseRecord.id));
184
- };
185
- await this.invokeLocal(param.func, promiseRecord.id, ...param.args, (0, options_1.options)({
186
- retryPolicy: param.retryPolicy,
187
- version: param.version,
188
- idempotencyKeyFn,
189
- }));
190
- }
191
- }
192
- }
193
- }
194
- catch (e) {
195
- // squash all errors and log,
196
- // transient errors will be ironed out in the next interval
197
- this.logger.error(e);
198
- }
199
- }
200
- async run(name, id, ...argsWithOverrides) {
201
- const handle = await this.invokeLocal(name, id, ...argsWithOverrides);
202
- return await handle.result();
203
- }
204
- async invokeLocal(name, id, ...argsWithOverrides) {
205
- if (this.#invocationHandles.has(id)) {
206
- return this.#invocationHandles.get(id);
207
- }
208
- const { args, opts: optionOverrides } = utils.split(argsWithOverrides);
209
- // version 0 means the latest registered version
210
- const givenVersion = optionOverrides?.version ?? 0;
211
- // guarantees we only use the overrides in case the user pass an object with more properties
212
- const { eidFn, idempotencyKeyFn, retryPolicy, tags, timeout, version } = optionOverrides;
213
- const { func, opts: registeredOpts } = this.registeredFunction(name, givenVersion);
214
- const opts = utils.mergeObjects({
215
- eidFn,
216
- idempotencyKeyFn,
217
- retryPolicy,
218
- tags,
219
- timeout,
220
- version,
221
- }, registeredOpts);
222
- // We want to preserve the registered version.
223
- opts.version = registeredOpts.version;
224
- // For tags we need to merge the objects themselves
225
- // giving priority to the passed tags and add the
226
- // resonate:invocation tag to identify a top level invocation
227
- opts.tags = { ...registeredOpts.tags, ...tags, "resonate:invocation": "true" };
228
- // locking is false by default
229
- opts.shouldLock = opts.shouldLock ?? false;
230
- const param = {
231
- func: name,
232
- version: opts.version,
233
- retryPolicy: opts.retryPolicy,
234
- args,
235
- };
236
- const idempotencyKey = opts.idempotencyKeyFn(id);
237
- const storedPromise = await this.promisesStore.create(id, idempotencyKey, false, undefined, opts.encoder.encode(param), Date.now() + opts.timeout, opts.tags);
238
- const ctx = Context.createRootContext(this, { id, name, opts, eid: opts.eidFn(id) });
239
- const resultPromise = _runFunc(func, ctx, args, idempotencyKey, storedPromise, this.store.locks, this.store.promises);
240
- const handle = new InvocationHandle(id, resultPromise);
241
- this.#invocationHandles.set(id, handle);
242
- return handle;
243
- }
244
- async schedule(name, cron, func, ...argsWithOpts) {
245
- const { args, opts: givenOpts } = utils.split(argsWithOpts);
246
- const opts = this.withDefaultOpts(givenOpts);
247
- if (typeof func === "function") {
248
- // if function is provided, the default version is 1
249
- // as opposed to 0 (alias for latest version)
250
- opts.version = opts.version || 1;
251
- this.register(name, func, opts);
252
- }
253
- const funcName = typeof func === "string" ? func : name;
254
- const { opts: { retryPolicy: retry, version, timeout, tags: promiseTags }, } = this.registeredFunction(funcName, opts.version);
255
- const idempotencyKey = opts.idempotencyKeyFn(funcName);
256
- const promiseParam = {
257
- func: funcName,
258
- version,
259
- retryPolicy: retry,
260
- args,
261
- };
262
- return await this.schedules.create(name, cron, "{{.id}}.{{.timestamp}}", timeout, {
263
- idempotencyKey,
264
- promiseParam,
265
- promiseTags,
266
- });
267
- }
268
- withDefaultOpts(givenOpts = {}) {
269
- // merge tags
270
- const tags = { ...this.defaultInvocationOptions.tags, ...givenOpts.tags };
271
- return {
272
- ...this.defaultInvocationOptions,
273
- ...givenOpts,
274
- tags,
275
- };
276
- }
277
- }
278
- exports.Resonate = Resonate;
279
- class Context {
280
- #resonate;
281
- #stopAllPolling = false;
282
- #invocationHandles;
283
- #aborted;
284
- #abortCause;
285
- #resources;
286
- #finalizers;
287
- childrenCount;
288
- invocationData;
289
- parent;
290
- root;
291
- constructor(resonate, invocationData, parent) {
292
- this.#resonate = resonate;
293
- this.#invocationHandles = new Map();
294
- this.#resources = new Map();
295
- this.#finalizers = [];
296
- this.#aborted = false;
297
- this.parent = parent;
298
- this.root = !parent ? this : parent.root;
299
- this.invocationData = invocationData;
300
- this.childrenCount = 0;
301
- }
302
- static createRootContext(resonate, invocationData) {
303
- return new Context(resonate, invocationData, undefined);
304
- }
305
- static createChildrenContext(parentCtx, invocationData) {
306
- return new Context(parentCtx.#resonate, invocationData, parentCtx);
307
- }
308
- async onRetry() {
309
- this.childrenCount = 0;
310
- await this.finalize();
311
- }
312
- async finalize() {
313
- // It is important to await all promises before finalizing the resources
314
- // doing it the other way around could cause problems
315
- await Promise.allSettled(Array.from(this.#invocationHandles, ([_, handle]) => handle.result()));
316
- // We need to run the finalizers in reverse insertion order since later set finalizers might have
317
- // a dependency in early set resources
318
- for (const finalizer of this.#finalizers.reverse()) {
319
- await finalizer();
320
- }
321
- this.#resources.clear();
322
- this.#finalizers = [];
323
- }
324
- abort(cause) {
325
- this.#aborted = true;
326
- this.#abortCause = cause;
327
- this.root.#aborted = true;
328
- this.root.#abortCause = cause;
329
- }
330
- get aborted() {
331
- return this.#aborted;
332
- }
333
- get abortCause() {
334
- return this.#abortCause;
335
- }
336
- /**
337
- * Adds a finalizer function to be executed at the end of the current context.
338
- * Finalizers are run in reverse order of their definition (last-in, first-out).
339
- *
340
- * @param fn - An asynchronous function to be executed as a finalizer.
341
- * It should return a Promise that resolves to void.
342
- *
343
- * @remarks
344
- * Finalizer functions must be non fallible.
345
- */
346
- addFinalizer(fn) {
347
- this.#finalizers.push(fn);
348
- }
349
- /**
350
- * Sets a named resource for the current context and optionally adds a finalizer.
351
- *
352
- * @param name - A unique string identifier for the resource.
353
- * @param resource - The resource to be stored. Can be of any type.
354
- * @param finalizer - Optional. An asynchronous function to be executed when the context ends.
355
- * Finalizers are run in reverse order of their addition to the context and
356
- * must not fail.
357
- * @throws {Error} Throws an error if a resource with the same name already exists in the current context.
358
- *
359
- * This method associates a resource with a unique name in the current context.
360
- * If a finalizer is provided, it will be executed when the context ends.
361
- * Finalizers are useful for cleanup operations, such as closing connections or freeing resources.
362
- */
363
- setResource(name, resource, finalizer) {
364
- if (this.#resources.has(name)) {
365
- throw new Error("Resource already set for this context");
366
- }
367
- this.#resources.set(name, resource);
368
- if (finalizer) {
369
- this.#finalizers.push(finalizer);
370
- }
371
- }
372
- /**
373
- * Retrieves a resource by name from the current context or its parent contexts.
374
- *
375
- * @template R - The expected type of the resource.
376
- * @param name - The unique string identifier of the resource to retrieve.
377
- * @returns The resource of type R if found, or undefined if not found.
378
- *
379
- * This method searches for a resource in the following order:
380
- * 1. In the current context.
381
- * 2. If not found, it recursively searches in parent contexts.
382
- * 3. Returns undefined if the resource is not found in any context.
383
- *
384
- * @remarks
385
- * The method uses type assertion to cast the resource to type R.
386
- * Ensure that the type parameter R matches the actual type of the stored resource
387
- * to avoid runtime type errors.
388
- */
389
- getResource(name) {
390
- const resource = this.#resources.get(name);
391
- if (resource) {
392
- return resource;
393
- }
394
- return this.parent ? this.parent.getResource(name) : undefined;
395
- }
396
- options(opts) {
397
- return (0, options_1.options)(opts);
398
- }
399
- async run(funcOrId, ...argsWithOpts) {
400
- let handle;
401
- if (typeof funcOrId === "string") {
402
- handle = await this.invokeRemote(funcOrId, ...argsWithOpts);
403
- }
404
- else {
405
- handle = await this.invokeLocal(funcOrId, ...argsWithOpts);
406
- }
407
- return await handle.result();
408
- }
409
- async invokeRemote(funcId, ...argsWithOpts) {
410
- if (this.#invocationHandles.has(funcId)) {
411
- return this.#invocationHandles.get(funcId);
412
- }
413
- const { opts: givenOpts } = utils.split(argsWithOpts);
414
- const { opts: registeredOpts } = this.#resonate.registeredFunction(this.root.invocationData.name, this.root.invocationData.opts.version);
415
- const opts = { ...registeredOpts, ...givenOpts };
416
- // Merge the tags
417
- opts.tags = { ...registeredOpts.tags, ...givenOpts?.tags };
418
- // Default lock is false for children execution
419
- opts.shouldLock = opts.shouldLock ?? false;
420
- // Children execution do not need params since we don't go trough the recovery path with children
421
- const param = {};
422
- const idempotencyKey = opts.idempotencyKeyFn(funcId);
423
- const storedPromise = await this.#resonate.promisesStore.create(funcId, idempotencyKey, false, undefined, opts.encoder.encode(param), Date.now() + opts.timeout, opts.tags);
424
- const runFunc = async () => {
425
- while (!this.#stopAllPolling) {
426
- const durablePromiseRecord = await this.#resonate.promisesStore.get(storedPromise.id);
427
- switch (durablePromiseRecord.state) {
428
- case "RESOLVED":
429
- return opts.encoder.decode(durablePromiseRecord.value.data);
430
- case "REJECTED":
431
- throw opts.encoder.decode(durablePromiseRecord.value.data);
432
- case "REJECTED_CANCELED":
433
- throw new errors_1.ResonateError("Resonate function canceled", errors_1.ErrorCodes.CANCELED, opts.encoder.decode(durablePromiseRecord.value.data));
434
- case "REJECTED_TIMEDOUT":
435
- throw new errors_1.ResonateError(`Resonate function timedout at ${new Date(durablePromiseRecord.timeout).toISOString()}`, errors_1.ErrorCodes.TIMEDOUT);
436
- case "PENDING":
437
- break;
438
- }
439
- // TODO: Consider using exponential backoff instead.
440
- (0, utils_1.sleep)(opts.pollFrequency);
441
- }
442
- throw new Error(`Polling of remote invocation with ${funcId} was stopped`);
443
- };
444
- const resultPromise = runFunc();
445
- const invocationHandle = new InvocationHandle(funcId, resultPromise);
446
- this.#invocationHandles.set(funcId, invocationHandle);
447
- return invocationHandle;
448
- }
449
- async invokeLocal(func, ...argsWithOpts) {
450
- const { args, opts: givenOpts } = utils.split(argsWithOpts);
451
- const { opts: registeredOpts } = this.#resonate.registeredFunction(this.root.invocationData.name, this.root.invocationData.opts.version);
452
- const opts = { ...registeredOpts, ...givenOpts };
453
- // Merge the tags
454
- opts.tags = { ...registeredOpts.tags, ...givenOpts.tags };
455
- // Default lock is false for children execution
456
- opts.shouldLock = opts.shouldLock ?? false;
457
- this.childrenCount++;
458
- // If it is an anonymous function give at anon name nested within the current invocation name
459
- const name = func.name ? func.name : `${this.invocationData.name}__anon${this.childrenCount}`;
460
- const id = `${this.invocationData.id}.${this.childrenCount}.${name}`;
461
- if (this.#invocationHandles.has(id)) {
462
- return this.#invocationHandles.get(id);
463
- }
464
- const ctx = Context.createChildrenContext(this, { name, id, eid: opts.eidFn(id), opts });
465
- if (!opts.durable) {
466
- const runFunc = async () => {
467
- const timeout = Date.now() + opts.timeout;
468
- return (await (0, retry_1.runWithRetry)(async () => await func(ctx, ...args), async () => await ctx.onRetry(), opts.retryPolicy, timeout));
469
- };
470
- const resultPromise = runFunc();
471
- const handle = new InvocationHandle(id, resultPromise);
472
- this.#invocationHandles.set(id, handle);
473
- return handle;
474
- }
475
- // Children execution do not need params since we don't go trough the recovery path with children
476
- const param = {};
477
- const idempotencyKey = opts.idempotencyKeyFn(id);
478
- const storedPromise = await this.#resonate.promisesStore.create(id, idempotencyKey, false, undefined, opts.encoder.encode(param), Date.now() + opts.timeout, opts.tags);
479
- const resultPromise = _runFunc(func, ctx, args, idempotencyKey, storedPromise, this.#resonate.store.locks, this.#resonate.store.promises);
480
- const invocationHandle = new InvocationHandle(id, resultPromise);
481
- this.#invocationHandles.set(id, invocationHandle);
482
- return invocationHandle;
483
- }
484
- /**
485
- * Durable version of sleep.
486
- * Sleep for the specified time (ms).
487
- *
488
- * @param ms Amount of time to sleep in milliseconds.
489
- * @returns A Promise that resolves after the specified time has elapsed.
490
- */
491
- async sleep(ms) {
492
- const id = `${this.invocationData.id}.${this.childrenCount++}`;
493
- const handle = await this.invokeRemote(id, (0, options_1.options)({ timeout: ms, pollFrequency: ms, tags: { "resonate:timeout": "true" }, durable: true }));
494
- await handle.result();
495
- }
496
- /**
497
- * Creates a Promise that is resolved with an array of results when all of the provided Promises
498
- * resolve, or rejected when any Promise is rejected.
499
- *
500
- * @param values An array of Promises.
501
- * @param opts Optional {@link options}.
502
- * @returns A new ResonatePromise.
503
- */
504
- all(values, opts = {}) {
505
- // catch all promises to prevent unhandled promise rejections,
506
- // since Promise.all will not be called in the case where the
507
- // durable promise already completed
508
- for (const v of values) {
509
- if (v instanceof Promise)
510
- v.catch(() => { });
511
- }
512
- // prettier-ignore
513
- return this.run(() => Promise.all(values), (0, options_1.options)({
514
- retryPolicy: retryPolicies.never(),
515
- ...opts,
516
- }));
517
- }
518
- /**
519
- * Creates a Promise that is fulfilled by the first given promise to be fulfilled, or rejected
520
- * with an AggregateError.
521
- *
522
- * @param values An array of Promises.
523
- * @param opts Optional {@link options}.
524
- * @returns A new ResonatePromise.
525
- */
526
- any(values, opts = {}) {
527
- // catch all promises to prevent unhandled promise rejections,
528
- // since Promise.any will not be called in the case where the
529
- // durable promise already completed
530
- for (const v of values) {
531
- if (v instanceof Promise)
532
- v.catch(() => { });
533
- }
534
- // prettier-ignore
535
- return this.run(() => Promise.any(values), (0, options_1.options)({
536
- retryPolicy: retryPolicies.never(),
537
- ...opts,
538
- }));
539
- }
540
- /**
541
- * Creates a Promise that is resolved or rejected when any of the provided Promises are resolved
542
- * or rejected.
543
- *
544
- * @param values An array of Promises.
545
- * @param opts Optional {@link options}.
546
- * @returns A new ResonatePromise.
547
- */
548
- race(values, opts = {}) {
549
- // catch all promises to prevent unhandled promise rejections,
550
- // since Promise.race will not be called in the case where the
551
- // durable promise already completed
552
- for (const v of values) {
553
- if (v instanceof Promise)
554
- v.catch(() => { });
555
- }
556
- // prettier-ignore
557
- return this.run(() => Promise.race(values), (0, options_1.options)({
558
- retryPolicy: retryPolicies.never(),
559
- ...opts,
560
- }));
561
- }
562
- /**
563
- * Creates a Promise that is resolved with an array of results when all of the provided Promises
564
- * resolve or reject.
565
- *
566
- * @param values An array of Promises.
567
- * @param opts Optional {@link options}.
568
- * @returns A new Promise.
569
- */
570
- allSettled(values, opts = {}) {
571
- // catch all promises to prevent unhandled promise rejections,
572
- // since Promise.allSettled will not be called in the case where the
573
- // durable promise already completed
574
- for (const v of values) {
575
- if (v instanceof Promise)
576
- v.catch(() => { });
577
- }
578
- // prettier-ignore
579
- return this.run(() => Promise.allSettled(values), (0, options_1.options)({
580
- retryPolicy: retryPolicies.never(),
581
- ...opts,
582
- }));
583
- }
584
- /**
585
- * Invoke a Resonate function in detached mode. Functions must first be registered with Resonate.
586
- * a detached invocation will not be implecitly awaited at the end of the current context, instead
587
- * it will be "supervised" as a top level invocation.
588
- *
589
- * @template R The return type of the function.
590
- * @param id A unique id for the function invocation.
591
- * @param name The function name.
592
- * @param argsWithOverrides The function arguments and options overrides.
593
- * @returns A Res.
594
- */
595
- async detached(name, id, ...argsWithOverrides) {
596
- return await this.#resonate.invokeLocal(name, id, ...argsWithOverrides);
597
- }
598
- }
599
- exports.Context = Context;
600
- class InvocationHandle {
601
- invocationId;
602
- resultPromise;
603
- constructor(invocationId, resultPromise) {
604
- this.invocationId = invocationId;
605
- this.resultPromise = resultPromise;
606
- }
607
- /**
608
- * get the current state of the resultPromise.
609
- *
610
- */
611
- async state() {
612
- return await utils.promiseState(this.resultPromise);
613
- }
614
- async result() {
615
- return this.resultPromise;
616
- }
617
- }
618
- exports.InvocationHandle = InvocationHandle;
619
- const acquireLock = async (id, eid, locksStore) => {
620
- try {
621
- return await locksStore.tryAcquire(id, eid);
622
- }
623
- catch (e) {
624
- // if lock is already acquired, return false so we can poll
625
- if (e instanceof errors_1.ResonateError && e.code === errors_1.ErrorCodes.STORE_FORBIDDEN) {
626
- return false;
627
- }
628
- throw e;
629
- }
630
- };
631
- const _runFunc = async (func, ctx, args, idempotencyKey, storedPromise, locksStore, promisesStore) => {
632
- const { id, eid, opts } = ctx.invocationData;
633
- // If the promise that comes back from the server is already completed, resolve or reject right away.
634
- switch (storedPromise.state) {
635
- case "RESOLVED":
636
- return opts.encoder.decode(storedPromise.value.data);
637
- case "REJECTED":
638
- throw opts.encoder.decode(storedPromise.value.data);
639
- case "REJECTED_CANCELED":
640
- throw new errors_1.ResonateError("Resonate function canceled", errors_1.ErrorCodes.CANCELED, opts.encoder.decode(storedPromise.value.data));
641
- case "REJECTED_TIMEDOUT":
642
- throw new errors_1.ResonateError(`Resonate function timedout at ${new Date(storedPromise.timeout).toISOString()}`, errors_1.ErrorCodes.TIMEDOUT);
643
- }
644
- // storedPromise.state === "PENDING"
645
- try {
646
- // Acquire the lock if necessary
647
- if (opts.shouldLock) {
648
- while (!(await acquireLock(id, eid, locksStore))) {
649
- await (0, utils_1.sleep)(opts.pollFrequency);
650
- }
651
- }
652
- let error;
653
- let value;
654
- // we need to hold on to a boolean to determine if the function was successful,
655
- // we cannot rely on the value or error as these values could be undefined
656
- let success = true;
657
- try {
658
- value = await (0, retry_1.runWithRetry)(async () => await func(ctx, ...args), async () => await ctx.onRetry(), opts.retryPolicy, storedPromise.timeout);
659
- }
660
- catch (e) {
661
- // We need to capture the error to be able to reject the durable promise,
662
- // after that we will then propagate this error by rejecting the result promise
663
- error = e;
664
- success = false;
665
- }
666
- finally {
667
- // Resonate will implicitly await all the invocationHandles as the last
668
- // thing it does with the context before it goes out of scope
669
- await ctx.finalize();
670
- }
671
- if (ctx.root.aborted) {
672
- throw new errors_1.ResonateError("Unrecoverable Error: Aborting", errors_1.ErrorCodes.ABORT, ctx.root.abortCause);
673
- }
674
- let completedPromiseRecord;
675
- if (success) {
676
- completedPromiseRecord = await promisesStore.resolve(id, idempotencyKey, false, storedPromise.value.headers, opts.encoder.encode(value));
677
- }
678
- else {
679
- completedPromiseRecord = await promisesStore.reject(id, idempotencyKey, false, storedPromise.value.headers, opts.encoder.encode(error));
680
- }
681
- // Because of eventual consistency and recovery paths it is possible that we get a
682
- // rejected promise even if we did call `resolve` on it or the other way around.
683
- // What should never happen is that we get a "PENDING" promise
684
- switch (completedPromiseRecord.state) {
685
- case "RESOLVED":
686
- return value;
687
- case "REJECTED":
688
- throw error;
689
- case "REJECTED_CANCELED":
690
- throw new errors_1.ResonateError("Resonate function canceled", errors_1.ErrorCodes.CANCELED, error);
691
- case "REJECTED_TIMEDOUT":
692
- throw new errors_1.ResonateError(`Resonate function timedout at ${new Date(completedPromiseRecord.timeout).toISOString()}`, errors_1.ErrorCodes.TIMEDOUT);
693
- case "PENDING":
694
- throw new Error("Unreachable");
695
- }
696
- }
697
- catch (err) {
698
- if (err instanceof errors_1.ResonateError && (err.code === errors_1.ErrorCodes.CANCELED || err.code === errors_1.ErrorCodes.TIMEDOUT)) {
699
- // Cancel and timeout errors, just forward them
700
- throw err;
701
- }
702
- else if (err instanceof errors_1.ResonateError && err.code !== errors_1.ErrorCodes.ABORT) {
703
- // Any other instance of ResonateError we must abort the current execution.
704
- ctx.abort(err);
705
- throw new errors_1.ResonateError("Unrecoverable Error: Aborting", errors_1.ErrorCodes.ABORT, err);
706
- }
707
- else {
708
- throw err;
709
- }
710
- }
711
- finally {
712
- // release lock if necessary
713
- if (opts.shouldLock) {
714
- await locksStore.release(id, eid);
715
- }
716
- }
717
- };
718
- //# sourceMappingURL=resonate.js.map