[feat] generics for TS builder; [feat] support row formating as DTO class
Signed-off-by: Anton Nesterov <anton@demiurg.io>
This commit is contained in:
parent
de9d8ad231
commit
0cecd1243f
128
dal/Builder.ts
128
dal/Builder.ts
|
@ -36,9 +36,15 @@ type Options = {
|
||||||
url: string;
|
url: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class Builder {
|
|
||||||
|
export default class Builder <
|
||||||
|
I extends abstract new (...args: any) => any,
|
||||||
|
>{
|
||||||
private request: Request;
|
private request: Request;
|
||||||
private url: string;
|
private url: string;
|
||||||
|
private dtoTemplate: new (...args: any) => any = Object;
|
||||||
|
private methodCalls: Map<string, unknown[]> = new Map(); // one call per method
|
||||||
|
private headerRow: unknown[] | null = null;
|
||||||
constructor(opts: Options) {
|
constructor(opts: Options) {
|
||||||
this.request = {
|
this.request = {
|
||||||
id: 0,
|
id: 0,
|
||||||
|
@ -47,78 +53,97 @@ export default class Builder {
|
||||||
};
|
};
|
||||||
this.url = opts.url;
|
this.url = opts.url;
|
||||||
}
|
}
|
||||||
private format(): void {
|
private formatRequest(): void {
|
||||||
this.request.commands = METHODS.map((method) => {
|
this.request.commands = []
|
||||||
const command = this.request.commands.find((command) => command.method === method);
|
METHODS.forEach((method) => {
|
||||||
return command;
|
const args = this.methodCalls.get(method);
|
||||||
}).filter(Boolean) as Request["commands"];
|
if (!args) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
In(table: string): Builder {
|
this.request.commands.push({ method, args });
|
||||||
this.request.commands.push({ method: "In", args: [table] });
|
})
|
||||||
|
}
|
||||||
|
private formatRow(data: unknown[]){
|
||||||
|
if (!this.dtoTemplate) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
const instance = new this.dtoTemplate(data);
|
||||||
|
for (const idx in this.headerRow!) {
|
||||||
|
const header = this.headerRow[idx] as string;
|
||||||
|
if (header in instance) {
|
||||||
|
instance[header] = data[idx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
In(table: string): Builder<I> {
|
||||||
|
this.methodCalls.set("In", [table]);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
Find(filter: FindFilter): Builder {
|
Find(filter: FindFilter): Builder<I> {
|
||||||
this.request.commands.push({ method: "Find", args: [filter] });
|
this.methodCalls.set("Find", [filter]);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
Select(fields: string[]): Builder {
|
Select(fields: string[]): Builder<I> {
|
||||||
this.request.commands.push({ method: "Select", args: fields });
|
this.methodCalls.set("Select", fields);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
Fields(fields: string[]): Builder {
|
Fields(fields: string[]): Builder<I> {
|
||||||
this.Select(fields);
|
this.Select(fields);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
Join(...joins: JoinFilter[]): Builder {
|
Join(...joins: JoinFilter[]): Builder<I> {
|
||||||
this.request.commands.push({ method: "Join", args: joins });
|
this.methodCalls.set("Join", joins);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
Group(fields: string[]): Builder {
|
Group(fields: string[]): Builder<I> {
|
||||||
this.request.commands.push({ method: "Group", args: fields });
|
this.methodCalls.set("Group", fields);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
Sort(fields: SortOptions): Builder {
|
Sort(fields: SortOptions): Builder<I> {
|
||||||
this.request.commands.push({ method: "Sort", args: fields });
|
this.methodCalls.set("Sort", [fields]);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
Limit(limit: number): Builder {
|
Limit(limit: number): Builder<I> {
|
||||||
this.request.commands.push({ method: "Limit", args: [limit] });
|
this.methodCalls.set("Limit", [limit]);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
Offset(offset: number): Builder {
|
Offset(offset: number): Builder<I> {
|
||||||
this.request.commands.push({ method: "Offset", args: [offset] });
|
this.methodCalls.set("Offset", [offset]);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
Delete(): Builder {
|
Delete(): Builder<I> {
|
||||||
this.request.commands.push({ method: "Delete", args: [] });
|
this.methodCalls.set("Delete", []);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
Insert(data: Record<string, unknown>): Builder {
|
Insert(data: Record<string, unknown>): Builder<I> {
|
||||||
this.request.commands.push({ method: "Insert", args: [data] });
|
this.methodCalls.set("Insert", [data]);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
Set(data: Record<string, unknown>): Builder {
|
Set(data: Record<string, unknown>): Builder<I> {
|
||||||
this.request.commands.push({ method: "Set", args: [data] });
|
this.methodCalls.set("Set", [data]);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
Update(data: Record<string, unknown>): Builder {
|
Update(data: Record<string, unknown>): Builder<I> {
|
||||||
this.Set(data);
|
this.Set(data);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
OnConflict(...fields: string[]): Builder {
|
OnConflict(...fields: string[]): Builder<I> {
|
||||||
this.request.commands.push({ method: "OnConflict", args: fields });
|
this.methodCalls.set("OnConflict", fields);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
DoUpdate(...fields: string[]): Builder {
|
DoUpdate(...fields: string[]): Builder<I> {
|
||||||
this.request.commands.push({ method: "DoUpdate", args: fields });
|
this.methodCalls.delete("DoNothing");
|
||||||
|
this.methodCalls.set("DoUpdate", fields);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
DoNothing(): Builder {
|
DoNothing(): Builder<I> {
|
||||||
this.request.commands.push({ method: "DoNothing", args: [] });
|
this.methodCalls.delete("DoUpdate");
|
||||||
|
this.methodCalls.set("DoNothing", []);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
async *Rows() {
|
async *Rows<T = InstanceType<I>>(): AsyncGenerator<T> {
|
||||||
this.format();
|
this.formatRequest();
|
||||||
const response = await fetch(this.url, {
|
const response = await fetch(this.url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: new Blob([encodeRequest(this.request)]),
|
body: new Blob([encodeRequest(this.request)]),
|
||||||
|
@ -130,14 +155,27 @@ export default class Builder {
|
||||||
throw new Error(await response.text());
|
throw new Error(await response.text());
|
||||||
}
|
}
|
||||||
|
|
||||||
for await (const row of decodeRowsIterator(response.body!)) {
|
const iterator = decodeRowsIterator(response.body!);
|
||||||
yield row;
|
for await (const row of iterator) {
|
||||||
|
if (this.headerRow === null) {
|
||||||
|
this.headerRow = row.r;
|
||||||
|
await iterator.next();
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
this.request = {
|
yield this.formatRow(row.r);
|
||||||
id: 0,
|
}
|
||||||
db: this.request.db,
|
}
|
||||||
commands: [],
|
As<T extends new (...args: any) => any>(template: T): Builder<T> {
|
||||||
};
|
this.dtoTemplate = template;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
async Query<T = InstanceType<I>>(): Promise<T[]> {
|
||||||
|
const rows = this.Rows();
|
||||||
|
const result = [];
|
||||||
|
for await (const row of rows) {
|
||||||
|
result.push(row);
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -55,7 +55,6 @@ export async function *decodeRowsIterator(stream: ReadableStream<Uint8Array>): A
|
||||||
for (;;) {
|
for (;;) {
|
||||||
const { value, done } = await reader.read();
|
const { value, done } = await reader.read();
|
||||||
if (done) {
|
if (done) {
|
||||||
console.log("done");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
buf = new Uint8Array([...buf, ...value]);
|
buf = new Uint8Array([...buf, ...value]);
|
||||||
|
|
61
dal/__test__/builder.test.ts
Normal file
61
dal/__test__/builder.test.ts
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import { test, expect } from "bun:test";
|
||||||
|
import { DAL } from ".."
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
database: "test.sqlite",
|
||||||
|
url: "http://localhost:8111",
|
||||||
|
}
|
||||||
|
|
||||||
|
class DTO {
|
||||||
|
id: number = 0;
|
||||||
|
name: string = "";
|
||||||
|
data: string = "";
|
||||||
|
age: number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Rows iter, no format", async () => {
|
||||||
|
const dal = new DAL(options);
|
||||||
|
const rows = dal
|
||||||
|
.In("test t")
|
||||||
|
.Find({
|
||||||
|
id: 1,
|
||||||
|
})
|
||||||
|
.Rows<any[]>();
|
||||||
|
for await (const row of rows) {
|
||||||
|
console.log(row);
|
||||||
|
expect(row.length).toBe(3);
|
||||||
|
}
|
||||||
|
expect(true).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Rows iter, format", async () => {
|
||||||
|
const dal = new DAL(options);
|
||||||
|
const rows = dal
|
||||||
|
.In("test t")
|
||||||
|
.Find({
|
||||||
|
id: 1,
|
||||||
|
})
|
||||||
|
.As(DTO)
|
||||||
|
.Rows();
|
||||||
|
for await (const row of rows) {
|
||||||
|
console.log(row);
|
||||||
|
expect(row.id).toBe(1);
|
||||||
|
}
|
||||||
|
expect(true).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Query format", async () => {
|
||||||
|
const dal = new DAL(options);
|
||||||
|
const rows = await dal
|
||||||
|
.In("test t")
|
||||||
|
.Find({
|
||||||
|
id: 1,
|
||||||
|
})
|
||||||
|
.As(DTO)
|
||||||
|
.Query();
|
||||||
|
for (const row of rows) {
|
||||||
|
expect(row.id).toBeDefined();
|
||||||
|
expect(row.age).toBeUndefined();
|
||||||
|
}
|
||||||
|
expect(true).toBe(true);
|
||||||
|
});
|
|
@ -1,24 +0,0 @@
|
||||||
import { test, expect } from "bun:test";
|
|
||||||
import { DAL } from ".."
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
database: "test.sqlite",
|
|
||||||
url: "http://localhost:8111",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
test("Rows iter", async () => {
|
|
||||||
const dal = new DAL(options);
|
|
||||||
const rows = dal
|
|
||||||
.In("test t")
|
|
||||||
.Find({
|
|
||||||
id: 1,
|
|
||||||
})
|
|
||||||
.Rows();
|
|
||||||
for await (const row of rows) {
|
|
||||||
// console.log(row);
|
|
||||||
//@ts-ignore
|
|
||||||
expect(row.r.length).toBe(3);
|
|
||||||
}
|
|
||||||
expect(true).toBe(true);
|
|
||||||
});
|
|
BIN
dal/__test__/srv/test.sqlite
Normal file
BIN
dal/__test__/srv/test.sqlite
Normal file
Binary file not shown.
Loading…
Reference in a new issue