@reboot-dev/reboot 0.44.0 → 0.45.1

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/README.md ADDED
@@ -0,0 +1,160 @@
1
+ <div align="center">
2
+
3
+ <img src="https://docs.reboot.dev/img/reboot-logo-green.svg"
4
+ alt="Reboot" width="200" />
5
+
6
+ # Reboot
7
+
8
+ **Build AI Chat Apps — and full-stack web apps — with reactive, durable backends.**
9
+
10
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
11
+ [![PyPI](https://img.shields.io/pypi/v/reboot)](https://pypi.org/project/reboot/)
12
+ [![npm](https://img.shields.io/npm/v/@reboot-dev/reboot)](https://www.npmjs.com/package/@reboot-dev/reboot)
13
+ [![Discord](https://img.shields.io/badge/Discord-join-5865F2?logo=discord&logoColor=white)](https://discord.gg/cRbdcS94Nr)
14
+ [![Docs](https://img.shields.io/badge/docs-reboot.dev-green)](https://docs.reboot.dev/)
15
+
16
+ </div>
17
+
18
+ ---
19
+
20
+ Reboot is a framework for building **reactive, stateful, multiplayer AI chat
21
+ apps** — visual apps that run inside ChatGPT, Claude, VS Code, Goose, and more.
22
+ It also builds full-stack web apps with reactive backends and React frontends.
23
+
24
+ With Reboot, you just write business logic — no wiring up databases, caches,
25
+ queues, or retry loops. State survives failures by default. ACID transactions
26
+ span multiple states. The React frontend stays in sync in real time. And your
27
+ backend is automatically an MCP server.
28
+
29
+ ## AI Chat Apps
30
+
31
+ Build visual, interactive apps that run inside AI chat interfaces. Define a
32
+ `Session` type as an entry point and your methods automatically become tools
33
+ the AI can call:
34
+
35
+ ```python
36
+ from reboot.api import (
37
+ API, Field, Methods, Model, Reader, Tool,
38
+ Transaction, Type, UI, Writer,
39
+ )
40
+
41
+
42
+ class CreateCounterResponse(Model):
43
+ counter_id: str = Field(tag=1)
44
+
45
+
46
+ class SessionState(Model):
47
+ pass
48
+
49
+
50
+ class CounterState(Model):
51
+ value: int = Field(tag=1, default=0)
52
+
53
+
54
+ class GetResponse(Model):
55
+ value: int = Field(tag=1)
56
+
57
+
58
+ class IncrementRequest(Model):
59
+ """Request with an amount parameter."""
60
+ amount: int | None = Field(tag=1, default=None)
61
+
62
+
63
+ api = API(
64
+ Session=Type(
65
+ state=SessionState,
66
+ methods=Methods(
67
+ create_counter=Transaction(
68
+ request=None,
69
+ response=CreateCounterResponse,
70
+ description="Create a new Counter.",
71
+ ),
72
+ ),
73
+ ),
74
+ Counter=Type(
75
+ state=CounterState,
76
+ methods=Methods(
77
+ show_clicker=UI(
78
+ request=None,
79
+ path="web/ui/clicker",
80
+ title="Counter Clicker",
81
+ description="Interactive clicker UI.",
82
+ ),
83
+ create=Writer(
84
+ request=None,
85
+ response=None,
86
+ factory=True,
87
+ ),
88
+ get=Reader(
89
+ request=None,
90
+ response=GetResponse,
91
+ description="Get the current counter "
92
+ "value.",
93
+ mcp=Tool(),
94
+ ),
95
+ increment=Writer(
96
+ request=IncrementRequest,
97
+ response=None,
98
+ description="Increment the counter.",
99
+ mcp=Tool(),
100
+ ),
101
+ ),
102
+ ),
103
+ )
104
+ ```
105
+
106
+ ### Dive in!
107
+
108
+ - [What is an AI Chat App?](https://docs.reboot.dev/ai_chat_apps/what_is)
109
+ - [Get Started (Python)](https://docs.reboot.dev/ai_chat_apps/get_started)
110
+ - [AI Chat App Examples](https://docs.reboot.dev/ai_chat_apps/examples)
111
+
112
+ ## Full-stack apps
113
+
114
+ Build reactive backends with React frontends — great as a full-page extension
115
+ of your AI chat app, or as a standalone web app.
116
+
117
+ - [TypeScript Quickstart](https://docs.reboot.dev/full_stack_apps/typescript)
118
+ - [Python Quickstart](https://docs.reboot.dev/full_stack_apps/python)
119
+ - [Full-stack Examples](https://docs.reboot.dev/full_stack_apps/examples)
120
+
121
+ ## Key features
122
+
123
+ **Automatic MCP server.** `Session` methods are automatically exposed as
124
+ MCP tools. Other types can opt in with `mcp=Tool()`. `UI` methods open
125
+ React apps in the AI's chat. No glue code.
126
+
127
+ **Durable state by default.** States survive process crashes, deployments, and
128
+ chaos. No external database required.
129
+
130
+ **ACID transactions across states.** `transaction` methods compose atomically
131
+ across many state instances running on different machines.
132
+
133
+ **Reactive React frontend.** Generated hooks keep your UI in sync
134
+ without manual management of WebSockets, caches, or polling.
135
+
136
+ **Method system.** Code is safer to write (and _read_) with a clear API
137
+ and methods with enforced constraints: `reader` (concurrent, read-only),
138
+ `writer` (serialized, mutating), `transaction` (ACID, cross-state),
139
+ `workflow` (long-running, durable, cancellable), `ui` (React app in AI
140
+ chat). The runtime enforces these guarantees.
141
+
142
+ **API-first, code-generated.** Define APIs using Pydantic (Python) or
143
+ Zod (TypeScript). Reboot generates type-safe client, server, and React
144
+ stubs.
145
+
146
+ ## Documentation
147
+
148
+ Full documentation at [docs.reboot.dev](https://docs.reboot.dev/).
149
+
150
+ ## Community
151
+
152
+ - **Discord**: [discord.gg/cRbdcS94Nr](https://discord.gg/cRbdcS94Nr) — fastest way to get help or share what you're building
153
+ - **Issues**: [github.com/reboot-dev/reboot/issues](https://github.com/reboot-dev/reboot/issues)
154
+
155
+ Contributions are welcome. Open an issue to discuss substantial changes before
156
+ sending a pull request.
157
+
158
+ ## License
159
+
160
+ [Apache 2.0](LICENSE)
package/index.d.ts CHANGED
@@ -60,7 +60,8 @@ export declare class Context {
60
60
  readonly cookie: string | null;
61
61
  readonly appInternal: boolean;
62
62
  readonly auth: Auth | null;
63
- constructor({ external, stateId, method, stateTypeName, callerBearerToken, cookie, appInternal, auth, cancelled, }: {
63
+ readonly workflowId: string | null;
64
+ constructor({ external, stateId, method, stateTypeName, callerBearerToken, cookie, appInternal, auth, workflowId, cancelled, }: {
64
65
  external: any;
65
66
  stateId: string;
66
67
  method: string;
@@ -69,6 +70,7 @@ export declare class Context {
69
70
  cookie: string | null;
70
71
  appInternal: boolean;
71
72
  auth: Auth | null;
73
+ workflowId: string | null;
72
74
  cancelled: Promise<void>;
73
75
  });
74
76
  static fromNativeExternal({ kind, ...options }: {
@@ -101,6 +103,10 @@ export type Interval = {
101
103
  export declare class WorkflowContext extends Context {
102
104
  #private;
103
105
  constructor(options: any);
106
+ makeIdempotencyKey({ alias, perWorkflow, }: {
107
+ alias: string;
108
+ perWorkflow?: boolean;
109
+ }): string;
104
110
  loop(alias: string, { interval }?: {
105
111
  interval?: Interval;
106
112
  }): AsyncGenerator<number, void, unknown>;
@@ -178,7 +184,7 @@ export declare abstract class Authorizer<StateType, RequestTypes> {
178
184
  * `errors_pb.PermissionDenied()` otherwise.
179
185
  */
180
186
  abstract authorize(methodName: string, context: ReaderContext, state?: StateType, request?: RequestTypes): Promise<AuthorizerDecision>;
181
- _authorize?: (external: any, cancelled: Promise<void>, bytesCall: Uint8Array) => Promise<Uint8Array>;
187
+ abstract _authorize(external: any, cancelled: Promise<void>, bytesCall: Uint8Array): Promise<Uint8Array>;
182
188
  }
183
189
  export type AuthorizerCallable<StateType, RequestType> = (args: {
184
190
  context: ReaderContext;
@@ -212,15 +218,30 @@ export declare function isAppInternal({ context, }: {
212
218
  state?: any;
213
219
  request?: any;
214
220
  }): errors_pb.PermissionDenied | errors_pb.Ok;
221
+ export type NativeLibrary = {
222
+ nativeLibraryModule: string;
223
+ nativeLibraryFunction: string;
224
+ authorizer?: Authorizer<unknown, unknown>;
225
+ };
226
+ export type TypeScriptLibrary = {
227
+ name: string;
228
+ servicers: () => ServicerFactory[];
229
+ requirements?: () => string[];
230
+ preRun?: (application: Application) => Promise<void>;
231
+ initialize?: (context: InitializeContext) => Promise<void>;
232
+ };
233
+ export type Library = TypeScriptLibrary | NativeLibrary;
215
234
  export declare class Application {
216
235
  #private;
217
- constructor({ servicers, initialize, initializeBearerToken, tokenVerifier, }: {
218
- servicers: ServicerFactory[];
236
+ constructor({ servicers, libraries, initialize, initializeBearerToken, tokenVerifier, }: {
237
+ servicers?: ServicerFactory[];
238
+ libraries?: Library[];
219
239
  initialize?: (context: InitializeContext) => Promise<void>;
220
240
  initializeBearerToken?: string;
221
241
  tokenVerifier?: TokenVerifier;
222
242
  });
223
243
  get servicers(): ServicerFactory[];
244
+ get libraries(): Library[];
224
245
  get tokenVerifier(): TokenVerifier;
225
246
  get http(): Application.Http;
226
247
  run(): Promise<any>;
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_servicers, _Application_tokenVerifier, _Application_express, _Application_http, _Application_servers, _Application_createExternalContext, _Application_external;
12
+ var _Reboot_external, _ExternalContext_external, _a, _Context_external, _Context_isInternalConstructing, _ReaderContext_kind, _WriterContext_kind, _TransactionContext_kind, _WorkflowContext_kind, _Application_servicers, _Application_libraries, _Application_tokenVerifier, _Application_express, _Application_http, _Application_servers, _Application_createExternalContext, _Application_external;
13
13
  import { assert, auth_pb, check_bufbuild_protobuf_library, errors_pb, nodejs_pb, parse, protobuf_es, stringify, tasks_pb, toCamelCase, } from "@reboot-dev/reboot-api";
14
14
  import { AsyncLocalStorage } from "node:async_hooks";
15
15
  import { fork } from "node:child_process";
16
16
  import { createRequire } from "node:module";
17
17
  import toobusy from "toobusy-js";
18
+ import { v5 as uuidv5 } from "uuid";
18
19
  import { z } from "zod/v4";
19
20
  import * as reboot_native from "./reboot_native.cjs";
20
21
  import { ensureError } from "./utils/errors.js";
@@ -169,7 +170,7 @@ export async function runWithContext(context, callback) {
169
170
  }, callback);
170
171
  }
171
172
  export class Context {
172
- constructor({ external, stateId, method, stateTypeName, callerBearerToken, cookie, appInternal, auth, cancelled, }) {
173
+ constructor({ external, stateId, method, stateTypeName, callerBearerToken, cookie, appInternal, auth, workflowId, cancelled, }) {
173
174
  _Context_external.set(this, void 0);
174
175
  if (!__classPrivateFieldGet(_a, _a, "f", _Context_isInternalConstructing)) {
175
176
  throw new TypeError("Context is not publicly constructable");
@@ -185,6 +186,7 @@ export class Context {
185
186
  this.cookie = cookie;
186
187
  this.appInternal = appInternal;
187
188
  this.auth = auth;
189
+ this.workflowId = workflowId;
188
190
  this.cancelled = cancelled;
189
191
  }
190
192
  static fromNativeExternal({ kind, ...options }) {
@@ -263,6 +265,17 @@ export class WorkflowContext extends Context {
263
265
  _WorkflowContext_kind.set(this, "workflow");
264
266
  }
265
267
  // TODO: implement workflow specific properties/methods.
268
+ makeIdempotencyKey({ alias, perWorkflow, }) {
269
+ assert(this.workflowId !== null);
270
+ if (!perWorkflow) {
271
+ const store = contextStorage.getStore();
272
+ assert(store !== undefined);
273
+ if (store.withinLoop) {
274
+ alias += ` (iteration #${store.loopIteration})`;
275
+ }
276
+ }
277
+ return uuidv5(alias, this.workflowId);
278
+ }
266
279
  async *loop(alias, { interval } = {}) {
267
280
  const iterate = await reboot_native.WorkflowContext_loop(this.__external, alias);
268
281
  const ms = (interval &&
@@ -384,6 +397,7 @@ export class TokenVerifier {
384
397
  cookie: call.context.cookie,
385
398
  appInternal: call.context.appInternal,
386
399
  auth: null,
400
+ workflowId: call.context.workflowId !== undefined ? call.context.workflowId : null,
387
401
  cancelled,
388
402
  });
389
403
  const auth = await this.verifyToken(context, call.token);
@@ -506,16 +520,52 @@ export function isAppInternal({ context, }) {
506
520
  }
507
521
  return new errors_pb.PermissionDenied();
508
522
  }
523
+ /**
524
+ * Prepares libraries for conversion. Wraps `initialize` method for better
525
+ * error handling.
526
+ *
527
+ * @param library Library to be prepared
528
+ * @returns
529
+ */
530
+ function prepareLibrary(library) {
531
+ // Skip Python libraries and libraries without an initialize.
532
+ if (library.nativeLibraryModule !== undefined ||
533
+ library.initialize === undefined) {
534
+ return library;
535
+ }
536
+ return {
537
+ ...library,
538
+ initialize: async (context) => {
539
+ try {
540
+ await library.initialize(context);
541
+ }
542
+ catch (e) {
543
+ // Ensure we have an `Error` and then `console.error()` it
544
+ // so that developers see a stack trace of what is going
545
+ // on.
546
+ const error = ensureError(e);
547
+ // Write an empty message which includes a newline to make
548
+ // it easier to identify the stack trace.
549
+ console.error("");
550
+ console.error(error);
551
+ console.error("");
552
+ throw error;
553
+ }
554
+ },
555
+ };
556
+ }
509
557
  export class Application {
510
- constructor({ servicers, initialize, initializeBearerToken, tokenVerifier, }) {
558
+ constructor({ servicers, libraries, initialize, initializeBearerToken, tokenVerifier, }) {
511
559
  _Application_servicers.set(this, void 0);
560
+ _Application_libraries.set(this, void 0);
512
561
  _Application_tokenVerifier.set(this, void 0);
513
562
  _Application_express.set(this, void 0);
514
563
  _Application_http.set(this, void 0);
515
564
  _Application_servers.set(this, void 0);
516
565
  _Application_createExternalContext.set(this, void 0);
517
566
  _Application_external.set(this, void 0);
518
- __classPrivateFieldSet(this, _Application_servicers, servicers, "f");
567
+ __classPrivateFieldSet(this, _Application_servicers, servicers || [], "f");
568
+ __classPrivateFieldSet(this, _Application_libraries, libraries || [], "f");
519
569
  __classPrivateFieldSet(this, _Application_tokenVerifier, tokenVerifier, "f");
520
570
  __classPrivateFieldSet(this, _Application_express, express(), "f");
521
571
  // We assume that our users will want these middleware.
@@ -533,7 +583,7 @@ export class Application {
533
583
  }
534
584
  assert(kind === "initialize");
535
585
  return InitializeContext.fromNativeExternal(external);
536
- }, servicers, {
586
+ }, __classPrivateFieldGet(this, _Application_servicers, "f"), {
537
587
  start: async (serverId, port, createExternalContext) => {
538
588
  // Store `createExternalContext` function before listening
539
589
  // so we don't attempt to serve any traffic and try and use
@@ -592,11 +642,14 @@ export class Application {
592
642
  throw error;
593
643
  }
594
644
  }
595
- }, initializeBearerToken, tokenVerifier), "f");
645
+ }, initializeBearerToken, tokenVerifier, __classPrivateFieldGet(this, _Application_libraries, "f").map(prepareLibrary)), "f");
596
646
  }
597
647
  get servicers() {
598
648
  return __classPrivateFieldGet(this, _Application_servicers, "f");
599
649
  }
650
+ get libraries() {
651
+ return __classPrivateFieldGet(this, _Application_libraries, "f");
652
+ }
600
653
  get tokenVerifier() {
601
654
  return __classPrivateFieldGet(this, _Application_tokenVerifier, "f");
602
655
  }
@@ -612,13 +665,25 @@ export class Application {
612
665
  please report this issue to the maintainers.`);
613
666
  });
614
667
  }
668
+ // Get all the TypeScript libraries and sort them by name.
669
+ const tsLibraries = __classPrivateFieldGet(this, _Application_libraries, "f")
670
+ .filter((library) => library.nativeLibraryModule === undefined)
671
+ .map((library) => library)
672
+ .sort((a, b) => (a.name > b.name ? 1 : b.name > a.name ? -1 : 0));
673
+ // Execute pre-run for TypeScript libraries.
674
+ // Python's NodeApplication responsible for running Python libraries.
675
+ for (const library of tsLibraries) {
676
+ if (library.preRun) {
677
+ library.preRun(this);
678
+ }
679
+ }
615
680
  return await reboot_native.Application_run(__classPrivateFieldGet(this, _Application_external, "f"));
616
681
  }
617
682
  get __external() {
618
683
  return __classPrivateFieldGet(this, _Application_external, "f");
619
684
  }
620
685
  }
621
- _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();
686
+ _Application_servicers = new WeakMap(), _Application_libraries = 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();
622
687
  (function (Application) {
623
688
  var _Http_express, _Http_createExternalContext;
624
689
  class Http {
package/package.json CHANGED
@@ -2,11 +2,11 @@
2
2
  "dependencies": {
3
3
  "@bufbuild/protoplugin": "1.10.1",
4
4
  "@bufbuild/protoc-gen-es": "1.10.1",
5
- "@reboot-dev/reboot-api": "0.44.0",
5
+ "@reboot-dev/reboot-api": "0.45.1",
6
6
  "chalk": "^4.1.2",
7
7
  "node-addon-api": "^7.0.0",
8
8
  "node-gyp": ">=10.2.0",
9
- "uuid": "^9.0.1",
9
+ "uuid": "11.1.0",
10
10
  "which-pm-runs": "^1.1.0",
11
11
  "extensionless": "^1.9.9",
12
12
  "esbuild": "^0.24.0",
@@ -22,7 +22,7 @@
22
22
  },
23
23
  "type": "module",
24
24
  "name": "@reboot-dev/reboot",
25
- "version": "0.44.0",
25
+ "version": "0.45.1",
26
26
  "description": "npm package for Reboot",
27
27
  "scripts": {
28
28
  "preinstall": "node preinstall.cjs",
@@ -35,7 +35,6 @@
35
35
  "typescript": "5.4.5",
36
36
  "@types/express": "^5.0.1",
37
37
  "@types/node": "20.11.5",
38
- "@types/uuid": "^9.0.4",
39
38
  "@types/express-serve-static-core": "^5.0.6"
40
39
  },
41
40
  "bin": {
package/reboot_native.cc CHANGED
@@ -86,8 +86,7 @@ struct PythonNodeAdaptor {
86
86
  dynamic_cast<const py::error_already_set*>(&e)) {
87
87
  // This is a Python exception. Is it an `InputError`?
88
88
  if (e_py->matches(
89
- py::module::import("rebootdev.aio.exceptions")
90
- .attr("InputError"))) {
89
+ py::module::import("reboot.aio.exceptions").attr("InputError"))) {
91
90
  // This is an InputError, which means it reports a mistake in the input
92
91
  // provided by the developer. We want to print _only_ the user-friendly
93
92
  // error message in the exception, without intimidating stack traces.
@@ -128,7 +127,7 @@ struct PythonNodeAdaptor {
128
127
  if (signal_fd) {
129
128
  uint64_t one = 1;
130
129
  // Writing to this file descriptor communicates to Python
131
- // (see `public/rebootdev/nodejs/python.py`) that
130
+ // (see `public/reboot/nodejs/python.py`) that
132
131
  // `run_functions()` should be called (while holding the Python
133
132
  // GIL). In `Initialize()` we've set up `run_functions()` to run
134
133
  // the `python_functions` we've just added our callback to.
@@ -398,7 +397,7 @@ void PythonNodeAdaptor::Initialize(
398
397
  py::initialize_interpreter();
399
398
 
400
399
  try {
401
- py::object module = py::module::import("rebootdev.nodejs.python");
400
+ py::object module = py::module::import("reboot.nodejs.python");
402
401
 
403
402
  module.attr("launch_subprocess_server") =
404
403
  py::cpp_function([](py::str py_base64_args) {
@@ -858,7 +857,7 @@ Napi::Promise NodePromiseFromPythonTaskWithContext(
858
857
  env,
859
858
  std::move(name),
860
859
  std::make_tuple(
861
- std::string("rebootdev.nodejs.python"),
860
+ std::string("reboot.nodejs.python"),
862
861
  std::string("create_task_with_context"),
863
862
  py_context),
864
863
  [js_context, // Ensures `py_context` remains valid.
@@ -963,7 +962,7 @@ py::object PythonFutureFromNodePromise(
963
962
  })});
964
963
  } catch (const std::exception& e) {
965
964
  // NOTE: we're just catching exception here vs `Napi::Error`
966
- // since all we care about is `what()` but we're making sure
965
+ // since all we care about is `what()` but we're making sue
967
966
  // that we'll get all `Napi::Error` with this
968
967
  // `static_assert`.
969
968
  static_assert(
@@ -1045,7 +1044,7 @@ void ImportPy(const Napi::CallbackInfo& info) {
1045
1044
  std::string base64_encoded_rbt_py = info[1].As<Napi::String>().Utf8Value();
1046
1045
 
1047
1046
  RunCallbackOnPythonEventLoop([&module, &base64_encoded_rbt_py]() {
1048
- py::module::import("rebootdev.nodejs.python")
1047
+ py::module::import("reboot.nodejs.python")
1049
1048
  .attr("import_py")(module, base64_encoded_rbt_py);
1050
1049
  });
1051
1050
  }
@@ -1081,7 +1080,7 @@ static const napi_type_tag reboot_aio_applications_Application =
1081
1080
 
1082
1081
 
1083
1082
  static const napi_type_tag reboot_aio_external_ExternalContext =
1084
- MakeTypeTag("rebootdev.aio.external", "ExternalContext");
1083
+ MakeTypeTag("reboot.aio.external", "ExternalContext");
1085
1084
 
1086
1085
 
1087
1086
  Napi::Value python3Path(const Napi::CallbackInfo& info) {
@@ -1268,8 +1267,8 @@ py::object make_py_authorizer(NapiSafeObjectReference js_authorizer) {
1268
1267
  py::is_method(py::none()));
1269
1268
 
1270
1269
  // Now define our subclass.
1271
- py::object py_parent_class = py::module::import("rebootdev.nodejs.python")
1272
- .attr("NodeAdaptorAuthorizer");
1270
+ py::object py_parent_class =
1271
+ py::module::import("reboot.nodejs.python").attr("NodeAdaptorAuthorizer");
1273
1272
 
1274
1273
  py::object py_parent_metaclass =
1275
1274
  py::reinterpret_borrow<py::object>((PyObject*) &PyType_Type);
@@ -1531,12 +1530,203 @@ py::list make_py_servicers(
1531
1530
 
1532
1531
  // Include memoize servicers by default!
1533
1532
  py_servicers.attr("extend")(
1534
- py::module::import("rebootdev.aio.memoize").attr("servicers")());
1533
+ py::module::import("reboot.aio.memoize").attr("servicers")());
1535
1534
 
1536
1535
  return py_servicers;
1537
1536
  }
1538
1537
 
1539
1538
 
1539
+ struct TypeScriptLibraryDetails {
1540
+ std::string name;
1541
+ std::vector<std::shared_ptr<ServicerDetails>> js_servicers;
1542
+ std::vector<std::string> js_requirements;
1543
+ std::optional<NapiSafeFunctionReference> js_initialize;
1544
+ };
1545
+
1546
+ struct PythonNativeLibraryDetails {
1547
+ std::string py_library_module;
1548
+ std::string py_library_function;
1549
+ std::optional<NapiSafeObjectReference> js_authorizer;
1550
+ };
1551
+
1552
+ using LibraryDetails =
1553
+ std::variant<TypeScriptLibraryDetails, PythonNativeLibraryDetails>;
1554
+ std::vector<std::shared_ptr<LibraryDetails>> make_library_details(
1555
+ Napi::Env env,
1556
+ const Napi::Array& js_libraries) {
1557
+ std::vector<std::shared_ptr<LibraryDetails>> library_details;
1558
+
1559
+ for (auto&& [_, v] : js_libraries) {
1560
+ Napi::Object js_library = Napi::Value(v).As<Napi::Object>();
1561
+
1562
+ if (!js_library.Get("nativeLibraryModule").IsUndefined()) {
1563
+ std::string module =
1564
+ js_library.Get("nativeLibraryModule").As<Napi::String>().Utf8Value();
1565
+ std::string function = js_library.Get("nativeLibraryFunction")
1566
+ .As<Napi::String>()
1567
+ .Utf8Value();
1568
+
1569
+ // Get authorizer.
1570
+ std::optional<NapiSafeObjectReference> js_authorizer;
1571
+ if (!js_library.Get("authorizer").IsUndefined()) {
1572
+ js_authorizer = NapiSafeObjectReference(
1573
+ js_library.Get("authorizer").As<Napi::Object>());
1574
+ }
1575
+
1576
+ library_details.push_back(
1577
+ std::make_shared<LibraryDetails>(
1578
+ PythonNativeLibraryDetails{module, function, js_authorizer}));
1579
+ } else if (
1580
+ !js_library.Get("name").IsUndefined()
1581
+ && !js_library.Get("servicers").IsUndefined()) {
1582
+ std::string name = js_library.Get("name").As<Napi::String>().Utf8Value();
1583
+
1584
+ // Get servicers.
1585
+ Napi::Function js_servicers_func =
1586
+ js_library.Get("servicers").As<Napi::Function>();
1587
+ Napi::Value js_servicers_value = js_servicers_func.Call(js_library, {});
1588
+ Napi::Array js_servicers = js_servicers_value.As<Napi::Array>();
1589
+ auto servicer_details = make_servicer_details(env, js_servicers);
1590
+
1591
+ // Get requirements.
1592
+ std::vector<std::string> requirements;
1593
+ if (!js_library.Get("requirements").IsUndefined()) {
1594
+ Napi::Function js_requirements_func =
1595
+ js_library.Get("requirements").As<Napi::Function>();
1596
+ Napi::Value js_requirements_value =
1597
+ js_requirements_func.Call(js_library, {});
1598
+ Napi::Array js_requirements = js_requirements_value.As<Napi::Array>();
1599
+ for (auto&& [_, requirements_v] : js_requirements) {
1600
+ std::string requirement =
1601
+ Napi::Value(requirements_v).As<Napi::String>().Utf8Value();
1602
+ requirements.push_back(requirement);
1603
+ }
1604
+ }
1605
+
1606
+ std::optional<NapiSafeFunctionReference> js_initialize;
1607
+ if (!js_library.Get("initialize").IsUndefined()) {
1608
+ js_initialize = NapiSafeFunctionReference(
1609
+ js_library.Get("initialize").As<Napi::Function>());
1610
+ }
1611
+
1612
+ library_details.push_back(
1613
+ std::make_shared<LibraryDetails>(TypeScriptLibraryDetails{
1614
+ name,
1615
+ servicer_details,
1616
+ requirements,
1617
+ js_initialize}));
1618
+ } else {
1619
+ Napi::Error::New(env, "Unexpected `library` type.")
1620
+ .ThrowAsJavaScriptException();
1621
+ }
1622
+ }
1623
+
1624
+ return library_details;
1625
+ }
1626
+
1627
+ py::object make_py_typescript_library(
1628
+ TypeScriptLibraryDetails& details,
1629
+ NapiSafeFunctionReference js_from_native_external) {
1630
+ // Construct a subclass of Library.
1631
+ //
1632
+ // What we do below is the recommended way to do that, see:
1633
+ // https://github.com/pybind/pybind11/issues/1193
1634
+
1635
+ // First create all of the attributes that we'll want this
1636
+ // subclass to have.
1637
+ py::dict attributes;
1638
+ attributes["name"] = details.name;
1639
+ attributes["_servicers"] = make_py_servicers(details.js_servicers);
1640
+ attributes["_requirements"] = details.js_requirements;
1641
+ attributes["_initialize"] = py::none();
1642
+
1643
+ if (details.js_initialize.has_value()) {
1644
+ NapiSafeFunctionReference js_initialize = details.js_initialize.value();
1645
+
1646
+ attributes["_initialize"] = py::cpp_function(
1647
+ [js_initialize = std::move(js_initialize),
1648
+ js_from_native_external /* Need a copy because it is shared. ??? */](
1649
+ py::object py_context) mutable {
1650
+ return PythonFutureFromNodePromise(
1651
+ [js_initialize,
1652
+ js_from_native_external, // NOTE: need a _copy_ of
1653
+ // both `js_initialize`
1654
+ // and
1655
+ // `js_from_native_external`
1656
+ // here since
1657
+ // `py_initialize` may be
1658
+ // called more than once!
1659
+ py_context = new py::object(py_context)](Napi::Env env) mutable {
1660
+ Napi::External<py::object> js_external_context =
1661
+ make_napi_external(
1662
+ env,
1663
+ py_context,
1664
+ &reboot_aio_external_ExternalContext);
1665
+ Napi::Object js_context =
1666
+ js_from_native_external.Value(env)
1667
+ .Call(
1668
+ env.Global(),
1669
+ {js_external_context,
1670
+ Napi::String::New(env, "initialize")})
1671
+ .As<Napi::Object>();
1672
+
1673
+ return js_initialize.Value(env)
1674
+ .Call(env.Global(), {js_context})
1675
+ .As<Napi::Object>();
1676
+ });
1677
+ });
1678
+ }
1679
+
1680
+ // Define the subclass.
1681
+ py::object py_parent_class =
1682
+ py::module::import("reboot.aio.applications").attr("NodeAdaptorLibrary");
1683
+
1684
+ py::object py_parent_metaclass =
1685
+ py::reinterpret_borrow<py::object>((PyObject*) &PyType_Type);
1686
+
1687
+ py::object py_library = py_parent_metaclass(
1688
+ "_NodeAdaptorLibrary",
1689
+ py::make_tuple(py_parent_class),
1690
+ attributes);
1691
+
1692
+ return py_library();
1693
+ }
1694
+
1695
+ // NOTE: must be called from within _Python_.
1696
+ py::list make_py_libraries(
1697
+ const std::vector<std::shared_ptr<LibraryDetails>>& library_details,
1698
+ NapiSafeFunctionReference js_from_native_external) {
1699
+ py::list py_libraries;
1700
+ for (const auto& details : library_details) {
1701
+ if (auto typescript_details =
1702
+ std::get_if<TypeScriptLibraryDetails>(details.get())) {
1703
+ py_libraries.append(make_py_typescript_library(
1704
+ *typescript_details,
1705
+ js_from_native_external));
1706
+ } else if (
1707
+ auto python_details =
1708
+ std::get_if<PythonNativeLibraryDetails>(details.get())) {
1709
+ // Call the library() function with `authorizer` if one is provided.
1710
+ py::object py_library;
1711
+ if (python_details->js_authorizer.has_value()) {
1712
+ py_library =
1713
+ py::module::import(python_details->py_library_module.c_str())
1714
+ .attr(python_details->py_library_function.c_str())(
1715
+ "authorizer"_a =
1716
+ make_py_authorizer(*python_details->js_authorizer));
1717
+ } else {
1718
+ py_library =
1719
+ py::module::import(python_details->py_library_module.c_str())
1720
+ .attr(python_details->py_library_function.c_str())();
1721
+ }
1722
+ py_libraries.append(py_library);
1723
+ }
1724
+ }
1725
+
1726
+ return py_libraries;
1727
+ }
1728
+
1729
+
1540
1730
  // NOTE: must be called from within _Python_.
1541
1731
  py::object make_py_token_verifier(NapiSafeObjectReference js_token_verifier) {
1542
1732
  // Construct a subclass of TokenVerifier.
@@ -1615,7 +1805,7 @@ py::object make_py_token_verifier(NapiSafeObjectReference js_token_verifier) {
1615
1805
  py::arg("bytes_call"),
1616
1806
  py::is_method(py::none()));
1617
1807
 
1618
- py::object py_parent_class = py::module::import("rebootdev.nodejs.python")
1808
+ py::object py_parent_class = py::module::import("reboot.nodejs.python")
1619
1809
  .attr("NodeAdaptorTokenVerifier");
1620
1810
 
1621
1811
  py::object py_parent_metaclass =
@@ -1672,12 +1862,6 @@ Napi::Value Reboot_up(const Napi::CallbackInfo& info) {
1672
1862
  }
1673
1863
  return py_reboot->attr("up")(
1674
1864
  py_application,
1675
- // NOTE: while we support subprocess servers
1676
- // for `rbt dev` and `rbt serve` we do not support
1677
- // them for tests because we don't have a way to
1678
- // clone a process like we do with multiprocessing
1679
- // in Python.
1680
- "in_process"_a = true,
1681
1865
  "local_envoy"_a = py_local_envoy,
1682
1866
  "local_envoy_port"_a = local_envoy_port);
1683
1867
  },
@@ -1763,8 +1947,8 @@ Napi::Value Reboot_stop(const Napi::CallbackInfo& info) {
1763
1947
  return js_promise;
1764
1948
  }
1765
1949
 
1766
- // NOTE: We block on a promise here, so this method should not be called outside
1767
- // of tests.
1950
+ // NOTE: We block on a promise here, so this method should not be called
1951
+ // outside of tests.
1768
1952
  Napi::Value Reboot_url(const Napi::CallbackInfo& info) {
1769
1953
  // NOTE: we immediately get a safe reference to the `Napi::External`
1770
1954
  // so that Node will not garbage collect it and the `py::object*` we
@@ -1916,7 +2100,7 @@ Napi::Value Task_await(const Napi::CallbackInfo& info) {
1916
2100
 
1917
2101
  return NodePromiseFromPythonTaskWithContext(
1918
2102
  info.Env(),
1919
- "rebootdev.nodejs.python.task_await(\"" + state_name + "\", \"" + method
2103
+ "reboot.nodejs.python.task_await(\"" + state_name + "\", \"" + method
1920
2104
  + "\", ...) in nodejs",
1921
2105
  js_external_context,
1922
2106
  [rbt_module = std::move(rbt_module),
@@ -1925,7 +2109,7 @@ Napi::Value Task_await(const Napi::CallbackInfo& info) {
1925
2109
  js_external_context, // Ensures `py_context` remains valid.
1926
2110
  py_context,
1927
2111
  json_task_id]() {
1928
- return py::module::import("rebootdev.nodejs.python")
2112
+ return py::module::import("reboot.nodejs.python")
1929
2113
  .attr("task_await")(
1930
2114
  py_context,
1931
2115
  py::module::import(rbt_module.c_str()).attr(state_name.c_str()),
@@ -1973,7 +2157,7 @@ Napi::Value ExternalContext_constructor(const Napi::CallbackInfo& info) {
1973
2157
  &idempotency_seed,
1974
2158
  &idempotency_required,
1975
2159
  &idempotency_required_reason]() {
1976
- py::object py_external = py::module::import("rebootdev.aio.external");
2160
+ py::object py_external = py::module::import("reboot.aio.external");
1977
2161
 
1978
2162
  auto convert_str =
1979
2163
  [](const std::optional<std::string>& optional) -> py::object {
@@ -2007,6 +2191,7 @@ Napi::Value Application_constructor(const Napi::CallbackInfo& info) {
2007
2191
  NapiSafeFunctionReference(info[0].As<Napi::Function>());
2008
2192
 
2009
2193
  Napi::Array js_servicers = info[1].As<Napi::Array>();
2194
+ auto servicer_details = make_servicer_details(info.Env(), js_servicers);
2010
2195
 
2011
2196
  Napi::Object js_web_framework = info[2].As<Napi::Object>();
2012
2197
 
@@ -2016,8 +2201,6 @@ Napi::Value Application_constructor(const Napi::CallbackInfo& info) {
2016
2201
  auto js_web_framework_stop = NapiSafeFunctionReference(
2017
2202
  js_web_framework.Get("stop").As<Napi::Function>());
2018
2203
 
2019
- auto servicer_details = make_servicer_details(info.Env(), js_servicers);
2020
-
2021
2204
  auto js_initialize = NapiSafeFunctionReference(info[3].As<Napi::Function>());
2022
2205
 
2023
2206
  std::optional<std::string> initialize_bearer_token;
@@ -2030,6 +2213,9 @@ Napi::Value Application_constructor(const Napi::CallbackInfo& info) {
2030
2213
  js_token_verifier = NapiSafeReference(info[5].As<Napi::Object>());
2031
2214
  }
2032
2215
 
2216
+ Napi::Array js_libraries = info[6].As<Napi::Array>();
2217
+ auto library_details = make_library_details(info.Env(), js_libraries);
2218
+
2033
2219
  py::object* py_application = RunCallbackOnPythonEventLoop(
2034
2220
  [servicer_details = std::move(servicer_details),
2035
2221
  js_web_framework_start = std::move(js_web_framework_start),
@@ -2037,8 +2223,11 @@ Napi::Value Application_constructor(const Napi::CallbackInfo& info) {
2037
2223
  initialize_bearer_token = std::move(initialize_bearer_token),
2038
2224
  js_initialize = std::move(js_initialize),
2039
2225
  js_token_verifier,
2040
- js_from_native_external = std::move(js_from_native_external)]() {
2226
+ js_from_native_external = std::move(js_from_native_external),
2227
+ library_details = std::move(library_details)]() {
2041
2228
  py::list py_servicers = make_py_servicers(servicer_details);
2229
+ py::list py_libraries =
2230
+ make_py_libraries(library_details, js_from_native_external);
2042
2231
 
2043
2232
  py::object py_web_framework_start = py::cpp_function(
2044
2233
  [js_web_framework_start = std::move(js_web_framework_start),
@@ -2105,7 +2294,7 @@ Napi::Value Application_constructor(const Napi::CallbackInfo& info) {
2105
2294
 
2106
2295
  return new py::object(
2107
2296
  py::module::import(
2108
- "rebootdev.aio.external")
2297
+ "reboot.aio.external")
2109
2298
  .attr("ExternalContext")(
2110
2299
  "name"_a = py::str(name),
2111
2300
  "channel_manager"_a =
@@ -2216,6 +2405,7 @@ Napi::Value Application_constructor(const Napi::CallbackInfo& info) {
2216
2405
  py::module::import("reboot.aio.applications")
2217
2406
  .attr("NodeApplication")(
2218
2407
  "servicers"_a = py_servicers,
2408
+ "libraries"_a = py_libraries,
2219
2409
  "web_framework_start"_a = py_web_framework_start,
2220
2410
  "web_framework_stop"_a = py_web_framework_stop,
2221
2411
  "initialize"_a = py_initialize,
@@ -2245,7 +2435,7 @@ Napi::Value Application_run(const Napi::CallbackInfo& info) {
2245
2435
  Napi::Promise js_promise = NodePromiseFromPythonTask(
2246
2436
  info.Env(),
2247
2437
  "Application.run() in nodejs",
2248
- {"rebootdev.nodejs.python", "create_task"},
2438
+ {"reboot.nodejs.python", "create_task"},
2249
2439
  [js_external_application, // Ensures `py_application` remains valid.
2250
2440
  py_application]() { return py_application->attr("run")(); });
2251
2441
 
@@ -2299,10 +2489,10 @@ Napi::Value Context_generateIdempotentStateId(const Napi::CallbackInfo& info) {
2299
2489
  if (js_alias.IsString()) {
2300
2490
  alias = js_alias.As<Napi::String>().Utf8Value();
2301
2491
  }
2302
- auto js_each_iteration = idempotency_options.Get("eachIteration");
2303
- std::optional<bool> each_iteration;
2304
- if (js_each_iteration.IsBoolean()) {
2305
- each_iteration = js_each_iteration.As<Napi::Boolean>();
2492
+ auto js_per_iteration = idempotency_options.Get("perIteration");
2493
+ std::optional<bool> per_iteration;
2494
+ if (js_per_iteration.IsBoolean()) {
2495
+ per_iteration = js_per_iteration.As<Napi::Boolean>();
2306
2496
  }
2307
2497
 
2308
2498
  return NodePromiseFromPythonCallback(
@@ -2314,11 +2504,11 @@ Napi::Value Context_generateIdempotentStateId(const Napi::CallbackInfo& info) {
2314
2504
  method = std::move(method),
2315
2505
  key = std::move(key),
2316
2506
  alias = std::move(alias),
2317
- each_iteration = std::move(each_iteration)]() {
2507
+ per_iteration = std::move(per_iteration)]() {
2318
2508
  // Need to use `call_with_context` to ensure that we have
2319
2509
  // `py_context` as a valid asyncio context variable.
2320
2510
  py::object py_idempotency =
2321
- py::module::import("rebootdev.nodejs.python")
2511
+ py::module::import("reboot.nodejs.python")
2322
2512
  .attr("call_with_context")(
2323
2513
  py::cpp_function([&]() {
2324
2514
  py::object py_key = py::none();
@@ -2329,16 +2519,17 @@ Napi::Value Context_generateIdempotentStateId(const Napi::CallbackInfo& info) {
2329
2519
  if (alias.has_value()) {
2330
2520
  py_alias = py::cast(*alias);
2331
2521
  }
2332
- py::object py_each_iteration = py::none();
2333
- if (each_iteration.has_value()) {
2334
- py_each_iteration = py::cast(*each_iteration);
2522
+ py::object py_how = py::none();
2523
+ if (per_iteration.has_value() && *per_iteration) {
2524
+ py_how = py::module::import("reboot.aio.idempotency")
2525
+ .attr("PER_ITERATION");
2335
2526
  }
2336
- return py::module::import("rebootdev.aio.contexts")
2527
+ return py::module::import("reboot.aio.contexts")
2337
2528
  .attr("Context")
2338
2529
  .attr("idempotency")(
2339
2530
  "key"_a = py_key,
2340
2531
  "alias"_a = py_alias,
2341
- "each_iteration"_a = py_each_iteration);
2532
+ "how"_a = py_how);
2342
2533
  }),
2343
2534
  py_context);
2344
2535
 
@@ -2394,7 +2585,7 @@ Napi::Value WorkflowContext_loop(const Napi::CallbackInfo& info) {
2394
2585
  [js_external_context, // Ensures `py_context` remains valid.
2395
2586
  py_context,
2396
2587
  alias = std::move(alias)]() {
2397
- return py::module::import("rebootdev.nodejs.python")
2588
+ return py::module::import("reboot.nodejs.python")
2398
2589
  .attr("loop")(py_context, alias);
2399
2590
  },
2400
2591
  [](py::object py_iterate) { return new py::object(py_iterate); },
@@ -2472,7 +2663,7 @@ Napi::Value retry_reactively_until(const Napi::CallbackInfo& info) {
2472
2663
  });
2473
2664
  });
2474
2665
 
2475
- return py::module::import("rebootdev.aio.contexts")
2666
+ return py::module::import("reboot.aio.contexts")
2476
2667
  .attr("retry_reactively_until")(py_context, py_condition);
2477
2668
  });
2478
2669
  }
@@ -2524,7 +2715,7 @@ Napi::Value memoize(const Napi::CallbackInfo& info) {
2524
2715
  });
2525
2716
  });
2526
2717
 
2527
- return py::module::import("rebootdev.aio.memoize")
2718
+ return py::module::import("reboot.aio.memoize")
2528
2719
  .attr("memoize")(
2529
2720
  py::make_tuple(alias, how),
2530
2721
  py_context,
package/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const REBOOT_VERSION = "0.44.0";
1
+ export declare const REBOOT_VERSION = "0.45.1";
package/version.js CHANGED
@@ -1 +1 @@
1
- export const REBOOT_VERSION = "0.44.0";
1
+ export const REBOOT_VERSION = "0.45.1";
package/zod-to-proto.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env tsx
2
- import { toCamelCase, toPascalCase, toSnakeCase, typeSchema, ZOD_ERROR_NAMES, } from "@reboot-dev/reboot-api";
2
+ import { ALLOWED_DEFAULT_BY_CONSTRUCTOR_NAME, toCamelCase, toPascalCase, toSnakeCase, typeSchema, ZOD_ERROR_NAMES, } from "@reboot-dev/reboot-api";
3
3
  import { strict as assert } from "assert";
4
4
  import chalk from "chalk";
5
5
  import { mkdtemp } from "fs/promises";
@@ -56,7 +56,7 @@ const generate = (proto, { schema, path, name, state = false, }) => {
56
56
  const { tag } = meta;
57
57
  if (tags.has(tag)) {
58
58
  // TODO: give "path" to this property.
59
- console.error(chalk.stderr.bold.red(`Trying to use tag ${tag} with property '${key}' already used by '${tags.get(tag)}'`));
59
+ console.error(chalk.stderr.bold.red(`Trying to use tag '${tag}' with property '${key}' already used by '${tags.get(tag)}'`));
60
60
  process.exit(-1);
61
61
  }
62
62
  tags.set(tag, key);
@@ -79,7 +79,8 @@ const generate = (proto, { schema, path, name, state = false, }) => {
79
79
  `primitive type (e.g., string, number, boolean) ` +
80
80
  `which is immutable or pass a function that creates ` +
81
81
  `a new ${Array.isArray(firstDefaultValue) ? "array" : "object"} ` +
82
- `each time\n`));
82
+ `each time (for example \`reboot_api.EMPTY_ARRAY\` or ` +
83
+ `\`reboot_api.EMPTY_RECORD\`).`));
83
84
  process.exit(-1);
84
85
  }
85
86
  // Otherwise, if objects are different, that means that
@@ -401,7 +402,7 @@ const generate = (proto, { schema, path, name, state = false, }) => {
401
402
  }
402
403
  const { tag } = meta;
403
404
  if (tags.has(tag)) {
404
- console.error(chalk.stderr.bold.red(`Trying to use already used tag ${tag} in discriminated union`));
405
+ console.error(chalk.stderr.bold.red(`Trying to use already used tag '${tag}' in discriminated union`));
405
406
  process.exit(-1);
406
407
  }
407
408
  tags.set(tag, [toSnakeCase(literal), typeName]);
@@ -424,6 +425,121 @@ const generate = (proto, { schema, path, name, state = false, }) => {
424
425
  throw new Error(`Unexpected type '${schema._zod.def.type}'`);
425
426
  }
426
427
  };
428
+ const validateProperDefaultSpecified = (schema, path) => {
429
+ if (schema instanceof z.ZodObject) {
430
+ const shape = schema._zod.def.shape;
431
+ for (const key in shape) {
432
+ validateProperDefaultSpecified(shape[key], `${path}.${key}`);
433
+ }
434
+ }
435
+ else if (schema instanceof z.ZodRecord) {
436
+ validateProperDefaultSpecified(schema.valueType, `${path}.[value]`);
437
+ }
438
+ else if (schema instanceof z.ZodArray) {
439
+ validateProperDefaultSpecified(schema.element, `${path}.[item]`);
440
+ }
441
+ else if (schema instanceof z.ZodDiscriminatedUnion) {
442
+ for (const option of schema.options) {
443
+ // NOTE: The path in the error will be something like:
444
+ // `api.typeName.methods.methodName.errors.[object Object].field`.
445
+ validateProperDefaultSpecified(option, `${path}.${option}`);
446
+ }
447
+ }
448
+ else if (schema instanceof z.ZodDefault) {
449
+ const innerType = schema._zod.def.innerType;
450
+ const isOptional = innerType instanceof z.ZodOptional;
451
+ const isObject = innerType instanceof z.ZodObject;
452
+ const defaultValue = schema._zod.def.defaultValue;
453
+ // TODO: Write the document about why it is challenging
454
+ // to have default values in the distributed systems
455
+ // and attach the link to the error message.
456
+ if (innerType instanceof z.ZodDiscriminatedUnion) {
457
+ // Discriminated unions cannot have default values,
458
+ // because its options will be always different
459
+ // `z.object` types and we don't support that currently.
460
+ console.error(chalk.stderr.bold.red(`'${path}' is a discriminated union type and cannot have a ` +
461
+ `\`default\` value.`));
462
+ process.exit(-1);
463
+ }
464
+ else if (isObject && !isOptional) {
465
+ console.error(chalk.stderr.bold.red(`'${path}' is a non-optional object type and cannot have a ` +
466
+ `\`default\` value. Use \`optional()\` for object types with empty default.`));
467
+ process.exit(-1);
468
+ }
469
+ else if (defaultValue === undefined && !isOptional) {
470
+ console.error(chalk.stderr.bold.red(`'${path}' is a non-optional type and cannot have an ` +
471
+ `\`undefined\` default value. Change the \`default\` or make the ` +
472
+ `field \`optional()\`.`));
473
+ process.exit(-1);
474
+ }
475
+ else if (defaultValue !== undefined && isOptional) {
476
+ console.error(chalk.stderr.bold.red(`'${path}' is an \`optional\` type and can only have ` +
477
+ `\`undefined\` default value.`));
478
+ process.exit(-1);
479
+ }
480
+ else if (isOptional) {
481
+ // If the field is `optional` and uses `default=undefined`,
482
+ // it is valid, check the inner type recursively.
483
+ validateProperDefaultSpecified(innerType, path);
484
+ return;
485
+ }
486
+ const isEmptyArray = innerType instanceof z.ZodArray &&
487
+ Array.isArray(defaultValue) &&
488
+ defaultValue.length === 0;
489
+ const isEmptyRecord = innerType instanceof z.ZodRecord &&
490
+ typeof defaultValue === "object" &&
491
+ defaultValue !== null &&
492
+ Object.keys(defaultValue).length === 0;
493
+ const innerTypeConstructorName = innerType.constructor.name;
494
+ const allowedDefault =
495
+ // Return `undefined` if not found.
496
+ ALLOWED_DEFAULT_BY_CONSTRUCTOR_NAME[innerTypeConstructorName];
497
+ if (innerType instanceof z.ZodArray) {
498
+ if (!isEmptyArray) {
499
+ console.error(chalk.stderr.bold.red(`'${path}' is an \`array\` with an unsupported default value. ` +
500
+ `Only empty default value is supported. Use ` +
501
+ `\`reboot_api.EMPTY_ARRAY\` to safely specify an empty array default.`));
502
+ process.exit(-1);
503
+ }
504
+ }
505
+ else if (innerType instanceof z.ZodRecord) {
506
+ if (!isEmptyRecord) {
507
+ console.error(chalk.stderr.bold.red(`'${path}' is a \`record\` with an unsupported default value. ` +
508
+ `Only empty default value is supported. Use ` +
509
+ `\`reboot_api.EMPTY_RECORD\` to safely specify an empty record default.`));
510
+ process.exit(-1);
511
+ }
512
+ }
513
+ else if (innerType instanceof z.ZodLiteral) {
514
+ // For Literal types, the default value must be the first literal
515
+ // value in the list (according to how Protobuf `enum`s work).
516
+ const firstLiteralValue = innerType._zod.def.values[0];
517
+ if (defaultValue !== firstLiteralValue) {
518
+ console.error(chalk.stderr.bold.red(`'${path}' is a \`literal\` with an unsupported default value. ` +
519
+ `Only the first literal value \`${firstLiteralValue}\` is ` +
520
+ `supported as the default.`));
521
+ process.exit(-1);
522
+ }
523
+ }
524
+ else if (allowedDefault === undefined) {
525
+ console.error(chalk.stderr.bold.red(`'${path}' uses \`default\` which is not supported for type ` +
526
+ `\`${innerTypeConstructorName}\`. Only ` +
527
+ `${Object.keys(ALLOWED_DEFAULT_BY_CONSTRUCTOR_NAME)
528
+ .map((name) => `\`${name}\``)
529
+ .join(", ")}` +
530
+ `, \`ZodArray\`, \`ZodRecord\`, \`ZodLiteral\`, and \`optional\` ` +
531
+ `types can have a \`default\` currently.`));
532
+ process.exit(-1);
533
+ }
534
+ else if (defaultValue !== allowedDefault) {
535
+ console.error(chalk.stderr.bold.red(`'${path}' has an unsupported default value. ` +
536
+ `Supported default value for type \`${innerTypeConstructorName}\` is ` +
537
+ `\`${JSON.stringify(allowedDefault)}\`.`));
538
+ process.exit(-1);
539
+ }
540
+ validateProperDefaultSpecified(innerType, path);
541
+ }
542
+ };
427
543
  const main = async () => {
428
544
  // We generate the `.proto` files in a temporary directory.
429
545
  const generatedProtosDirectory = await mkdtemp(path.join(os.tmpdir(), "protos-"));
@@ -501,6 +617,7 @@ const main = async () => {
501
617
  // It is safe to do so, because right before that we call
502
618
  // 'safeParse()', which ensures that the type is correct.
503
619
  const type = result.data;
620
+ validateProperDefaultSpecified(type.state instanceof z.ZodObject ? type.state : z.object(type.state), `api.${typeName}.state`);
504
621
  generate(proto, {
505
622
  schema: type.state instanceof z.ZodObject ? type.state : z.object(type.state),
506
623
  path: `api.${typeName}.state`,
@@ -511,6 +628,7 @@ const main = async () => {
511
628
  for (const methodName in type.methods) {
512
629
  // TODO: ensure `methodName` is PascalCase.
513
630
  const { request, response } = type.methods[methodName];
631
+ validateProperDefaultSpecified(request instanceof z.ZodObject ? request : z.object(request), `api.${typeName}.methods.${methodName}.request`);
514
632
  const requestTypeName = `${typeName}${toPascalCase(methodName)}Request`;
515
633
  generate(proto, {
516
634
  schema: request instanceof z.ZodObject ? request : z.object(request),
@@ -520,6 +638,7 @@ const main = async () => {
520
638
  if (response instanceof z.ZodVoid) {
521
639
  continue;
522
640
  }
641
+ validateProperDefaultSpecified(response instanceof z.ZodObject ? response : z.object(response), `api.${typeName}.methods.${methodName}.response`);
523
642
  const responseTypeName = `${typeName}${toPascalCase(methodName)}Response`;
524
643
  generate(proto, {
525
644
  schema: response instanceof z.ZodObject ? response : z.object(response),
@@ -554,6 +673,7 @@ const main = async () => {
554
673
  const errors = method.errors instanceof z.ZodDiscriminatedUnion
555
674
  ? method.errors
556
675
  : z.discriminatedUnion("type", method.errors);
676
+ validateProperDefaultSpecified(errors, `api.${typeName}.methods.${methodName}.errors`);
557
677
  const path = `api.${typeName}.methods.${methodName}.errors`;
558
678
  const name = `${typeName}${toPascalCase(methodName)}Errors`;
559
679
  errorsToGenerate.push(() => {