Skip to content

Commit 885c507

Browse files
[Release] Synchronize for release (#70)
1 parent ce4d985 commit 885c507

36 files changed

+2408
-788
lines changed

.bazelrc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,21 @@ build --action_env=PATH=/usr/local/bin:/usr/bin:/bin
138138
# credentials isn't a problem.
139139
common --repo_env=OCI_GET_TOKEN_ALLOW_FAIL=1
140140

141+
# Normally, on restart, Bazel will check that its `bazel-bin` and
142+
# `bazel-out` directories are untouched. As part of this for every file
143+
# in those directories it will check the modification time, possibly
144+
# compute a checksum, and write metadata to disk. Our directories are so
145+
# large that on a GitHub Codespace (which can have pretty poor I/O
146+
# performance) this "Checking Cached Actions" process can take 15
147+
# minutes or more - almost as much as a full rebuild. :-(
148+
#
149+
# Given that we don't modify our `bazel-bin` and `bazel-out` directories
150+
# manually, we opt out of this costly check with the following flag. The
151+
# drawback is that if we do ever accidentally modify the `bazel-bin` or
152+
# `bazel-out` directories, we must run `bazel clean` to get back into a
153+
# sane state.
154+
common --noexperimental_check_output_files
155+
141156
# Allow users to add local preferences that override the above.
142157
try-import %workspace%/user.bazelrc
143158

charts/reboot/Chart.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
apiVersion: 3.3.2
22
name: reboot
3-
version: "0.43.0"
3+
version: "0.44.0"
44
description: Reboot is a programming framework that enables transactional microservices built with the developer in mind.
55
type: application
66
keywords:
@@ -10,4 +10,4 @@ keywords:
1010
- scalable
1111
- reactive
1212
home: https://docs.reboot.dev/
13-
appVersion: "0.43.0"
13+
appVersion: "0.44.0"

rbt/v1alpha1/application_config.proto

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,15 @@ message ApplicationConfig {
101101
// it's confusing.
102102
uint32 revision_number = 10;
103103

104-
// The number of servers to run for this application.
105-
// NOTE: Must be a power of 2.
106-
// Unset means: use default.
104+
// The number of replicas to run for this application. Must be a
105+
// power of 2. This must be set for any Reboot application new
106+
// enough to support horizontal scaling; for backwards compatibility
107+
// however, an unset value will be interpreted as 1.
108+
optional uint32 replicas = 12;
109+
110+
// The number of servers to run for _each replica_ of this
111+
// application. Must be a power of 2. Unset means: use an arbitrary
112+
// default.
107113
optional uint32 servers = 7;
108114

109115
// The version of Reboot running in this application. This is used

rbt/v1alpha1/database.proto

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,12 @@ message RecoverRequest {
314314
// Shard IDs to recover data for. Only data belonging to these shards
315315
// will be returned. At least one shard ID must be specified.
316316
repeated string shard_ids = 2;
317+
318+
// Idempotent mutations are now recovered on demand via
319+
// `RecoverIdempotentMutations`, but for backwards compatibility
320+
// they are still recovered in `Recover` also unless explicitly
321+
// toggled off with this flag.
322+
bool skip_idempotent_mutations = 3;
317323
}
318324

319325
message RecoverResponse {
@@ -337,6 +343,21 @@ message RecoverResponse {
337343

338344
////////////////////////////////////////////////////////////////////////
339345

346+
message RecoverIdempotentMutationsRequest {
347+
// State ref to recover idempotent mutations for.
348+
string state_type = 1;
349+
string state_ref = 2;
350+
351+
// Recover an idempotent mutation for the specific key.
352+
optional bytes idempotency_key = 3;
353+
}
354+
355+
message RecoverIdempotentMutationsResponse {
356+
repeated IdempotentMutation idempotent_mutations = 1;
357+
}
358+
359+
////////////////////////////////////////////////////////////////////////
360+
340361
message TransactionParticipantPrepareRequest {
341362
string state_type = 1;
342363
string state_ref = 2;
@@ -455,6 +476,11 @@ service Database {
455476
// Returns state needed to recover the server after a potential restart.
456477
rpc Recover(RecoverRequest) returns (stream RecoverResponse);
457478

479+
// Returns the idempotent mutations so a server that has restarted
480+
// can determine if a mutation has already occured.
481+
rpc RecoverIdempotentMutations(RecoverIdempotentMutationsRequest)
482+
returns (stream RecoverIdempotentMutationsResponse);
483+
458484
rpc TransactionParticipantPrepare(TransactionParticipantPrepareRequest)
459485
returns (TransactionParticipantPrepareResponse);
460486

rbt/v1alpha1/index.ts

Lines changed: 107 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,23 +1033,35 @@ export function zodToProtoJson<T>(
10331033
schema: z.ZodType | Record<string, z.ZodType>,
10341034
input: T
10351035
): any {
1036-
assert(schema instanceof z.ZodVoid || input !== undefined);
1036+
assert(
1037+
schema instanceof z.ZodVoid || input !== undefined,
1038+
"zodToProtoJson: `input` must be defined unless `schema` is ZodVoid"
1039+
);
10371040

10381041
if (!(schema instanceof z.ZodType)) {
10391042
return zodToProtoJson(z.strictObject(schema), input);
10401043
} else if (schema instanceof z.ZodPipe) {
10411044
const meta = schema.meta();
1042-
assert(meta !== undefined && meta !== null);
1045+
assert(
1046+
meta !== undefined && meta !== null,
1047+
"zodToProtoJson: ZodPipe `schema` must have `meta`"
1048+
);
10431049
const schemaWithMeta = (schema.in as z.ZodType).meta(meta);
10441050
return zodToProtoJson(schemaWithMeta, input);
10451051
} else if (schema instanceof z.ZodOptional) {
10461052
const meta = schema.meta();
1047-
assert(meta !== undefined && meta !== null);
1053+
assert(
1054+
meta !== undefined && meta !== null,
1055+
"zodToProtoJson: ZodOptional `schema` must have `meta`"
1056+
);
10481057
const schemaWithMeta = (schema._zod.def.innerType as z.ZodType).meta(meta);
10491058
return zodToProtoJson(schemaWithMeta, input);
10501059
} else if (schema instanceof z.ZodDefault) {
10511060
const meta = schema.meta();
1052-
assert(meta !== undefined && meta !== null);
1061+
assert(
1062+
meta !== undefined && meta !== null,
1063+
"zodToProtoJson: ZodDefault `schema` must have `meta`"
1064+
);
10531065
const schemaWithMeta = (schema._zod.def.innerType as z.ZodType).meta(meta);
10541066
return zodToProtoJson(schemaWithMeta, input);
10551067
} else if (schema._zod.def.type === "string") {
@@ -1061,10 +1073,19 @@ export function zodToProtoJson<T>(
10611073
} else if (schema instanceof z.ZodBoolean) {
10621074
return input;
10631075
} else if (schema instanceof z.ZodLiteral) {
1064-
assert(typeof input === "string");
1065-
return input;
1076+
assert(
1077+
typeof input === "string",
1078+
"zodToProtoJson: ZodLiteral `input` must be a string"
1079+
);
1080+
// Since we need to prepend the field name to the actual `enum`
1081+
// value name in Protobuf schema, we need to return the index
1082+
// of the value here instead of the value itself.
1083+
return schema._zod.def.values.indexOf(input);
10661084
} else if (schema instanceof z.ZodRecord) {
1067-
assert(typeof input === "object");
1085+
assert(
1086+
typeof input === "object",
1087+
"zodToProtoJson: ZodRecord `input` must be an object"
1088+
);
10681089
// Need to _add_ the extra level of indirection we use to encode a
10691090
// record.
10701091
const output: { record: Record<string, any> } = { record: {} };
@@ -1081,18 +1102,27 @@ export function zodToProtoJson<T>(
10811102
}
10821103
return output;
10831104
} else if (schema instanceof z.ZodArray) {
1084-
assert(Array.isArray(input));
1105+
assert(
1106+
Array.isArray(input),
1107+
"zodToProtoJson: ZodArray `input` must be an array"
1108+
);
10851109
return {
10861110
items: input.map((item) =>
10871111
zodToProtoJson(schema.element as z.ZodType, item)
10881112
),
10891113
};
10901114
} else if (schema instanceof z.ZodObject) {
1091-
assert(typeof input === "object" && input !== null);
1115+
assert(
1116+
typeof input === "object" && input !== null,
1117+
"zodToProtoJson: ZodObject `input` must be a non-null object"
1118+
);
10921119
const shape = schema.shape;
10931120
const output: Record<string, any> = {};
10941121
for (const property in input) {
1095-
assert(property in schema.shape);
1122+
assert(
1123+
property in schema.shape,
1124+
`zodToProtoJson: \`property\` '${property}' not found in ZodObject \`shape\``
1125+
);
10961126
// Drop all `undefined` properties as they are technically not
10971127
// valid JSON and can't be encoded/decoded via protobuf.
10981128
if (input[property] === undefined) {
@@ -1103,15 +1133,22 @@ export function zodToProtoJson<T>(
11031133
return output;
11041134
} else if (schema instanceof z.ZodDiscriminatedUnion) {
11051135
const discriminator = schema._zod.def.discriminator;
1106-
assert(typeof discriminator === "string");
1136+
assert(
1137+
typeof discriminator === "string",
1138+
"zodToProtoJson: ZodDiscriminatedUnion `discriminator` must be a string"
1139+
);
11071140
assert(
11081141
typeIs(input, (input): input is T & { [key: string]: any } => {
11091142
return typeof input === "object" && discriminator in input;
1110-
})
1143+
}),
1144+
"zodToProtoJson: ZodDiscriminatedUnion `input` must be an object with `discriminator` property"
11111145
);
11121146

11131147
for (const option of schema.options as z.ZodObject[]) {
1114-
assert(discriminator in option.shape);
1148+
assert(
1149+
discriminator in option.shape,
1150+
"zodToProtoJson: `discriminator` must be present in ZodDiscriminatedUnion option `shape`"
1151+
);
11151152
if (
11161153
option.shape[discriminator]._zod.def.values[0] === input[discriminator]
11171154
) {
@@ -1126,15 +1163,21 @@ export function zodToProtoJson<T>(
11261163
}
11271164
throw new Error(`Invalid discriminated union`);
11281165
} else if (schema instanceof z.ZodVoid) {
1129-
assert(input === undefined);
1166+
assert(
1167+
input === undefined,
1168+
"zodToProtoJson: ZodVoid `input` must be undefined"
1169+
);
11301170
// Need this to be `google.protobuf.Empty`.
11311171
return {};
11321172
} else if (schema instanceof z.ZodCustom) {
11331173
// Currently we expect a custom schema to only be to alias a
11341174
// protobuf type that is otherwise well defined and does not
11351175
// require any conversion.
11361176
const meta = schema.meta();
1137-
assert(meta !== undefined && meta !== null && "protobuf" in meta);
1177+
assert(
1178+
meta !== undefined && meta !== null && "protobuf" in meta,
1179+
"zodToProtoJson: ZodCustom `schema` must have `meta` with 'protobuf' property"
1180+
);
11381181
return input;
11391182
} else if (
11401183
schema instanceof z.ZodLazy &&
@@ -1155,23 +1198,32 @@ export function protoToZod<T>(
11551198
schema: z.ZodType | Record<string, z.ZodType>,
11561199
input: T
11571200
): any {
1158-
assert(input !== undefined);
1201+
assert(input !== undefined, "protoToZod: `input` must be defined");
11591202

11601203
if (!(schema instanceof z.ZodType)) {
11611204
return protoToZod(z.strictObject(schema), input);
11621205
} else if (schema instanceof z.ZodPipe) {
11631206
const meta = schema.meta();
1164-
assert(meta !== undefined && meta !== null);
1207+
assert(
1208+
meta !== undefined && meta !== null,
1209+
"protoToZod: ZodPipe `schema` must have `meta`"
1210+
);
11651211
const schemaWithMeta = (schema.in as z.ZodType).meta(meta);
11661212
return protoToZod(schemaWithMeta, input);
11671213
} else if (schema instanceof z.ZodOptional) {
11681214
const meta = schema.meta();
1169-
assert(meta !== undefined && meta !== null);
1215+
assert(
1216+
meta !== undefined && meta !== null,
1217+
"protoToZod: ZodOptional `schema` must have `meta`"
1218+
);
11701219
const schemaWithMeta = (schema._zod.def.innerType as z.ZodType).meta(meta);
11711220
return protoToZod(schemaWithMeta, input);
11721221
} else if (schema instanceof z.ZodDefault) {
11731222
const meta = schema.meta();
1174-
assert(meta !== undefined && meta !== null);
1223+
assert(
1224+
meta !== undefined && meta !== null,
1225+
"protoToZod: ZodDefault `schema` must have `meta`"
1226+
);
11751227
const schemaWithMeta = (schema._zod.def.innerType as z.ZodType).meta(meta);
11761228
return protoToZod(schemaWithMeta, input);
11771229
} else if (schema._zod.def.type === "string") {
@@ -1183,15 +1235,24 @@ export function protoToZod<T>(
11831235
} else if (schema instanceof z.ZodBoolean) {
11841236
return input;
11851237
} else if (schema instanceof z.ZodLiteral) {
1186-
assert(typeof input === "number");
1238+
assert(
1239+
typeof input === "number",
1240+
"protoToZod: ZodLiteral `input` must be a number (enum index)"
1241+
);
11871242
// We're relying on the fact that `schema._zod.def.values` is ordered
11881243
// based on what was originally passed in.
11891244
return schema._zod.def.values[input];
11901245
} else if (schema instanceof z.ZodRecord) {
1191-
assert(input instanceof protobuf_es.Message);
1246+
assert(
1247+
input instanceof protobuf_es.Message,
1248+
"protoToZod: ZodRecord `input` must be a protobuf Message"
1249+
);
11921250
// Need to _remove_ the extra level of indirection we use to
11931251
// encode a record.
1194-
assert(input !== null && input !== undefined && "record" in input);
1252+
assert(
1253+
input !== null && input !== undefined && "record" in input,
1254+
"protoToZod: ZodRecord `input` must have a `record` property"
1255+
);
11951256
const record = (input as { record: Record<string, any> }).record;
11961257
const output: Record<string, any> = {};
11971258
for (const property in record as any) {
@@ -1215,7 +1276,7 @@ export function protoToZod<T>(
12151276
Array.isArray(input.items)
12161277
);
12171278
}),
1218-
"schema is ZodArray, but input is not a protobuf message"
1279+
"protoToZod: ZodArray `input` must be a protobuf Message with `items` array"
12191280
);
12201281

12211282
return input.items.map((item: any) =>
@@ -1226,13 +1287,16 @@ export function protoToZod<T>(
12261287
typeIs(input, (input): input is T & { [key: string]: any } => {
12271288
return input instanceof protobuf_es.Message && input !== null;
12281289
}),
1229-
"schema is ZodObject, but input is not a protobuf message"
1290+
"protoToZod: ZodObject `input` must be a protobuf Message"
12301291
);
12311292

12321293
const shape = schema.shape;
12331294
const output: Record<string, any> = {};
12341295
for (const property in input) {
1235-
assert(property in shape);
1296+
assert(
1297+
property in shape,
1298+
`protoToZod: \`property\` '${property}' not found in ZodObject \`shape\``
1299+
);
12361300
// Drop all `undefined` properties as they are technically not
12371301
// valid JSON and can't be encoded/decoded via protobuf.
12381302
if (input[property] === undefined) {
@@ -1243,7 +1307,10 @@ export function protoToZod<T>(
12431307
return output;
12441308
} else if (schema instanceof z.ZodDiscriminatedUnion) {
12451309
const discriminator = schema._zod.def.discriminator;
1246-
assert(typeof discriminator === "string");
1310+
assert(
1311+
typeof discriminator === "string",
1312+
"protoToZod: ZodDiscriminatedUnion `discriminator` must be a string"
1313+
);
12471314

12481315
assert(
12491316
typeIs(input, (input): input is T & { [key: string]: any } => {
@@ -1253,11 +1320,14 @@ export function protoToZod<T>(
12531320
discriminator in input
12541321
);
12551322
}),
1256-
"schema is ZodDiscriminatedUnion, but input is not a protobuf message"
1323+
"protoToZod: ZodDiscriminatedUnion `input` must be a protobuf Message with `discriminator` property"
12571324
);
12581325

12591326
for (const option of schema.options as z.ZodObject[]) {
1260-
assert(discriminator in option.shape);
1327+
assert(
1328+
discriminator in option.shape,
1329+
"protoToZod: `discriminator` must be present in ZodDiscriminatedUnion option `shape`"
1330+
);
12611331
if (
12621332
toCamelCase(option.shape[discriminator]._zod.def.values[0]) ===
12631333
input[discriminator].case
@@ -1277,17 +1347,23 @@ export function protoToZod<T>(
12771347
// require any conversion.
12781348
assert(
12791349
input instanceof protobuf_es.Message,
1280-
"schema is ZodCustom, but input is not a protobuf message"
1350+
"protoToZod: ZodCustom `input` must be a protobuf Message"
12811351
);
12821352
const meta = (schema as z.ZodCustom).meta();
1283-
assert(meta !== undefined && "protobuf" in meta);
1353+
assert(
1354+
meta !== undefined && "protobuf" in meta,
1355+
"protoToZod: ZodCustom `schema` must have `meta` with 'protobuf' property"
1356+
);
12841357
return input;
12851358
} else if (
12861359
schema instanceof z.ZodLazy &&
12871360
schema._zod.def.getter?.toString() ===
12881361
(z.json() as z.ZodType as z.ZodLazy)._zod.def.getter.toString()
12891362
) {
1290-
assert(input instanceof protobuf_es.Value);
1363+
assert(
1364+
input instanceof protobuf_es.Value,
1365+
"protoToZod: ZodLazy (json) `input` must be a protobuf Value"
1366+
);
12911367
return input.toJson();
12921368
} else {
12931369
throw new Error(`Unexpected schema type '${schema._zod.def.type}'`);

0 commit comments

Comments
 (0)