@reboot-dev/reboot 0.29.5 → 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 +82 -14
- package/index.js +182 -55
- package/package.json +2 -2
- package/reboot_native.cc +123 -55
- 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,9 @@ 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;
|
|
47
|
+
export declare function getLoopIteration(): number;
|
|
45
48
|
export declare function runWithContext<T>(context: Context, callback: () => T): Promise<T>;
|
|
46
49
|
export declare class Context {
|
|
47
50
|
#private;
|
|
@@ -52,7 +55,6 @@ export declare class Context {
|
|
|
52
55
|
get auth(): Auth | null;
|
|
53
56
|
get stateId(): string;
|
|
54
57
|
get callerBearerToken(): string | null;
|
|
55
|
-
get iteration(): number;
|
|
56
58
|
get cookie(): string;
|
|
57
59
|
get appInternal(): boolean;
|
|
58
60
|
generateIdempotentStateId(stateType: string, serviceName: string, method: string, idempotency: IdempotencyOptions): Promise<any>;
|
|
@@ -70,9 +72,19 @@ export declare class TransactionContext extends Context {
|
|
|
70
72
|
#private;
|
|
71
73
|
constructor(external: any, cancelled: any);
|
|
72
74
|
}
|
|
75
|
+
export type Interval = {
|
|
76
|
+
ms?: number;
|
|
77
|
+
secs?: number;
|
|
78
|
+
mins?: number;
|
|
79
|
+
hours?: number;
|
|
80
|
+
days?: number;
|
|
81
|
+
};
|
|
73
82
|
export declare class WorkflowContext extends Context {
|
|
74
83
|
#private;
|
|
75
84
|
constructor(external: any, cancelled: any);
|
|
85
|
+
loop(alias: string, { interval }?: {
|
|
86
|
+
interval?: Interval;
|
|
87
|
+
}): AsyncGenerator<number, void, unknown>;
|
|
76
88
|
}
|
|
77
89
|
export declare function clearField(field: any, target: any): void;
|
|
78
90
|
export declare function clearFields(target: any): void;
|
|
@@ -214,18 +226,50 @@ export declare namespace Application {
|
|
|
214
226
|
post(path: Http.Path, ...handlers: Http.Handler[]): void;
|
|
215
227
|
}
|
|
216
228
|
}
|
|
217
|
-
export declare
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
export declare
|
|
222
|
-
export
|
|
223
|
-
export
|
|
229
|
+
export declare function retryReactivelyUntil(context: WorkflowContext, condition: () => Promise<boolean>): Promise<void>;
|
|
230
|
+
export declare function retryReactivelyUntil<T>(context: WorkflowContext, condition: () => Promise<false | Exclude<T, boolean>>): Promise<Exclude<T, boolean>>;
|
|
231
|
+
export declare const ALWAYS: "ALWAYS";
|
|
232
|
+
export declare const PER_WORKFLOW: "PER_WORKFLOW";
|
|
233
|
+
export declare const PER_ITERATION: "PER_ITERATION";
|
|
234
|
+
export type How = "ALWAYS" | "PER_WORKFLOW" | "PER_ITERATION";
|
|
235
|
+
export type AtMostLeastOnceTupleType = [
|
|
236
|
+
string,
|
|
237
|
+
"PER_WORKFLOW" | "PER_ITERATION"
|
|
238
|
+
];
|
|
239
|
+
export declare function atMostOnce(idempotencyAliasOrTuple: string | AtMostLeastOnceTupleType, context: WorkflowContext, callable: () => Promise<void>, options?: {
|
|
240
|
+
stringify?: undefined;
|
|
241
|
+
parse?: undefined;
|
|
242
|
+
validate?: undefined;
|
|
243
|
+
}): Promise<void>;
|
|
244
|
+
export declare function atMostOnce<T>(idempotencyAliasOrTuple: string | AtMostLeastOnceTupleType, context: WorkflowContext, callable: () => Promise<T>, options: {
|
|
245
|
+
stringify?: (result: T) => string;
|
|
246
|
+
parse: (value: string) => T;
|
|
247
|
+
validate?: undefined;
|
|
248
|
+
} | {
|
|
249
|
+
stringify?: (result: T) => string;
|
|
250
|
+
parse?: undefined;
|
|
251
|
+
validate: (result: T) => boolean;
|
|
252
|
+
}): Promise<T>;
|
|
253
|
+
export declare function atMostOncePerWorkflow(idempotencyAlias: string, context: WorkflowContext, callable: () => Promise<void>, options?: {
|
|
254
|
+
stringify?: undefined;
|
|
255
|
+
parse?: undefined;
|
|
256
|
+
validate?: undefined;
|
|
257
|
+
}): Promise<void>;
|
|
258
|
+
export declare function atMostOncePerWorkflow<T>(idempotencyAlias: string, context: WorkflowContext, callable: () => Promise<T>, options: {
|
|
259
|
+
stringify?: (result: T) => string;
|
|
260
|
+
parse: (value: string) => T;
|
|
261
|
+
validate?: undefined;
|
|
262
|
+
} | {
|
|
263
|
+
stringify?: (result: T) => string;
|
|
264
|
+
parse?: undefined;
|
|
265
|
+
validate: (result: T) => boolean;
|
|
266
|
+
}): Promise<T>;
|
|
267
|
+
export declare function atLeastOnce(idempotencyAliasOrTuple: string | AtMostLeastOnceTupleType, context: WorkflowContext, callable: () => Promise<void>, options?: {
|
|
224
268
|
stringify?: undefined;
|
|
225
269
|
parse?: undefined;
|
|
226
270
|
validate?: undefined;
|
|
227
271
|
}): Promise<void>;
|
|
228
|
-
export declare function
|
|
272
|
+
export declare function atLeastOnce<T>(idempotencyAliasOrTuple: string | AtMostLeastOnceTupleType, context: WorkflowContext, callable: () => Promise<T>, options: {
|
|
229
273
|
stringify?: (result: T) => string;
|
|
230
274
|
parse: (value: string) => T;
|
|
231
275
|
validate?: undefined;
|
|
@@ -234,12 +278,12 @@ export declare function atMostOnce<T>(idempotencyAlias: string, context: Workflo
|
|
|
234
278
|
parse?: undefined;
|
|
235
279
|
validate: (result: T) => boolean;
|
|
236
280
|
}): Promise<T>;
|
|
237
|
-
export declare function
|
|
281
|
+
export declare function atLeastOncePerWorkflow(idempotencyAlias: string, context: WorkflowContext, callable: () => Promise<void>, options?: {
|
|
238
282
|
stringify?: undefined;
|
|
239
283
|
parse?: undefined;
|
|
240
284
|
validate?: undefined;
|
|
241
285
|
}): Promise<void>;
|
|
242
|
-
export declare function
|
|
286
|
+
export declare function atLeastOncePerWorkflow<T>(idempotencyAlias: string, context: WorkflowContext, callable: () => Promise<T>, options: {
|
|
243
287
|
stringify?: (result: T) => string;
|
|
244
288
|
parse: (value: string) => T;
|
|
245
289
|
validate?: undefined;
|
|
@@ -248,12 +292,30 @@ export declare function atLeastOnce<T>(idempotencyAlias: string, context: Workfl
|
|
|
248
292
|
parse?: undefined;
|
|
249
293
|
validate: (result: T) => boolean;
|
|
250
294
|
}): Promise<T>;
|
|
251
|
-
export
|
|
295
|
+
export type UntilTupleType = [
|
|
296
|
+
string,
|
|
297
|
+
"ALWAYS" | "PER_WORKFLOW" | "PER_ITERATION"
|
|
298
|
+
];
|
|
299
|
+
export declare function until(idempotencyAliasOrTuple: string | UntilTupleType, context: WorkflowContext, callable: () => Promise<boolean>, options?: {
|
|
300
|
+
stringify?: undefined;
|
|
301
|
+
parse?: undefined;
|
|
302
|
+
validate?: undefined;
|
|
303
|
+
}): Promise<void>;
|
|
304
|
+
export declare function until<T>(idempotencyAliasOrTuple: string | UntilTupleType, context: WorkflowContext, callable: () => Promise<false | Exclude<T, boolean>>, options: {
|
|
305
|
+
stringify?: (result: T) => string;
|
|
306
|
+
parse: (value: string) => T;
|
|
307
|
+
validate?: undefined;
|
|
308
|
+
} | {
|
|
309
|
+
stringify?: (result: T) => string;
|
|
310
|
+
parse?: undefined;
|
|
311
|
+
validate: (result: T) => boolean;
|
|
312
|
+
}): Promise<Exclude<T, boolean>>;
|
|
313
|
+
export declare function untilPerWorkflow(idempotencyAlias: string, context: WorkflowContext, callable: () => Promise<boolean>, options?: {
|
|
252
314
|
stringify?: undefined;
|
|
253
315
|
parse?: undefined;
|
|
254
316
|
validate?: undefined;
|
|
255
317
|
}): Promise<void>;
|
|
256
|
-
export declare function
|
|
318
|
+
export declare function untilPerWorkflow<T>(idempotencyAlias: string, context: WorkflowContext, callable: () => Promise<false | Exclude<T, boolean>>, options: {
|
|
257
319
|
stringify?: (result: T) => string;
|
|
258
320
|
parse: (value: string) => T;
|
|
259
321
|
validate?: undefined;
|
|
@@ -262,6 +324,12 @@ export declare function until<T>(idempotencyAlias: string, context: WorkflowCont
|
|
|
262
324
|
parse?: undefined;
|
|
263
325
|
validate: (result: T) => boolean;
|
|
264
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>;
|
|
265
333
|
export declare const zod: {
|
|
266
334
|
tasks: {
|
|
267
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";
|
|
@@ -128,18 +128,44 @@ 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;
|
|
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;
|
|
136
157
|
}
|
|
137
158
|
export async function runWithContext(context, callback) {
|
|
138
|
-
return await contextStorage.run(
|
|
159
|
+
return await contextStorage.run({
|
|
160
|
+
context,
|
|
161
|
+
withinLoop: false,
|
|
162
|
+
withinUntil: false,
|
|
163
|
+
}, callback);
|
|
139
164
|
}
|
|
140
165
|
export class Context {
|
|
141
166
|
constructor(external, cancelled) {
|
|
142
167
|
_Context_external.set(this, void 0);
|
|
168
|
+
_Context_stateId.set(this, void 0);
|
|
143
169
|
if (!__classPrivateFieldGet(_a, _a, "f", _Context_isInternalConstructing)) {
|
|
144
170
|
throw new TypeError("Context is not publicly constructable");
|
|
145
171
|
}
|
|
@@ -174,14 +200,14 @@ export class Context {
|
|
|
174
200
|
return null;
|
|
175
201
|
}
|
|
176
202
|
get stateId() {
|
|
177
|
-
|
|
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");
|
|
178
207
|
}
|
|
179
208
|
get callerBearerToken() {
|
|
180
209
|
return reboot_native.Context_callerBearerToken(__classPrivateFieldGet(this, _Context_external, "f"));
|
|
181
210
|
}
|
|
182
|
-
get iteration() {
|
|
183
|
-
return reboot_native.Context_iteration(__classPrivateFieldGet(this, _Context_external, "f"));
|
|
184
|
-
}
|
|
185
211
|
get cookie() {
|
|
186
212
|
return reboot_native.Context_cookie(__classPrivateFieldGet(this, _Context_external, "f"));
|
|
187
213
|
}
|
|
@@ -192,7 +218,7 @@ export class Context {
|
|
|
192
218
|
return reboot_native.Context_generateIdempotentStateId(__classPrivateFieldGet(this, _Context_external, "f"), stateType, serviceName, method, idempotency);
|
|
193
219
|
}
|
|
194
220
|
}
|
|
195
|
-
_a = Context, _Context_external = new WeakMap();
|
|
221
|
+
_a = Context, _Context_external = new WeakMap(), _Context_stateId = new WeakMap();
|
|
196
222
|
_Context_isInternalConstructing = { value: false };
|
|
197
223
|
export class ReaderContext extends Context {
|
|
198
224
|
constructor(external, cancelled) {
|
|
@@ -243,6 +269,41 @@ export class WorkflowContext extends Context {
|
|
|
243
269
|
// context type that structurally looks equivalent.
|
|
244
270
|
_WorkflowContext_kind.set(this, "workflow");
|
|
245
271
|
}
|
|
272
|
+
// TODO: implement workflow specific properties/methods.
|
|
273
|
+
async *loop(alias, { interval } = {}) {
|
|
274
|
+
const iterate = await reboot_native.WorkflowContext_loop(this.__external, alias);
|
|
275
|
+
const ms = (interval &&
|
|
276
|
+
(interval?.ms || 0) +
|
|
277
|
+
(interval?.secs * 1000 || 0) +
|
|
278
|
+
(interval?.mins * 60 * 1000 || 0) +
|
|
279
|
+
(interval?.hours * 60 * 60 * 1000 || 0) +
|
|
280
|
+
(interval?.days * 24 * 60 * 60 * 1000 || 0)) ||
|
|
281
|
+
0;
|
|
282
|
+
const store = contextStorage.getStore();
|
|
283
|
+
assert(store !== undefined);
|
|
284
|
+
store.withinLoop = true;
|
|
285
|
+
let iteration = null;
|
|
286
|
+
try {
|
|
287
|
+
while (true) {
|
|
288
|
+
iteration = await iterate(true);
|
|
289
|
+
if (iteration === null) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
store.loopIteration = iteration;
|
|
293
|
+
yield iteration;
|
|
294
|
+
if (ms > 0) {
|
|
295
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
finally {
|
|
300
|
+
if (iteration !== null) {
|
|
301
|
+
await iterate(false);
|
|
302
|
+
}
|
|
303
|
+
store.withinLoop = false;
|
|
304
|
+
delete store.loopIteration;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
246
307
|
}
|
|
247
308
|
_WorkflowContext_kind = new WeakMap();
|
|
248
309
|
// Helper for clearing a specific field of a protobuf-es message.
|
|
@@ -613,14 +674,12 @@ _Application_servicers = new WeakMap(), _Application_tokenVerifier = new WeakMap
|
|
|
613
674
|
_Http_express = new WeakMap(), _Http_createExternalContext = new WeakMap();
|
|
614
675
|
Application.Http = Http;
|
|
615
676
|
})(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) {
|
|
677
|
+
export async function retryReactivelyUntil(context, condition) {
|
|
622
678
|
let t = undefined;
|
|
623
|
-
await reboot_native.retry_reactively_until(context.__external, async () => {
|
|
679
|
+
await reboot_native.retry_reactively_until(context.__external, AsyncLocalStorage.bind(async () => {
|
|
680
|
+
const store = contextStorage.getStore();
|
|
681
|
+
assert(store !== undefined);
|
|
682
|
+
store.withinUntil = true;
|
|
624
683
|
try {
|
|
625
684
|
const result = await condition();
|
|
626
685
|
if (typeof result === "boolean") {
|
|
@@ -632,45 +691,71 @@ export async function retry_reactively_until(context, condition) {
|
|
|
632
691
|
}
|
|
633
692
|
}
|
|
634
693
|
catch (e) {
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
else {
|
|
639
|
-
throw new Error(`${e}`);
|
|
640
|
-
}
|
|
694
|
+
const error = ensureError(e);
|
|
695
|
+
console.error(error);
|
|
696
|
+
throw error;
|
|
641
697
|
}
|
|
642
|
-
|
|
698
|
+
finally {
|
|
699
|
+
store.withinUntil = false;
|
|
700
|
+
}
|
|
701
|
+
}));
|
|
643
702
|
return t;
|
|
644
703
|
}
|
|
645
|
-
|
|
704
|
+
// NOTE: we're not using an enum because the values that can be used in
|
|
705
|
+
// `atMostOnce` and `atLeastOnce` are different than `until`.
|
|
706
|
+
export const ALWAYS = "ALWAYS";
|
|
707
|
+
export const PER_WORKFLOW = "PER_WORKFLOW";
|
|
708
|
+
export const PER_ITERATION = "PER_ITERATION";
|
|
709
|
+
async function memoize(idempotencyAliasOrTuple, context, callable, { stringify = JSON.stringify, parse = JSON.parse, validate, atMostOnce, until = false, }) {
|
|
646
710
|
assert(stringify !== undefined);
|
|
647
711
|
assert(parse !== undefined);
|
|
648
712
|
assert(atMostOnce !== undefined);
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
713
|
+
if (!(typeof idempotencyAliasOrTuple === "string" ||
|
|
714
|
+
(Array.isArray(idempotencyAliasOrTuple) &&
|
|
715
|
+
idempotencyAliasOrTuple.length === 2))) {
|
|
716
|
+
throw new TypeError(`Expecting either a 'string' or a tuple for first argument passed to '${atMostOnce ? "atMostOnce" : until ? "until" : "atLeastOnce"}'`);
|
|
717
|
+
}
|
|
718
|
+
const result = await reboot_native.memoize(context.__external, typeof idempotencyAliasOrTuple === "string"
|
|
719
|
+
? [idempotencyAliasOrTuple, PER_ITERATION]
|
|
720
|
+
: idempotencyAliasOrTuple,
|
|
721
|
+
// Bind with async local storage so we can check things like
|
|
722
|
+
// `isWithinLoop`, etc.
|
|
723
|
+
AsyncLocalStorage.bind(async () => {
|
|
724
|
+
try {
|
|
725
|
+
const t = await callable();
|
|
726
|
+
if (t !== undefined) {
|
|
727
|
+
if (validate === undefined) {
|
|
728
|
+
// TODO: link to docs about why this is required, when those docs exist.
|
|
729
|
+
throw new Error(`Result of function passed to '${atMostOnce ? "atMostOnce" : until ? "until" : "atLeastOnce"}' is not 'boolean' but 'validate' option is undefined`);
|
|
730
|
+
}
|
|
731
|
+
else if (!validate(t)) {
|
|
732
|
+
throw new Error(`Calling 'validate' on result of function passed to '${atMostOnce ? "atMostOnce" : until ? "until" : "atLeastOnce"}' returned 'false'`);
|
|
733
|
+
}
|
|
734
|
+
// NOTE: to differentiate `callable` returning `void` (or
|
|
735
|
+
// explicitly `undefined`) from `stringify` returning an empty
|
|
736
|
+
// string we use `{ value: stringify(t) }`.
|
|
737
|
+
const result = { value: stringify(t) };
|
|
738
|
+
return JSON.stringify(result);
|
|
655
739
|
}
|
|
656
|
-
|
|
657
|
-
|
|
740
|
+
// Fail early if the developer thinks that they have some value
|
|
741
|
+
// that they want to validate but we got `undefined`.
|
|
742
|
+
if (validate !== undefined) {
|
|
743
|
+
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
744
|
}
|
|
659
|
-
// NOTE: to
|
|
660
|
-
//
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
}, atMostOnce);
|
|
745
|
+
// NOTE: using the empty string to represent a `callable`
|
|
746
|
+
// returning `void` (or explicitly `undefined`).
|
|
747
|
+
return "";
|
|
748
|
+
}
|
|
749
|
+
catch (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;
|
|
757
|
+
}
|
|
758
|
+
}), atMostOnce, until);
|
|
674
759
|
// NOTE: we parse and validate `value` every time (even the first
|
|
675
760
|
// time, even though we validate above). These semantics are the
|
|
676
761
|
// same as Python (although Python uses the `type` keyword argument
|
|
@@ -692,9 +777,9 @@ async function memoize(idempotencyAlias, context, callable, { stringify = JSON.s
|
|
|
692
777
|
// Otherwise `callable` must have returned void (or explicitly
|
|
693
778
|
// `undefined`), fall through.
|
|
694
779
|
}
|
|
695
|
-
export async function atMostOnce(
|
|
780
|
+
export async function atMostOnce(idempotencyAliasOrTuple, context, callable, options = { validate: undefined }) {
|
|
696
781
|
try {
|
|
697
|
-
return await memoize(
|
|
782
|
+
return await memoize(idempotencyAliasOrTuple, context, callable, {
|
|
698
783
|
...options,
|
|
699
784
|
atMostOnce: true,
|
|
700
785
|
});
|
|
@@ -706,25 +791,67 @@ export async function atMostOnce(idempotencyAlias, context, callable, options =
|
|
|
706
791
|
throw e;
|
|
707
792
|
}
|
|
708
793
|
}
|
|
709
|
-
export async function
|
|
710
|
-
return
|
|
794
|
+
export async function atMostOncePerWorkflow(idempotencyAlias, context, callable, options = { validate: undefined }) {
|
|
795
|
+
return atMostOnce([idempotencyAlias, PER_WORKFLOW], context, callable, options);
|
|
796
|
+
}
|
|
797
|
+
export async function atLeastOnce(idempotencyAliasOrTuple, context, callable, options = { validate: undefined }) {
|
|
798
|
+
return memoize(idempotencyAliasOrTuple, context, callable, {
|
|
711
799
|
...options,
|
|
712
800
|
atMostOnce: false,
|
|
713
801
|
});
|
|
714
802
|
}
|
|
715
|
-
export async function
|
|
803
|
+
export async function atLeastOncePerWorkflow(idempotencyAlias, context, callable, options = { validate: undefined }) {
|
|
804
|
+
return await atLeastOnce([idempotencyAlias, PER_WORKFLOW], context, callable, options);
|
|
805
|
+
}
|
|
806
|
+
export async function until(idempotencyAliasOrTuple, context, callable, options = { validate: undefined }) {
|
|
716
807
|
// TODO(benh): figure out how to not use `as` type assertions here
|
|
717
808
|
// to appease the TypeScript compiler which otherwise isn't happy
|
|
718
809
|
// with passing on these types.
|
|
719
810
|
const converge = () => {
|
|
720
|
-
return
|
|
811
|
+
return retryReactivelyUntil(context, callable);
|
|
721
812
|
};
|
|
722
|
-
|
|
813
|
+
// TODO: should we not memoize if passed `ALWAYS`? There still might
|
|
814
|
+
// be value in having a "paper trail" of what happened ...
|
|
815
|
+
return memoize(idempotencyAliasOrTuple, context, converge, {
|
|
723
816
|
...options,
|
|
724
817
|
atMostOnce: false,
|
|
725
818
|
until: true,
|
|
726
819
|
});
|
|
727
820
|
}
|
|
821
|
+
export async function untilPerWorkflow(idempotencyAlias, context, callable, options = { validate: undefined }) {
|
|
822
|
+
return await until([idempotencyAlias, PER_WORKFLOW], context, callable, options);
|
|
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
|
+
}
|
|
728
855
|
const launchSubprocessConsensus = (base64_args) => {
|
|
729
856
|
// Create a child process via `fork` (which does not mean `fork` as
|
|
730
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.
|
|
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.
|
|
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
|
@@ -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,22 +2661,30 @@ 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>());
|
|
2612
2672
|
|
|
2613
2673
|
bool at_most_once = info[3].As<Napi::Boolean>();
|
|
2614
2674
|
|
|
2675
|
+
bool until = info[4].As<Napi::Boolean>();
|
|
2676
|
+
|
|
2615
2677
|
return NodePromiseFromPythonTaskWithContext(
|
|
2616
2678
|
info.Env(),
|
|
2617
2679
|
"memoize(...) in nodejs",
|
|
2618
2680
|
js_external_context,
|
|
2619
2681
|
[js_external_context, // Ensures `py_context` remains valid.
|
|
2620
2682
|
py_context,
|
|
2683
|
+
alias = std::move(alias),
|
|
2684
|
+
how = std::move(how),
|
|
2621
2685
|
js_callable = std::move(js_callable),
|
|
2622
|
-
|
|
2623
|
-
|
|
2686
|
+
at_most_once,
|
|
2687
|
+
until]() {
|
|
2624
2688
|
py::object py_callable = py::cpp_function(
|
|
2625
2689
|
[js_callable = std::move(js_callable)]() mutable {
|
|
2626
2690
|
return PythonFutureFromNodePromise(
|
|
@@ -2637,13 +2701,14 @@ Napi::Value memoize(const Napi::CallbackInfo& info) {
|
|
|
2637
2701
|
});
|
|
2638
2702
|
});
|
|
2639
2703
|
|
|
2640
|
-
return py::module::import("
|
|
2704
|
+
return py::module::import("rebootdev.aio.memoize")
|
|
2641
2705
|
.attr("memoize")(
|
|
2642
|
-
py::
|
|
2706
|
+
py::make_tuple(alias, how),
|
|
2643
2707
|
py_context,
|
|
2644
2708
|
py_callable,
|
|
2645
2709
|
"type_t"_a = py::eval("str"),
|
|
2646
|
-
"at_most_once"_a = at_most_once
|
|
2710
|
+
"at_most_once"_a = at_most_once,
|
|
2711
|
+
"until"_a = until);
|
|
2647
2712
|
},
|
|
2648
2713
|
[](py::object py_json) {
|
|
2649
2714
|
return py_json.cast<std::string>();
|
|
@@ -2667,6 +2732,8 @@ Napi::Value Servicer_read(const Napi::CallbackInfo& info) {
|
|
|
2667
2732
|
|
|
2668
2733
|
// CHECK(...CheckTypeTag(...));
|
|
2669
2734
|
|
|
2735
|
+
std::string json_options = info[2].As<Napi::String>().Utf8Value();
|
|
2736
|
+
|
|
2670
2737
|
py::object* py_context = js_external_context.Value(info.Env()).Data();
|
|
2671
2738
|
|
|
2672
2739
|
return NodePromiseFromPythonTaskWithContext(
|
|
@@ -2676,8 +2743,9 @@ Napi::Value Servicer_read(const Napi::CallbackInfo& info) {
|
|
|
2676
2743
|
[js_external_servicer, // Ensures `py_servicer` remains valid.
|
|
2677
2744
|
py_servicer,
|
|
2678
2745
|
js_external_context, // Ensures `py_context` remains valid.
|
|
2679
|
-
py_context
|
|
2680
|
-
|
|
2746
|
+
py_context,
|
|
2747
|
+
json_options = std::move(json_options)]() {
|
|
2748
|
+
return py_servicer->attr("_read")(py_context, json_options);
|
|
2681
2749
|
},
|
|
2682
2750
|
[](py::object py_json) {
|
|
2683
2751
|
return py_json.cast<std::string>();
|
|
@@ -2815,10 +2883,6 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
|
2815
2883
|
Napi::String::New(env, "Context_callerBearerToken"),
|
|
2816
2884
|
Napi::Function::New<Context_callerBearerToken>(env));
|
|
2817
2885
|
|
|
2818
|
-
exports.Set(
|
|
2819
|
-
Napi::String::New(env, "Context_iteration"),
|
|
2820
|
-
Napi::Function::New<Context_iteration>(env));
|
|
2821
|
-
|
|
2822
2886
|
exports.Set(
|
|
2823
2887
|
Napi::String::New(env, "Context_cookie"),
|
|
2824
2888
|
Napi::Function::New<Context_cookie>(env));
|
|
@@ -2835,6 +2899,10 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
|
2835
2899
|
Napi::String::New(env, "WriterContext_set_sync"),
|
|
2836
2900
|
Napi::Function::New<WriterContext_set_sync>(env));
|
|
2837
2901
|
|
|
2902
|
+
exports.Set(
|
|
2903
|
+
Napi::String::New(env, "WorkflowContext_loop"),
|
|
2904
|
+
Napi::Function::New<WorkflowContext_loop>(env));
|
|
2905
|
+
|
|
2838
2906
|
exports.Set(
|
|
2839
2907
|
Napi::String::New(env, "retry_reactively_until"),
|
|
2840
2908
|
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.31.0";
|
package/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const REBOOT_VERSION = "0.
|
|
1
|
+
export const REBOOT_VERSION = "0.31.0";
|