Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions crates/bindings-typescript/src/lib/reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,6 @@ export type ReducerCtx<SchemaDef extends UntypedSchemaDef> = Readonly<{
identity: Identity;
timestamp: Timestamp;
connectionId: ConnectionId | null;
// **Note:** must be 0..=u32::MAX
counter_uuid: { value: number };
db: DbView<SchemaDef>;
senderAuth: AuthCtx;
newUuidV4(): Uuid;
Expand Down
8 changes: 5 additions & 3 deletions crates/bindings-typescript/src/server/procedures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { MODULE_DEF, type UntypedSchemaDef } from '../lib/schema';
import { Timestamp } from '../lib/timestamp';
import { Uuid } from '../lib/uuid';
import { httpClient } from './http_internal';
import { callUserFunction, makeReducerCtx, sys } from './runtime';
import { callUserFunction, ReducerCtxImpl, sys } from './runtime';

const { freeze } = Object;

Expand Down Expand Up @@ -45,8 +45,10 @@ export function callProcedure(
const timestamp = sys.procedure_start_mut_tx();

try {
const ctx: TransactionCtx<UntypedSchemaDef> = freeze(
makeReducerCtx(sender, new Timestamp(timestamp), connectionId)
const ctx: TransactionCtx<UntypedSchemaDef> = new ReducerCtxImpl(
sender,
new Timestamp(timestamp),
connectionId
);
return body(ctx);
} catch (e) {
Expand Down
108 changes: 64 additions & 44 deletions crates/bindings-typescript/src/server/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
type JsonObject,
type JwtClaims,
type ReducerCtx,
type ReducerCtx as IReducerCtx,
} from '../lib/reducers';
import {
MODULE_DEF,
Expand Down Expand Up @@ -182,45 +183,66 @@ class AuthCtxImpl implements AuthCtx {
}
}

export const makeReducerCtx = (
sender: Identity,
timestamp: Timestamp,
connectionId: ConnectionId | null
): ReducerCtx<UntypedSchemaDef> => {
return {
sender,
get identity() {
return new Identity(sys.identity().__identity__);
},
timestamp,
connectionId,
db: getDbView(),
senderAuth: AuthCtxImpl.fromSystemTables(connectionId, sender),
counter_uuid: { value: Number(0) },

/**
* Create a new random {@link Uuid} `v4` using the {@link crypto} RNG.
*
* WARN: Until we use a spacetime RNG this make calls non-deterministic.
*/
newUuidV4(): Uuid {
// TODO: Use a spacetime RNG when available
const bytes = crypto.getRandomValues(new Uint8Array(16));
return Uuid.fromRandomBytesV4(bytes);
},
// Using a class expression rather than declaration keeps the class out of the
// type namespace, so that `ReducerCtx` still refers to the interface.
export const ReducerCtxImpl = class ReducerCtx<
SchemaDef extends UntypedSchemaDef,
> implements IReducerCtx<SchemaDef>
{
#identity: Identity | undefined;
#senderAuth: AuthCtx | undefined;
#uuidCounter: { value: number } | undefined;
sender: Identity;
timestamp: Timestamp;
connectionId: ConnectionId | null;
db: DbView<SchemaDef>;

/**
* Create a new sortable {@link Uuid} `v7` using the {@link crypto} RNG, counter,
* and the timestamp.
*
* WARN: Until we use a spacetime RNG this make calls non-deterministic.
*/
newUuidV7(): Uuid {
// TODO: Use a spacetime RNG when available
const bytes = crypto.getRandomValues(new Uint8Array(4));
return Uuid.fromCounterV7(this.counter_uuid, this.timestamp, bytes);
},
};
constructor(
sender: Identity,
timestamp: Timestamp,
connectionId: ConnectionId | null
) {
Object.seal(this);
this.sender = sender;
this.timestamp = timestamp;
this.connectionId = connectionId;
this.db = getDbView();
}

get identity() {
return (this.#identity ??= new Identity(sys.identity().__identity__));
}

get senderAuth() {
return (this.#senderAuth ??= AuthCtxImpl.fromSystemTables(
this.connectionId,
this.sender
));
}

/**
* Create a new random {@link Uuid} `v4` using the {@link crypto} RNG.
*
* WARN: Until we use a spacetime RNG this make calls non-deterministic.
*/
newUuidV4(): Uuid {
// TODO: Use a spacetime RNG when available
const bytes = crypto.getRandomValues(new Uint8Array(16));
return Uuid.fromRandomBytesV4(bytes);
}

/**
* Create a new sortable {@link Uuid} `v7` using the {@link crypto} RNG, counter,
* and the timestamp.
*
* WARN: Until we use a spacetime RNG this make calls non-deterministic.
*/
newUuidV7(): Uuid {
// TODO: Use a spacetime RNG when available
const bytes = crypto.getRandomValues(new Uint8Array(4));
const counter = (this.#uuidCounter ??= { value: 0 });
return Uuid.fromCounterV7(counter, this.timestamp, bytes);
}
};

/**
Expand Down Expand Up @@ -256,12 +278,10 @@ export const hooks: ModuleHooks = {
MODULE_DEF.typespace
);
const senderIdentity = new Identity(sender);
const ctx: ReducerCtx<any> = freeze(
makeReducerCtx(
senderIdentity,
new Timestamp(timestamp),
ConnectionId.nullIfZero(new ConnectionId(connId))
)
const ctx: ReducerCtx<any> = new ReducerCtxImpl(
senderIdentity,
new Timestamp(timestamp),
ConnectionId.nullIfZero(new ConnectionId(connId))
);
try {
return callUserFunction(REDUCERS[reducerId], ctx, args) ?? { tag: 'ok' };
Expand Down
Loading