Skip to content

Commit b61972b

Browse files
committed
Add SenderError
1 parent 12124f7 commit b61972b

4 files changed

Lines changed: 64 additions & 34 deletions

File tree

crates/bindings-typescript/src/server/errors.ts

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
/**
2-
* Base class for all Spacetime errors.
3-
* Each subclass must define a static CODE and MESSAGE property.
2+
* Base class for all Spacetime host errors (i.e. errors that may be thrown
3+
* by database functions).
4+
*
45
* Instances of SpacetimeError can be created with just an error code,
56
* which will return the appropriate subclass instance.
67
*/
7-
export class SpacetimeError {
8+
export class SpacetimeHostError extends Error {
89
public readonly code: number;
910
public readonly message: string;
1011
constructor(code: number) {
12+
super();
1113
const proto = Object.getPrototypeOf(this);
1214
let cls;
1315
if (errorProtoypes.has(proto)) {
1416
cls = proto.constructor;
1517
if (code !== cls.CODE)
1618
throw new TypeError(`invalid error code for ${cls.name}`);
17-
} else if (proto === SpacetimeError.prototype) {
19+
} else if (proto === SpacetimeHostError.prototype) {
1820
cls = errnoToClass.get(code);
1921
if (!cls) throw new RangeError(`unknown error code ${code}`);
2022
} else {
@@ -24,12 +26,30 @@ export class SpacetimeError {
2426
this.code = cls.CODE;
2527
this.message = cls.MESSAGE;
2628
}
29+
get name(): string {
30+
return errnoToClass.get(this.code)?.name ?? 'SpacetimeHostError';
31+
}
32+
}
33+
34+
/**
35+
* An error thrown by a reducer that indicates a problem to the sender.
36+
*
37+
* When this error is thrown by a reducer, the sender will be notified
38+
* that the reducer failed gracefully with the given message.
39+
*/
40+
export class SenderError extends Error {
41+
constructor(message: string) {
42+
super(message);
43+
}
44+
get name() {
45+
return 'SenderError';
46+
}
2747
}
2848

2949
/**
3050
* A generic error class for unknown error codes.
3151
*/
32-
export class HostCallFailure extends SpacetimeError {
52+
export class HostCallFailure extends SpacetimeHostError {
3353
static CODE = 1;
3454
static MESSAGE = 'ABI called by host returned an error';
3555
constructor() {
@@ -40,7 +60,7 @@ export class HostCallFailure extends SpacetimeError {
4060
/**
4161
* Error indicating that an ABI call was made outside of a transaction.
4262
*/
43-
export class NotInTransaction extends SpacetimeError {
63+
export class NotInTransaction extends SpacetimeHostError {
4464
static CODE = 2;
4565
static MESSAGE = 'ABI call can only be made while in a transaction';
4666
constructor() {
@@ -52,7 +72,7 @@ export class NotInTransaction extends SpacetimeError {
5272
* Error indicating that BSATN decoding failed.
5373
* This typically means that the data could not be decoded to the expected type.
5474
*/
55-
export class BsatnDecodeError extends SpacetimeError {
75+
export class BsatnDecodeError extends SpacetimeHostError {
5676
static CODE = 3;
5777
static MESSAGE = "Couldn't decode the BSATN to the expected type";
5878
constructor() {
@@ -63,7 +83,7 @@ export class BsatnDecodeError extends SpacetimeError {
6383
/**
6484
* Error indicating that a specified table does not exist.
6585
*/
66-
export class NoSuchTable extends SpacetimeError {
86+
export class NoSuchTable extends SpacetimeHostError {
6787
static CODE = 4;
6888
static MESSAGE = 'No such table';
6989
constructor() {
@@ -74,7 +94,7 @@ export class NoSuchTable extends SpacetimeError {
7494
/**
7595
* Error indicating that a specified index does not exist.
7696
*/
77-
export class NoSuchIndex extends SpacetimeError {
97+
export class NoSuchIndex extends SpacetimeHostError {
7898
static CODE = 5;
7999
static MESSAGE = 'No such index';
80100
constructor() {
@@ -85,7 +105,7 @@ export class NoSuchIndex extends SpacetimeError {
85105
/**
86106
* Error indicating that a specified row iterator is not valid.
87107
*/
88-
export class NoSuchIter extends SpacetimeError {
108+
export class NoSuchIter extends SpacetimeHostError {
89109
static CODE = 6;
90110
static MESSAGE = 'The provided row iterator is not valid';
91111
constructor() {
@@ -96,7 +116,7 @@ export class NoSuchIter extends SpacetimeError {
96116
/**
97117
* Error indicating that a specified console timer does not exist.
98118
*/
99-
export class NoSuchConsoleTimer extends SpacetimeError {
119+
export class NoSuchConsoleTimer extends SpacetimeHostError {
100120
static CODE = 7;
101121
static MESSAGE = 'The provided console timer does not exist';
102122
constructor() {
@@ -107,7 +127,7 @@ export class NoSuchConsoleTimer extends SpacetimeError {
107127
/**
108128
* Error indicating that a specified bytes source or sink is not valid.
109129
*/
110-
export class NoSuchBytes extends SpacetimeError {
130+
export class NoSuchBytes extends SpacetimeHostError {
111131
static CODE = 8;
112132
static MESSAGE = 'The provided bytes source or sink is not valid';
113133
constructor() {
@@ -118,7 +138,7 @@ export class NoSuchBytes extends SpacetimeError {
118138
/**
119139
* Error indicating that a provided sink has no more space left.
120140
*/
121-
export class NoSpace extends SpacetimeError {
141+
export class NoSpace extends SpacetimeHostError {
122142
static CODE = 9;
123143
static MESSAGE = 'The provided sink has no more space left';
124144
constructor() {
@@ -129,7 +149,7 @@ export class NoSpace extends SpacetimeError {
129149
/**
130150
* Error indicating that there is no more space in the database.
131151
*/
132-
export class BufferTooSmall extends SpacetimeError {
152+
export class BufferTooSmall extends SpacetimeHostError {
133153
static CODE = 11;
134154
static MESSAGE = 'The provided buffer is not large enough to store the data';
135155
constructor() {
@@ -140,7 +160,7 @@ export class BufferTooSmall extends SpacetimeError {
140160
/**
141161
* Error indicating that a value with a given unique identifier already exists.
142162
*/
143-
export class UniqueAlreadyExists extends SpacetimeError {
163+
export class UniqueAlreadyExists extends SpacetimeHostError {
144164
static CODE = 12;
145165
static MESSAGE = 'Value with given unique identifier already exists';
146166
constructor() {
@@ -151,7 +171,7 @@ export class UniqueAlreadyExists extends SpacetimeError {
151171
/**
152172
* Error indicating that the specified delay in scheduling a row was too long.
153173
*/
154-
export class ScheduleAtDelayTooLong extends SpacetimeError {
174+
export class ScheduleAtDelayTooLong extends SpacetimeHostError {
155175
static CODE = 13;
156176
static MESSAGE = 'Specified delay in scheduling row was too long';
157177
constructor() {
@@ -162,7 +182,7 @@ export class ScheduleAtDelayTooLong extends SpacetimeError {
162182
/**
163183
* Error indicating that an index was not unique when it was expected to be.
164184
*/
165-
export class IndexNotUnique extends SpacetimeError {
185+
export class IndexNotUnique extends SpacetimeHostError {
166186
static CODE = 14;
167187
static MESSAGE = 'The index was not unique';
168188
constructor() {
@@ -173,7 +193,7 @@ export class IndexNotUnique extends SpacetimeError {
173193
/**
174194
* Error indicating that an index was not unique when it was expected to be.
175195
*/
176-
export class NoSuchRow extends SpacetimeError {
196+
export class NoSuchRow extends SpacetimeHostError {
177197
static CODE = 15;
178198
static MESSAGE = 'The row was not found, e.g., in an update call';
179199
constructor() {
@@ -184,7 +204,7 @@ export class NoSuchRow extends SpacetimeError {
184204
/**
185205
* Error indicating that an auto-increment sequence has overflowed.
186206
*/
187-
export class AutoIncOverflow extends SpacetimeError {
207+
export class AutoIncOverflow extends SpacetimeHostError {
188208
static CODE = 16;
189209
static MESSAGE = 'The auto-increment sequence overflowed';
190210
constructor() {
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
export * from './type_builders';
22
export { schema } from './schema';
33
export { table } from './table';
4+
export * as errors from './errors';
5+
export { SenderError } from './errors';
46

57
import './polyfills'; // Ensure polyfills are loaded
68
import './register_hooks'; // Ensure module hooks are registered

crates/bindings-typescript/src/server/runtime.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ import { ConnectionId } from '../lib/connection_id';
77
import { Identity } from '../lib/identity';
88
import { Timestamp } from '../lib/timestamp';
99
import { BinaryReader, BinaryWriter } from '../sdk';
10-
import { AutoIncOverflow, SpacetimeError, UniqueAlreadyExists } from './errors';
10+
import {
11+
AutoIncOverflow,
12+
SenderError,
13+
SpacetimeHostError,
14+
UniqueAlreadyExists,
15+
} from './errors';
1116
import { Range, type Bound } from './range';
1217
import {
1318
type Index,
@@ -56,7 +61,14 @@ export const hooks: ModuleHooks = {
5661
connectionId: ConnectionId.nullIfZero(new ConnectionId(connId)),
5762
db: getDbView(),
5863
});
59-
return REDUCERS[reducerId](ctx, args) ?? { tag: 'ok' };
64+
try {
65+
return REDUCERS[reducerId](ctx, args) ?? { tag: 'ok' };
66+
} catch (e) {
67+
if (e instanceof SenderError) {
68+
return { tag: 'err', value: e.message };
69+
}
70+
throw e;
71+
}
6072
},
6173
};
6274

@@ -423,7 +435,7 @@ function wrapSyscall<F extends (...args: any[]) => any>(
423435
hasOwn(e, '__code_error__') &&
424436
typeof e.__code_error__ == 'number'
425437
) {
426-
throw new SpacetimeError(e.__code_error__);
438+
throw new SpacetimeHostError(e.__code_error__);
427439
}
428440
throw e;
429441
}

modules/quickstart-chat-ts/src/index.ts

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// ─────────────────────────────────────────────────────────────────────────────
22
// IMPORTS
33
// ─────────────────────────────────────────────────────────────────────────────
4-
import { schema, t, table } from 'spacetimedb/server';
4+
import { schema, t, table, SenderError } from 'spacetimedb/server';
55

66
const User = table(
77
{ name: 'user', public: true },
@@ -19,31 +19,27 @@ const Message = table(
1919

2020
const spacetimedb = schema(User, Message);
2121

22-
function validateName(name: string): { tag: 'err'; value: string } | null {
23-
if (!name) return { tag: 'err', value: 'Names must not be empty' };
24-
return null;
22+
function validateName(name: string) {
23+
if (!name) throw new SenderError('Names must not be empty');
2524
}
2625

2726
spacetimedb.reducer('set_name', { name: t.string() }, (ctx, { name }) => {
28-
let err = validateName(name);
29-
if (err) return err;
27+
validateName(name);
3028
const user = ctx.db.user.identity.find(ctx.sender);
31-
if (!user) return { tag: 'err', value: 'Cannot set name for unknown user' };
29+
if (!user) throw new SenderError('Cannot set name for unknown user');
3230
console.info(`User ${ctx.sender} sets name to ${name}`);
3331
ctx.db.user.identity.update({ ...user, name });
3432
});
3533

36-
function validateMessage(text: string): { tag: 'err'; value: string } | null {
37-
if (!text) return { tag: 'err', value: 'Messages must not be empty' };
38-
return null;
34+
function validateMessage(text: string) {
35+
if (!text) throw new SenderError('Messages must not be empty');
3936
}
4037

4138
spacetimedb.reducer('send_message', { text: t.string() }, (ctx, { text }) => {
4239
// Things to consider:
4340
// - Rate-limit messages per-user.
4441
// - Reject messages from unnamed user.
45-
const err = validateMessage(text);
46-
if (err) return err;
42+
validateMessage(text);
4743
console.info(`User ${ctx.sender}: ${text}`);
4844
ctx.db.message.insert({
4945
sender: ctx.sender,

0 commit comments

Comments
 (0)