@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 +9 -0
- package/index.js +73 -13
- package/package.json +2 -2
- package/reboot_native.cc +51 -2
- package/reboot_native.cjs +2 -0
- package/version.d.ts +1 -1
- package/version.js +1 -1
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
|
-
|
|
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
|
-
|
|
682
|
-
|
|
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
|
-
|
|
741
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
1
|
+
export declare const REBOOT_VERSION = "0.32.0";
|
package/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const REBOOT_VERSION = "0.
|
|
1
|
+
export const REBOOT_VERSION = "0.32.0";
|