Skip to content

Commit

Permalink
Merge pull request #48 from prisma-idb/pidb-46
Browse files Browse the repository at this point in the history
Implement aggregate()
  • Loading branch information
sundaram123krishnan authored Nov 11, 2024
2 parents 0ac6dd6 + 8f02a5e commit bc37df3
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 1 deletion.
99 changes: 99 additions & 0 deletions packages/generator/src/Aggregate Functions/aggregate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { ClassDeclaration } from "ts-morph";

export function addAggregateMethod(modelClass: ClassDeclaration) {
modelClass.addMethod({
name: "aggregate",
isAsync: true,
typeParameters: [{ name: "Q", constraint: "Prisma.Args<T, 'aggregate'>" }],
parameters: [{ name: "query", type: "Q" }],
returnType: `Promise<Prisma.Result<T, Q, "aggregate">>`,
statements: (writer) => {
writer
.writeLine("let records = await this.client.db.getAll(`${toCamelCase(this.model.name)}`);")
.blankLine()
.writeLine("if (query.where) {")
.writeLine(
"records = filterByWhereClause(await this.client.db.getAll(toCamelCase(this.model.name)),this.keyPath,query?.where,);",
)
.writeLine("}")
.blankLine()
.writeLine("const results = {};")
.blankLine()
.writeLine("if (query._count) {")
.indent(() => {
writer
.writeLine("const calculateCount = (records, countQuery) => {")
.indent(() => {
writer
.writeLine("const [key] = Object.keys(countQuery);")
.writeLine("return records.filter(record => key in record && record[key] === countQuery[key]).length;");
})
.writeLine("};")
.writeLine("results._count = calculateCount(records, query._count);");
})
.writeLine("}")
.blankLine()
.writeLine("if (query._sum) {")
.indent(() => {
writer
.writeLine("const calculateSum = (records, sumQuery) => {")
.indent(() => {
writer
.writeLine("const [key] = Object.keys(sumQuery);")
.writeLine("const numericValues = records")
.indent(() => {
writer
.writeLine(".map(record => (typeof record[key] === 'number' ? record[key] : null))")
.writeLine(".filter(value => value !== null);");
})
.writeLine("return numericValues.length ? numericValues.reduce((acc, val) => acc + val, 0) : 0;");
})
.writeLine("};")
.writeLine("results._sum = calculateSum(records, query._sum);");
})
.writeLine("}")
.blankLine()
.writeLine("if (query._min) {")
.indent(() => {
writer
.writeLine("const calculateMin = (records, minQuery) => {")
.indent(() => {
writer
.writeLine("const [key] = Object.keys(minQuery);")
.writeLine("const numericValues = records")
.indent(() => {
writer
.writeLine(".map(record => (typeof record[key] === 'number' ? record[key] : null))")
.writeLine(".filter(value => value !== null);");
})
.writeLine("return numericValues.length ? Math.min(...numericValues) : null;");
})
.writeLine("};")
.writeLine("results._min = calculateMin(records, query._min);");
})
.writeLine("}")
.blankLine()
.writeLine("if (query._max) {")
.indent(() => {
writer
.writeLine("const calculateMax = (records, maxQuery) => {")
.indent(() => {
writer
.writeLine("const [key] = Object.keys(maxQuery);")
.writeLine("const numericValues = records")
.indent(() => {
writer
.writeLine(".map(record => (typeof record[key] === 'number' ? record[key] : null))")
.writeLine(".filter(value => value !== null);");
})
.writeLine("return numericValues.length ? Math.max(...numericValues) : null;");
})
.writeLine("};")
.writeLine("results._max = calculateMax(records, query._max);");
})
.writeLine("}")
.blankLine()
.writeLine("return results as Prisma.Result<T, Q, 'aggregate'>;");
},
});
}
2 changes: 2 additions & 0 deletions packages/generator/src/fileBaseFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { generateIDBKey, getModelFieldData, toCamelCase } from "./utils";
import { addDeleteManyMethod } from "./CRUD/deleteMany";
import { addUpdateMethod } from "./CRUD/update";
import { addCountMethod } from "./Aggregate Functions/count";
import { addAggregateMethod } from "./Aggregate Functions/aggregate";

export function addImports(file: SourceFile) {
file.addImportDeclaration({ moduleSpecifier: "idb", namedImports: ["openDB"] });
Expand Down Expand Up @@ -176,6 +177,7 @@ export function addBaseModelClass(file: SourceFile) {

// Aggregate function methods
addCountMethod(baseModelClass);
addAggregateMethod(baseModelClass);
}

export function addEventEmitters(baseModelClass: ClassDeclaration) {
Expand Down
68 changes: 68 additions & 0 deletions packages/usage/src/lib/prisma-idb/prisma-idb-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,19 @@ export class PrismaIDBClient {
isGenerated: false,
isUpdatedAt: false,
},
{
name: "timeToComplete",
kind: "scalar",
isList: false,
isRequired: true,
isUnique: false,
isId: false,
isReadOnly: false,
hasDefaultValue: false,
type: "Int",
isGenerated: false,
isUpdatedAt: false,
},
],
primaryKey: null,
uniqueFields: [],
Expand Down Expand Up @@ -281,4 +294,59 @@ class BaseIDBModelClass<T extends ModelDelegate> {
);
return records.length as Prisma.Result<T, Q, "count">;
}

async aggregate<Q extends Prisma.Args<T, "aggregate">>(query: Q): Promise<Prisma.Result<T, Q, "aggregate">> {
let records = await this.client.db.getAll(`${toCamelCase(this.model.name)}`);
if (query.where) {
records = filterByWhereClause(records, this.keyPath, query.where);
}

const results: Partial<Prisma.Result<T, Q, "aggregate">> = {};

let count = 0;
let sum = 0;
let min: number | null = null;
let max: number | null = null;

records.forEach((record) => {
if (query._count) {
const key = Object.keys(query._count)[0];
console.log(query._count[key]);
if (record[key] === query._count[key]) {
count += 1;
}
}

if (query._sum) {
const key = Object.keys(query._sum)[0];
const value = record[key];
if (typeof value === "number") {
sum += value;
}
}

if (query._min) {
const key = Object.keys(query._min)[0];
const value = record[key];
if (typeof value === "number") {
min = min === null ? value : Math.min(min, value);
}
}

if (query._max) {
const key = Object.keys(query._max)[0];
const value = record[key];
if (typeof value === "number") {
max = max === null ? value : Math.max(max, value);
}
}
});

if (query._count) results._count = count as Prisma.Result<T, Q, "aggregate">["_count"];
if (query._sum) results._sum = sum as Prisma.Result<T, Q, "aggregate">["_sum"];
if (query._min) results._min = min;
if (query._max) results._max = max;

return results as unknown as Prisma.Result<T, Q, "aggregate">;
}
}
1 change: 1 addition & 0 deletions packages/usage/src/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ model Todo {
id String @id @default(uuid())
task String
isCompleted Boolean
timeToComplete Int
}
28 changes: 27 additions & 1 deletion packages/usage/src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,21 @@
let allTodos = $state<Todo[]>([]);
let totalCompletedTodos = $state<number>(0);
let task = $state("");
let timeToComplete = $state<number>(0);
let totalTimeToComplete = $state<number>(0);
function handleChange(event: Event) {
const target = event.target as HTMLSelectElement;
task = target.value;
}
function handleInputNumber(event: Event) {
const target = event.target as HTMLInputElement;
timeToComplete = Number(target.value);
}
async function addTask() {
await client.todo.create({ data: { isCompleted: false, task } });
await client.todo.create({ data: { isCompleted: false, task, timeToComplete } });
allTodos = await client.todo.findMany();
task = "";
}
Expand All @@ -31,6 +38,18 @@
});
}
async function totalTimeToCompleteTasks() {
let data = await client.todo.aggregate({
where: {
isCompleted: false,
},
_sum: {
timeToComplete: true,
},
});
totalTimeToComplete = Number(data._sum);
}
async function updateStatus(id: string, event: Event) {
const target = event.target as HTMLInputElement;
await client.todo.update({
Expand All @@ -50,6 +69,9 @@
client = await PrismaIDBClient.create();
allTodos = await client.todo.findMany();
client.todo.subscribe("update", countCompletedTodos); // use update event listener
client.todo.subscribe("create", totalTimeToCompleteTasks);
client.todo.subscribe("delete", totalTimeToCompleteTasks);
client.todo.subscribe("update", totalTimeToCompleteTasks);
});
</script>

Expand All @@ -58,16 +80,19 @@
<div class="flex flex-col items-center space-y-4">
<div class="flex items-center justify-center space-x-2">
<Input type="text" placeholder="Enter Task" class="max-w-xs" bind:value={task} oninput={handleChange} />
<Input type="number" class="w-16" bind:value={timeToComplete} oninput={handleInputNumber} />
<Button variant="secondary" onclick={addTask}>Add Task</Button>
</div>
<div><h1 class="font-bold">Completed Tasks: {totalCompletedTodos}</h1></div>
<div><h1 class="font-bold">Total Time To Complete Task: {totalTimeToComplete}</h1></div>
</div>
<div>
<Table.Root>
<Table.Header>
<Table.Row>
<Table.Head>Task Id</Table.Head>
<Table.Head>Task</Table.Head>
<Table.Head>Time To Complete</Table.Head>
<Table.Head>Status</Table.Head>
<Table.Head>Actions</Table.Head>
</Table.Row>
Expand All @@ -77,6 +102,7 @@
<Table.Row class="w-fit">
<Table.Cell>{allTodo.id}</Table.Cell>
<Table.Cell>{allTodo.task}</Table.Cell>
<Table.Cell>{allTodo.timeToComplete}</Table.Cell>
<Table.Cell>
<input
type="checkbox"
Expand Down

0 comments on commit bc37df3

Please sign in to comment.