[chore] format ts

Signed-off-by: Anton Nesterov <anton@demiurg.io>
This commit is contained in:
Anton Nesterov 2024-08-15 15:04:44 +02:00
parent 0cecd1243f
commit 60d213f21c
No known key found for this signature in database
GPG key ID: 59121E8AE2851FB5
10 changed files with 282 additions and 280 deletions

View file

@ -5,17 +5,14 @@ Data Accees Layer for SQL databases written in Go.
Mongodb inspired query interface: Mongodb inspired query interface:
```typescript ```typescript
const query = Db const query = Db.In("users")
.In("users")
.Find({ .Find({
fullname: { $glob: "*son" } fullname: { $glob: "*son" },
}) })
.Query() .Query();
// Result: // Result:
console.log(users) console.log(users)[
[ ({ id: 25, fullname: "John Menson" }, { id: 76, fullname: "John Johnson" })
{ id: 25, fullname: "John Menson" }, ];
{ id: 76, fullname: "John Johnson" }
]
``` ```

BIN
bun.lockb

Binary file not shown.

View file

@ -4,22 +4,22 @@ import { METHODS, encodeRequest, decodeRowsIterator } from "./Protocol";
type Primitive = string | number | boolean | null; type Primitive = string | number | boolean | null;
interface Filter extends Record<string, unknown> { interface Filter extends Record<string, unknown> {
$eq?: Primitive; $eq?: Primitive;
$ne?: Primitive; $ne?: Primitive;
$gt?: Primitive; $gt?: Primitive;
$gte?: Primitive; $gte?: Primitive;
$lt?: Primitive; $lt?: Primitive;
$lte?: Primitive; $lte?: Primitive;
$in?: Primitive[]; $in?: Primitive[];
$nin?: Primitive[]; $nin?: Primitive[];
$like?: string; $like?: string;
$nlike?: string; $nlike?: string;
$glob?: string; $glob?: string;
$between?: [Primitive, Primitive]; $between?: [Primitive, Primitive];
$nbetween?: [Primitive, Primitive]; $nbetween?: [Primitive, Primitive];
} }
interface FindFilter { interface FindFilter {
[key: string]: Primitive | Filter | Filter[] | undefined; [key: string]: Primitive | Filter | Filter[] | undefined;
} }
type JoinCondition = "inner" | "left" | "cross" | "full outer"; type JoinCondition = "inner" | "left" | "cross" | "full outer";
@ -32,150 +32,146 @@ type JoinFilter = {
type SortOptions = Record<string, 1 | -1 | "asc" | "desc">; type SortOptions = Record<string, 1 | -1 | "asc" | "desc">;
type Options = { type Options = {
database: string; database: string;
url: string; url: string;
}; };
export default class Builder<I extends abstract new (...args: any) => any> {
export default class Builder < private request: Request;
I extends abstract new (...args: any) => any, private url: string;
>{ private dtoTemplate: new (...args: any) => any = Object;
private request: Request; private methodCalls: Map<string, unknown[]> = new Map(); // one call per method
private url: string; private headerRow: unknown[] | null = null;
private dtoTemplate: new (...args: any) => any = Object; constructor(opts: Options) {
private methodCalls: Map<string, unknown[]> = new Map(); // one call per method this.request = {
private headerRow: unknown[] | null = null; id: 0,
constructor(opts: Options) { db: opts.database,
this.request = { commands: [],
id: 0, };
db: opts.database, this.url = opts.url;
commands: [], }
}; private formatRequest(): void {
this.url = opts.url; this.request.commands = [];
METHODS.forEach((method) => {
const args = this.methodCalls.get(method);
if (!args) {
return;
}
this.request.commands.push({ method, args });
});
}
private formatRow(data: unknown[]) {
if (!this.dtoTemplate) {
return data;
} }
private formatRequest(): void { const instance = new this.dtoTemplate(data);
this.request.commands = [] for (const idx in this.headerRow!) {
METHODS.forEach((method) => { const header = this.headerRow[idx] as string;
const args = this.methodCalls.get(method); if (header in instance) {
if (!args) { instance[header] = data[idx];
return; }
}
this.request.commands.push({ method, args });
})
} }
private formatRow(data: unknown[]){ return instance;
if (!this.dtoTemplate) { }
return data; In(table: string): Builder<I> {
} this.methodCalls.set("In", [table]);
const instance = new this.dtoTemplate(data); return this;
for (const idx in this.headerRow!) { }
const header = this.headerRow[idx] as string; Find(filter: FindFilter): Builder<I> {
if (header in instance) { this.methodCalls.set("Find", [filter]);
instance[header] = data[idx]; return this;
} }
} Select(fields: string[]): Builder<I> {
return instance; this.methodCalls.set("Select", fields);
} return this;
In(table: string): Builder<I> { }
this.methodCalls.set("In", [table]); Fields(fields: string[]): Builder<I> {
return this; this.Select(fields);
} return this;
Find(filter: FindFilter): Builder<I> { }
this.methodCalls.set("Find", [filter]); Join(...joins: JoinFilter[]): Builder<I> {
return this; this.methodCalls.set("Join", joins);
} return this;
Select(fields: string[]): Builder<I> { }
this.methodCalls.set("Select", fields); Group(fields: string[]): Builder<I> {
return this; this.methodCalls.set("Group", fields);
} return this;
Fields(fields: string[]): Builder<I> { }
this.Select(fields); Sort(fields: SortOptions): Builder<I> {
return this; this.methodCalls.set("Sort", [fields]);
} return this;
Join(...joins: JoinFilter[]): Builder<I> { }
this.methodCalls.set("Join", joins); Limit(limit: number): Builder<I> {
return this; this.methodCalls.set("Limit", [limit]);
} return this;
Group(fields: string[]): Builder<I> { }
this.methodCalls.set("Group", fields); Offset(offset: number): Builder<I> {
return this; this.methodCalls.set("Offset", [offset]);
} return this;
Sort(fields: SortOptions): Builder<I> { }
this.methodCalls.set("Sort", [fields]); Delete(): Builder<I> {
return this; this.methodCalls.set("Delete", []);
} return this;
Limit(limit: number): Builder<I> { }
this.methodCalls.set("Limit", [limit]); Insert(data: Record<string, unknown>): Builder<I> {
return this; this.methodCalls.set("Insert", [data]);
} return this;
Offset(offset: number): Builder<I> { }
this.methodCalls.set("Offset", [offset]); Set(data: Record<string, unknown>): Builder<I> {
return this; this.methodCalls.set("Set", [data]);
} return this;
Delete(): Builder<I> { }
this.methodCalls.set("Delete", []); Update(data: Record<string, unknown>): Builder<I> {
return this; this.Set(data);
} return this;
Insert(data: Record<string, unknown>): Builder<I> { }
this.methodCalls.set("Insert", [data]); OnConflict(...fields: string[]): Builder<I> {
return this; this.methodCalls.set("OnConflict", fields);
} return this;
Set(data: Record<string, unknown>): Builder<I> { }
this.methodCalls.set("Set", [data]); DoUpdate(...fields: string[]): Builder<I> {
return this; this.methodCalls.delete("DoNothing");
} this.methodCalls.set("DoUpdate", fields);
Update(data: Record<string, unknown>): Builder<I> { return this;
this.Set(data); }
return this; DoNothing(): Builder<I> {
} this.methodCalls.delete("DoUpdate");
OnConflict(...fields: string[]): Builder<I> { this.methodCalls.set("DoNothing", []);
this.methodCalls.set("OnConflict", fields); return this;
return this; }
} async *Rows<T = InstanceType<I>>(): AsyncGenerator<T> {
DoUpdate(...fields: string[]): Builder<I> { this.formatRequest();
this.methodCalls.delete("DoNothing"); const response = await fetch(this.url, {
this.methodCalls.set("DoUpdate", fields); method: "POST",
return this; body: new Blob([encodeRequest(this.request)]),
} headers: {
DoNothing(): Builder<I> { "Content-Type": "application/x-msgpack",
this.methodCalls.delete("DoUpdate"); },
this.methodCalls.set("DoNothing", []); });
return this; if (response.status !== 200) {
} throw new Error(await response.text());
async *Rows<T = InstanceType<I>>(): AsyncGenerator<T> {
this.formatRequest();
const response = await fetch(this.url, {
method: "POST",
body: new Blob([encodeRequest(this.request)]),
headers: {
"Content-Type": "application/x-msgpack",
},
});
if (response.status !== 200) {
throw new Error(await response.text());
}
const iterator = decodeRowsIterator(response.body!);
for await (const row of iterator) {
if (this.headerRow === null) {
this.headerRow = row.r;
await iterator.next();
continue;
}
yield this.formatRow(row.r);
}
}
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
} }
const iterator = decodeRowsIterator(response.body!);
for await (const row of iterator) {
if (this.headerRow === null) {
this.headerRow = row.r;
await iterator.next();
continue;
}
yield this.formatRow(row.r);
}
}
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;
}
} }

View file

@ -1,68 +1,73 @@
import { encode, decode } from '@msgpack/msgpack'; import { encode, decode } from "@msgpack/msgpack";
export interface Method { export interface Method {
method: string; method: string;
args: any; args: any;
} }
export interface Request { export interface Request {
id: number; id: number;
db: string; db: string;
commands: Method[]; commands: Method[];
} }
export const METHODS = "In|Find|Select|Fields|Join|Group|Sort|Limit|Offset|Delete|Insert|Set|Update|OnConflict|DoUpdate|DoNothing".split("|"); export const METHODS =
"In|Find|Select|Fields|Join|Group|Sort|Limit|Offset|Delete|Insert|Set|Update|OnConflict|DoUpdate|DoNothing".split(
"|",
);
export function encodeRequest(request: Request): Uint8Array { export function encodeRequest(request: Request): Uint8Array {
return encode(request); return encode(request);
} }
export interface Row { export interface Row {
r: unknown[]; r: unknown[];
} }
const ROW_TAG = [0x81, 0xa1, 0x72]; const ROW_TAG = [0x81, 0xa1, 0x72];
export function decodeRows(input: Uint8Array): Row[] { export function decodeRows(input: Uint8Array): Row[] {
const rows = []; const rows = [];
let count = 0; let count = 0;
let buf = []; let buf = [];
while (count < input.length) { while (count < input.length) {
if (input.at(count) != 0x81) { if (input.at(count) != 0x81) {
buf.push(input.at(count)); buf.push(input.at(count));
count++; count++;
continue continue;
}
const [a, b, c] = ROW_TAG;
const [aa, bb, cc] = input.slice(count, count + 4);
if (aa == a && bb == b && cc == c) {
rows.push([...ROW_TAG, ...buf]);
buf = [];
count += 3;
} else {
buf.push(input.at(count));
count++;
}
} }
rows.push([...ROW_TAG, ...buf]); const [a, b, c] = ROW_TAG;
rows.shift(); const [aa, bb, cc] = input.slice(count, count + 4);
return rows.map((row) => decode(new Uint8Array(row as number[]))) as Row[]; if (aa == a && bb == b && cc == c) {
rows.push([...ROW_TAG, ...buf]);
buf = [];
count += 3;
} else {
buf.push(input.at(count));
count++;
}
}
rows.push([...ROW_TAG, ...buf]);
rows.shift();
return rows.map((row) => decode(new Uint8Array(row as number[]))) as Row[];
} }
export async function *decodeRowsIterator(stream: ReadableStream<Uint8Array>): AsyncGenerator<Row> { export async function* decodeRowsIterator(
const reader = stream.getReader(); stream: ReadableStream<Uint8Array>,
let buf = new Uint8Array(); ): AsyncGenerator<Row> {
for (;;) { const reader = stream.getReader();
const { value, done } = await reader.read(); let buf = new Uint8Array();
if (done) { for (;;) {
break; const { value, done } = await reader.read();
} if (done) {
buf = new Uint8Array([...buf, ...value]); break;
// the server flushes after each row
// so we decode "complete" rows
const rows = decodeRows(buf);
for (const row of rows) {
yield row;
}
} }
buf = new Uint8Array([...buf, ...value]);
// the server flushes after each row
// so we decode "complete" rows
const rows = decodeRows(buf);
for (const row of rows) {
yield row;
}
}
} }

View file

@ -1,61 +1,61 @@
import { test, expect } from "bun:test"; import { test, expect } from "bun:test";
import { DAL } from ".." import { DAL } from "..";
const options = { const options = {
database: "test.sqlite", database: "test.sqlite",
url: "http://localhost:8111", url: "http://localhost:8111",
} };
class DTO { class DTO {
id: number = 0; id: number = 0;
name: string = ""; name: string = "";
data: string = ""; data: string = "";
age: number | undefined; age: number | undefined;
} }
test("Rows iter, no format", async () => { test("Rows iter, no format", async () => {
const dal = new DAL(options); const dal = new DAL(options);
const rows = dal const rows = dal
.In("test t") .In("test t")
.Find({ .Find({
id: 1, id: 1,
}) })
.Rows<any[]>(); .Rows<any[]>();
for await (const row of rows) { for await (const row of rows) {
console.log(row); console.log(row);
expect(row.length).toBe(3); expect(row.length).toBe(3);
} }
expect(true).toBe(true); expect(true).toBe(true);
}); });
test("Rows iter, format", async () => { test("Rows iter, format", async () => {
const dal = new DAL(options); const dal = new DAL(options);
const rows = dal const rows = dal
.In("test t") .In("test t")
.Find({ .Find({
id: 1, id: 1,
}) })
.As(DTO) .As(DTO)
.Rows(); .Rows();
for await (const row of rows) { for await (const row of rows) {
console.log(row); console.log(row);
expect(row.id).toBe(1); expect(row.id).toBe(1);
} }
expect(true).toBe(true); expect(true).toBe(true);
}); });
test("Query format", async () => { test("Query format", async () => {
const dal = new DAL(options); const dal = new DAL(options);
const rows = await dal const rows = await dal
.In("test t") .In("test t")
.Find({ .Find({
id: 1, id: 1,
}) })
.As(DTO) .As(DTO)
.Query(); .Query();
for (const row of rows) { for (const row of rows) {
expect(row.id).toBeDefined(); expect(row.id).toBeDefined();
expect(row.age).toBeUndefined(); expect(row.age).toBeUndefined();
} }
expect(true).toBe(true); expect(true).toBe(true);
}); });

Binary file not shown.

View file

@ -1 +1 @@
export { default as DAL } from './Builder'; export { default as DAL } from "./Builder";

View file

@ -1,3 +1,3 @@
# [wip] DAL # [wip] DAL
NodeJS Client for the [DAL]() Server.
NodeJS Client for the [DAL]() Server.

View file

@ -3,7 +3,8 @@
"module": "dal/index.ts", "module": "dal/index.ts",
"type": "module", "type": "module",
"devDependencies": { "devDependencies": {
"@types/bun": "latest" "@types/bun": "latest",
"prettier": "^3.3.3"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "^5.0.0" "typescript": "^5.0.0"
@ -13,7 +14,8 @@
}, },
"scripts": { "scripts": {
"test:client": "bun test:*", "test:client": "bun test:*",
"test:dal" : "bun test dal/__test__", "test:dal": "bun test dal/__test__",
"test:serve": "cd dal/__test__/srv && go run main.go" "test:serve": "cd dal/__test__/srv && go run main.go",
"fmt": "prettier --write ."
} }
} }

View file

@ -2,17 +2,19 @@
import { encode } from "https://deno.land/x/msgpack@v1.2/mod.ts"; import { encode } from "https://deno.land/x/msgpack@v1.2/mod.ts";
const Query = { const Query = {
"db": "database.sqlite", db: "database.sqlite",
"commands": [ commands: [
{"method": "In", "args": ["data"]}, { method: "In", args: ["data"] },
{ {
"method": "Find", method: "Find",
"args": [{ args: [
"a": 1, {
"b": { a: 1,
"$gt": 2, b: {
}, $gt: 2,
}] },
},
],
}, },
], ],
}; };