Skip to content
Open
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
136 changes: 136 additions & 0 deletions benches/bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
const MAX_ITER = 10;
const MAX_DURATION = 60_000;

type BenchFn = (cb: () => Promise<void>) => void;

type TimeitFn = (name: string, cb: () => Promise<void>, opt?: BenchOptions) => void;

interface BenchOptions {
maxIter?: number;
maxDuration?: number;
}

export const bench = async (
cb: (params: { beforeAll: BenchFn; afterAll: BenchFn, time: TimeitFn }) => Promise<void>,
opt?: BenchOptions
) => {
const { maxIter = MAX_ITER, maxDuration = MAX_DURATION } = opt ?? {};
const beforePromises: (() => Promise<void>)[] = [];
const afterPromises: (() => Promise<void>)[] = [];
const beforeAll = (cb: () => Promise<void>) => {
beforePromises.push(cb);
};
const afterAll = (cb: () => Promise<void>) => {
afterPromises.push(cb);
};
const variants: { name: string, cb: () => Promise<void>, durations: number[], totalDuration: number, maxIter: number, maxDuration: number }[] = [];
const time = (name: string, cb: () => Promise<void>, opt?: BenchOptions) => {
variants.push({
name,
cb,
durations: [],
totalDuration: 0,
maxIter: opt?.maxIter ?? maxIter,
maxDuration: opt?.maxDuration ?? maxDuration,
});
};

await cb({ beforeAll, afterAll, time });

await Promise.all(beforePromises.map(async (cb) => cb()));

for (const variant of variants) {
console.log(`Running "${variant.name}"...`);
while (variant.durations.length < variant.maxIter && variant.totalDuration < variant.maxDuration) {
try {
const start = performance.now();
await variant.cb();
const duration = performance.now() - start;
variant.durations.push(duration);
variant.totalDuration += duration;
} catch (error) {
console.error(`Error running "${variant.name}":`, error);
break;
}
}
}

await Promise.all(afterPromises.map((cb) => cb()));

const summary = summarize(variants);
console.log(format(summary));
};

interface Summary {
name: string;
iter: number;
first: number;
min: number;
max: number;
mean: number;
median: number;
p90: number;
p95: number;
}

const summarize = (variants: { name: string, durations: number[] }[]): Summary[] => {
return variants.map((variant) => {
const sorted = [...variant.durations].sort((a, b) => a - b);
const total = sorted.reduce((a, b) => a + b, 0);
const count = sorted.length;

const min = sorted[0] || 0;
const max = sorted[count - 1] || 0;
const mean = count > 0 ? total / count : 0;
const median =
count === 0
? 0
: count % 2 === 0
? (sorted[count / 2 - 1] + sorted[count / 2]) / 2
: sorted[Math.floor(count / 2)];

const p90 = count === 0 ? 0 : sorted[Math.floor(count * 0.9)];
const p95 = count === 0 ? 0 : sorted[Math.floor(count * 0.95)];

return {
name: variant.name,
iter: variant.durations.length,
first: variant.durations[0] ?? 0,
min,
max,
mean,
median,
p90,
p95,
};
});
};

const format = (summary: Summary[]): string => {
const headers = ["name", "iter", "first", "min", "max", "mean", "median"] as const;

const rows = summary.map((s) => ({
name: s.name.slice(0, 50),
iter: s.iter.toString(),
first: s.first.toFixed(4),
min: s.min.toFixed(4),
max: s.max.toFixed(4),
mean: s.mean.toFixed(4),
median: s.median.toFixed(4),
}));

const allRows = [
{ name: "name", iter: "iter", first: "first", min: "min", max: "max", mean: "mean", median: "median" },
...rows,
];

const widths = headers.map((h) =>
Math.max(...allRows.map((r) => r[h].length))
);

return allRows
.map((row) =>
headers.map((h, i) => row[h].padEnd(widths[i])).join(" | ")
)
.join("\n");
};
90 changes: 90 additions & 0 deletions benches/bench.v2.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/usr/bin/env bash

CONTAINER_NAME=borm_bench_v2
USER=borm_bench
PASSWORD=borm_bench
NAMESPACE=borm_bench
DATABASE=borm_bench
SCHEMA_FILE="./benches/schema.v2.surql"

# Function to clean up the container
cleanup() {
echo "Stopping and removing container..."
docker stop ${CONTAINER_NAME} >/dev/null 2>&1
docker rm ${CONTAINER_NAME} >/dev/null 2>&1
exit ${EXIT_CODE:-1} # Default to 1 if EXIT_CODE is unset (e.g. early crash)
}

# Set up trap to call cleanup function on script exit
trap cleanup EXIT INT TERM

# Function to parse command line arguments
parse_args() {
VITEST_ARGS=()
for arg in "$@"
do
case $arg in
-link=*)
# We'll ignore this parameter now
;;
*)
VITEST_ARGS+=("$arg")
;;
esac
done
}

# Parse the command line arguments
parse_args "$@"

# Start the container
if ! docker run \
--rm \
--detach \
--name $CONTAINER_NAME \
--user root \
-p 8002:8002 \
--pull always \
surrealdb/surrealdb:v2.3.7 \
start \
--allow-all \
-u $USER \
-p $PASSWORD \
--bind 0.0.0.0:8002 \
rocksdb:///data/blitz.db; then
echo "Failed to start SurrealDB container"
exit 1
fi

until [ "`docker inspect -f {{.State.Running}} $CONTAINER_NAME`" == "true" ]; do
sleep 0.1;
done;

# Wait for SurrealDB to be ready
echo "Waiting for SurrealDB to be ready..."
until docker exec $CONTAINER_NAME ./surreal is-ready --endpoint http://localhost:8002 2>/dev/null; do
sleep 0.5;
done;
echo "SurrealDB is ready!"

# Setup surrealdb database: create the namespace, database, and user dynamically
docker exec -i $CONTAINER_NAME ./surreal sql -u $USER -p $PASSWORD --endpoint http://localhost:8002 <<EOF
DEFINE NAMESPACE $NAMESPACE;
USE NS $NAMESPACE;
DEFINE DATABASE $DATABASE;
DEFINE USER $USER ON NAMESPACE PASSWORD '$PASSWORD' ROLES OWNER;
EOF

# Create the schema
docker cp $SCHEMA_FILE $CONTAINER_NAME:/tmp/schema.surql
docker exec -i $CONTAINER_NAME ./surreal import -u $USER -p $PASSWORD --namespace $NAMESPACE --database $DATABASE --endpoint http://localhost:8002 /tmp/schema.surql

# Always stop container, but exit with 1 when tests are failing
# if CONTAINER_NAME=${CONTAINER_NAME} npx vitest bench "${VITEST_ARGS[@]}"; then
if CONTAINER_NAME=${CONTAINER_NAME} tsx benches/v2-2.bench.ts; then
echo "Bench passed. Container ${CONTAINER_NAME} is still running."
EXIT_CODE=0
else
echo "Bench failed. Container ${CONTAINER_NAME} is still running."
EXIT_CODE=1
fi
87 changes: 87 additions & 0 deletions benches/generateData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
export interface Base {
id: string;
string_1: string;
number_1: number;
boolean_1: boolean;
datetime_1: Date;
}

export interface A extends Base {
one: B['id'];
few: B['id'][];
many: B['id'][];
}

export type B = Base;

export const generateData = (params: {
records: number;
few: { min: number; max: number };
many: { min: number; max: number };
}): { a: A[]; b: B[]; } => {
const a: A[] = [];
const b: B[] = [];

const randomInt = (min: number, max: number) => Math.floor(Math.random() * (max - min + 1)) + min;
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const randomString = (min: number, max: number) => {
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Rule violated: Ensure all TypeScript code adheres to ECMAScript 2025 standards

String concatenation in loops is a less performant older pattern. Consider using Array.from() with .join() for more idiomatic modern JavaScript.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At benches/generateData.ts, line 27:

<comment>String concatenation in loops is a less performant older pattern. Consider using `Array.from()` with `.join()` for more idiomatic modern JavaScript.</comment>

<file context>
@@ -0,0 +1,87 @@
+
+  const randomInt = (min: number, max: number) =&gt; Math.floor(Math.random() * (max - min + 1)) + min;
+  const chars = &#39;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789&#39;;
+  const randomString = (min: number, max: number) =&gt; {
+      const length = randomInt(min, max);
+      let result = &#39;&#39;;
</file context>
Fix with Cubic

const length = randomInt(min, max);
let result = '';
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
};
const randomBoolean = () => Math.random() < 0.5;
const randomDate = () => {
const start = new Date('2020-01-01').getTime();
const end = new Date('2026-01-01').getTime();
return new Date(start + Math.random() * (end - start));
};

const generateBase = (): Base => ({
id: uid(),
string_1: randomString(10, 20),
number_1: Math.floor(Math.random() * Number.MAX_SAFE_INTEGER),
boolean_1: randomBoolean(),
datetime_1: randomDate(),
});

for (let i = 0; i < params.records; i++) {
b.push(generateBase());
}

for (let i = 0; i < params.records; i++) {
const fewLength = randomInt(params.few.min, params.few.max);
const manyLength = randomInt(params.many.min, params.many.max);
const fewSet = new Set<string>();
const manySet = new Set<string>();

while (fewSet.size < fewLength) {
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Potential infinite loop: if params.few.max or params.many.max exceeds params.records, these while loops will never terminate because the Set cannot contain more unique IDs than exist in array b. Consider adding a guard condition or capping the length to Math.min(fewLength, b.length).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At benches/generateData.ts, line 60:

<comment>Potential infinite loop: if `params.few.max` or `params.many.max` exceeds `params.records`, these while loops will never terminate because the Set cannot contain more unique IDs than exist in array `b`. Consider adding a guard condition or capping the length to `Math.min(fewLength, b.length)`.</comment>

<file context>
@@ -0,0 +1,87 @@
+    const fewSet = new Set&lt;string&gt;();
+    const manySet = new Set&lt;string&gt;();
+
+    while (fewSet.size &lt; fewLength) {
+      fewSet.add(b[randomInt(0, b.length - 1)].id);
+    }
</file context>
Fix with Cubic

fewSet.add(b[randomInt(0, b.length - 1)].id);
}

while (manySet.size < manyLength) {
manySet.add(b[randomInt(0, b.length - 1)].id);
}

a.push({
...generateBase(),
one: b[i].id,
few: Array.from(fewSet),
many: Array.from(manySet),
});
}

return { a, b };
}

const uid = () => {
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Rule violated: Ensure all TypeScript code adheres to ECMAScript 2025 standards

Use nanoid instead of custom uid() implementation. The project already has nanoid@^5.1.5 as a dependency, which is a modern, cryptographically secure, and performant unique ID generator. The custom implementation using Math.random() is an older pattern that's less reliable.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At benches/generateData.ts, line 79:

<comment>Use `nanoid` instead of custom `uid()` implementation. The project already has `nanoid@^5.1.5` as a dependency, which is a modern, cryptographically secure, and performant unique ID generator. The custom implementation using `Math.random()` is an older pattern that&#39;s less reliable.</comment>

<file context>
@@ -0,0 +1,87 @@
+  return { a, b };
+}
+
+const uid = () =&gt; {
+  const firstChar = &#39;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz&#39;;
+  const chars = &#39;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789&#39;;
</file context>
Fix with Cubic

const firstChar = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = firstChar.charAt(Math.floor(Math.random() * firstChar.length));
for (let i = 0; i < 15; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
};
Loading