@reboot-dev/reboot 0.30.0 → 0.32.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;
@@ -53,6 +54,8 @@ export declare class Context {
53
54
  get __external(): any;
54
55
  get auth(): Auth | null;
55
56
  get stateId(): string;
57
+ get stateTypeName(): string;
58
+ get method(): string;
56
59
  get callerBearerToken(): string | null;
57
60
  get cookie(): string;
58
61
  get appInternal(): boolean;
@@ -323,6 +326,12 @@ export declare function untilPerWorkflow<T>(idempotencyAlias: string, context: W
323
326
  parse?: undefined;
324
327
  validate: (result: T) => boolean;
325
328
  }): Promise<Exclude<T, boolean>>;
329
+ export declare function untilChanges<T>(idempotencyAlias: string, context: WorkflowContext, callable: () => Promise<T>, options: {
330
+ equals: (previous: T, current: T) => boolean;
331
+ stringify?: (result: T) => string;
332
+ parse?: (value: string) => T;
333
+ validate?: (result: T) => boolean;
334
+ }): Promise<T>;
326
335
  export declare const zod: {
327
336
  tasks: {
328
337
  TaskId: z.ZodCustom<protobuf_es.PartialMessage<tasks_pb.TaskId>, protobuf_es.PartialMessage<tasks_pb.TaskId>>;
package/index.js CHANGED
@@ -9,8 +9,8 @@ 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;
13
- import { auth_pb, errors_pb, protobuf_es, tasks_pb, } from "@reboot-dev/reboot-api";
12
+ var _Reboot_external, _ExternalContext_external, _a, _Context_external, _Context_isInternalConstructing, _Context_stateId, _Context_method, _Context_stateTypeName, _ReaderContext_kind, _WriterContext_kind, _TransactionContext_kind, _WorkflowContext_kind, _Application_servicers, _Application_tokenVerifier, _Application_express, _Application_http, _Application_servers, _Application_createExternalContext, _Application_external;
13
+ import { auth_pb, errors_pb, protobuf_es, toCamelCase, tasks_pb, } from "@reboot-dev/reboot-api";
14
14
  import { strict as assert } from "assert";
15
15
  import { AsyncLocalStorage } from "node:async_hooks";
16
16
  import { fork } from "node:child_process";
@@ -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,9 @@ 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);
169
+ _Context_method.set(this, void 0);
170
+ _Context_stateTypeName.set(this, void 0);
161
171
  if (!__classPrivateFieldGet(_a, _a, "f", _Context_isInternalConstructing)) {
162
172
  throw new TypeError("Context is not publicly constructable");
163
173
  }
@@ -192,7 +202,22 @@ export class Context {
192
202
  return null;
193
203
  }
194
204
  get stateId() {
195
- return reboot_native.Context_stateId(__classPrivateFieldGet(this, _Context_external, "f"));
205
+ if (__classPrivateFieldGet(this, _Context_stateId, "f") === undefined) {
206
+ __classPrivateFieldSet(this, _Context_stateId, reboot_native.Context_stateId(__classPrivateFieldGet(this, _Context_external, "f")), "f");
207
+ }
208
+ return __classPrivateFieldGet(this, _Context_stateId, "f");
209
+ }
210
+ get stateTypeName() {
211
+ if (__classPrivateFieldGet(this, _Context_stateTypeName, "f") === undefined) {
212
+ __classPrivateFieldSet(this, _Context_stateTypeName, reboot_native.Context_stateTypeName(__classPrivateFieldGet(this, _Context_external, "f")), "f");
213
+ }
214
+ return __classPrivateFieldGet(this, _Context_stateTypeName, "f");
215
+ }
216
+ get method() {
217
+ if (__classPrivateFieldGet(this, _Context_method, "f") === undefined) {
218
+ __classPrivateFieldSet(this, _Context_method, toCamelCase(reboot_native.Context_method(__classPrivateFieldGet(this, _Context_external, "f"))), "f");
219
+ }
220
+ return __classPrivateFieldGet(this, _Context_method, "f");
196
221
  }
197
222
  get callerBearerToken() {
198
223
  return reboot_native.Context_callerBearerToken(__classPrivateFieldGet(this, _Context_external, "f"));
@@ -207,7 +232,7 @@ export class Context {
207
232
  return reboot_native.Context_generateIdempotentStateId(__classPrivateFieldGet(this, _Context_external, "f"), stateType, serviceName, method, idempotency);
208
233
  }
209
234
  }
210
- _a = Context, _Context_external = new WeakMap();
235
+ _a = Context, _Context_external = new WeakMap(), _Context_stateId = new WeakMap(), _Context_method = new WeakMap(), _Context_stateTypeName = new WeakMap();
211
236
  _Context_isInternalConstructing = { value: false };
212
237
  export class ReaderContext extends Context {
213
238
  constructor(external, cancelled) {
@@ -278,6 +303,7 @@ export class WorkflowContext extends Context {
278
303
  if (iteration === null) {
279
304
  return;
280
305
  }
306
+ store.loopIteration = iteration;
281
307
  yield iteration;
282
308
  if (ms > 0) {
283
309
  await new Promise((resolve) => setTimeout(resolve, ms));
@@ -289,6 +315,7 @@ export class WorkflowContext extends Context {
289
315
  await iterate(false);
290
316
  }
291
317
  store.withinLoop = false;
318
+ delete store.loopIteration;
292
319
  }
293
320
  }
294
321
  }
@@ -678,12 +705,9 @@ export async function retryReactivelyUntil(context, condition) {
678
705
  }
679
706
  }
680
707
  catch (e) {
681
- if (e instanceof Error) {
682
- throw e;
683
- }
684
- else {
685
- throw new Error(`${e}`);
686
- }
708
+ const error = ensureError(e);
709
+ console.error(error);
710
+ throw error;
687
711
  }
688
712
  finally {
689
713
  store.withinUntil = false;
@@ -737,10 +761,15 @@ async function memoize(idempotencyAliasOrTuple, context, callable, { stringify =
737
761
  return "";
738
762
  }
739
763
  catch (e) {
740
- console.warn(e);
741
- throw e;
764
+ const error = ensureError(e);
765
+ // We handle printing the exception for `until` in
766
+ // `retryReactivelyUntil`.
767
+ if (!until) {
768
+ console.error(error);
769
+ }
770
+ throw error;
742
771
  }
743
- }), atMostOnce);
772
+ }), atMostOnce, until);
744
773
  // NOTE: we parse and validate `value` every time (even the first
745
774
  // time, even though we validate above). These semantics are the
746
775
  // same as Python (although Python uses the `type` keyword argument
@@ -806,6 +835,37 @@ export async function until(idempotencyAliasOrTuple, context, callable, options
806
835
  export async function untilPerWorkflow(idempotencyAlias, context, callable, options = { validate: undefined }) {
807
836
  return await until([idempotencyAlias, PER_WORKFLOW], context, callable, options);
808
837
  }
838
+ export async function untilChanges(idempotencyAlias, context, callable, options) {
839
+ const iteration = getLoopIteration();
840
+ if (iteration === undefined) {
841
+ throw new Error("Waiting for changes must be done _within_ a control loop");
842
+ }
843
+ if (options.equals === undefined) {
844
+ // TODO: don't make `equals` required, instead use one of the
845
+ // various libraries that does deep equality.
846
+ throw new Error("Missing 'equals' option");
847
+ }
848
+ const { equals, ...optionsWithoutEquals } = options;
849
+ let previous = null;
850
+ if (iteration > 0) {
851
+ // Get the previous memoized result!
852
+ previous = (await untilPerWorkflow(`${idempotencyAlias} #${iteration - 1}`, context, (async () => {
853
+ throw new Error(`Missing memoized value for '${idempotencyAlias}'`);
854
+ }), optionsWithoutEquals));
855
+ }
856
+ // Wait until previous result does not equal current result.
857
+ return (await untilPerWorkflow(`${idempotencyAlias} #${iteration}`, context, (async () => {
858
+ const current = await callable();
859
+ if (iteration === 0) {
860
+ return current;
861
+ }
862
+ assert(previous !== null);
863
+ if (!equals(previous, current)) {
864
+ return current;
865
+ }
866
+ return false;
867
+ }), optionsWithoutEquals));
868
+ }
809
869
  const launchSubprocessConsensus = (base64_args) => {
810
870
  // Create a child process via `fork` (which does not mean `fork` as
811
871
  // 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.32.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.32.0",
22
22
  "description": "npm package for Reboot",
23
23
  "scripts": {
24
24
  "preinstall": "node preinstall.cjs",
package/reboot_native.cc CHANGED
@@ -2374,6 +2374,43 @@ Napi::Value Context_stateId(const Napi::CallbackInfo& info) {
2374
2374
  return Napi::String::New(info.Env(), state_id);
2375
2375
  }
2376
2376
 
2377
+
2378
+ Napi::Value Context_stateTypeName(const Napi::CallbackInfo& info) {
2379
+ Napi::External<py::object> js_external_context =
2380
+ info[0].As<Napi::External<py::object>>();
2381
+
2382
+ // CHECK(...CheckTypeTag(...));
2383
+
2384
+ py::object* py_context = js_external_context.Data();
2385
+
2386
+ std::string state_type_name = RunCallbackOnPythonEventLoop(
2387
+ [py_context]() {
2388
+ py::str state_type_name = py_context->attr("state_type_name");
2389
+ return std::string(state_type_name);
2390
+ });
2391
+
2392
+ return Napi::String::New(info.Env(), state_type_name);
2393
+ }
2394
+
2395
+
2396
+ Napi::Value Context_method(const Napi::CallbackInfo& info) {
2397
+ Napi::External<py::object> js_external_context =
2398
+ info[0].As<Napi::External<py::object>>();
2399
+
2400
+ // CHECK(...CheckTypeTag(...));
2401
+
2402
+ py::object* py_context = js_external_context.Data();
2403
+
2404
+ std::string method = RunCallbackOnPythonEventLoop(
2405
+ [py_context]() {
2406
+ py::str method = py_context->attr("method");
2407
+ return std::string(method);
2408
+ });
2409
+
2410
+ return Napi::String::New(info.Env(), method);
2411
+ }
2412
+
2413
+
2377
2414
  Napi::Value Context_callerBearerToken(
2378
2415
  const Napi::CallbackInfo& info) {
2379
2416
  Napi::External<py::object> js_external_context =
@@ -2672,6 +2709,8 @@ Napi::Value memoize(const Napi::CallbackInfo& info) {
2672
2709
 
2673
2710
  bool at_most_once = info[3].As<Napi::Boolean>();
2674
2711
 
2712
+ bool until = info[4].As<Napi::Boolean>();
2713
+
2675
2714
  return NodePromiseFromPythonTaskWithContext(
2676
2715
  info.Env(),
2677
2716
  "memoize(...) in nodejs",
@@ -2681,7 +2720,8 @@ Napi::Value memoize(const Napi::CallbackInfo& info) {
2681
2720
  alias = std::move(alias),
2682
2721
  how = std::move(how),
2683
2722
  js_callable = std::move(js_callable),
2684
- at_most_once]() {
2723
+ at_most_once,
2724
+ until]() {
2685
2725
  py::object py_callable = py::cpp_function(
2686
2726
  [js_callable = std::move(js_callable)]() mutable {
2687
2727
  return PythonFutureFromNodePromise(
@@ -2704,7 +2744,8 @@ Napi::Value memoize(const Napi::CallbackInfo& info) {
2704
2744
  py_context,
2705
2745
  py_callable,
2706
2746
  "type_t"_a = py::eval("str"),
2707
- "at_most_once"_a = at_most_once);
2747
+ "at_most_once"_a = at_most_once,
2748
+ "until"_a = until);
2708
2749
  },
2709
2750
  [](py::object py_json) {
2710
2751
  return py_json.cast<std::string>();
@@ -2875,6 +2916,14 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
2875
2916
  Napi::String::New(env, "Context_stateId"),
2876
2917
  Napi::Function::New<Context_stateId>(env));
2877
2918
 
2919
+ exports.Set(
2920
+ Napi::String::New(env, "Context_stateTypeName"),
2921
+ Napi::Function::New<Context_stateTypeName>(env));
2922
+
2923
+ exports.Set(
2924
+ Napi::String::New(env, "Context_method"),
2925
+ Napi::Function::New<Context_method>(env));
2926
+
2878
2927
  exports.Set(
2879
2928
  Napi::String::New(env, "Context_callerBearerToken"),
2880
2929
  Napi::Function::New<Context_callerBearerToken>(env));
package/reboot_native.cjs CHANGED
@@ -76,6 +76,8 @@ exports.Reboot_down = reboot_native.exports.Reboot_down;
76
76
  exports.Reboot_url = reboot_native.exports.Reboot_url;
77
77
  exports.Context_auth = reboot_native.exports.Context_auth;
78
78
  exports.Context_stateId = reboot_native.exports.Context_stateId;
79
+ exports.Context_stateTypeName = reboot_native.exports.Context_stateTypeName;
80
+ exports.Context_method = reboot_native.exports.Context_method;
79
81
  exports.Context_callerBearerToken =
80
82
  reboot_native.exports.Context_callerBearerToken;
81
83
  exports.Context_cookie = reboot_native.exports.Context_cookie;
package/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const REBOOT_VERSION = "0.30.0";
1
+ export declare const REBOOT_VERSION = "0.32.0";
package/version.js CHANGED
@@ -1 +1 @@
1
- export const REBOOT_VERSION = "0.30.0";
1
+ export const REBOOT_VERSION = "0.32.0";