Skip to content

Commit e8f9079

Browse files
authored
Use a class for ReducerCtx (#3958)
# Description of Changes #3538 added a `uuid_counter` field to ReducerCtx, which has no need to be public. Implementing ReducerCtx as a class allows us to encapsulate better, and lets us enumerate exactly the data that it needs to hold so that the runtime could possibly optimize it. # Expected complexity level and risk 1: straightforward switch # Testing - [x] Automated testing is sufficient.
1 parent 8dd18f0 commit e8f9079

3 files changed

Lines changed: 69 additions & 49 deletions

File tree

crates/bindings-typescript/src/lib/reducers.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,6 @@ export type ReducerCtx<SchemaDef extends UntypedSchemaDef> = Readonly<{
119119
identity: Identity;
120120
timestamp: Timestamp;
121121
connectionId: ConnectionId | null;
122-
// **Note:** must be 0..=u32::MAX
123-
counter_uuid: { value: number };
124122
db: DbView<SchemaDef>;
125123
senderAuth: AuthCtx;
126124
newUuidV4(): Uuid;

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { MODULE_DEF, type UntypedSchemaDef } from '../lib/schema';
1212
import { Timestamp } from '../lib/timestamp';
1313
import { Uuid } from '../lib/uuid';
1414
import { httpClient } from './http_internal';
15-
import { callUserFunction, makeReducerCtx, sys } from './runtime';
15+
import { callUserFunction, ReducerCtxImpl, sys } from './runtime';
1616

1717
const { freeze } = Object;
1818

@@ -45,8 +45,10 @@ export function callProcedure(
4545
const timestamp = sys.procedure_start_mut_tx();
4646

4747
try {
48-
const ctx: TransactionCtx<UntypedSchemaDef> = freeze(
49-
makeReducerCtx(sender, new Timestamp(timestamp), connectionId)
48+
const ctx: TransactionCtx<UntypedSchemaDef> = new ReducerCtxImpl(
49+
sender,
50+
new Timestamp(timestamp),
51+
connectionId
5052
);
5153
return body(ctx);
5254
} catch (e) {

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

Lines changed: 64 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
type JsonObject,
2828
type JwtClaims,
2929
type ReducerCtx,
30+
type ReducerCtx as IReducerCtx,
3031
} from '../lib/reducers';
3132
import {
3233
MODULE_DEF,
@@ -185,45 +186,66 @@ class AuthCtxImpl implements AuthCtx {
185186
}
186187
}
187188

188-
export const makeReducerCtx = (
189-
sender: Identity,
190-
timestamp: Timestamp,
191-
connectionId: ConnectionId | null
192-
): ReducerCtx<UntypedSchemaDef> => {
193-
return {
194-
sender,
195-
get identity() {
196-
return new Identity(sys.identity().__identity__);
197-
},
198-
timestamp,
199-
connectionId,
200-
db: getDbView(),
201-
senderAuth: AuthCtxImpl.fromSystemTables(connectionId, sender),
202-
counter_uuid: { value: Number(0) },
203-
204-
/**
205-
* Create a new random {@link Uuid} `v4` using the {@link crypto} RNG.
206-
*
207-
* WARN: Until we use a spacetime RNG this make calls non-deterministic.
208-
*/
209-
newUuidV4(): Uuid {
210-
// TODO: Use a spacetime RNG when available
211-
const bytes = crypto.getRandomValues(new Uint8Array(16));
212-
return Uuid.fromRandomBytesV4(bytes);
213-
},
189+
// Using a class expression rather than declaration keeps the class out of the
190+
// type namespace, so that `ReducerCtx` still refers to the interface.
191+
export const ReducerCtxImpl = class ReducerCtx<
192+
SchemaDef extends UntypedSchemaDef,
193+
> implements IReducerCtx<SchemaDef>
194+
{
195+
#identity: Identity | undefined;
196+
#senderAuth: AuthCtx | undefined;
197+
#uuidCounter: { value: number } | undefined;
198+
sender: Identity;
199+
timestamp: Timestamp;
200+
connectionId: ConnectionId | null;
201+
db: DbView<SchemaDef>;
214202

215-
/**
216-
* Create a new sortable {@link Uuid} `v7` using the {@link crypto} RNG, counter,
217-
* and the timestamp.
218-
*
219-
* WARN: Until we use a spacetime RNG this make calls non-deterministic.
220-
*/
221-
newUuidV7(): Uuid {
222-
// TODO: Use a spacetime RNG when available
223-
const bytes = crypto.getRandomValues(new Uint8Array(4));
224-
return Uuid.fromCounterV7(this.counter_uuid, this.timestamp, bytes);
225-
},
226-
};
203+
constructor(
204+
sender: Identity,
205+
timestamp: Timestamp,
206+
connectionId: ConnectionId | null
207+
) {
208+
Object.seal(this);
209+
this.sender = sender;
210+
this.timestamp = timestamp;
211+
this.connectionId = connectionId;
212+
this.db = getDbView();
213+
}
214+
215+
get identity() {
216+
return (this.#identity ??= new Identity(sys.identity().__identity__));
217+
}
218+
219+
get senderAuth() {
220+
return (this.#senderAuth ??= AuthCtxImpl.fromSystemTables(
221+
this.connectionId,
222+
this.sender
223+
));
224+
}
225+
226+
/**
227+
* Create a new random {@link Uuid} `v4` using the {@link crypto} RNG.
228+
*
229+
* WARN: Until we use a spacetime RNG this make calls non-deterministic.
230+
*/
231+
newUuidV4(): Uuid {
232+
// TODO: Use a spacetime RNG when available
233+
const bytes = crypto.getRandomValues(new Uint8Array(16));
234+
return Uuid.fromRandomBytesV4(bytes);
235+
}
236+
237+
/**
238+
* Create a new sortable {@link Uuid} `v7` using the {@link crypto} RNG, counter,
239+
* and the timestamp.
240+
*
241+
* WARN: Until we use a spacetime RNG this make calls non-deterministic.
242+
*/
243+
newUuidV7(): Uuid {
244+
// TODO: Use a spacetime RNG when available
245+
const bytes = crypto.getRandomValues(new Uint8Array(4));
246+
const counter = (this.#uuidCounter ??= { value: 0 });
247+
return Uuid.fromCounterV7(counter, this.timestamp, bytes);
248+
}
227249
};
228250

229251
/**
@@ -257,12 +279,10 @@ export const hooks: ModuleHooks = {
257279
MODULE_DEF.typespace
258280
);
259281
const senderIdentity = new Identity(sender);
260-
const ctx: ReducerCtx<any> = freeze(
261-
makeReducerCtx(
262-
senderIdentity,
263-
new Timestamp(timestamp),
264-
ConnectionId.nullIfZero(new ConnectionId(connId))
265-
)
282+
const ctx: ReducerCtx<any> = new ReducerCtxImpl(
283+
senderIdentity,
284+
new Timestamp(timestamp),
285+
ConnectionId.nullIfZero(new ConnectionId(connId))
266286
);
267287
try {
268288
return callUserFunction(REDUCERS[reducerId], ctx, args) ?? { tag: 'ok' };

0 commit comments

Comments
 (0)