@reboot-dev/reboot 0.29.4 → 0.30.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 +75 -14
- package/index.js +127 -46
- package/package.json +2 -2
- package/reboot_native.cc +117 -53
- package/reboot_native.cjs +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
package/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { errors_pb, IdempotencyOptions, protobuf_es,
|
|
1
|
+
import { errors_pb, IdempotencyOptions, protobuf_es, tasks_pb } from "@reboot-dev/reboot-api";
|
|
2
2
|
import { z } from "zod/v4";
|
|
3
3
|
import * as reboot_native from "./reboot_native.cjs";
|
|
4
4
|
import { Application as ExpressApplication, NextFunction as ExpressNextFunction, Request as ExpressRequest, Response as ExpressResponse } from "express";
|
|
@@ -42,6 +42,8 @@ export declare class ExternalContext {
|
|
|
42
42
|
generateIdempotentStateId(stateType: string, serviceName: string, method: string, idempotency: IdempotencyOptions): Promise<string>;
|
|
43
43
|
}
|
|
44
44
|
export declare function getContext(): Context;
|
|
45
|
+
export declare function isWithinUntil(): boolean;
|
|
46
|
+
export declare function isWithinLoop(): boolean;
|
|
45
47
|
export declare function runWithContext<T>(context: Context, callback: () => T): Promise<T>;
|
|
46
48
|
export declare class Context {
|
|
47
49
|
#private;
|
|
@@ -52,7 +54,6 @@ export declare class Context {
|
|
|
52
54
|
get auth(): Auth | null;
|
|
53
55
|
get stateId(): string;
|
|
54
56
|
get callerBearerToken(): string | null;
|
|
55
|
-
get iteration(): number;
|
|
56
57
|
get cookie(): string;
|
|
57
58
|
get appInternal(): boolean;
|
|
58
59
|
generateIdempotentStateId(stateType: string, serviceName: string, method: string, idempotency: IdempotencyOptions): Promise<any>;
|
|
@@ -70,9 +71,19 @@ export declare class TransactionContext extends Context {
|
|
|
70
71
|
#private;
|
|
71
72
|
constructor(external: any, cancelled: any);
|
|
72
73
|
}
|
|
74
|
+
export type Interval = {
|
|
75
|
+
ms?: number;
|
|
76
|
+
secs?: number;
|
|
77
|
+
mins?: number;
|
|
78
|
+
hours?: number;
|
|
79
|
+
days?: number;
|
|
80
|
+
};
|
|
73
81
|
export declare class WorkflowContext extends Context {
|
|
74
82
|
#private;
|
|
75
83
|
constructor(external: any, cancelled: any);
|
|
84
|
+
loop(alias: string, { interval }?: {
|
|
85
|
+
interval?: Interval;
|
|
86
|
+
}): AsyncGenerator<number, void, unknown>;
|
|
76
87
|
}
|
|
77
88
|
export declare function clearField(field: any, target: any): void;
|
|
78
89
|
export declare function clearFields(target: any): void;
|
|
@@ -214,18 +225,50 @@ export declare namespace Application {
|
|
|
214
225
|
post(path: Http.Path, ...handlers: Http.Handler[]): void;
|
|
215
226
|
}
|
|
216
227
|
}
|
|
217
|
-
export declare
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
export declare
|
|
222
|
-
export
|
|
223
|
-
export
|
|
228
|
+
export declare function retryReactivelyUntil(context: WorkflowContext, condition: () => Promise<boolean>): Promise<void>;
|
|
229
|
+
export declare function retryReactivelyUntil<T>(context: WorkflowContext, condition: () => Promise<false | Exclude<T, boolean>>): Promise<Exclude<T, boolean>>;
|
|
230
|
+
export declare const ALWAYS: "ALWAYS";
|
|
231
|
+
export declare const PER_WORKFLOW: "PER_WORKFLOW";
|
|
232
|
+
export declare const PER_ITERATION: "PER_ITERATION";
|
|
233
|
+
export type How = "ALWAYS" | "PER_WORKFLOW" | "PER_ITERATION";
|
|
234
|
+
export type AtMostLeastOnceTupleType = [
|
|
235
|
+
string,
|
|
236
|
+
"PER_WORKFLOW" | "PER_ITERATION"
|
|
237
|
+
];
|
|
238
|
+
export declare function atMostOnce(idempotencyAliasOrTuple: string | AtMostLeastOnceTupleType, context: WorkflowContext, callable: () => Promise<void>, options?: {
|
|
239
|
+
stringify?: undefined;
|
|
240
|
+
parse?: undefined;
|
|
241
|
+
validate?: undefined;
|
|
242
|
+
}): Promise<void>;
|
|
243
|
+
export declare function atMostOnce<T>(idempotencyAliasOrTuple: string | AtMostLeastOnceTupleType, context: WorkflowContext, callable: () => Promise<T>, options: {
|
|
244
|
+
stringify?: (result: T) => string;
|
|
245
|
+
parse: (value: string) => T;
|
|
246
|
+
validate?: undefined;
|
|
247
|
+
} | {
|
|
248
|
+
stringify?: (result: T) => string;
|
|
249
|
+
parse?: undefined;
|
|
250
|
+
validate: (result: T) => boolean;
|
|
251
|
+
}): Promise<T>;
|
|
252
|
+
export declare function atMostOncePerWorkflow(idempotencyAlias: string, context: WorkflowContext, callable: () => Promise<void>, options?: {
|
|
253
|
+
stringify?: undefined;
|
|
254
|
+
parse?: undefined;
|
|
255
|
+
validate?: undefined;
|
|
256
|
+
}): Promise<void>;
|
|
257
|
+
export declare function atMostOncePerWorkflow<T>(idempotencyAlias: string, context: WorkflowContext, callable: () => Promise<T>, options: {
|
|
258
|
+
stringify?: (result: T) => string;
|
|
259
|
+
parse: (value: string) => T;
|
|
260
|
+
validate?: undefined;
|
|
261
|
+
} | {
|
|
262
|
+
stringify?: (result: T) => string;
|
|
263
|
+
parse?: undefined;
|
|
264
|
+
validate: (result: T) => boolean;
|
|
265
|
+
}): Promise<T>;
|
|
266
|
+
export declare function atLeastOnce(idempotencyAliasOrTuple: string | AtMostLeastOnceTupleType, context: WorkflowContext, callable: () => Promise<void>, options?: {
|
|
224
267
|
stringify?: undefined;
|
|
225
268
|
parse?: undefined;
|
|
226
269
|
validate?: undefined;
|
|
227
270
|
}): Promise<void>;
|
|
228
|
-
export declare function
|
|
271
|
+
export declare function atLeastOnce<T>(idempotencyAliasOrTuple: string | AtMostLeastOnceTupleType, context: WorkflowContext, callable: () => Promise<T>, options: {
|
|
229
272
|
stringify?: (result: T) => string;
|
|
230
273
|
parse: (value: string) => T;
|
|
231
274
|
validate?: undefined;
|
|
@@ -234,12 +277,12 @@ export declare function atMostOnce<T>(idempotencyAlias: string, context: Workflo
|
|
|
234
277
|
parse?: undefined;
|
|
235
278
|
validate: (result: T) => boolean;
|
|
236
279
|
}): Promise<T>;
|
|
237
|
-
export declare function
|
|
280
|
+
export declare function atLeastOncePerWorkflow(idempotencyAlias: string, context: WorkflowContext, callable: () => Promise<void>, options?: {
|
|
238
281
|
stringify?: undefined;
|
|
239
282
|
parse?: undefined;
|
|
240
283
|
validate?: undefined;
|
|
241
284
|
}): Promise<void>;
|
|
242
|
-
export declare function
|
|
285
|
+
export declare function atLeastOncePerWorkflow<T>(idempotencyAlias: string, context: WorkflowContext, callable: () => Promise<T>, options: {
|
|
243
286
|
stringify?: (result: T) => string;
|
|
244
287
|
parse: (value: string) => T;
|
|
245
288
|
validate?: undefined;
|
|
@@ -248,12 +291,30 @@ export declare function atLeastOnce<T>(idempotencyAlias: string, context: Workfl
|
|
|
248
291
|
parse?: undefined;
|
|
249
292
|
validate: (result: T) => boolean;
|
|
250
293
|
}): Promise<T>;
|
|
251
|
-
export
|
|
294
|
+
export type UntilTupleType = [
|
|
295
|
+
string,
|
|
296
|
+
"ALWAYS" | "PER_WORKFLOW" | "PER_ITERATION"
|
|
297
|
+
];
|
|
298
|
+
export declare function until(idempotencyAliasOrTuple: string | UntilTupleType, context: WorkflowContext, callable: () => Promise<boolean>, options?: {
|
|
299
|
+
stringify?: undefined;
|
|
300
|
+
parse?: undefined;
|
|
301
|
+
validate?: undefined;
|
|
302
|
+
}): Promise<void>;
|
|
303
|
+
export declare function until<T>(idempotencyAliasOrTuple: string | UntilTupleType, context: WorkflowContext, callable: () => Promise<false | Exclude<T, boolean>>, options: {
|
|
304
|
+
stringify?: (result: T) => string;
|
|
305
|
+
parse: (value: string) => T;
|
|
306
|
+
validate?: undefined;
|
|
307
|
+
} | {
|
|
308
|
+
stringify?: (result: T) => string;
|
|
309
|
+
parse?: undefined;
|
|
310
|
+
validate: (result: T) => boolean;
|
|
311
|
+
}): Promise<Exclude<T, boolean>>;
|
|
312
|
+
export declare function untilPerWorkflow(idempotencyAlias: string, context: WorkflowContext, callable: () => Promise<boolean>, options?: {
|
|
252
313
|
stringify?: undefined;
|
|
253
314
|
parse?: undefined;
|
|
254
315
|
validate?: undefined;
|
|
255
316
|
}): Promise<void>;
|
|
256
|
-
export declare function
|
|
317
|
+
export declare function untilPerWorkflow<T>(idempotencyAlias: string, context: WorkflowContext, callable: () => Promise<false | Exclude<T, boolean>>, options: {
|
|
257
318
|
stringify?: (result: T) => string;
|
|
258
319
|
parse: (value: string) => T;
|
|
259
320
|
validate?: undefined;
|
package/index.js
CHANGED
|
@@ -128,14 +128,32 @@ export class ExternalContext {
|
|
|
128
128
|
_ExternalContext_external = new WeakMap();
|
|
129
129
|
const contextStorage = new AsyncLocalStorage();
|
|
130
130
|
export function getContext() {
|
|
131
|
-
const
|
|
132
|
-
if (!
|
|
131
|
+
const store = contextStorage.getStore();
|
|
132
|
+
if (!store) {
|
|
133
133
|
throw new Error("`getContext` may only be called within a `Servicer` method.");
|
|
134
134
|
}
|
|
135
|
-
return context;
|
|
135
|
+
return store.context;
|
|
136
|
+
}
|
|
137
|
+
export function isWithinUntil() {
|
|
138
|
+
const store = contextStorage.getStore();
|
|
139
|
+
if (!store) {
|
|
140
|
+
throw new Error("`isWithinUntil` may only be called within a `Servicer` method.");
|
|
141
|
+
}
|
|
142
|
+
return store.withinUntil;
|
|
143
|
+
}
|
|
144
|
+
export function isWithinLoop() {
|
|
145
|
+
const store = contextStorage.getStore();
|
|
146
|
+
if (!store) {
|
|
147
|
+
throw new Error("`isWithinLoop` may only be called within a `Servicer` method.");
|
|
148
|
+
}
|
|
149
|
+
return store.withinLoop;
|
|
136
150
|
}
|
|
137
151
|
export async function runWithContext(context, callback) {
|
|
138
|
-
return await contextStorage.run(
|
|
152
|
+
return await contextStorage.run({
|
|
153
|
+
context,
|
|
154
|
+
withinLoop: false,
|
|
155
|
+
withinUntil: false,
|
|
156
|
+
}, callback);
|
|
139
157
|
}
|
|
140
158
|
export class Context {
|
|
141
159
|
constructor(external, cancelled) {
|
|
@@ -179,9 +197,6 @@ export class Context {
|
|
|
179
197
|
get callerBearerToken() {
|
|
180
198
|
return reboot_native.Context_callerBearerToken(__classPrivateFieldGet(this, _Context_external, "f"));
|
|
181
199
|
}
|
|
182
|
-
get iteration() {
|
|
183
|
-
return reboot_native.Context_iteration(__classPrivateFieldGet(this, _Context_external, "f"));
|
|
184
|
-
}
|
|
185
200
|
get cookie() {
|
|
186
201
|
return reboot_native.Context_cookie(__classPrivateFieldGet(this, _Context_external, "f"));
|
|
187
202
|
}
|
|
@@ -243,6 +258,39 @@ export class WorkflowContext extends Context {
|
|
|
243
258
|
// context type that structurally looks equivalent.
|
|
244
259
|
_WorkflowContext_kind.set(this, "workflow");
|
|
245
260
|
}
|
|
261
|
+
// TODO: implement workflow specific properties/methods.
|
|
262
|
+
async *loop(alias, { interval } = {}) {
|
|
263
|
+
const iterate = await reboot_native.WorkflowContext_loop(this.__external, alias);
|
|
264
|
+
const ms = (interval &&
|
|
265
|
+
(interval?.ms || 0) +
|
|
266
|
+
(interval?.secs * 1000 || 0) +
|
|
267
|
+
(interval?.mins * 60 * 1000 || 0) +
|
|
268
|
+
(interval?.hours * 60 * 60 * 1000 || 0) +
|
|
269
|
+
(interval?.days * 24 * 60 * 60 * 1000 || 0)) ||
|
|
270
|
+
0;
|
|
271
|
+
const store = contextStorage.getStore();
|
|
272
|
+
assert(store !== undefined);
|
|
273
|
+
store.withinLoop = true;
|
|
274
|
+
let iteration = null;
|
|
275
|
+
try {
|
|
276
|
+
while (true) {
|
|
277
|
+
iteration = await iterate(true);
|
|
278
|
+
if (iteration === null) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
yield iteration;
|
|
282
|
+
if (ms > 0) {
|
|
283
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
finally {
|
|
288
|
+
if (iteration !== null) {
|
|
289
|
+
await iterate(false);
|
|
290
|
+
}
|
|
291
|
+
store.withinLoop = false;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
246
294
|
}
|
|
247
295
|
_WorkflowContext_kind = new WeakMap();
|
|
248
296
|
// Helper for clearing a specific field of a protobuf-es message.
|
|
@@ -613,14 +661,12 @@ _Application_servicers = new WeakMap(), _Application_tokenVerifier = new WeakMap
|
|
|
613
661
|
_Http_express = new WeakMap(), _Http_createExternalContext = new WeakMap();
|
|
614
662
|
Application.Http = Http;
|
|
615
663
|
})(Application || (Application = {}));
|
|
616
|
-
export
|
|
617
|
-
constructor(options) {
|
|
618
|
-
this.when = options?.when || new Date();
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
export async function retry_reactively_until(context, condition) {
|
|
664
|
+
export async function retryReactivelyUntil(context, condition) {
|
|
622
665
|
let t = undefined;
|
|
623
|
-
await reboot_native.retry_reactively_until(context.__external, async () => {
|
|
666
|
+
await reboot_native.retry_reactively_until(context.__external, AsyncLocalStorage.bind(async () => {
|
|
667
|
+
const store = contextStorage.getStore();
|
|
668
|
+
assert(store !== undefined);
|
|
669
|
+
store.withinUntil = true;
|
|
624
670
|
try {
|
|
625
671
|
const result = await condition();
|
|
626
672
|
if (typeof result === "boolean") {
|
|
@@ -639,38 +685,62 @@ export async function retry_reactively_until(context, condition) {
|
|
|
639
685
|
throw new Error(`${e}`);
|
|
640
686
|
}
|
|
641
687
|
}
|
|
642
|
-
|
|
688
|
+
finally {
|
|
689
|
+
store.withinUntil = false;
|
|
690
|
+
}
|
|
691
|
+
}));
|
|
643
692
|
return t;
|
|
644
693
|
}
|
|
645
|
-
|
|
694
|
+
// NOTE: we're not using an enum because the values that can be used in
|
|
695
|
+
// `atMostOnce` and `atLeastOnce` are different than `until`.
|
|
696
|
+
export const ALWAYS = "ALWAYS";
|
|
697
|
+
export const PER_WORKFLOW = "PER_WORKFLOW";
|
|
698
|
+
export const PER_ITERATION = "PER_ITERATION";
|
|
699
|
+
async function memoize(idempotencyAliasOrTuple, context, callable, { stringify = JSON.stringify, parse = JSON.parse, validate, atMostOnce, until = false, }) {
|
|
646
700
|
assert(stringify !== undefined);
|
|
647
701
|
assert(parse !== undefined);
|
|
648
702
|
assert(atMostOnce !== undefined);
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
703
|
+
if (!(typeof idempotencyAliasOrTuple === "string" ||
|
|
704
|
+
(Array.isArray(idempotencyAliasOrTuple) &&
|
|
705
|
+
idempotencyAliasOrTuple.length === 2))) {
|
|
706
|
+
throw new TypeError(`Expecting either a 'string' or a tuple for first argument passed to '${atMostOnce ? "atMostOnce" : until ? "until" : "atLeastOnce"}'`);
|
|
707
|
+
}
|
|
708
|
+
const result = await reboot_native.memoize(context.__external, typeof idempotencyAliasOrTuple === "string"
|
|
709
|
+
? [idempotencyAliasOrTuple, PER_ITERATION]
|
|
710
|
+
: idempotencyAliasOrTuple,
|
|
711
|
+
// Bind with async local storage so we can check things like
|
|
712
|
+
// `isWithinLoop`, etc.
|
|
713
|
+
AsyncLocalStorage.bind(async () => {
|
|
714
|
+
try {
|
|
715
|
+
const t = await callable();
|
|
716
|
+
if (t !== undefined) {
|
|
717
|
+
if (validate === undefined) {
|
|
718
|
+
// TODO: link to docs about why this is required, when those docs exist.
|
|
719
|
+
throw new Error(`Result of function passed to '${atMostOnce ? "atMostOnce" : until ? "until" : "atLeastOnce"}' is not 'boolean' but 'validate' option is undefined`);
|
|
720
|
+
}
|
|
721
|
+
else if (!validate(t)) {
|
|
722
|
+
throw new Error(`Calling 'validate' on result of function passed to '${atMostOnce ? "atMostOnce" : until ? "until" : "atLeastOnce"}' returned 'false'`);
|
|
723
|
+
}
|
|
724
|
+
// NOTE: to differentiate `callable` returning `void` (or
|
|
725
|
+
// explicitly `undefined`) from `stringify` returning an empty
|
|
726
|
+
// string we use `{ value: stringify(t) }`.
|
|
727
|
+
const result = { value: stringify(t) };
|
|
728
|
+
return JSON.stringify(result);
|
|
655
729
|
}
|
|
656
|
-
|
|
657
|
-
|
|
730
|
+
// Fail early if the developer thinks that they have some value
|
|
731
|
+
// that they want to validate but we got `undefined`.
|
|
732
|
+
if (validate !== undefined) {
|
|
733
|
+
throw new Error("Not expecting `validate` as you are returning `void` (or explicitly `undefined`); did you mean to return a value (or if you want to explicitly return the absence of a value use `null`)");
|
|
658
734
|
}
|
|
659
|
-
// NOTE: to
|
|
660
|
-
//
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
throw new Error("Not expecting `validate` as you are returning `void` (or explicitly `undefined`); did you mean to return a value (or if you want to explicitly return the absence of a value use `null`)");
|
|
669
|
-
}
|
|
670
|
-
// NOTE: using the empty string to represent a `callable`
|
|
671
|
-
// returning `void` (or explicitly `undefined`).
|
|
672
|
-
return "";
|
|
673
|
-
}, atMostOnce);
|
|
735
|
+
// NOTE: using the empty string to represent a `callable`
|
|
736
|
+
// returning `void` (or explicitly `undefined`).
|
|
737
|
+
return "";
|
|
738
|
+
}
|
|
739
|
+
catch (e) {
|
|
740
|
+
console.warn(e);
|
|
741
|
+
throw e;
|
|
742
|
+
}
|
|
743
|
+
}), atMostOnce);
|
|
674
744
|
// NOTE: we parse and validate `value` every time (even the first
|
|
675
745
|
// time, even though we validate above). These semantics are the
|
|
676
746
|
// same as Python (although Python uses the `type` keyword argument
|
|
@@ -692,9 +762,9 @@ async function memoize(idempotencyAlias, context, callable, { stringify = JSON.s
|
|
|
692
762
|
// Otherwise `callable` must have returned void (or explicitly
|
|
693
763
|
// `undefined`), fall through.
|
|
694
764
|
}
|
|
695
|
-
export async function atMostOnce(
|
|
765
|
+
export async function atMostOnce(idempotencyAliasOrTuple, context, callable, options = { validate: undefined }) {
|
|
696
766
|
try {
|
|
697
|
-
return await memoize(
|
|
767
|
+
return await memoize(idempotencyAliasOrTuple, context, callable, {
|
|
698
768
|
...options,
|
|
699
769
|
atMostOnce: true,
|
|
700
770
|
});
|
|
@@ -706,25 +776,36 @@ export async function atMostOnce(idempotencyAlias, context, callable, options =
|
|
|
706
776
|
throw e;
|
|
707
777
|
}
|
|
708
778
|
}
|
|
709
|
-
export async function
|
|
710
|
-
return
|
|
779
|
+
export async function atMostOncePerWorkflow(idempotencyAlias, context, callable, options = { validate: undefined }) {
|
|
780
|
+
return atMostOnce([idempotencyAlias, PER_WORKFLOW], context, callable, options);
|
|
781
|
+
}
|
|
782
|
+
export async function atLeastOnce(idempotencyAliasOrTuple, context, callable, options = { validate: undefined }) {
|
|
783
|
+
return memoize(idempotencyAliasOrTuple, context, callable, {
|
|
711
784
|
...options,
|
|
712
785
|
atMostOnce: false,
|
|
713
786
|
});
|
|
714
787
|
}
|
|
715
|
-
export async function
|
|
788
|
+
export async function atLeastOncePerWorkflow(idempotencyAlias, context, callable, options = { validate: undefined }) {
|
|
789
|
+
return await atLeastOnce([idempotencyAlias, PER_WORKFLOW], context, callable, options);
|
|
790
|
+
}
|
|
791
|
+
export async function until(idempotencyAliasOrTuple, context, callable, options = { validate: undefined }) {
|
|
716
792
|
// TODO(benh): figure out how to not use `as` type assertions here
|
|
717
793
|
// to appease the TypeScript compiler which otherwise isn't happy
|
|
718
794
|
// with passing on these types.
|
|
719
795
|
const converge = () => {
|
|
720
|
-
return
|
|
796
|
+
return retryReactivelyUntil(context, callable);
|
|
721
797
|
};
|
|
722
|
-
|
|
798
|
+
// TODO: should we not memoize if passed `ALWAYS`? There still might
|
|
799
|
+
// be value in having a "paper trail" of what happened ...
|
|
800
|
+
return memoize(idempotencyAliasOrTuple, context, converge, {
|
|
723
801
|
...options,
|
|
724
802
|
atMostOnce: false,
|
|
725
803
|
until: true,
|
|
726
804
|
});
|
|
727
805
|
}
|
|
806
|
+
export async function untilPerWorkflow(idempotencyAlias, context, callable, options = { validate: undefined }) {
|
|
807
|
+
return await until([idempotencyAlias, PER_WORKFLOW], context, callable, options);
|
|
808
|
+
}
|
|
728
809
|
const launchSubprocessConsensus = (base64_args) => {
|
|
729
810
|
// Create a child process via `fork` (which does not mean `fork` as
|
|
730
811
|
// 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.30.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.30.0",
|
|
22
22
|
"description": "npm package for Reboot",
|
|
23
23
|
"scripts": {
|
|
24
24
|
"preinstall": "node preinstall.cjs",
|
package/reboot_native.cc
CHANGED
|
@@ -1533,7 +1533,7 @@ py::list make_py_servicers(
|
|
|
1533
1533
|
|
|
1534
1534
|
// Include memoize servicers by default!
|
|
1535
1535
|
py_servicers.attr("extend")(
|
|
1536
|
-
py::module::import("
|
|
1536
|
+
py::module::import("rebootdev.aio.memoize").attr("servicers")());
|
|
1537
1537
|
|
|
1538
1538
|
return py_servicers;
|
|
1539
1539
|
}
|
|
@@ -2401,33 +2401,6 @@ Napi::Value Context_callerBearerToken(
|
|
|
2401
2401
|
}
|
|
2402
2402
|
|
|
2403
2403
|
|
|
2404
|
-
Napi::Value Context_iteration(const Napi::CallbackInfo& info) {
|
|
2405
|
-
Napi::External<py::object> js_external_context =
|
|
2406
|
-
info[0].As<Napi::External<py::object>>();
|
|
2407
|
-
|
|
2408
|
-
// CHECK(...CheckTypeTag(...));
|
|
2409
|
-
|
|
2410
|
-
py::object* py_context = js_external_context.Data();
|
|
2411
|
-
|
|
2412
|
-
std::optional<int> iteration = RunCallbackOnPythonEventLoop(
|
|
2413
|
-
[py_context]() -> std::optional<int> {
|
|
2414
|
-
py::object iteration = py_context->attr("iteration");
|
|
2415
|
-
|
|
2416
|
-
if (iteration.is_none()) {
|
|
2417
|
-
return std::nullopt;
|
|
2418
|
-
} else {
|
|
2419
|
-
return iteration.cast<int>();
|
|
2420
|
-
}
|
|
2421
|
-
});
|
|
2422
|
-
|
|
2423
|
-
if (iteration.has_value()) {
|
|
2424
|
-
return Napi::Number::New(info.Env(), *iteration);
|
|
2425
|
-
} else {
|
|
2426
|
-
return info.Env().Undefined();
|
|
2427
|
-
}
|
|
2428
|
-
}
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
2404
|
Napi::Value Context_cookie(const Napi::CallbackInfo& info) {
|
|
2432
2405
|
Napi::External<py::object> js_external_context =
|
|
2433
2406
|
info[0].As<Napi::External<py::object>>();
|
|
@@ -2493,6 +2466,11 @@ Napi::Value Context_generateIdempotentStateId(const Napi::CallbackInfo& info) {
|
|
|
2493
2466
|
if (js_alias.IsString()) {
|
|
2494
2467
|
alias = js_alias.As<Napi::String>().Utf8Value();
|
|
2495
2468
|
}
|
|
2469
|
+
auto js_each_iteration = idempotency_options.Get("eachIteration");
|
|
2470
|
+
std::optional<bool> each_iteration;
|
|
2471
|
+
if (js_each_iteration.IsBoolean()) {
|
|
2472
|
+
each_iteration = js_each_iteration.As<Napi::Boolean>();
|
|
2473
|
+
}
|
|
2496
2474
|
|
|
2497
2475
|
return NodePromiseFromPythonCallback(
|
|
2498
2476
|
info.Env(),
|
|
@@ -2502,21 +2480,34 @@ Napi::Value Context_generateIdempotentStateId(const Napi::CallbackInfo& info) {
|
|
|
2502
2480
|
service_name = std::move(service_name),
|
|
2503
2481
|
method = std::move(method),
|
|
2504
2482
|
key = std::move(key),
|
|
2505
|
-
alias = std::move(alias)
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2483
|
+
alias = std::move(alias),
|
|
2484
|
+
each_iteration = std::move(each_iteration)]() {
|
|
2485
|
+
// Need to use `call_with_context` to ensure that we have
|
|
2486
|
+
// `py_context` as a valid asyncio context variable.
|
|
2487
|
+
py::object py_idempotency =
|
|
2488
|
+
py::module::import("rebootdev.nodejs.python")
|
|
2489
|
+
.attr("call_with_context")(
|
|
2490
|
+
py::cpp_function([&]() {
|
|
2491
|
+
py::object py_key = py::none();
|
|
2492
|
+
if (key.has_value()) {
|
|
2493
|
+
py_key = py::cast(*key);
|
|
2494
|
+
}
|
|
2495
|
+
py::object py_alias = py::none();
|
|
2496
|
+
if (alias.has_value()) {
|
|
2497
|
+
py_alias = py::cast(*alias);
|
|
2498
|
+
}
|
|
2499
|
+
py::object py_each_iteration = py::none();
|
|
2500
|
+
if (each_iteration.has_value()) {
|
|
2501
|
+
py_each_iteration = py::cast(*each_iteration);
|
|
2502
|
+
}
|
|
2503
|
+
return py::module::import("rebootdev.aio.contexts")
|
|
2504
|
+
.attr("Context")
|
|
2505
|
+
.attr("idempotency")(
|
|
2506
|
+
"key"_a = py_key,
|
|
2507
|
+
"alias"_a = py_alias,
|
|
2508
|
+
"each_iteration"_a = py_each_iteration);
|
|
2509
|
+
}),
|
|
2510
|
+
py_context);
|
|
2520
2511
|
|
|
2521
2512
|
py::object py_generate_idempotent_state_id =
|
|
2522
2513
|
py_context->attr("generate_idempotent_state_id");
|
|
@@ -2555,6 +2546,71 @@ Napi::Value WriterContext_set_sync(const Napi::CallbackInfo& info) {
|
|
|
2555
2546
|
}
|
|
2556
2547
|
|
|
2557
2548
|
|
|
2549
|
+
Napi::Value WorkflowContext_loop(const Napi::CallbackInfo& info) {
|
|
2550
|
+
auto js_external_context = NapiSafeReference(
|
|
2551
|
+
info[0].As<Napi::External<py::object>>());
|
|
2552
|
+
|
|
2553
|
+
// CHECK(...CheckTypeTag(...));
|
|
2554
|
+
|
|
2555
|
+
py::object* py_context = js_external_context.Value(info.Env()).Data();
|
|
2556
|
+
|
|
2557
|
+
std::string alias = info[1].As<Napi::String>().Utf8Value();
|
|
2558
|
+
|
|
2559
|
+
return NodePromiseFromPythonTaskWithContext(
|
|
2560
|
+
info.Env(),
|
|
2561
|
+
"context.loop(...) in nodejs",
|
|
2562
|
+
js_external_context,
|
|
2563
|
+
[js_external_context, // Ensures `py_context` remains valid.
|
|
2564
|
+
py_context,
|
|
2565
|
+
alias = std::move(alias)]() {
|
|
2566
|
+
return py::module::import("rebootdev.nodejs.python")
|
|
2567
|
+
.attr("loop")(py_context, alias);
|
|
2568
|
+
},
|
|
2569
|
+
[](py::object py_iterate) {
|
|
2570
|
+
return new py::object(py_iterate);
|
|
2571
|
+
},
|
|
2572
|
+
[js_external_context](Napi::Env env, py::object* py_iterate) mutable {
|
|
2573
|
+
Napi::External<py::object> js_iterate_external =
|
|
2574
|
+
make_napi_external(env, py_iterate);
|
|
2575
|
+
|
|
2576
|
+
Napi::Function js_iterate =
|
|
2577
|
+
Napi::Function::New(
|
|
2578
|
+
env,
|
|
2579
|
+
[js_external_context,
|
|
2580
|
+
js_iterate_external = // Ensures `py_iterate` remains valid.
|
|
2581
|
+
NapiSafeReference(js_iterate_external),
|
|
2582
|
+
py_iterate](
|
|
2583
|
+
const Napi::CallbackInfo& info) mutable {
|
|
2584
|
+
bool more = info[0].As<Napi::Boolean>();
|
|
2585
|
+
return NodePromiseFromPythonTaskWithContext(
|
|
2586
|
+
info.Env(),
|
|
2587
|
+
"iterate(...) in nodejs",
|
|
2588
|
+
js_external_context,
|
|
2589
|
+
[js_iterate_external, py_iterate, more]() {
|
|
2590
|
+
return (*py_iterate)(more);
|
|
2591
|
+
},
|
|
2592
|
+
[](py::object py_iteration) -> std::optional<int> {
|
|
2593
|
+
if (!py_iteration.is_none()) {
|
|
2594
|
+
return py_iteration.cast<int>();
|
|
2595
|
+
} else {
|
|
2596
|
+
return std::nullopt;
|
|
2597
|
+
}
|
|
2598
|
+
},
|
|
2599
|
+
[](Napi::Env env,
|
|
2600
|
+
std::optional<int>&& iteration) -> Napi::Value {
|
|
2601
|
+
if (iteration.has_value()) {
|
|
2602
|
+
return Napi::Number::New(env, *iteration);
|
|
2603
|
+
} else {
|
|
2604
|
+
return env.Null();
|
|
2605
|
+
}
|
|
2606
|
+
});
|
|
2607
|
+
});
|
|
2608
|
+
|
|
2609
|
+
return js_iterate;
|
|
2610
|
+
});
|
|
2611
|
+
}
|
|
2612
|
+
|
|
2613
|
+
|
|
2558
2614
|
Napi::Value retry_reactively_until(const Napi::CallbackInfo& info) {
|
|
2559
2615
|
auto js_external_context = NapiSafeReference(
|
|
2560
2616
|
info[0].As<Napi::External<py::object>>());
|
|
@@ -2605,7 +2661,11 @@ Napi::Value memoize(const Napi::CallbackInfo& info) {
|
|
|
2605
2661
|
|
|
2606
2662
|
py::object* py_context = js_external_context.Value(info.Env()).Data();
|
|
2607
2663
|
|
|
2608
|
-
|
|
2664
|
+
Napi::Object idempotency_tuple = info[1].As<Napi::Array>();
|
|
2665
|
+
auto js_alias = idempotency_tuple.Get((uint32_t) 0);
|
|
2666
|
+
std::string alias = js_alias.As<Napi::String>().Utf8Value();
|
|
2667
|
+
auto js_how = idempotency_tuple.Get((uint32_t) 1);
|
|
2668
|
+
std::string how = js_how.As<Napi::String>().Utf8Value();
|
|
2609
2669
|
|
|
2610
2670
|
auto js_callable = NapiSafeFunctionReference(
|
|
2611
2671
|
info[2].As<Napi::Function>());
|
|
@@ -2618,8 +2678,9 @@ Napi::Value memoize(const Napi::CallbackInfo& info) {
|
|
|
2618
2678
|
js_external_context,
|
|
2619
2679
|
[js_external_context, // Ensures `py_context` remains valid.
|
|
2620
2680
|
py_context,
|
|
2681
|
+
alias = std::move(alias),
|
|
2682
|
+
how = std::move(how),
|
|
2621
2683
|
js_callable = std::move(js_callable),
|
|
2622
|
-
idempotency_alias = std::move(idempotency_alias),
|
|
2623
2684
|
at_most_once]() {
|
|
2624
2685
|
py::object py_callable = py::cpp_function(
|
|
2625
2686
|
[js_callable = std::move(js_callable)]() mutable {
|
|
@@ -2637,9 +2698,9 @@ Napi::Value memoize(const Napi::CallbackInfo& info) {
|
|
|
2637
2698
|
});
|
|
2638
2699
|
});
|
|
2639
2700
|
|
|
2640
|
-
return py::module::import("
|
|
2701
|
+
return py::module::import("rebootdev.aio.memoize")
|
|
2641
2702
|
.attr("memoize")(
|
|
2642
|
-
py::
|
|
2703
|
+
py::make_tuple(alias, how),
|
|
2643
2704
|
py_context,
|
|
2644
2705
|
py_callable,
|
|
2645
2706
|
"type_t"_a = py::eval("str"),
|
|
@@ -2667,6 +2728,8 @@ Napi::Value Servicer_read(const Napi::CallbackInfo& info) {
|
|
|
2667
2728
|
|
|
2668
2729
|
// CHECK(...CheckTypeTag(...));
|
|
2669
2730
|
|
|
2731
|
+
std::string json_options = info[2].As<Napi::String>().Utf8Value();
|
|
2732
|
+
|
|
2670
2733
|
py::object* py_context = js_external_context.Value(info.Env()).Data();
|
|
2671
2734
|
|
|
2672
2735
|
return NodePromiseFromPythonTaskWithContext(
|
|
@@ -2676,8 +2739,9 @@ Napi::Value Servicer_read(const Napi::CallbackInfo& info) {
|
|
|
2676
2739
|
[js_external_servicer, // Ensures `py_servicer` remains valid.
|
|
2677
2740
|
py_servicer,
|
|
2678
2741
|
js_external_context, // Ensures `py_context` remains valid.
|
|
2679
|
-
py_context
|
|
2680
|
-
|
|
2742
|
+
py_context,
|
|
2743
|
+
json_options = std::move(json_options)]() {
|
|
2744
|
+
return py_servicer->attr("_read")(py_context, json_options);
|
|
2681
2745
|
},
|
|
2682
2746
|
[](py::object py_json) {
|
|
2683
2747
|
return py_json.cast<std::string>();
|
|
@@ -2815,10 +2879,6 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
|
2815
2879
|
Napi::String::New(env, "Context_callerBearerToken"),
|
|
2816
2880
|
Napi::Function::New<Context_callerBearerToken>(env));
|
|
2817
2881
|
|
|
2818
|
-
exports.Set(
|
|
2819
|
-
Napi::String::New(env, "Context_iteration"),
|
|
2820
|
-
Napi::Function::New<Context_iteration>(env));
|
|
2821
|
-
|
|
2822
2882
|
exports.Set(
|
|
2823
2883
|
Napi::String::New(env, "Context_cookie"),
|
|
2824
2884
|
Napi::Function::New<Context_cookie>(env));
|
|
@@ -2835,6 +2895,10 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
|
2835
2895
|
Napi::String::New(env, "WriterContext_set_sync"),
|
|
2836
2896
|
Napi::Function::New<WriterContext_set_sync>(env));
|
|
2837
2897
|
|
|
2898
|
+
exports.Set(
|
|
2899
|
+
Napi::String::New(env, "WorkflowContext_loop"),
|
|
2900
|
+
Napi::Function::New<WorkflowContext_loop>(env));
|
|
2901
|
+
|
|
2838
2902
|
exports.Set(
|
|
2839
2903
|
Napi::String::New(env, "retry_reactively_until"),
|
|
2840
2904
|
Napi::Function::New<retry_reactively_until>(env));
|
package/reboot_native.cjs
CHANGED
|
@@ -80,10 +80,10 @@ exports.Context_callerBearerToken =
|
|
|
80
80
|
reboot_native.exports.Context_callerBearerToken;
|
|
81
81
|
exports.Context_cookie = reboot_native.exports.Context_cookie;
|
|
82
82
|
exports.Context_appInternal = reboot_native.exports.Context_appInternal;
|
|
83
|
-
exports.Context_iteration = reboot_native.exports.Context_iteration;
|
|
84
83
|
exports.Context_generateIdempotentStateId =
|
|
85
84
|
reboot_native.exports.Context_generateIdempotentStateId;
|
|
86
85
|
exports.WriterContext_set_sync = reboot_native.exports.WriterContext_set_sync;
|
|
86
|
+
exports.WorkflowContext_loop = reboot_native.exports.WorkflowContext_loop;
|
|
87
87
|
exports.retry_reactively_until = reboot_native.exports.retry_reactively_until;
|
|
88
88
|
exports.memoize = reboot_native.exports.memoize;
|
|
89
89
|
exports.Servicer_read = reboot_native.exports.Servicer_read;
|
package/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const REBOOT_VERSION = "0.
|
|
1
|
+
export declare const REBOOT_VERSION = "0.30.0";
|
package/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const REBOOT_VERSION = "0.
|
|
1
|
+
export const REBOOT_VERSION = "0.30.0";
|