Skip to content
Merged
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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 0.2.0-next.7

- **Entities pagination cap raised 1000 → 5000, matching Base44.** `list` and
`filter` still page (50 rows by default) but now allow up to 5000 rows per
call. A `limit` above the cap **throws** instead of silently truncating, so
over-large reads fail loudly rather than returning a partial result the caller
mistakes for the whole set. Page larger tables with `limit` + `skip`.

## 0.2.0-next.6

Combines the entities data layer (next.0–next.4) with the auth fail-safe fix
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ tested, and upgradable independently of any one app.
```
Filters use MongoDB-style operators (`$eq $ne $gt $gte $lt $lte $in $nin
$exists $regex $all $not`, plus `$and`/`$or`/`$nor`); sort is a `-col` string.
`list`/`filter` are paged: **50 rows by default, 5000 max per call** (over-cap
throws) — page larger tables with the `limit` + `skip` args. `updateMany` /
`deleteMany` act on every matching row regardless of page size.
- **Data + Storage through the Bool gateway.** `client.db` is a standard
[supabase-js](https://supabase.com/docs/reference/javascript) client (what
`entities` is built on) whose REST and Storage traffic is routed to the Bool
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bool-sdk",
"version": "0.2.0-next.6",
"version": "0.2.0-next.7",
"description": "Client SDK for apps built on Bool — gateway data access, end-user auth, and the React auth layer.",
"type": "module",
"main": "./dist/index.js",
Expand Down
22 changes: 18 additions & 4 deletions src/entities.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,25 @@ describe("entities: read paths → PostgREST", () => {
expect(reqQuery(calls[0]!).get("order")).toBe("title.asc");
});

test("list caps the limit at 1000", async () => {
test("list() allows an explicit limit up to the max (5000)", async () => {
const bool = createBoolClient(CONFIG);
await bool.entities.todos.list("-created_at", 99999);
// range(0, 999) → limit 1000
expect(reqQuery(calls[0]!).get("limit")).toBe("1000");
await bool.entities.todos.list("-created_at", 5000);
expect(reqQuery(calls[0]!).get("limit")).toBe("5000");
});

test("list() throws on an over-cap limit instead of silently truncating", async () => {
const bool = createBoolClient(CONFIG);
await expect(bool.entities.todos.list("-created_at", 5001)).rejects.toThrow(/maximum/i);
// It fails before touching the network — no partial/truncated request goes out.
expect(calls).toHaveLength(0);
});

test("filter() defaults to the same 50-row page", async () => {
const bool = createBoolClient(CONFIG);
await bool.entities.todos.filter({ status: "active" });
const q = reqQuery(calls[0]!);
expect(q.get("status")).toBe("eq.active");
expect(q.get("limit")).toBe("50");
});

test("fields limits the selected columns", async () => {
Expand Down
18 changes: 13 additions & 5 deletions src/entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,9 @@ export type ImportResult<T = any> = {
/** CRUD + realtime for one entity table. `T` defaults to `any` (untyped);
* apps can pass a row type for autocomplete. Mirrors Base44's EntityHandler. */
export interface EntityHandler<T = any> {
/** All rows, newest first by default. `limit` defaults to 50, max 1000.
* `fields` restricts the columns returned. */
/** Rows, newest first by default. `limit` defaults to 50, max 5000 — page
* through larger result sets with `limit` + `skip` (an over-cap `limit`
* throws rather than silently truncating). `fields` restricts the columns. */
list(sort?: SortSpec, limit?: number, skip?: number, fields?: (keyof T & string)[]): Promise<T[]>;
/** Rows matching `query`. See {@link FilterQuery}. */
filter(
Expand Down Expand Up @@ -145,7 +146,7 @@ export interface EntitiesModule {

const DEFAULT_SORT = "-created_at";
const DEFAULT_LIMIT = 50;
const MAX_LIMIT = 1000;
const MAX_LIMIT = 5000;

// A supabase-js filter/query builder is chainable and thenable; we only need
// the handful of methods below, so keep it loosely typed rather than importing
Expand Down Expand Up @@ -346,8 +347,15 @@ function createEntityHandler(
return fields && fields.length ? fields.join(",") : "*";
}
function paginate(query: QueryBuilder, limit: number, skip: number): QueryBuilder {
const capped = Math.min(limit, MAX_LIMIT);
return query.range(skip, skip + capped - 1);
// A single request returns at most MAX_LIMIT rows. Refuse an over-cap limit
// loudly rather than silently truncating — page through with limit + skip.
if (limit > MAX_LIMIT) {
throw new Error(
`limit ${limit} exceeds the ${MAX_LIMIT}-row maximum per request; ` +
`page through larger result sets with limit + skip.`,
);
}
return query.range(skip, skip + limit - 1);
}
async function unwrap<R>(query: QueryBuilder): Promise<R> {
const { data, error } = await query;
Expand Down
Loading