Small typescript sdk optimizations (#4640)

# Description of Changes

Was doing some profiling and found some low hanging optimizations for
the ts client. Namely caching table primary keys and reusing the writer
for reducer args.

# API and ABI breaking changes

None

# Expected complexity level and risk

1

# Testing

...
This commit is contained in:
joshua-spacetime
2026-04-01 19:51:46 -07:00
committed by GitHub
parent 037acfcc29
commit cdd8ba77ac
2 changed files with 26 additions and 18 deletions
@@ -158,6 +158,10 @@ export class DbConnectionImpl<RemoteModule extends UntypedRemoteModule>
#reducerCallInfo = new Map<number, { name: string; args: object }>();
#procedureCallbacks = new Map<number, ProcedureCallback>();
#rowDeserializers: Record<string, Deserializer<any>>;
#rowIdMetadata: Record<
string,
{ primaryKeyColName?: string; primaryKeyColType?: AlgebraicType }
>;
#reducerArgsSerializers: Record<
string,
{ serialize: Serializer<any>; deserialize: Deserializer<any> }
@@ -205,6 +209,7 @@ export class DbConnectionImpl<RemoteModule extends UntypedRemoteModule>
this.#emitter = emitter;
this.#rowDeserializers = Object.create(null);
this.#rowIdMetadata = Object.create(null);
this.#sourceNameToTableDef = Object.create(null);
for (const table of Object.values(remoteModule.tables)) {
this.#rowDeserializers[table.sourceName] = ProductType.makeDeserializer(
@@ -213,6 +218,15 @@ export class DbConnectionImpl<RemoteModule extends UntypedRemoteModule>
this.#sourceNameToTableDef[table.sourceName] = table as Values<
RemoteModule['tables']
>;
const primaryKeyColumn = Object.entries(table.columns).find(
([, column]) => column.columnMetadata.isPrimaryKey
);
this.#rowIdMetadata[table.sourceName] = primaryKeyColumn
? {
primaryKeyColName: primaryKeyColumn[0],
primaryKeyColType: primaryKeyColumn[1].typeBuilder.algebraicType,
}
: {};
}
this.#reducerArgsSerializers = Object.create(null);
@@ -302,8 +316,6 @@ export class DbConnectionImpl<RemoteModule extends UntypedRemoteModule>
#makeReducers(def: RemoteModule): ReducersView<RemoteModule> {
const out: Record<string, unknown> = {};
const writer = new BinaryWriter(1024);
for (const reducer of def.reducers) {
const reducerName = reducer.name;
const key = reducer.accessorName;
@@ -312,6 +324,7 @@ export class DbConnectionImpl<RemoteModule extends UntypedRemoteModule>
this.#reducerArgsSerializers[reducerName];
(out as any)[key] = (params: InferTypeOfRow<typeof reducer.params>) => {
const writer = this.#reducerArgsEncoder;
writer.clear();
serializeArgs(writer, params);
const argsBuffer = writer.getBuffer();
@@ -428,20 +441,13 @@ export class DbConnectionImpl<RemoteModule extends UntypedRemoteModule>
const rows: Operation[] = [];
const deserializeRow = this.#rowDeserializers[tableName];
const table = this.#sourceNameToTableDef[tableName];
// TODO: performance
const columnsArray = Object.entries(table.columns);
const primaryKeyColumnEntry = columnsArray.find(
col => col[1].columnMetadata.isPrimaryKey
);
const { primaryKeyColName, primaryKeyColType } =
this.#rowIdMetadata[tableName];
let previousOffset = 0;
while (reader.remaining > 0) {
const row = deserializeRow(reader);
let rowId: ComparablePrimitive | undefined = undefined;
if (primaryKeyColumnEntry !== undefined) {
const primaryKeyColName = primaryKeyColumnEntry[0];
const primaryKeyColType =
primaryKeyColumnEntry[1].typeBuilder.algebraicType;
if (primaryKeyColName !== undefined && primaryKeyColType !== undefined) {
rowId = AlgebraicType.intoMapKey(
primaryKeyColType,
row[primaryKeyColName]
@@ -548,6 +554,7 @@ export class DbConnectionImpl<RemoteModule extends UntypedRemoteModule>
}
}
#reducerArgsEncoder = new BinaryWriter(1024);
#clientMessageEncoder = new BinaryWriter(1024);
#sendMessage(message: ClientMessage): void {
const writer = this.#clientMessageEncoder;
@@ -906,7 +913,8 @@ export class DbConnectionImpl<RemoteModule extends UntypedRemoteModule>
_paramsType: ProductType,
params: object
): Promise<void> {
const writer = new BinaryWriter(1024);
const writer = this.#reducerArgsEncoder;
writer.clear();
this.#reducerArgsSerializers[reducerName].serialize(writer, params);
const argsBuffer = writer.getBuffer();
return this.callReducer(reducerName, argsBuffer, params);
@@ -66,6 +66,7 @@ export class TableCacheImpl<
TableName extends TableNamesOf<RemoteModule>,
> implements ClientTableCoreImplementable<RemoteModule, TableName>
{
private readonly hasPrimaryKey: boolean;
private rows: Map<
ComparablePrimitive,
[RowType<TableDefForTableName<RemoteModule, TableName>>, number]
@@ -83,6 +84,9 @@ export class TableCacheImpl<
this.tableDef = tableDef;
this.rows = new Map();
this.emitter = new EventEmitter();
this.hasPrimaryKey = Object.values(this.tableDef.columns).some(
col => col.columnMetadata.isPrimaryKey === true
);
// Build index views from the resolved runtime index metadata.
//
// We intentionally use `resolvedIndexes` rather than `indexes`:
@@ -281,11 +285,7 @@ export class TableCacheImpl<
return pendingCallbacks;
}
// TODO: performance
const hasPrimaryKey = Object.values(this.tableDef.columns).some(
col => col.columnMetadata.isPrimaryKey === true
);
if (hasPrimaryKey) {
if (this.hasPrimaryKey) {
const insertMap = new Map<
ComparablePrimitive,
[