@reboot-dev/reboot 0.23.0 → 0.24.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 +33 -8
- package/index.js +50 -29
- package/package.json +3 -2
- package/reboot_native.cc +55 -28
- package/reboot_native.cjs +2 -1
- package/reboot_native.d.ts +4 -3
- package/secrets/index.d.ts +37 -0
- package/secrets/index.js +96 -0
- package/version.d.ts +1 -1
- package/version.js +1 -1
package/index.d.ts
CHANGED
|
@@ -50,6 +50,7 @@ export declare class Context {
|
|
|
50
50
|
get auth(): Auth | null;
|
|
51
51
|
get stateId(): any;
|
|
52
52
|
get iteration(): any;
|
|
53
|
+
get cookie(): any;
|
|
53
54
|
generateIdempotentStateId(stateType: string, serviceName: string, method: string, idempotency: IdempotencyOptions): Promise<any>;
|
|
54
55
|
}
|
|
55
56
|
export declare class ReaderContext extends Context {
|
|
@@ -117,8 +118,8 @@ export declare abstract class TokenVerifier {
|
|
|
117
118
|
* Returns:
|
|
118
119
|
* `Auth` information if the token is valid, null otherwise.
|
|
119
120
|
*/
|
|
120
|
-
abstract verifyToken(context: ReaderContext, token
|
|
121
|
-
_verifyToken(context: ReaderContext, token
|
|
121
|
+
abstract verifyToken(context: ReaderContext, token?: string): Promise<Auth | null>;
|
|
122
|
+
_verifyToken(context: ReaderContext, token?: string): Promise<Uint8Array | null>;
|
|
122
123
|
}
|
|
123
124
|
export type AuthorizerDecision = errors_pb.Unauthenticated | errors_pb.PermissionDenied | errors_pb.Ok;
|
|
124
125
|
/**
|
|
@@ -171,20 +172,44 @@ export declare class Loop {
|
|
|
171
172
|
export declare function retry_reactively_until(context: WorkflowContext, condition: () => Promise<boolean>): Promise<void>;
|
|
172
173
|
export declare function retry_reactively_until<T>(context: WorkflowContext, condition: () => Promise<false | Exclude<T, boolean>>): Promise<Exclude<T, boolean>>;
|
|
173
174
|
export declare function atMostOnce(idempotencyAlias: string, context: WorkflowContext, callable: () => Promise<void>, options?: {
|
|
174
|
-
|
|
175
|
+
stringify?: undefined;
|
|
176
|
+
parse?: undefined;
|
|
177
|
+
validate?: undefined;
|
|
175
178
|
}): Promise<void>;
|
|
176
179
|
export declare function atMostOnce<T>(idempotencyAlias: string, context: WorkflowContext, callable: () => Promise<T>, options: {
|
|
177
|
-
|
|
180
|
+
stringify?: (result: T) => string;
|
|
181
|
+
parse: (value: string) => T;
|
|
182
|
+
validate?: undefined;
|
|
183
|
+
} | {
|
|
184
|
+
stringify?: (result: T) => string;
|
|
185
|
+
parse?: undefined;
|
|
186
|
+
validate: (result: T) => boolean;
|
|
178
187
|
}): Promise<T>;
|
|
179
188
|
export declare function atLeastOnce(idempotencyAlias: string, context: WorkflowContext, callable: () => Promise<void>, options?: {
|
|
180
|
-
|
|
189
|
+
stringify?: undefined;
|
|
190
|
+
parse?: undefined;
|
|
191
|
+
validate?: undefined;
|
|
181
192
|
}): Promise<void>;
|
|
182
193
|
export declare function atLeastOnce<T>(idempotencyAlias: string, context: WorkflowContext, callable: () => Promise<T>, options: {
|
|
183
|
-
|
|
194
|
+
stringify?: (result: T) => string;
|
|
195
|
+
parse: (value: string) => T;
|
|
196
|
+
validate?: undefined;
|
|
197
|
+
} | {
|
|
198
|
+
stringify?: (result: T) => string;
|
|
199
|
+
parse?: undefined;
|
|
200
|
+
validate: (result: T) => boolean;
|
|
184
201
|
}): Promise<T>;
|
|
185
202
|
export declare function until(idempotencyAlias: string, context: WorkflowContext, callable: () => Promise<boolean>, options?: {
|
|
186
|
-
|
|
203
|
+
stringify?: undefined;
|
|
204
|
+
parse?: undefined;
|
|
205
|
+
validate?: undefined;
|
|
187
206
|
}): Promise<void>;
|
|
188
207
|
export declare function until<T>(idempotencyAlias: string, context: WorkflowContext, callable: () => Promise<false | Exclude<T, boolean>>, options: {
|
|
189
|
-
|
|
208
|
+
stringify?: (result: T) => string;
|
|
209
|
+
parse: (value: string) => T;
|
|
210
|
+
validate?: undefined;
|
|
211
|
+
} | {
|
|
212
|
+
stringify?: (result: T) => string;
|
|
213
|
+
parse?: undefined;
|
|
214
|
+
validate: (result: T) => boolean;
|
|
190
215
|
}): Promise<Exclude<T, boolean>>;
|
package/index.js
CHANGED
|
@@ -49,6 +49,12 @@ if (process != null) {
|
|
|
49
49
|
// exits due to a SIGINT will exit with a code of 130.
|
|
50
50
|
checkIfNoOtherListenersAndIfSoExit("SIGINT", 130);
|
|
51
51
|
});
|
|
52
|
+
process.on("unhandledRejection", (reason, promise) => {
|
|
53
|
+
// We install a slightly quieter unhandled-rejection handler because the
|
|
54
|
+
// native portion of Reboot renders useful error messages before raising.
|
|
55
|
+
console.error("Exiting:", reason);
|
|
56
|
+
checkIfNoOtherListenersAndIfSoExit("unhandledRejection", 1);
|
|
57
|
+
});
|
|
52
58
|
}
|
|
53
59
|
export class Reboot {
|
|
54
60
|
constructor() {
|
|
@@ -169,6 +175,9 @@ export class Context {
|
|
|
169
175
|
get iteration() {
|
|
170
176
|
return reboot_native.Context_iteration(__classPrivateFieldGet(this, _Context_external, "f"));
|
|
171
177
|
}
|
|
178
|
+
get cookie() {
|
|
179
|
+
return reboot_native.Context_cookie(__classPrivateFieldGet(this, _Context_external, "f"));
|
|
180
|
+
}
|
|
172
181
|
async generateIdempotentStateId(stateType, serviceName, method, idempotency) {
|
|
173
182
|
return reboot_native.Context_generateIdempotentStateId(__classPrivateFieldGet(this, _Context_external, "f"), stateType, serviceName, method, idempotency);
|
|
174
183
|
}
|
|
@@ -395,43 +404,55 @@ export async function retry_reactively_until(context, condition) {
|
|
|
395
404
|
});
|
|
396
405
|
return t;
|
|
397
406
|
}
|
|
398
|
-
async function atLeastOrMostOnce(idempotencyAlias, context, callable,
|
|
399
|
-
|
|
407
|
+
async function atLeastOrMostOnce(idempotencyAlias, context, callable, { stringify = JSON.stringify, parse = JSON.parse, validate, atMostOnce, }) {
|
|
408
|
+
assert(stringify !== undefined);
|
|
409
|
+
assert(parse !== undefined);
|
|
410
|
+
assert(atMostOnce !== undefined);
|
|
400
411
|
const result = await reboot_native.atLeastOrMostOnce(context.__external, idempotencyAlias, async () => {
|
|
401
|
-
t = await callable();
|
|
412
|
+
const t = await callable();
|
|
402
413
|
if (t !== undefined) {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
414
|
+
// NOTE: to differentiate `callable` returning `void` (or
|
|
415
|
+
// explicitly `undefined`) from `stringify` returning an empty
|
|
416
|
+
// string we use `{ value: stringify(t) }`.
|
|
417
|
+
const result = { value: stringify(t) };
|
|
418
|
+
return JSON.stringify(result);
|
|
419
|
+
}
|
|
420
|
+
// Fail early if the developer thinks that they have some value
|
|
421
|
+
// that they want to validate but we got `undefined`.
|
|
422
|
+
if (validate !== undefined) {
|
|
423
|
+
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`)");
|
|
411
424
|
}
|
|
412
425
|
// NOTE: using the empty string to represent a `callable`
|
|
413
|
-
// returning void or explicitly `undefined
|
|
426
|
+
// returning `void` (or explicitly `undefined`).
|
|
414
427
|
return "";
|
|
415
|
-
},
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
428
|
+
}, atMostOnce);
|
|
429
|
+
// NOTE: we parse and validate `value` every time, even the first
|
|
430
|
+
// time, so as to catch bugs where the `value` returned from
|
|
431
|
+
// `callable` might not parse or be valid. We will have already
|
|
432
|
+
// persisted `result`, so in the event of a bug the developer will
|
|
433
|
+
// have to change the idempotency alias so that `callable` is
|
|
434
|
+
// re-executed. These semantics are the same as Python (although
|
|
435
|
+
// Python uses the `type` keyword argument instead of the
|
|
436
|
+
// `parse` and `validate` properties we use here).
|
|
419
437
|
assert(result !== undefined);
|
|
420
438
|
if (result !== "") {
|
|
421
|
-
|
|
422
|
-
|
|
439
|
+
const { value } = JSON.parse(result);
|
|
440
|
+
const t = parse(value);
|
|
441
|
+
if (parse !== JSON.parse) {
|
|
442
|
+
if (validate === undefined) {
|
|
443
|
+
// TODO: link to docs about why this is required, when those docs exist.
|
|
444
|
+
throw new Error("Missing `validate` property");
|
|
445
|
+
}
|
|
446
|
+
else if (!validate(t)) {
|
|
447
|
+
throw new Error("Failed to validate memoized result");
|
|
448
|
+
}
|
|
423
449
|
}
|
|
424
|
-
return
|
|
425
|
-
}
|
|
426
|
-
assert(result === "");
|
|
427
|
-
// Let end user decide what they want to do with `undefined` if
|
|
428
|
-
// they specify `options.parse`.
|
|
429
|
-
if (options.parse !== undefined) {
|
|
430
|
-
return options.parse(undefined);
|
|
450
|
+
return t;
|
|
431
451
|
}
|
|
432
|
-
// Otherwise `callable` must
|
|
452
|
+
// Otherwise `callable` must have returned void (or explicitly
|
|
453
|
+
// `undefined`), fall through.
|
|
433
454
|
}
|
|
434
|
-
export async function atMostOnce(idempotencyAlias, context, callable, options = {
|
|
455
|
+
export async function atMostOnce(idempotencyAlias, context, callable, options = { validate: undefined }) {
|
|
435
456
|
try {
|
|
436
457
|
return await atLeastOrMostOnce(idempotencyAlias, context, callable, {
|
|
437
458
|
...options,
|
|
@@ -445,13 +466,13 @@ export async function atMostOnce(idempotencyAlias, context, callable, options =
|
|
|
445
466
|
throw e;
|
|
446
467
|
}
|
|
447
468
|
}
|
|
448
|
-
export async function atLeastOnce(idempotencyAlias, context, callable, options = {
|
|
469
|
+
export async function atLeastOnce(idempotencyAlias, context, callable, options = { validate: undefined }) {
|
|
449
470
|
return atLeastOrMostOnce(idempotencyAlias, context, callable, {
|
|
450
471
|
...options,
|
|
451
472
|
atMostOnce: false,
|
|
452
473
|
});
|
|
453
474
|
}
|
|
454
|
-
export async function until(idempotencyAlias, context, callable, options = {
|
|
475
|
+
export async function until(idempotencyAlias, context, callable, options = { validate: undefined }) {
|
|
455
476
|
// TODO(benh): figure out how to not use `as` type assertions here
|
|
456
477
|
// to appease the TypeScript compiler which otherwise isn't happy
|
|
457
478
|
// with passing on these types.
|
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.24.0",
|
|
7
7
|
"chalk": "^4.1.2",
|
|
8
8
|
"node-addon-api": "^7.0.0",
|
|
9
9
|
"node-gyp": ">=10.2.0",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"type": "module",
|
|
16
16
|
"name": "@reboot-dev/reboot",
|
|
17
|
-
"version": "0.
|
|
17
|
+
"version": "0.24.0",
|
|
18
18
|
"description": "npm package for Reboot",
|
|
19
19
|
"scripts": {
|
|
20
20
|
"postinstall": "rbt || exit 0",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"reboot_native.cc",
|
|
55
55
|
"reboot_native.cjs",
|
|
56
56
|
"reboot_native.d.ts",
|
|
57
|
+
"secrets",
|
|
57
58
|
"utils",
|
|
58
59
|
"venv.d.ts",
|
|
59
60
|
"venv.js",
|
package/reboot_native.cc
CHANGED
|
@@ -534,13 +534,13 @@ class NapiSafeReference {
|
|
|
534
534
|
public:
|
|
535
535
|
NapiSafeReference(T t)
|
|
536
536
|
: _reference(
|
|
537
|
-
|
|
538
|
-
|
|
537
|
+
new Napi::Reference<T>(Napi::Persistent(std::move(t))),
|
|
538
|
+
NapiReferenceDeleter()) {}
|
|
539
539
|
|
|
540
540
|
NapiSafeReference(Napi::Reference<T>&& reference)
|
|
541
541
|
: _reference(
|
|
542
|
-
|
|
543
|
-
|
|
542
|
+
new Napi::Reference<T>(std::move(reference)),
|
|
543
|
+
NapiReferenceDeleter()) {}
|
|
544
544
|
|
|
545
545
|
// Helper for getting the value of the reference. We require a
|
|
546
546
|
// `Napi::Env` to ensure we only try and get the value from within a
|
|
@@ -1156,7 +1156,7 @@ py::object make_py_token_verifier(NapiSafeObjectReference js_token_verifier) {
|
|
|
1156
1156
|
[](py::object self,
|
|
1157
1157
|
py::object py_reader_context,
|
|
1158
1158
|
py::object py_aborted,
|
|
1159
|
-
|
|
1159
|
+
py::object token) {
|
|
1160
1160
|
py::object py_future =
|
|
1161
1161
|
py::module::import("asyncio").attr("Future")();
|
|
1162
1162
|
|
|
@@ -1168,7 +1168,7 @@ py::object make_py_token_verifier(NapiSafeObjectReference js_token_verifier) {
|
|
|
1168
1168
|
[js_token_verifier_reference,
|
|
1169
1169
|
py_reader_context = new py::object(py_reader_context),
|
|
1170
1170
|
py_aborted = new py::object(py_aborted),
|
|
1171
|
-
token,
|
|
1171
|
+
token = new py::object(token),
|
|
1172
1172
|
py_future = new py::object(py_future)](Napi::Env env) {
|
|
1173
1173
|
std::vector<Napi::Value> js_args;
|
|
1174
1174
|
|
|
@@ -1179,7 +1179,12 @@ py::object make_py_token_verifier(NapiSafeObjectReference js_token_verifier) {
|
|
|
1179
1179
|
py_aborted,
|
|
1180
1180
|
"reader"));
|
|
1181
1181
|
|
|
1182
|
-
|
|
1182
|
+
if (token->is_none()) {
|
|
1183
|
+
js_args.push_back(env.Undefined());
|
|
1184
|
+
} else {
|
|
1185
|
+
js_args.push_back(
|
|
1186
|
+
Napi::String::New(env, std::string(py::str(*token))));
|
|
1187
|
+
}
|
|
1183
1188
|
|
|
1184
1189
|
Napi::Object js_token_verifier =
|
|
1185
1190
|
js_token_verifier_reference->Value(env);
|
|
@@ -1694,19 +1699,9 @@ Napi::Value Service_call(const Napi::CallbackInfo& info) {
|
|
|
1694
1699
|
}
|
|
1695
1700
|
|
|
1696
1701
|
|
|
1697
|
-
Napi::Value
|
|
1702
|
+
Napi::Value Task_await(const Napi::CallbackInfo& info) {
|
|
1698
1703
|
Napi::Object js_args = info[0].As<Napi::Object>();
|
|
1699
1704
|
|
|
1700
|
-
// NOTE: we immediately get a safe reference to the `Napi::External`
|
|
1701
|
-
// so that Node will not garbage collect it and the `py::object*` we
|
|
1702
|
-
// get out of it will remain valid.
|
|
1703
|
-
auto js_external_service = NapiSafeReference(
|
|
1704
|
-
js_args.Get("external").As<Napi::External<py::object>>());
|
|
1705
|
-
|
|
1706
|
-
// CHECK(js_external_service.CheckTypeTag(...));
|
|
1707
|
-
|
|
1708
|
-
py::object* py_service = js_external_service.Value(info.Env()).Data();
|
|
1709
|
-
|
|
1710
1705
|
// NOTE: we immediately get a safe reference to the `Napi::External`
|
|
1711
1706
|
// so that Node will not garbage collect it and the `py::object*` we
|
|
1712
1707
|
// get out of it will remain valid.
|
|
@@ -1718,6 +1713,10 @@ Napi::Value Future_await(const Napi::CallbackInfo& info) {
|
|
|
1718
1713
|
py::object* py_context =
|
|
1719
1714
|
js_external_context.Value(info.Env()).Data();
|
|
1720
1715
|
|
|
1716
|
+
std::string rbt_module = js_args.Get("rbtModule").As<Napi::String>();
|
|
1717
|
+
|
|
1718
|
+
std::string state_name = js_args.Get("stateName").As<Napi::String>();
|
|
1719
|
+
|
|
1721
1720
|
std::string method = js_args.Get("method").As<Napi::String>();
|
|
1722
1721
|
|
|
1723
1722
|
std::string json_task_id = js_args.Get("jsonTaskId").As<Napi::String>();
|
|
@@ -1727,9 +1726,9 @@ Napi::Value Future_await(const Napi::CallbackInfo& info) {
|
|
|
1727
1726
|
auto promise = deferred->Promise();
|
|
1728
1727
|
|
|
1729
1728
|
adaptor->ScheduleCallbackOnPythonEventLoop(
|
|
1730
|
-
[
|
|
1731
|
-
|
|
1732
|
-
method,
|
|
1729
|
+
[rbt_module = std::move(rbt_module),
|
|
1730
|
+
state_name = std::move(state_name),
|
|
1731
|
+
method = std::move(method),
|
|
1733
1732
|
// Ensures `py_context` remains valid.
|
|
1734
1733
|
js_external_context,
|
|
1735
1734
|
py_context,
|
|
@@ -1738,12 +1737,16 @@ Napi::Value Future_await(const Napi::CallbackInfo& info) {
|
|
|
1738
1737
|
py::object py_task =
|
|
1739
1738
|
py::module::import("reboot.nodejs.python")
|
|
1740
1739
|
.attr("create_task_with_context")(
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1740
|
+
py::module::import("reboot.nodejs.python")
|
|
1741
|
+
.attr("task_await")(
|
|
1742
|
+
py_context,
|
|
1743
|
+
py::module::import(rbt_module.c_str())
|
|
1744
|
+
.attr(state_name.c_str()),
|
|
1745
|
+
method,
|
|
1746
|
+
json_task_id),
|
|
1745
1747
|
py_context,
|
|
1746
|
-
"name"_a = ("
|
|
1748
|
+
"name"_a = ("reboot.nodejs.python.task_await(\""
|
|
1749
|
+
+ state_name + "\", \""
|
|
1747
1750
|
+ method + "\", ...) in nodejs")
|
|
1748
1751
|
.c_str());
|
|
1749
1752
|
py_task.attr("add_done_callback")(py::cpp_function(
|
|
@@ -2200,6 +2203,26 @@ Napi::Value Context_iteration(const Napi::CallbackInfo& info) {
|
|
|
2200
2203
|
}
|
|
2201
2204
|
|
|
2202
2205
|
|
|
2206
|
+
Napi::Value Context_cookie(const Napi::CallbackInfo& info) {
|
|
2207
|
+
Napi::External<py::object> js_external_context =
|
|
2208
|
+
info[0].As<Napi::External<py::object>>();
|
|
2209
|
+
|
|
2210
|
+
// CHECK(...CheckTypeTag(...));
|
|
2211
|
+
|
|
2212
|
+
py::object* py_context = js_external_context.Data();
|
|
2213
|
+
|
|
2214
|
+
std::promise<std::string> promise;
|
|
2215
|
+
|
|
2216
|
+
adaptor->ScheduleCallbackOnPythonEventLoop(
|
|
2217
|
+
[py_context, &promise]() {
|
|
2218
|
+
py::str cookie = py_context->attr("cookie");
|
|
2219
|
+
promise.set_value(std::string(cookie));
|
|
2220
|
+
});
|
|
2221
|
+
|
|
2222
|
+
return Napi::String::New(info.Env(), promise.get_future().get());
|
|
2223
|
+
}
|
|
2224
|
+
|
|
2225
|
+
|
|
2203
2226
|
Napi::Value Context_generateIdempotentStateId(const Napi::CallbackInfo& info) {
|
|
2204
2227
|
// NOTE: we immediately get a safe reference to the `Napi::External`
|
|
2205
2228
|
// so that Node will not garbage collect it and the `py::object*` we
|
|
@@ -2874,6 +2897,10 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
|
2874
2897
|
Napi::String::New(env, "Context_iteration"),
|
|
2875
2898
|
Napi::Function::New<Context_iteration>(env));
|
|
2876
2899
|
|
|
2900
|
+
exports.Set(
|
|
2901
|
+
Napi::String::New(env, "Context_cookie"),
|
|
2902
|
+
Napi::Function::New<Context_cookie>(env));
|
|
2903
|
+
|
|
2877
2904
|
exports.Set(
|
|
2878
2905
|
Napi::String::New(env, "Context_generateIdempotentStateId"),
|
|
2879
2906
|
Napi::Function::New<Context_generateIdempotentStateId>(env));
|
|
@@ -2891,8 +2918,8 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
|
2891
2918
|
Napi::Function::New<atLeastOrMostOnce>(env));
|
|
2892
2919
|
|
|
2893
2920
|
exports.Set(
|
|
2894
|
-
Napi::String::New(env, "
|
|
2895
|
-
Napi::Function::New<
|
|
2921
|
+
Napi::String::New(env, "Task_await"),
|
|
2922
|
+
Napi::Function::New<Task_await>(env));
|
|
2896
2923
|
|
|
2897
2924
|
exports.Set(
|
|
2898
2925
|
Napi::String::New(env, "ExternalContext_constructor"),
|
package/reboot_native.cjs
CHANGED
|
@@ -52,7 +52,7 @@ process.dlopen(
|
|
|
52
52
|
exports.python3Path = reboot_native.exports.python3Path;
|
|
53
53
|
exports.Service_constructor = reboot_native.exports.Service_constructor;
|
|
54
54
|
exports.Service_call = reboot_native.exports.Service_call;
|
|
55
|
-
exports.
|
|
55
|
+
exports.Task_await = reboot_native.exports.Task_await;
|
|
56
56
|
exports.ExternalContext_constructor =
|
|
57
57
|
reboot_native.exports.ExternalContext_constructor;
|
|
58
58
|
exports.Application_constructor = reboot_native.exports.Application_constructor;
|
|
@@ -67,6 +67,7 @@ exports.Reboot_down = reboot_native.exports.Reboot_down;
|
|
|
67
67
|
exports.Reboot_url = reboot_native.exports.Reboot_url;
|
|
68
68
|
exports.Context_auth = reboot_native.exports.Context_auth;
|
|
69
69
|
exports.Context_stateId = reboot_native.exports.Context_stateId;
|
|
70
|
+
exports.Context_cookie = reboot_native.exports.Context_cookie;
|
|
70
71
|
exports.Context_iteration = reboot_native.exports.Context_iteration;
|
|
71
72
|
exports.Context_generateIdempotentStateId =
|
|
72
73
|
reboot_native.exports.Context_generateIdempotentStateId;
|
package/reboot_native.d.ts
CHANGED
|
@@ -31,9 +31,10 @@ interface Service_callProps {
|
|
|
31
31
|
jsonRequest: string;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
interface
|
|
35
|
-
external: NapiExternal;
|
|
34
|
+
interface Task_awaitProps {
|
|
36
35
|
context: Context | ExternalContext;
|
|
36
|
+
rbtModule: string;
|
|
37
|
+
stateName: string;
|
|
37
38
|
method: string;
|
|
38
39
|
jsonTaskId: string;
|
|
39
40
|
}
|
|
@@ -41,7 +42,7 @@ interface Future_awaitProps {
|
|
|
41
42
|
export namespace rbt_native {
|
|
42
43
|
function Service_constructor(props: Service_constructorProps): NapiExternal;
|
|
43
44
|
function Service_call(props: Service_callProps): string;
|
|
44
|
-
function
|
|
45
|
+
function Task_await(props: Task_awaitProps): string;
|
|
45
46
|
function ExternalContext_constructor(
|
|
46
47
|
name: string,
|
|
47
48
|
url: string,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export declare const ENVVAR_RBT_SECRETS_DIRECTORY = "RBT_SECRETS_DIRECTORY";
|
|
2
|
+
declare abstract class SecretSource {
|
|
3
|
+
abstract get(secretName: string): Promise<Buffer>;
|
|
4
|
+
}
|
|
5
|
+
export declare class Secrets {
|
|
6
|
+
private static _staticSecretSource;
|
|
7
|
+
private _secretCache;
|
|
8
|
+
private _secretSource;
|
|
9
|
+
constructor();
|
|
10
|
+
static setSecretSource(secretSource: SecretSource | null): void;
|
|
11
|
+
get secretSource(): SecretSource;
|
|
12
|
+
get(secretName: string, { ttlSecs }?: {
|
|
13
|
+
ttlSecs?: number;
|
|
14
|
+
}): Promise<Buffer>;
|
|
15
|
+
}
|
|
16
|
+
export declare class SecretNotFoundException extends Error {
|
|
17
|
+
constructor(message: string);
|
|
18
|
+
}
|
|
19
|
+
export declare class DirectorySecretSource extends SecretSource {
|
|
20
|
+
directory: string;
|
|
21
|
+
constructor(directory: string);
|
|
22
|
+
get(secretName: string): Promise<Buffer>;
|
|
23
|
+
}
|
|
24
|
+
export declare class EnvironmentSecretSource extends SecretSource {
|
|
25
|
+
ENVIRONMENT_VARIABLE_PREFIX: string;
|
|
26
|
+
get(secretName: string): Promise<Buffer>;
|
|
27
|
+
}
|
|
28
|
+
export declare class MockSecretSource extends SecretSource {
|
|
29
|
+
secrets: {
|
|
30
|
+
[key: string]: Buffer;
|
|
31
|
+
};
|
|
32
|
+
constructor(secrets: {
|
|
33
|
+
[key: string]: Buffer;
|
|
34
|
+
});
|
|
35
|
+
get(secretName: string): Promise<Buffer>;
|
|
36
|
+
}
|
|
37
|
+
export {};
|
package/secrets/index.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import * as path from "path";
|
|
2
|
+
import { promises as fs } from "fs";
|
|
3
|
+
export const ENVVAR_RBT_SECRETS_DIRECTORY = "RBT_SECRETS_DIRECTORY";
|
|
4
|
+
class SecretSource {
|
|
5
|
+
}
|
|
6
|
+
export class Secrets {
|
|
7
|
+
constructor() {
|
|
8
|
+
this._secretCache = {};
|
|
9
|
+
if (Secrets._staticSecretSource) {
|
|
10
|
+
this._secretSource = Secrets._staticSecretSource;
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const secretsDirectory = process.env[ENVVAR_RBT_SECRETS_DIRECTORY];
|
|
14
|
+
if (secretsDirectory) {
|
|
15
|
+
this._secretSource = new DirectorySecretSource(path.resolve(secretsDirectory));
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
this._secretSource = new EnvironmentSecretSource();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
static setSecretSource(secretSource) {
|
|
22
|
+
Secrets._staticSecretSource = secretSource;
|
|
23
|
+
}
|
|
24
|
+
get secretSource() {
|
|
25
|
+
return this._secretSource;
|
|
26
|
+
}
|
|
27
|
+
async get(secretName, { ttlSecs = 15.0 } = {}) {
|
|
28
|
+
const now = Date.now();
|
|
29
|
+
const cachedSecret = this._secretCache[secretName];
|
|
30
|
+
if (cachedSecret && cachedSecret.cachedAt + ttlSecs > now) {
|
|
31
|
+
return cachedSecret.value;
|
|
32
|
+
}
|
|
33
|
+
const value = await this._secretSource.get(secretName);
|
|
34
|
+
this._secretCache[secretName] = new _CachedSecret(value, now);
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
Secrets._staticSecretSource = null;
|
|
39
|
+
export class SecretNotFoundException extends Error {
|
|
40
|
+
constructor(message) {
|
|
41
|
+
super(message);
|
|
42
|
+
this.name = "SecretNotFoundException";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export class DirectorySecretSource extends SecretSource {
|
|
46
|
+
constructor(directory) {
|
|
47
|
+
super();
|
|
48
|
+
this.directory = directory;
|
|
49
|
+
}
|
|
50
|
+
async get(secretName) {
|
|
51
|
+
const secretPath = path.join(this.directory, secretName);
|
|
52
|
+
try {
|
|
53
|
+
return await fs.readFile(secretPath);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
if (error.code === "ENOENT") {
|
|
57
|
+
throw new SecretNotFoundException(`No secret is stored for secretName=${secretName} (at \`${secretPath}\`).`);
|
|
58
|
+
}
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export class EnvironmentSecretSource extends SecretSource {
|
|
64
|
+
constructor() {
|
|
65
|
+
super(...arguments);
|
|
66
|
+
this.ENVIRONMENT_VARIABLE_PREFIX = "RBT_SECRET_";
|
|
67
|
+
}
|
|
68
|
+
async get(secretName) {
|
|
69
|
+
const environmentVariableName = `${this.ENVIRONMENT_VARIABLE_PREFIX}${secretName.toUpperCase().replace(/-/g, "_")}`;
|
|
70
|
+
const value = process.env[environmentVariableName];
|
|
71
|
+
if (value === undefined) {
|
|
72
|
+
throw new SecretNotFoundException(`No environment variable was set for secretName=${secretName}; ` +
|
|
73
|
+
`expected \`${environmentVariableName}\` to be set`);
|
|
74
|
+
}
|
|
75
|
+
return Buffer.from(value);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export class MockSecretSource extends SecretSource {
|
|
79
|
+
constructor(secrets) {
|
|
80
|
+
super();
|
|
81
|
+
this.secrets = secrets;
|
|
82
|
+
}
|
|
83
|
+
async get(secretName) {
|
|
84
|
+
const value = this.secrets[secretName];
|
|
85
|
+
if (value === undefined) {
|
|
86
|
+
throw new SecretNotFoundException(`No mock secret was stored for secretName=${secretName}.`);
|
|
87
|
+
}
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
class _CachedSecret {
|
|
92
|
+
constructor(value, cachedAt) {
|
|
93
|
+
this.value = value;
|
|
94
|
+
this.cachedAt = cachedAt;
|
|
95
|
+
}
|
|
96
|
+
}
|
package/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const REBOOT_VERSION = "0.
|
|
1
|
+
export declare const REBOOT_VERSION = "0.24.0";
|
package/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const REBOOT_VERSION = "0.
|
|
1
|
+
export const REBOOT_VERSION = "0.24.0";
|