@reboot-dev/reboot 0.30.0 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -44,6 +44,7 @@ export declare class ExternalContext {
44
44
  export declare function getContext(): Context;
45
45
  export declare function isWithinUntil(): boolean;
46
46
  export declare function isWithinLoop(): boolean;
47
+ export declare function getLoopIteration(): number;
47
48
  export declare function runWithContext<T>(context: Context, callback: () => T): Promise<T>;
48
49
  export declare class Context {
49
50
  #private;
@@ -323,6 +324,12 @@ export declare function untilPerWorkflow<T>(idempotencyAlias: string, context: W
323
324
  parse?: undefined;
324
325
  validate: (result: T) => boolean;
325
326
  }): Promise<Exclude<T, boolean>>;
327
+ export declare function untilChanges<T>(idempotencyAlias: string, context: WorkflowContext, callable: () => Promise<T>, options: {
328
+ equals: (previous: T, current: T) => boolean;
329
+ stringify?: (result: T) => string;
330
+ parse?: (value: string) => T;
331
+ validate?: (result: T) => boolean;
332
+ }): Promise<T>;
326
333
  export declare const zod: {
327
334
  tasks: {
328
335
  TaskId: z.ZodCustom<protobuf_es.PartialMessage<tasks_pb.TaskId>, protobuf_es.PartialMessage<tasks_pb.TaskId>>;
package/index.js CHANGED
@@ -9,7 +9,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
9
9
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
- var _Reboot_external, _ExternalContext_external, _a, _Context_external, _Context_isInternalConstructing, _ReaderContext_kind, _WriterContext_kind, _TransactionContext_kind, _WorkflowContext_kind, _Application_servicers, _Application_tokenVerifier, _Application_express, _Application_http, _Application_servers, _Application_createExternalContext, _Application_external;
12
+ var _Reboot_external, _ExternalContext_external, _a, _Context_external, _Context_isInternalConstructing, _Context_stateId, _ReaderContext_kind, _WriterContext_kind, _TransactionContext_kind, _WorkflowContext_kind, _Application_servicers, _Application_tokenVerifier, _Application_express, _Application_http, _Application_servers, _Application_createExternalContext, _Application_external;
13
13
  import { auth_pb, errors_pb, protobuf_es, tasks_pb, } from "@reboot-dev/reboot-api";
14
14
  import { strict as assert } from "assert";
15
15
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -148,6 +148,13 @@ export function isWithinLoop() {
148
148
  }
149
149
  return store.withinLoop;
150
150
  }
151
+ export function getLoopIteration() {
152
+ const store = contextStorage.getStore();
153
+ if (!store) {
154
+ throw new Error("`getLoopIteration` may only be called within a `Servicer` method.");
155
+ }
156
+ return store.loopIteration;
157
+ }
151
158
  export async function runWithContext(context, callback) {
152
159
  return await contextStorage.run({
153
160
  context,
@@ -158,6 +165,7 @@ export async function runWithContext(context, callback) {
158
165
  export class Context {
159
166
  constructor(external, cancelled) {
160
167
  _Context_external.set(this, void 0);
168
+ _Context_stateId.set(this, void 0);
161
169
  if (!__classPrivateFieldGet(_a, _a, "f", _Context_isInternalConstructing)) {
162
170
  throw new TypeError("Context is not publicly constructable");
163
171
  }
@@ -192,7 +200,10 @@ export class Context {
192
200
  return null;
193
201
  }
194
202
  get stateId() {
195
- return reboot_native.Context_stateId(__classPrivateFieldGet(this, _Context_external, "f"));
203
+ if (__classPrivateFieldGet(this, _Context_stateId, "f") === undefined) {
204
+ __classPrivateFieldSet(this, _Context_stateId, reboot_native.Context_stateId(__classPrivateFieldGet(this, _Context_external, "f")), "f");
205
+ }
206
+ return __classPrivateFieldGet(this, _Context_stateId, "f");
196
207
  }
197
208
  get callerBearerToken() {
198
209
  return reboot_native.Context_callerBearerToken(__classPrivateFieldGet(this, _Context_external, "f"));
@@ -207,7 +218,7 @@ export class Context {
207
218
  return reboot_native.Context_generateIdempotentStateId(__classPrivateFieldGet(this, _Context_external, "f"), stateType, serviceName, method, idempotency);
208
219
  }
209
220
  }
210
- _a = Context, _Context_external = new WeakMap();
221
+ _a = Context, _Context_external = new WeakMap(), _Context_stateId = new WeakMap();
211
222
  _Context_isInternalConstructing = { value: false };
212
223
  export class ReaderContext extends Context {
213
224
  constructor(external, cancelled) {
@@ -278,6 +289,7 @@ export class WorkflowContext extends Context {
278
289
  if (iteration === null) {
279
290
  return;
280
291
  }
292
+ store.loopIteration = iteration;
281
293
  yield iteration;
282
294
  if (ms > 0) {
283
295
  await new Promise((resolve) => setTimeout(resolve, ms));
@@ -289,6 +301,7 @@ export class WorkflowContext extends Context {
289
301
  await iterate(false);
290
302
  }
291
303
  store.withinLoop = false;
304
+ delete store.loopIteration;
292
305
  }
293
306
  }
294
307
  }
@@ -678,12 +691,9 @@ export async function retryReactivelyUntil(context, condition) {
678
691
  }
679
692
  }
680
693
  catch (e) {
681
- if (e instanceof Error) {
682
- throw e;
683
- }
684
- else {
685
- throw new Error(`${e}`);
686
- }
694
+ const error = ensureError(e);
695
+ console.error(error);
696
+ throw error;
687
697
  }
688
698
  finally {
689
699
  store.withinUntil = false;
@@ -737,10 +747,15 @@ async function memoize(idempotencyAliasOrTuple, context, callable, { stringify =
737
747
  return "";
738
748
  }
739
749
  catch (e) {
740
- console.warn(e);
741
- throw e;
750
+ const error = ensureError(e);
751
+ // We handle printing the exception for `until` in
752
+ // `retryReactivelyUntil`.
753
+ if (!until) {
754
+ console.error(error);
755
+ }
756
+ throw error;
742
757
  }
743
- }), atMostOnce);
758
+ }), atMostOnce, until);
744
759
  // NOTE: we parse and validate `value` every time (even the first
745
760
  // time, even though we validate above). These semantics are the
746
761
  // same as Python (although Python uses the `type` keyword argument
@@ -806,6 +821,37 @@ export async function until(idempotencyAliasOrTuple, context, callable, options
806
821
  export async function untilPerWorkflow(idempotencyAlias, context, callable, options = { validate: undefined }) {
807
822
  return await until([idempotencyAlias, PER_WORKFLOW], context, callable, options);
808
823
  }
824
+ export async function untilChanges(idempotencyAlias, context, callable, options) {
825
+ const iteration = getLoopIteration();
826
+ if (iteration === undefined) {
827
+ throw new Error("Waiting for changes must be done _within_ a control loop");
828
+ }
829
+ if (options.equals === undefined) {
830
+ // TODO: don't make `equals` required, instead use one of the
831
+ // various libraries that does deep equality.
832
+ throw new Error("Missing 'equals' option");
833
+ }
834
+ const { equals, ...optionsWithoutEquals } = options;
835
+ let previous = null;
836
+ if (iteration > 0) {
837
+ // Get the previous memoized result!
838
+ previous = (await untilPerWorkflow(`${idempotencyAlias} #${iteration - 1}`, context, (async () => {
839
+ throw new Error(`Missing memoized value for '${idempotencyAlias}'`);
840
+ }), optionsWithoutEquals));
841
+ }
842
+ // Wait until previous result does not equal current result.
843
+ return (await untilPerWorkflow(`${idempotencyAlias} #${iteration}`, context, (async () => {
844
+ const current = await callable();
845
+ if (iteration === 0) {
846
+ return current;
847
+ }
848
+ assert(previous !== null);
849
+ if (!equals(previous, current)) {
850
+ return current;
851
+ }
852
+ return false;
853
+ }), optionsWithoutEquals));
854
+ }
809
855
  const launchSubprocessConsensus = (base64_args) => {
810
856
  // Create a child process via `fork` (which does not mean `fork` as
811
857
  // in POSIX fork/clone) that uses the exact same module that was
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "@bufbuild/protobuf": "1.3.2",
4
4
  "@bufbuild/protoplugin": "1.3.2",
5
5
  "@bufbuild/protoc-gen-es": "1.3.2",
6
- "@reboot-dev/reboot-api": "0.30.0",
6
+ "@reboot-dev/reboot-api": "0.31.0",
7
7
  "chalk": "^4.1.2",
8
8
  "node-addon-api": "^7.0.0",
9
9
  "node-gyp": ">=10.2.0",
@@ -18,7 +18,7 @@
18
18
  },
19
19
  "type": "module",
20
20
  "name": "@reboot-dev/reboot",
21
- "version": "0.30.0",
21
+ "version": "0.31.0",
22
22
  "description": "npm package for Reboot",
23
23
  "scripts": {
24
24
  "preinstall": "node preinstall.cjs",
package/reboot_native.cc CHANGED
@@ -2672,6 +2672,8 @@ Napi::Value memoize(const Napi::CallbackInfo& info) {
2672
2672
 
2673
2673
  bool at_most_once = info[3].As<Napi::Boolean>();
2674
2674
 
2675
+ bool until = info[4].As<Napi::Boolean>();
2676
+
2675
2677
  return NodePromiseFromPythonTaskWithContext(
2676
2678
  info.Env(),
2677
2679
  "memoize(...) in nodejs",
@@ -2681,7 +2683,8 @@ Napi::Value memoize(const Napi::CallbackInfo& info) {
2681
2683
  alias = std::move(alias),
2682
2684
  how = std::move(how),
2683
2685
  js_callable = std::move(js_callable),
2684
- at_most_once]() {
2686
+ at_most_once,
2687
+ until]() {
2685
2688
  py::object py_callable = py::cpp_function(
2686
2689
  [js_callable = std::move(js_callable)]() mutable {
2687
2690
  return PythonFutureFromNodePromise(
@@ -2704,7 +2707,8 @@ Napi::Value memoize(const Napi::CallbackInfo& info) {
2704
2707
  py_context,
2705
2708
  py_callable,
2706
2709
  "type_t"_a = py::eval("str"),
2707
- "at_most_once"_a = at_most_once);
2710
+ "at_most_once"_a = at_most_once,
2711
+ "until"_a = until);
2708
2712
  },
2709
2713
  [](py::object py_json) {
2710
2714
  return py_json.cast<std::string>();
package/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const REBOOT_VERSION = "0.30.0";
1
+ export declare const REBOOT_VERSION = "0.31.0";
package/version.js CHANGED
@@ -1 +1 @@
1
- export const REBOOT_VERSION = "0.30.0";
1
+ export const REBOOT_VERSION = "0.31.0";