Use URLSearchParams for params construction and fix filter splitting in JS.

This commit is contained in:
Sebastian Jeltsch
2024-12-08 00:50:04 +01:00
parent fff83ea810
commit 711536c12c
5 changed files with 39 additions and 31 deletions

View File

@@ -1,14 +1,14 @@
# JS/TS Client for TrailBase
# TrailBase Client
TrailBase is a [blazingly](https://trailbase.io/reference/benchmarks/) fast,
single-file, open-source application server with type-safe APIs, built-in
JS/ES6/TS Runtime, Auth, and Admin UI built on Rust+SQLite+V8.
open-source application server with type-safe APIs, built-in JS/ES6/TS Runtime,
Auth, and Admin UI built on Rust, SQLite & V8.
For more context, documentation, and an online demo, check out our website
For more context, documentation, and an online demo, check out the website:
[trailbase.io](https://trailbase.io).
This is the first-party client for hooking up your PWA, SPAs and JS backend
applications with TrailBase.
While we're working on better documentation, an example setup can be found under
[`/examples/blog/web`](https://github.com/trailbaseio/trailbase/tree/main/examples/blog/web)
in the repository.
This is the first-party client for hooking up your web and headless (node.js,
deno, ...) apps with TrailBase.
While we're working on better documentation, an example web app can be found in
the repository under:
[`/examples/blog/web`](https://github.com/trailbaseio/trailbase/tree/main/examples/blog/web).

View File

@@ -1,7 +1,7 @@
{
"name": "trailbase",
"description": "Official TrailBase client",
"version": "0.1.2",
"version": "0.1.3",
"license": "OSL-3.0",
"type": "module",
"main": "./src/index.ts",

View File

@@ -115,34 +115,33 @@ export class RecordApi {
order?: string[];
filters?: string[];
}): Promise<T[]> {
const params: [string, string][] = [];
const params = new URLSearchParams();
const pagination = opts?.pagination;
if (pagination) {
const cursor = pagination.cursor;
if (cursor) params.push(["cursor", cursor]);
if (cursor) params.append("cursor", cursor);
const limit = pagination.limit;
if (limit) params.push(["limit", limit.toString()]);
if (limit) params.append("limit", limit.toString());
}
const order = opts?.order;
if (order) params.push(["order", order.join(",")]);
if (order) params.append("order", order.join(","));
const filters = opts?.filters;
if (filters) {
for (const filter of filters) {
const [nameOp, value] = filter.split("=", 2);
if (value === undefined) {
const pos = filter.indexOf("=");
if (pos <= 0) {
throw Error(`Filter '${filter}' does not match: 'name[op]=value'`);
}
params.push([nameOp, value]);
const nameOp = filter.slice(0, pos);
const value = filter.slice(pos + 1);
params.append(nameOp, value);
}
}
const queryParams = encodeURI(
params.map(([key, value]) => `${key}=${value}`).join("&"),
);
const response = await this.client.fetch(
`${RecordApi._recordApi}/${this.name}?${queryParams}`,
`${RecordApi._recordApi}/${this.name}?${params}`,
);
return (await response.json()) as T[];
}

View File

@@ -71,7 +71,11 @@ test("Record integration tests", async () => {
const api = client.records("simple_strict_table");
const now = new Date().getTime();
const messages = [`ts client test 0: ${now}`, `ts client test 1: ${now}`];
// Throw in some url characters for good measure.
const messages = [
`ts client test 1: =?&${now}`,
`ts client test 2: =?&${now}`,
];
const ids: string[] = [];
for (const msg of messages) {
@@ -90,7 +94,7 @@ test("Record integration tests", async () => {
{
const records = await api.list<SimpleStrict>({
filters: [`text_not_null[like]=%${now}`],
filters: [`text_not_null[like]=% =?&${now}`],
order: ["+text_not_null"],
});
expect(records.map((el) => el.text_not_null)).toStrictEqual(messages);
@@ -157,7 +161,7 @@ test("record error tests", async () => {
String.fromCharCode.apply(null, uuidParse(uuidv7())),
);
const nonExistantApi = client.records("non-existant");
expect(
await expect(
async () => await nonExistantApi.read<SimpleStrict>(nonExistantId),
).rejects.toThrowError(
expect.objectContaining({
@@ -166,14 +170,14 @@ test("record error tests", async () => {
);
const api = client.records("simple_strict_table");
expect(
await expect(
async () => await api.read<SimpleStrict>("invalid id"),
).rejects.toThrowError(
expect.objectContaining({
status: status.BAD_REQUEST,
}),
);
expect(
await expect(
async () => await api.read<SimpleStrict>(nonExistantId),
).rejects.toThrowError(
expect.objectContaining({

View File

@@ -25,12 +25,17 @@ function Table() {
const [acidity, setAcidity] = useState(8);
const [sweetness, setSweetness] = useState(8);
const [data, setData] = useState<Array<Array<object>> | undefined>();
type Record = [string, number, number, number, number];
const [data, setData] = useState<Record[]>([]);
useEffect(() => {
const URL = import.meta.env.DEV ? "http://localhost:4000" : "";
const params = Object.entries({ aroma, flavor, acidity, sweetness })
.map(([k, v]) => `${k}=${v}`)
.join("&");
const params = new URLSearchParams({
aroma: `${aroma}`,
flavor: `${flavor}`,
acidity: `${acidity}`,
sweetness: `${sweetness}`,
});
fetch(`${URL}/search?${params}`).then(async (r) => setData(await r.json()));
}, [aroma, flavor, acidity, sweetness]);
@@ -57,7 +62,7 @@ function Table() {
</thead>
<tbody>
{(data ?? []).map((row) => (
{data.map((row) => (
<tr>
{row.map((d) => (
<td>{d.toString()}</td>