Skip to content

Commit 14f9ccd

Browse files
arosenanclaude
andcommitted
entities: align the API with common practice
- aggregate spec: `query` (not `match`), camelCase keys (`groupBy`, `dateBucket`, `countDistinct`), and no rule against combining countDistinct with other measures - cursor pages default to 100 rows; the maximum stays 5,000 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 77cef17 commit 14f9ccd

4 files changed

Lines changed: 27 additions & 25 deletions

File tree

‎src/modules/entities.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ function parseRealtimeMessage<T = any>(dataStr: string): RealtimeEvent<T> | null
8181
}
8282
}
8383

84+
const DEFAULT_PAGE_LIMIT = 100;
85+
8486
function isListOptions(value: unknown): value is EntityListOptions<any, any> {
8587
return typeof value === "object" && value !== null;
8688
}
@@ -128,7 +130,7 @@ function createEntityHandler<T = any>(
128130
const params: Record<string, string | number> = {};
129131
if (query) params.q = JSON.stringify(query);
130132
if (options.sort) params.sort = options.sort;
131-
if (options.limit) params.limit = options.limit;
133+
params.limit = options.limit || DEFAULT_PAGE_LIMIT;
132134
if (options.cursor) params.cursor = options.cursor;
133135
if (options.fields) params.fields = fieldsParam(options.fields)!;
134136
return axios.get(`${baseURL}/v2/list`, { params });

‎src/modules/entities.types.ts‎

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export interface UpdateManyResult {
6666
export interface EntityListOptions<T, K extends keyof T = keyof T> {
6767
/** Sort parameter, such as `'-created_date'` for descending. Defaults to `'-created_date'`. Every page of one walk must use the same sort. */
6868
sort?: SortField<T>;
69-
/** Maximum number of records per page, up to 5,000. Defaults to 5,000. */
69+
/** Maximum number of records per page, up to 5,000. Defaults to 100. */
7070
limit?: number;
7171
/** `next_cursor` from the previous page. Omit or pass `null` for the first page. */
7272
cursor?: string | null;
@@ -90,7 +90,7 @@ export interface EntityPage<T> {
9090
}
9191

9292
/**
93-
* Time unit for {@linkcode EntityAggregateSpec.date_bucket | date_bucket}.
93+
* Time unit for {@linkcode EntityAggregateSpec.dateBucket | dateBucket}.
9494
*/
9595
export type EntityDateBucketUnit = "day" | "week" | "month" | "year";
9696

@@ -104,11 +104,11 @@ export type EntityDateBucketUnit = "day" | "week" | "month" | "year";
104104
*/
105105
export interface EntityAggregateSpec<T> {
106106
/** Filter applied before grouping, in the same form {@linkcode EntityHandler.filter | filter()} accepts. Defaults to all records. */
107-
match?: EntityFilterQuery<T>;
107+
query?: EntityFilterQuery<T>;
108108
/** Field, or up to four fields, to group by. Omit to get one total row. */
109-
group_by?: (keyof T & string) | (keyof T & string)[];
109+
groupBy?: (keyof T & string) | (keyof T & string)[];
110110
/** Group by a time bucket of a date field. `created_date` and `updated_date` support every unit; date fields of your schema support `day`, `month` and `year`. */
111-
date_bucket?: { field: keyof T & string; unit: EntityDateBucketUnit };
111+
dateBucket?: { field: keyof T & string; unit: EntityDateBucketUnit };
112112
/** Whether to include the number of records per group as `count`. Defaults to `true`. */
113113
count?: boolean;
114114
/** Field, or fields, to sum. Each appears in the rows as `sum_<field>`. */
@@ -119,8 +119,8 @@ export interface EntityAggregateSpec<T> {
119119
min?: (keyof T & string) | (keyof T & string)[];
120120
/** Field, or fields, to take the maximum of. Each appears in the rows as `max_<field>`. */
121121
max?: (keyof T & string) | (keyof T & string)[];
122-
/** Field whose distinct values to count per group, returned as `count_distinct_<field>`. Can't be combined with `count`, `sum`, `avg`, `min` or `max`. */
123-
count_distinct?: keyof T & string;
122+
/** Field whose distinct values to count per group, returned as `count_distinct_<field>`. */
123+
countDistinct?: keyof T & string;
124124
/** Filter on the computed fields, applied after grouping. For example `{ count: { $gt: 1 } }` keeps only duplicated groups. */
125125
having?: Record<string, any>;
126126
/** Computed or group field to sort the rows by, with a `-` prefix for descending. For example `'-count'`. */
@@ -792,8 +792,8 @@ export interface EntityHandler<T = any> {
792792
* ```typescript
793793
* // Sales per agent this month, biggest first
794794
* const { rows } = await base44.entities.Sale.aggregate({
795-
* match: { sale_date: { $gte: '2026-09-01' } },
796-
* group_by: 'agent_id',
795+
* query: { sale_date: { $gte: '2026-09-01' } },
796+
* groupBy: 'agent_id',
797797
* sum: 'amount',
798798
* sort: '-sum_amount'
799799
* });
@@ -804,15 +804,15 @@ export interface EntityHandler<T = any> {
804804
* ```typescript
805805
* // Records created per day
806806
* const { rows } = await base44.entities.Visit.aggregate({
807-
* date_bucket: { field: 'created_date', unit: 'day' }
807+
* dateBucket: { field: 'created_date', unit: 'day' }
808808
* });
809809
* ```
810810
*
811811
* @example
812812
* ```typescript
813813
* // Find duplicated external ids
814814
* const { rows } = await base44.entities.Contact.aggregate({
815-
* group_by: 'external_id',
815+
* groupBy: 'external_id',
816816
* having: { count: { $gt: 1 } }
817817
* });
818818
* ```
@@ -821,8 +821,8 @@ export interface EntityHandler<T = any> {
821821
* ```typescript
822822
* // Unique visitors per page
823823
* const { rows } = await base44.entities.PageView.aggregate({
824-
* group_by: 'path',
825-
* count_distinct: 'session_id'
824+
* groupBy: 'path',
825+
* countDistinct: 'session_id'
826826
* });
827827
* ```
828828
*/

‎tests/types/entities-primitives.types.ts‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,29 +14,29 @@ interface Sale {
1414
}
1515

1616
const perAgent = {
17-
match: { sale_date: { $gte: "2026-09-01" } },
18-
group_by: "agent_id",
17+
query: { sale_date: { $gte: "2026-09-01" } },
18+
groupBy: "agent_id",
1919
sum: ["amount"],
2020
avg: "amount",
2121
sort: "-sum_amount",
2222
limit: 50,
2323
} satisfies EntityAggregateSpec<Sale>;
2424

2525
const perDay = {
26-
date_bucket: { field: "created_date", unit: "day" },
27-
count_distinct: "agent_id",
26+
dateBucket: { field: "created_date", unit: "day" },
27+
countDistinct: "agent_id",
2828
} satisfies EntityAggregateSpec<Sale>;
2929

3030
const duplicates = {
31-
group_by: ["agent_id", "store"],
31+
groupBy: ["agent_id", "store"],
3232
having: { count: { $gt: 1 } },
3333
} satisfies EntityAggregateSpec<Sale>;
3434

3535
// @ts-expect-error unknown field names are rejected
36-
const badGroup = { group_by: "region" } satisfies EntityAggregateSpec<Sale>;
36+
const badGroup = { groupBy: "region" } satisfies EntityAggregateSpec<Sale>;
3737

3838
// @ts-expect-error unknown bucket unit
39-
const badUnit = { date_bucket: { field: "created_date", unit: "hour" } } satisfies EntityAggregateSpec<Sale>;
39+
const badUnit = { dateBucket: { field: "created_date", unit: "hour" } } satisfies EntityAggregateSpec<Sale>;
4040

4141
const firstPage = {
4242
sort: "-created_date",

‎tests/unit/entities-primitives.test.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,10 @@ describe("Entities scan-free primitives", () => {
7070
expect(scope.isDone()).toBe(true);
7171
});
7272

73-
test("list() with an options object reads the first page when cursor is null", async () => {
73+
test("list() with an options object reads the first page when cursor is null, 100 rows by default", async () => {
7474
scope
7575
.get(`${base}/v2/list`)
76-
.query((q) => q.sort === "amount" && q.cursor === undefined && q.q === undefined)
76+
.query((q) => q.sort === "amount" && q.limit === "100" && q.cursor === undefined && q.q === undefined)
7777
.reply(200, { items: [], next_cursor: null, has_more: false });
7878

7979
const page = await base44.entities.Order.list({ sort: "amount", cursor: null });
@@ -118,8 +118,8 @@ describe("Entities scan-free primitives", () => {
118118

119119
test("aggregate() posts the spec as-is to /aggregate", async () => {
120120
const spec = {
121-
match: { status: "paid" },
122-
group_by: "agent_id",
121+
query: { status: "paid" },
122+
groupBy: "agent_id",
123123
sum: "amount",
124124
having: { count: { $gt: 1 } },
125125
sort: "-sum_amount",

0 commit comments

Comments
 (0)