@reboot-dev/reboot 0.23.0 → 0.25.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 +94 -19
- package/index.js +306 -42
- package/package.json +7 -4
- package/rbt.js +5 -2
- package/reboot_native.cc +1207 -1383
- package/reboot_native.cjs +3 -1
- package/reboot_native.d.ts +12 -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
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { errors_pb, IdempotencyOptions, protobuf_es, ScheduleOptions } from "@reboot-dev/reboot-api";
|
|
2
|
+
import express from "express";
|
|
2
3
|
import * as reboot_native from "./reboot_native.cjs";
|
|
3
4
|
export { reboot_native };
|
|
4
5
|
export * from "./utils/index.js";
|
|
5
|
-
type
|
|
6
|
+
type ApplicationRevision = {
|
|
6
7
|
applicationId: string;
|
|
7
8
|
};
|
|
8
9
|
export declare class Reboot {
|
|
@@ -16,10 +17,9 @@ export declare class Reboot {
|
|
|
16
17
|
}): ExternalContext;
|
|
17
18
|
start(): Promise<void>;
|
|
18
19
|
stop(): Promise<void>;
|
|
19
|
-
up(
|
|
20
|
-
tokenVerifier?: TokenVerifier;
|
|
20
|
+
up(application: Application, options?: {
|
|
21
21
|
localEnvoy?: boolean;
|
|
22
|
-
}): Promise<
|
|
22
|
+
}): Promise<ApplicationRevision>;
|
|
23
23
|
down(): Promise<void>;
|
|
24
24
|
url(): string;
|
|
25
25
|
}
|
|
@@ -48,8 +48,10 @@ export declare class Context {
|
|
|
48
48
|
static fromNativeExternal(external: any, kind: string, aborted: Promise<void>): ReaderContext | WriterContext | TransactionContext | WorkflowContext;
|
|
49
49
|
get __external(): any;
|
|
50
50
|
get auth(): Auth | null;
|
|
51
|
-
get stateId():
|
|
52
|
-
get iteration():
|
|
51
|
+
get stateId(): string;
|
|
52
|
+
get iteration(): number;
|
|
53
|
+
get cookie(): string;
|
|
54
|
+
get appInternal(): boolean;
|
|
53
55
|
generateIdempotentStateId(stateType: string, serviceName: string, method: string, idempotency: IdempotencyOptions): Promise<any>;
|
|
54
56
|
}
|
|
55
57
|
export declare class ReaderContext extends Context {
|
|
@@ -117,8 +119,8 @@ export declare abstract class TokenVerifier {
|
|
|
117
119
|
* Returns:
|
|
118
120
|
* `Auth` information if the token is valid, null otherwise.
|
|
119
121
|
*/
|
|
120
|
-
abstract verifyToken(context: ReaderContext, token
|
|
121
|
-
_verifyToken(context: ReaderContext, token
|
|
122
|
+
abstract verifyToken(context: ReaderContext, token?: string): Promise<Auth | null>;
|
|
123
|
+
_verifyToken(context: ReaderContext, token?: string): Promise<Uint8Array | null>;
|
|
122
124
|
}
|
|
123
125
|
export type AuthorizerDecision = errors_pb.Unauthenticated | errors_pb.PermissionDenied | errors_pb.Ok;
|
|
124
126
|
/**
|
|
@@ -144,12 +146,38 @@ export declare abstract class Authorizer<StateType, RequestTypes> {
|
|
|
144
146
|
abstract authorize(methodName: string, context: ReaderContext, state?: StateType, request?: RequestTypes): Promise<AuthorizerDecision>;
|
|
145
147
|
_authorize?: (methodName: string, context: ReaderContext, bytesState?: Uint8Array, bytesRequest?: Uint8Array) => Promise<Uint8Array>;
|
|
146
148
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
149
|
+
export type AuthorizerCallable<StateType, RequestType> = (args: {
|
|
150
|
+
context: ReaderContext;
|
|
151
|
+
state?: StateType;
|
|
152
|
+
request?: RequestType;
|
|
153
|
+
}) => Promise<AuthorizerDecision> | AuthorizerDecision;
|
|
154
|
+
export declare abstract class AuthorizerRule<StateType, RequestType> {
|
|
155
|
+
abstract execute(args: {
|
|
156
|
+
context: ReaderContext;
|
|
157
|
+
state?: StateType;
|
|
158
|
+
request?: RequestType;
|
|
159
|
+
}): Promise<AuthorizerDecision>;
|
|
152
160
|
}
|
|
161
|
+
export declare function deny(): AuthorizerRule<protobuf_es.Message, protobuf_es.Message>;
|
|
162
|
+
export declare function allow(): AuthorizerRule<protobuf_es.Message, protobuf_es.Message>;
|
|
163
|
+
export declare function allowIf<StateType, RequestType>(args: {
|
|
164
|
+
all: AuthorizerCallable<StateType, RequestType>[];
|
|
165
|
+
any?: never;
|
|
166
|
+
}): AuthorizerRule<StateType, RequestType>;
|
|
167
|
+
export declare function allowIf<StateType, RequestType>(args: {
|
|
168
|
+
all?: never;
|
|
169
|
+
any: AuthorizerCallable<StateType, RequestType>[];
|
|
170
|
+
}): AuthorizerRule<StateType, RequestType>;
|
|
171
|
+
export declare function hasVerifiedToken({ context, }: {
|
|
172
|
+
context: ReaderContext;
|
|
173
|
+
state?: protobuf_es.Message;
|
|
174
|
+
request?: protobuf_es.Message;
|
|
175
|
+
}): errors_pb.Unauthenticated | errors_pb.Ok;
|
|
176
|
+
export declare function isAppInternal({ context, }: {
|
|
177
|
+
context: ReaderContext;
|
|
178
|
+
state?: protobuf_es.Message;
|
|
179
|
+
request?: protobuf_es.Message;
|
|
180
|
+
}): errors_pb.Unauthenticated | errors_pb.Ok;
|
|
153
181
|
export declare class Application {
|
|
154
182
|
#private;
|
|
155
183
|
constructor({ servicers, initialize, initializeBearerToken, tokenVerifier, }: {
|
|
@@ -158,7 +186,30 @@ export declare class Application {
|
|
|
158
186
|
initializeBearerToken?: string;
|
|
159
187
|
tokenVerifier?: TokenVerifier;
|
|
160
188
|
});
|
|
189
|
+
get servicers(): ServicerFactory[];
|
|
190
|
+
get tokenVerifier(): TokenVerifier;
|
|
191
|
+
get http(): Application.Http;
|
|
161
192
|
run(): Promise<any>;
|
|
193
|
+
get __external(): any;
|
|
194
|
+
}
|
|
195
|
+
export declare namespace Application {
|
|
196
|
+
namespace Http {
|
|
197
|
+
type Path = string | RegExp | (string | RegExp)[];
|
|
198
|
+
type ReqResHandler = (context: ExternalContext, req: express.Request, res: express.Response) => any;
|
|
199
|
+
type ReqResNextHandler = (context: ExternalContext, req: express.Request, res: express.Response, next: express.NextFunction) => any;
|
|
200
|
+
type ErrReqResNextHandler = (context: ExternalContext, err: any, req: express.Request, res: express.Response, next: express.NextFunction) => any;
|
|
201
|
+
type Handler = ReqResHandler | ReqResNextHandler | ErrReqResNextHandler;
|
|
202
|
+
}
|
|
203
|
+
class Http {
|
|
204
|
+
#private;
|
|
205
|
+
constructor(express: express.Application, createExternalContext: (args: {
|
|
206
|
+
name: string;
|
|
207
|
+
bearerToken?: string;
|
|
208
|
+
}) => Promise<ExternalContext>);
|
|
209
|
+
private add;
|
|
210
|
+
get(path: Http.Path, ...handlers: Http.Handler[]): void;
|
|
211
|
+
post(path: Http.Path, ...handlers: Http.Handler[]): void;
|
|
212
|
+
}
|
|
162
213
|
}
|
|
163
214
|
/**
|
|
164
215
|
* @deprecated testWithReboot is deprecated in favor of the manual start of Reboot in the test setup.
|
|
@@ -171,20 +222,44 @@ export declare class Loop {
|
|
|
171
222
|
export declare function retry_reactively_until(context: WorkflowContext, condition: () => Promise<boolean>): Promise<void>;
|
|
172
223
|
export declare function retry_reactively_until<T>(context: WorkflowContext, condition: () => Promise<false | Exclude<T, boolean>>): Promise<Exclude<T, boolean>>;
|
|
173
224
|
export declare function atMostOnce(idempotencyAlias: string, context: WorkflowContext, callable: () => Promise<void>, options?: {
|
|
174
|
-
|
|
225
|
+
stringify?: undefined;
|
|
226
|
+
parse?: undefined;
|
|
227
|
+
validate?: undefined;
|
|
175
228
|
}): Promise<void>;
|
|
176
229
|
export declare function atMostOnce<T>(idempotencyAlias: string, context: WorkflowContext, callable: () => Promise<T>, options: {
|
|
177
|
-
|
|
230
|
+
stringify?: (result: T) => string;
|
|
231
|
+
parse: (value: string) => T;
|
|
232
|
+
validate?: undefined;
|
|
233
|
+
} | {
|
|
234
|
+
stringify?: (result: T) => string;
|
|
235
|
+
parse?: undefined;
|
|
236
|
+
validate: (result: T) => boolean;
|
|
178
237
|
}): Promise<T>;
|
|
179
238
|
export declare function atLeastOnce(idempotencyAlias: string, context: WorkflowContext, callable: () => Promise<void>, options?: {
|
|
180
|
-
|
|
239
|
+
stringify?: undefined;
|
|
240
|
+
parse?: undefined;
|
|
241
|
+
validate?: undefined;
|
|
181
242
|
}): Promise<void>;
|
|
182
243
|
export declare function atLeastOnce<T>(idempotencyAlias: string, context: WorkflowContext, callable: () => Promise<T>, options: {
|
|
183
|
-
|
|
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;
|
|
184
251
|
}): Promise<T>;
|
|
185
252
|
export declare function until(idempotencyAlias: string, context: WorkflowContext, callable: () => Promise<boolean>, options?: {
|
|
186
|
-
|
|
253
|
+
stringify?: undefined;
|
|
254
|
+
parse?: undefined;
|
|
255
|
+
validate?: undefined;
|
|
187
256
|
}): Promise<void>;
|
|
188
257
|
export declare function until<T>(idempotencyAlias: string, context: WorkflowContext, callable: () => Promise<false | Exclude<T, boolean>>, options: {
|
|
189
|
-
|
|
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;
|
|
190
265
|
}): Promise<Exclude<T, boolean>>;
|
package/index.js
CHANGED
|
@@ -9,12 +9,13 @@ 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_external;
|
|
12
|
+
var _Reboot_external, _ExternalContext_external, _a, _Context_external, _Context_isInternalConstructing, _ReaderContext_kind, _WriterContext_kind, _TransactionContext_kind, _WorkflowContext_kind, _Application_servicers, _Application_tokenVerifier, _Application_express, _Application_http, _Application_servers, _Application_createExternalContext, _Application_external;
|
|
13
13
|
import { auth_pb, errors_pb, protobuf_es, } from "@reboot-dev/reboot-api";
|
|
14
14
|
import { strict as assert } from "assert";
|
|
15
|
+
import express from "express";
|
|
16
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
15
17
|
import { fork } from "node:child_process";
|
|
16
18
|
import { test } from "node:test";
|
|
17
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
18
19
|
import * as reboot_native from "./reboot_native.cjs";
|
|
19
20
|
import { ensurePythonVenv } from "./venv.js";
|
|
20
21
|
export { reboot_native };
|
|
@@ -49,6 +50,12 @@ if (process != null) {
|
|
|
49
50
|
// exits due to a SIGINT will exit with a code of 130.
|
|
50
51
|
checkIfNoOtherListenersAndIfSoExit("SIGINT", 130);
|
|
51
52
|
});
|
|
53
|
+
process.on("unhandledRejection", (reason, promise) => {
|
|
54
|
+
// We install a slightly quieter unhandled-rejection handler because the
|
|
55
|
+
// native portion of Reboot renders useful error messages before raising.
|
|
56
|
+
console.error("Exiting:", reason);
|
|
57
|
+
checkIfNoOtherListenersAndIfSoExit("unhandledRejection", 1);
|
|
58
|
+
});
|
|
52
59
|
}
|
|
53
60
|
export class Reboot {
|
|
54
61
|
constructor() {
|
|
@@ -78,10 +85,10 @@ export class Reboot {
|
|
|
78
85
|
startedInstances.splice(indexOfTimestamp, 1);
|
|
79
86
|
}
|
|
80
87
|
}
|
|
81
|
-
async up(
|
|
88
|
+
async up(application, options) {
|
|
82
89
|
// TODO(benh): determine module and file name so that we can
|
|
83
90
|
// namespace if we have more than one implementation of servicers.
|
|
84
|
-
return await reboot_native.Reboot_up(__classPrivateFieldGet(this, _Reboot_external, "f"),
|
|
91
|
+
return await reboot_native.Reboot_up(__classPrivateFieldGet(this, _Reboot_external, "f"), application.__external, options?.localEnvoy);
|
|
85
92
|
}
|
|
86
93
|
async down() {
|
|
87
94
|
await reboot_native.Reboot_down(__classPrivateFieldGet(this, _Reboot_external, "f"));
|
|
@@ -169,6 +176,12 @@ export class Context {
|
|
|
169
176
|
get iteration() {
|
|
170
177
|
return reboot_native.Context_iteration(__classPrivateFieldGet(this, _Context_external, "f"));
|
|
171
178
|
}
|
|
179
|
+
get cookie() {
|
|
180
|
+
return reboot_native.Context_cookie(__classPrivateFieldGet(this, _Context_external, "f"));
|
|
181
|
+
}
|
|
182
|
+
get appInternal() {
|
|
183
|
+
return reboot_native.Context_appInternal(__classPrivateFieldGet(this, _Context_external, "f"));
|
|
184
|
+
}
|
|
172
185
|
async generateIdempotentStateId(stateType, serviceName, method, idempotency) {
|
|
173
186
|
return reboot_native.Context_generateIdempotentStateId(__classPrivateFieldGet(this, _Context_external, "f"), stateType, serviceName, method, idempotency);
|
|
174
187
|
}
|
|
@@ -315,21 +328,173 @@ export class TokenVerifier {
|
|
|
315
328
|
*/
|
|
316
329
|
export class Authorizer {
|
|
317
330
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
331
|
+
export class AuthorizerRule {
|
|
332
|
+
}
|
|
333
|
+
export function deny() {
|
|
334
|
+
return new (class extends AuthorizerRule {
|
|
335
|
+
async execute(args) {
|
|
336
|
+
return new errors_pb.PermissionDenied();
|
|
337
|
+
}
|
|
338
|
+
})();
|
|
339
|
+
}
|
|
340
|
+
export function allow() {
|
|
341
|
+
return new (class extends AuthorizerRule {
|
|
342
|
+
async execute(args) {
|
|
343
|
+
return new errors_pb.Ok();
|
|
325
344
|
}
|
|
345
|
+
})();
|
|
346
|
+
}
|
|
347
|
+
export function allowIf(args) {
|
|
348
|
+
const all = args.all;
|
|
349
|
+
const any = args.any;
|
|
350
|
+
if ((all === undefined && any === undefined) ||
|
|
351
|
+
(all !== undefined && any !== undefined)) {
|
|
352
|
+
throw new Error("Exactly one of `all` or `any` must be passed");
|
|
353
|
+
}
|
|
354
|
+
const callables = all ?? any;
|
|
355
|
+
return new (class extends AuthorizerRule {
|
|
356
|
+
async execute({ context, state, request }) {
|
|
357
|
+
// NOTE: we invoke each authorizer callable **one at a time**
|
|
358
|
+
// instead of concurrently so that:
|
|
359
|
+
//
|
|
360
|
+
// (1) We support dependency semantics for `all`, i.e.,
|
|
361
|
+
// callable's later can assume earlier callables did not
|
|
362
|
+
// return `Unauthenticated` or `PermissionDenied`.
|
|
363
|
+
//
|
|
364
|
+
// (2) We support short-circuiting`, i.e., cheaper authorizer
|
|
365
|
+
// callables can be listed first so more expensive ones
|
|
366
|
+
// aren't executed unless necessary.
|
|
367
|
+
//
|
|
368
|
+
// PLEASE KEEP SEMANTICS IN SYNC WITH PYTHON.
|
|
369
|
+
// Remember if we had any `PermissionDenied` for `any` so that
|
|
370
|
+
// we return that instead of `Unauthenticated`.
|
|
371
|
+
let denied = false;
|
|
372
|
+
for (const callable of callables) {
|
|
373
|
+
const decision = await callable({ context, state, request });
|
|
374
|
+
if (decision instanceof errors_pb.Ok) {
|
|
375
|
+
if (all !== undefined) {
|
|
376
|
+
// All callables must return `Ok`, keep checking.
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
// Only needed one `Ok` and we got it, short-circuit.
|
|
381
|
+
return decision;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
else if (decision instanceof errors_pb.Unauthenticated) {
|
|
385
|
+
if (all !== undefined) {
|
|
386
|
+
// All callables must return `Ok`, short-circuit.
|
|
387
|
+
return decision;
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
// Just need one `Ok`, keep checking.
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
else if (decision instanceof errors_pb.PermissionDenied) {
|
|
395
|
+
if (all !== undefined) {
|
|
396
|
+
// All callables must return `Ok`, short-circuit.
|
|
397
|
+
return decision;
|
|
398
|
+
}
|
|
399
|
+
else {
|
|
400
|
+
// Remember that we got at least one `PermissionDenied` so
|
|
401
|
+
// we can return it later.
|
|
402
|
+
denied = true;
|
|
403
|
+
// Only need one `Ok`, keep checking.
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
// If this was `all`, then they must have all been `Ok`!
|
|
409
|
+
if (all !== undefined) {
|
|
410
|
+
// TODO: assert !denied
|
|
411
|
+
return new errors_pb.Ok();
|
|
412
|
+
}
|
|
413
|
+
// Must be `any`, check if we got a `PermissionDenied` otherwise
|
|
414
|
+
// return `Unauthenticated`.
|
|
415
|
+
if (denied) {
|
|
416
|
+
return new errors_pb.PermissionDenied();
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
return new errors_pb.Unauthenticated();
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
})();
|
|
423
|
+
}
|
|
424
|
+
export function hasVerifiedToken({ context, }) {
|
|
425
|
+
if (!context.auth) {
|
|
426
|
+
return new errors_pb.Unauthenticated();
|
|
427
|
+
}
|
|
428
|
+
return new errors_pb.Ok();
|
|
429
|
+
}
|
|
430
|
+
export function isAppInternal({ context, }) {
|
|
431
|
+
if (context.appInternal) {
|
|
326
432
|
return new errors_pb.Ok();
|
|
327
433
|
}
|
|
434
|
+
return new errors_pb.Unauthenticated();
|
|
328
435
|
}
|
|
329
436
|
export class Application {
|
|
330
437
|
constructor({ servicers, initialize, initializeBearerToken, tokenVerifier, }) {
|
|
438
|
+
_Application_servicers.set(this, void 0);
|
|
439
|
+
_Application_tokenVerifier.set(this, void 0);
|
|
440
|
+
_Application_express.set(this, void 0);
|
|
441
|
+
_Application_http.set(this, void 0);
|
|
442
|
+
_Application_servers.set(this, void 0);
|
|
443
|
+
_Application_createExternalContext.set(this, void 0);
|
|
331
444
|
_Application_external.set(this, void 0);
|
|
332
|
-
__classPrivateFieldSet(this,
|
|
445
|
+
__classPrivateFieldSet(this, _Application_servicers, servicers, "f");
|
|
446
|
+
__classPrivateFieldSet(this, _Application_tokenVerifier, tokenVerifier, "f");
|
|
447
|
+
__classPrivateFieldSet(this, _Application_express, express(), "f");
|
|
448
|
+
// We assume that our users will want these middleware.
|
|
449
|
+
// TODO: expose `.use()` to allow users to add their own middleware.
|
|
450
|
+
__classPrivateFieldGet(this, _Application_express, "f").use(express.json());
|
|
451
|
+
__classPrivateFieldGet(this, _Application_express, "f").use(express.urlencoded({ extended: true }));
|
|
452
|
+
__classPrivateFieldSet(this, _Application_http, new Application.Http(__classPrivateFieldGet(this, _Application_express, "f"), async (args) => {
|
|
453
|
+
return await __classPrivateFieldGet(this, _Application_createExternalContext, "f").call(this, args);
|
|
454
|
+
}), "f");
|
|
455
|
+
__classPrivateFieldSet(this, _Application_servers, new Map(), "f");
|
|
456
|
+
__classPrivateFieldSet(this, _Application_external, reboot_native.Application_constructor(ExternalContext.fromNativeExternal, servicers, {
|
|
457
|
+
start: async (consensusId, port, createExternalContext) => {
|
|
458
|
+
// Store `createExternalContext` function before listening
|
|
459
|
+
// so we don't attempt to serve any traffic and try and use
|
|
460
|
+
// an `undefined` function.
|
|
461
|
+
__classPrivateFieldSet(this, _Application_createExternalContext, createExternalContext, "f");
|
|
462
|
+
let server;
|
|
463
|
+
[server, port] = await new Promise((resolve, reject) => {
|
|
464
|
+
const server = __classPrivateFieldGet(this, _Application_express, "f").listen(port ?? 0, (error) => {
|
|
465
|
+
if (error) {
|
|
466
|
+
reject(error);
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
// We requested a port so we should have an
|
|
470
|
+
// `AddressInfo` not a string representing a file
|
|
471
|
+
// descriptor path for a pipe or socket, etc.
|
|
472
|
+
const address = server.address();
|
|
473
|
+
assert(typeof address !== "string");
|
|
474
|
+
resolve([server, address.port]);
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
});
|
|
478
|
+
__classPrivateFieldGet(this, _Application_servers, "f").set(consensusId, server);
|
|
479
|
+
return port;
|
|
480
|
+
},
|
|
481
|
+
stop: async (consensusId) => {
|
|
482
|
+
const server = __classPrivateFieldGet(this, _Application_servers, "f").get(consensusId);
|
|
483
|
+
if (server !== undefined) {
|
|
484
|
+
await new Promise((resolve, reject) => {
|
|
485
|
+
server.close((err) => {
|
|
486
|
+
if (err) {
|
|
487
|
+
reject(err);
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
resolve();
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
});
|
|
494
|
+
__classPrivateFieldGet(this, _Application_servers, "f").delete(consensusId);
|
|
495
|
+
}
|
|
496
|
+
},
|
|
497
|
+
}, async (context) => {
|
|
333
498
|
if (initialize !== undefined) {
|
|
334
499
|
try {
|
|
335
500
|
await initialize(context);
|
|
@@ -345,11 +510,98 @@ export class Application {
|
|
|
345
510
|
}
|
|
346
511
|
}, initializeBearerToken, tokenVerifier), "f");
|
|
347
512
|
}
|
|
513
|
+
get servicers() {
|
|
514
|
+
return __classPrivateFieldGet(this, _Application_servicers, "f");
|
|
515
|
+
}
|
|
516
|
+
get tokenVerifier() {
|
|
517
|
+
return __classPrivateFieldGet(this, _Application_tokenVerifier, "f");
|
|
518
|
+
}
|
|
519
|
+
get http() {
|
|
520
|
+
return __classPrivateFieldGet(this, _Application_http, "f");
|
|
521
|
+
}
|
|
348
522
|
async run() {
|
|
349
523
|
return await reboot_native.Application_run(__classPrivateFieldGet(this, _Application_external, "f"));
|
|
350
524
|
}
|
|
525
|
+
get __external() {
|
|
526
|
+
return __classPrivateFieldGet(this, _Application_external, "f");
|
|
527
|
+
}
|
|
351
528
|
}
|
|
352
|
-
_Application_external = new WeakMap();
|
|
529
|
+
_Application_servicers = new WeakMap(), _Application_tokenVerifier = new WeakMap(), _Application_express = new WeakMap(), _Application_http = new WeakMap(), _Application_servers = new WeakMap(), _Application_createExternalContext = new WeakMap(), _Application_external = new WeakMap();
|
|
530
|
+
(function (Application) {
|
|
531
|
+
var _Http_express, _Http_createExternalContext;
|
|
532
|
+
class Http {
|
|
533
|
+
constructor(express, createExternalContext) {
|
|
534
|
+
_Http_express.set(this, void 0);
|
|
535
|
+
_Http_createExternalContext.set(this, void 0);
|
|
536
|
+
__classPrivateFieldSet(this, _Http_express, express, "f");
|
|
537
|
+
__classPrivateFieldSet(this, _Http_createExternalContext, createExternalContext, "f");
|
|
538
|
+
}
|
|
539
|
+
add(method, path, handlers) {
|
|
540
|
+
const methods = {
|
|
541
|
+
GET: (path, ...handlers) => __classPrivateFieldGet(this, _Http_express, "f").get(path, ...handlers),
|
|
542
|
+
POST: (path, ...handlers) => __classPrivateFieldGet(this, _Http_express, "f").post(path, ...handlers),
|
|
543
|
+
};
|
|
544
|
+
const createExternalContext = (name, authorization) => {
|
|
545
|
+
let bearerToken = undefined;
|
|
546
|
+
if (authorization !== undefined) {
|
|
547
|
+
const parts = authorization.split(" ");
|
|
548
|
+
if (parts.length === 2 && parts[0].toLowerCase() === "bearer") {
|
|
549
|
+
bearerToken = parts[1];
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return __classPrivateFieldGet(this, _Http_createExternalContext, "f").call(this, {
|
|
553
|
+
name,
|
|
554
|
+
bearerToken,
|
|
555
|
+
});
|
|
556
|
+
};
|
|
557
|
+
methods[method](path, ...handlers.map((handler) => {
|
|
558
|
+
// Express allows for three different kinds of handlers, a
|
|
559
|
+
// "normal" handler that takes two args, `req` and `res`, a
|
|
560
|
+
// "middlware" handler that takes three args, `req, `res`,
|
|
561
|
+
// and `next`, and an "error" handler that takes four args
|
|
562
|
+
// `err`, `req`, `res`, and `next`. To the best of our
|
|
563
|
+
// understanding they determine what arguments to pass to
|
|
564
|
+
// handlers based on how many arguments the handler takes,
|
|
565
|
+
// and so that is exactly what we do below as well, except
|
|
566
|
+
// accounting for an extra first arg for the
|
|
567
|
+
// `ExternalContext`.
|
|
568
|
+
switch (handler.length) {
|
|
569
|
+
// (context, req, res) => ...
|
|
570
|
+
case 3:
|
|
571
|
+
return async (req, res) => {
|
|
572
|
+
const context = await createExternalContext(`HTTP ${method} '${req.path}'`, req.header("Authorization"));
|
|
573
|
+
// @ts-ignore
|
|
574
|
+
handler(context, req, res);
|
|
575
|
+
};
|
|
576
|
+
// (context, req, res, next) => ...
|
|
577
|
+
case 4:
|
|
578
|
+
return async (req, res, next) => {
|
|
579
|
+
const context = await createExternalContext(`HTTP ${method} '${req.path}'`, req.header("Authorization"));
|
|
580
|
+
// @ts-ignore
|
|
581
|
+
handler(context, req, res, next);
|
|
582
|
+
};
|
|
583
|
+
// (context, err, req, res, next) => ...
|
|
584
|
+
case 5:
|
|
585
|
+
return async (err, req, res, next) => {
|
|
586
|
+
const context = await createExternalContext(`HTTP ${method} '${req.path}'`, req.header("Authorization"));
|
|
587
|
+
handler(context, err, req, res, next);
|
|
588
|
+
};
|
|
589
|
+
default:
|
|
590
|
+
throw new Error(`HTTP ${method} handler for '${path}' has unexpected ` +
|
|
591
|
+
`number of arguments`);
|
|
592
|
+
}
|
|
593
|
+
}));
|
|
594
|
+
}
|
|
595
|
+
get(path, ...handlers) {
|
|
596
|
+
this.add("GET", path, handlers);
|
|
597
|
+
}
|
|
598
|
+
post(path, ...handlers) {
|
|
599
|
+
this.add("POST", path, handlers);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
_Http_express = new WeakMap(), _Http_createExternalContext = new WeakMap();
|
|
603
|
+
Application.Http = Http;
|
|
604
|
+
})(Application || (Application = {}));
|
|
353
605
|
/**
|
|
354
606
|
* @deprecated testWithReboot is deprecated in favor of the manual start of Reboot in the test setup.
|
|
355
607
|
*/
|
|
@@ -395,43 +647,55 @@ export async function retry_reactively_until(context, condition) {
|
|
|
395
647
|
});
|
|
396
648
|
return t;
|
|
397
649
|
}
|
|
398
|
-
async function atLeastOrMostOnce(idempotencyAlias, context, callable,
|
|
399
|
-
|
|
650
|
+
async function atLeastOrMostOnce(idempotencyAlias, context, callable, { stringify = JSON.stringify, parse = JSON.parse, validate, atMostOnce, }) {
|
|
651
|
+
assert(stringify !== undefined);
|
|
652
|
+
assert(parse !== undefined);
|
|
653
|
+
assert(atMostOnce !== undefined);
|
|
400
654
|
const result = await reboot_native.atLeastOrMostOnce(context.__external, idempotencyAlias, async () => {
|
|
401
|
-
t = await callable();
|
|
655
|
+
const t = await callable();
|
|
402
656
|
if (t !== undefined) {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
657
|
+
// NOTE: to differentiate `callable` returning `void` (or
|
|
658
|
+
// explicitly `undefined`) from `stringify` returning an empty
|
|
659
|
+
// string we use `{ value: stringify(t) }`.
|
|
660
|
+
const result = { value: stringify(t) };
|
|
661
|
+
return JSON.stringify(result);
|
|
662
|
+
}
|
|
663
|
+
// Fail early if the developer thinks that they have some value
|
|
664
|
+
// that they want to validate but we got `undefined`.
|
|
665
|
+
if (validate !== undefined) {
|
|
666
|
+
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
667
|
}
|
|
412
668
|
// NOTE: using the empty string to represent a `callable`
|
|
413
|
-
// returning void or explicitly `undefined
|
|
669
|
+
// returning `void` (or explicitly `undefined`).
|
|
414
670
|
return "";
|
|
415
|
-
},
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
671
|
+
}, atMostOnce);
|
|
672
|
+
// NOTE: we parse and validate `value` every time, even the first
|
|
673
|
+
// time, so as to catch bugs where the `value` returned from
|
|
674
|
+
// `callable` might not parse or be valid. We will have already
|
|
675
|
+
// persisted `result`, so in the event of a bug the developer will
|
|
676
|
+
// have to change the idempotency alias so that `callable` is
|
|
677
|
+
// re-executed. These semantics are the same as Python (although
|
|
678
|
+
// Python uses the `type` keyword argument instead of the
|
|
679
|
+
// `parse` and `validate` properties we use here).
|
|
419
680
|
assert(result !== undefined);
|
|
420
681
|
if (result !== "") {
|
|
421
|
-
|
|
422
|
-
|
|
682
|
+
const { value } = JSON.parse(result);
|
|
683
|
+
const t = parse(value);
|
|
684
|
+
if (parse !== JSON.parse) {
|
|
685
|
+
if (validate === undefined) {
|
|
686
|
+
// TODO: link to docs about why this is required, when those docs exist.
|
|
687
|
+
throw new Error("Missing `validate` property");
|
|
688
|
+
}
|
|
689
|
+
else if (!validate(t)) {
|
|
690
|
+
throw new Error("Failed to validate memoized result");
|
|
691
|
+
}
|
|
423
692
|
}
|
|
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);
|
|
693
|
+
return t;
|
|
431
694
|
}
|
|
432
|
-
// Otherwise `callable` must
|
|
695
|
+
// Otherwise `callable` must have returned void (or explicitly
|
|
696
|
+
// `undefined`), fall through.
|
|
433
697
|
}
|
|
434
|
-
export async function atMostOnce(idempotencyAlias, context, callable, options = {
|
|
698
|
+
export async function atMostOnce(idempotencyAlias, context, callable, options = { validate: undefined }) {
|
|
435
699
|
try {
|
|
436
700
|
return await atLeastOrMostOnce(idempotencyAlias, context, callable, {
|
|
437
701
|
...options,
|
|
@@ -445,13 +709,13 @@ export async function atMostOnce(idempotencyAlias, context, callable, options =
|
|
|
445
709
|
throw e;
|
|
446
710
|
}
|
|
447
711
|
}
|
|
448
|
-
export async function atLeastOnce(idempotencyAlias, context, callable, options = {
|
|
712
|
+
export async function atLeastOnce(idempotencyAlias, context, callable, options = { validate: undefined }) {
|
|
449
713
|
return atLeastOrMostOnce(idempotencyAlias, context, callable, {
|
|
450
714
|
...options,
|
|
451
715
|
atMostOnce: false,
|
|
452
716
|
});
|
|
453
717
|
}
|
|
454
|
-
export async function until(idempotencyAlias, context, callable, options = {
|
|
718
|
+
export async function until(idempotencyAlias, context, callable, options = { validate: undefined }) {
|
|
455
719
|
// TODO(benh): figure out how to not use `as` type assertions here
|
|
456
720
|
// to appease the TypeScript compiler which otherwise isn't happy
|
|
457
721
|
// with passing on these types.
|
package/package.json
CHANGED
|
@@ -3,18 +3,19 @@
|
|
|
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.25.0",
|
|
7
7
|
"chalk": "^4.1.2",
|
|
8
8
|
"node-addon-api": "^7.0.0",
|
|
9
9
|
"node-gyp": ">=10.2.0",
|
|
10
10
|
"uuid": "^9.0.1",
|
|
11
11
|
"which-pm-runs": "^1.1.0",
|
|
12
12
|
"extensionless": "^1.9.9",
|
|
13
|
-
"esbuild": "^0.
|
|
13
|
+
"esbuild": "^0.25.0",
|
|
14
|
+
"express": "^5.1.0"
|
|
14
15
|
},
|
|
15
16
|
"type": "module",
|
|
16
17
|
"name": "@reboot-dev/reboot",
|
|
17
|
-
"version": "0.
|
|
18
|
+
"version": "0.25.0",
|
|
18
19
|
"description": "npm package for Reboot",
|
|
19
20
|
"scripts": {
|
|
20
21
|
"postinstall": "rbt || exit 0",
|
|
@@ -24,7 +25,8 @@
|
|
|
24
25
|
"devDependencies": {
|
|
25
26
|
"typescript": ">=4.9.5",
|
|
26
27
|
"@types/node": "20.11.5",
|
|
27
|
-
"@types/uuid": "^9.0.4"
|
|
28
|
+
"@types/uuid": "^9.0.4",
|
|
29
|
+
"@types/express": "^5.0.1"
|
|
28
30
|
},
|
|
29
31
|
"bin": {
|
|
30
32
|
"rbt": "./rbt.js",
|
|
@@ -54,6 +56,7 @@
|
|
|
54
56
|
"reboot_native.cc",
|
|
55
57
|
"reboot_native.cjs",
|
|
56
58
|
"reboot_native.d.ts",
|
|
59
|
+
"secrets",
|
|
57
60
|
"utils",
|
|
58
61
|
"venv.d.ts",
|
|
59
62
|
"venv.js",
|
package/rbt.js
CHANGED
|
@@ -27,13 +27,16 @@ function addExtensionlessToNodeOptions() {
|
|
|
27
27
|
// If we have one of those two versions then we can pre import
|
|
28
28
|
// `extensionless/register` to use the `module.register()` function,
|
|
29
29
|
// otherwise we need to fall back to `--experimental-loader`.
|
|
30
|
-
if (major
|
|
30
|
+
if (major >= 21 ||
|
|
31
31
|
(major == 20 && minor >= 6) ||
|
|
32
32
|
(major == 18 && minor >= 19)) {
|
|
33
33
|
process.env.NODE_OPTIONS += "--import=extensionless/register";
|
|
34
34
|
}
|
|
35
35
|
else {
|
|
36
|
-
|
|
36
|
+
throw new Error(`The current version of Node.js is not supported.
|
|
37
|
+
Supported versions are:
|
|
38
|
+
* greater than or equal to 20.6
|
|
39
|
+
* greater than or equal to 18.19 but less than 19`);
|
|
37
40
|
}
|
|
38
41
|
}
|
|
39
42
|
async function main() {
|