@taylordb/query-builder 0.16.2 → 0.16.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,111 @@
1
+ # Conditions (Filtering)
2
+
3
+ Use `.where()` and `.orWhere()` to filter records. Both methods are available on `selectFrom`, `update`, `deleteFrom`, and aggregation queries.
4
+
5
+ ## Simple condition
6
+
7
+ ```ts
8
+ .where(field, operator, value)
9
+ ```
10
+
11
+ ```ts
12
+ const users = await qb
13
+ .selectFrom('users')
14
+ .select(['id', 'name'])
15
+ .where('age', '>', 30)
16
+ .execute();
17
+ ```
18
+
19
+ The available operators and their accepted value types depend on the column type — see [field-types.md](./field-types.md) for the full operator table per type.
20
+
21
+ ## Multiple AND conditions
22
+
23
+ Chaining `.where()` adds each condition with AND logic.
24
+
25
+ ```ts
26
+ const users = await qb
27
+ .selectFrom('users')
28
+ .selectAll()
29
+ .where('age', '>', 18)
30
+ .where('role', '=', 'admin')
31
+ .execute();
32
+ // age > 18 AND role = 'admin'
33
+ ```
34
+
35
+ ## OR conditions
36
+
37
+ `.orWhere()` has the same signature as `.where()` but switches the conjunction to OR for all conditions in the current group.
38
+
39
+ ```ts
40
+ const users = await qb
41
+ .selectFrom('users')
42
+ .selectAll()
43
+ .where('name', '=', 'Alice')
44
+ .orWhere('name', '=', 'Bob')
45
+ .execute();
46
+ // name = 'Alice' OR name = 'Bob'
47
+ ```
48
+
49
+ ## Grouped conditions (nested logic)
50
+
51
+ Pass a callback to `.where()` to create a nested group. The callback receives a fresh builder scoped to the same table and only the conditions added inside it form the group.
52
+
53
+ ```ts
54
+ const users = await qb
55
+ .selectFrom('users')
56
+ .selectAll()
57
+ .where('status', '=', 'active')
58
+ .where(qb =>
59
+ qb
60
+ .where('role', '=', 'admin')
61
+ .orWhere('role', '=', 'editor')
62
+ )
63
+ .execute();
64
+ // status = 'active' AND (role = 'admin' OR role = 'editor')
65
+ ```
66
+
67
+ ## Cross-table filtering (link fields)
68
+
69
+ When filtering on a link field you can pass a callback instead of a plain value. The callback receives a builder scoped to the **linked** table, letting you filter based on properties of the related records.
70
+
71
+ ```ts
72
+ const users = await qb
73
+ .selectFrom('users')
74
+ .selectAll()
75
+ .where('posts', 'hasAnyOf', qb =>
76
+ qb.where('isPublished', '=', true)
77
+ )
78
+ .execute();
79
+ // users who have at least one published post
80
+ ```
81
+
82
+ The operator you choose on the link field still applies (`hasAnyOf`, `hasAllOf`, `isExactly`, `hasNoneOf`), but the value is resolved by running the inner filter against the linked table.
83
+
84
+ ## isEmpty / isNotEmpty
85
+
86
+ For operators that take no value (`isEmpty`, `isNotEmpty`), omit the third argument or pass `undefined`.
87
+
88
+ ```ts
89
+ .where('bio', 'isEmpty')
90
+ .where('avatar', 'isNotEmpty')
91
+ ```
92
+
93
+ ## Checkbox fields
94
+
95
+ Checkbox filters use numeric values — `1` for true, `0` for false.
96
+
97
+ ```ts
98
+ .where('isVerified', '=', 1)
99
+ ```
100
+
101
+ ## Date shorthand values
102
+
103
+ Date fields accept named shorthands in addition to exact values.
104
+
105
+ ```ts
106
+ .where('createdAt', '=', 'today')
107
+ .where('dueDate', '<', ['daysFromNow', 7])
108
+ .where('updatedAt', 'isWithIn', 'pastWeek')
109
+ ```
110
+
111
+ See [field-types.md](./field-types.md) for the full list of date filter values.
@@ -0,0 +1,50 @@
1
+ # Current User
2
+
3
+ The query builder exposes a built-in way to get the currently authenticated user's profile record.
4
+
5
+ ## Usage
6
+
7
+ ```ts
8
+ const user = await qb.auth.getUser();
9
+ ```
10
+
11
+ ## Return value
12
+
13
+ ```ts
14
+ {
15
+ id: number;
16
+ name: string;
17
+ email: string;
18
+ avatar: string;
19
+ } | null
20
+ ```
21
+
22
+ Returns `null` when no matching collaborator record is found.
23
+
24
+ ## How it works
25
+
26
+ `getUser()` pulls the user ID from the active WebSocket/HTTP connection, then looks up that ID in the `bambooCollaborators` internal table using a filter on `externalId`. Only active collaborators (those with `status = 'ACTIVE'`) are considered.
27
+
28
+ ## Requirement
29
+
30
+ Authentication must have been established at the connection level — i.e. the `apiKey` provided to `createQueryBuilder` must be a valid user token, not just a public API key. If the connection has no user ID attached, `getUser()` throws:
31
+
32
+ ```
33
+ Error: User ID not available from the connection
34
+ ```
35
+
36
+ ## Example
37
+
38
+ ```ts
39
+ const qb = createQueryBuilder<TaylorDatabase>({
40
+ baseUrl: 'https://your-instance.taylordb.ai',
41
+ baseId: 'your-base-id',
42
+ apiKey: 'user-bearer-token',
43
+ });
44
+
45
+ const me = await qb.auth.getUser();
46
+
47
+ if (me) {
48
+ console.log(`Hello ${me.name} (${me.email})`);
49
+ }
50
+ ```
@@ -0,0 +1,215 @@
1
+ # Field Types
2
+
3
+ Every column in a TaylorDB table is represented by a strongly-typed column type. These types encode what value you can write on insert/update, what value you read back on select, and which filter operators are valid for that field.
4
+
5
+ ## Column type anatomy
6
+
7
+ ```ts
8
+ ColumnType<Select, Update, Insert, IsRequired, Filters, Aggregations>
9
+ ```
10
+
11
+ | Slot | Meaning |
12
+ |------|---------|
13
+ | `Select` (`raw`) | The TypeScript type you receive when you read the field |
14
+ | `Update` | The type accepted by `.set({ field: value })` |
15
+ | `Insert` | The type accepted by `.values({ field: value })` |
16
+ | `IsRequired` | `true` — field must be provided on insert; `false` — optional |
17
+ | `Filters` | The operators available in `.where(field, operator, value)` |
18
+ | `Aggregations` | The aggregation functions available for this field |
19
+
20
+ ---
21
+
22
+ ## Text — `TextColumnType<IsRequired>`
23
+
24
+ Maps to field types: `singleLineText`, `text`, `longText`, `url`, `email`, `phoneNumber`, `json`.
25
+
26
+ - **Select / Insert / Update**: `string`
27
+
28
+ **Available filter operators**
29
+
30
+ | Operator | Value type |
31
+ |----------|-----------|
32
+ | `=` | `string` |
33
+ | `!=` | `string` |
34
+ | `caseEqual` | `string` |
35
+ | `contains` | `string` |
36
+ | `doesNotContain` | `string` |
37
+ | `startsWith` | `string` |
38
+ | `endsWith` | `string` |
39
+ | `hasAnyOf` | `string[]` |
40
+ | `isEmpty` | _(no value)_ |
41
+ | `isNotEmpty` | _(no value)_ |
42
+
43
+ ---
44
+
45
+ ## Number — `NumberColumnType<IsRequired>`
46
+
47
+ Maps to field types: `number`, `currency`, `percent`, `duration`, `serial`, `decimalSerial`, `position`.
48
+
49
+ - **Select / Insert / Update**: `number`
50
+
51
+ **Available filter operators**
52
+
53
+ | Operator | Value type |
54
+ |----------|-----------|
55
+ | `=` | `number` |
56
+ | `!=` | `number` |
57
+ | `>` | `number` |
58
+ | `>=` | `number` |
59
+ | `<` | `number` |
60
+ | `<=` | `number` |
61
+ | `hasAnyOf` | `number[]` |
62
+ | `hasNoneOf` | `number[]` |
63
+ | `isEmpty` | _(no value)_ |
64
+ | `isNotEmpty` | _(no value)_ |
65
+
66
+ **Available aggregations**: `sum`, `average`, `median`, `min`, `max`, `range`, `standardDeviation`, `histogram`, `empty`, `filled`, `unique`, `percentEmpty`, `percentFilled`, `percentUnique`
67
+
68
+ ---
69
+
70
+ ## Auto-generated number — `AutoGeneratedNumberColumnType`
71
+
72
+ Maps to field type: `autoNumber`, system field `id`.
73
+
74
+ - **Select**: `number`
75
+ - **Insert / Update**: `never` — cannot be written
76
+
77
+ Same filter operators and aggregations as `NumberColumnType`.
78
+
79
+ ---
80
+
81
+ ## Checkbox — `CheckboxColumnType<IsRequired>`
82
+
83
+ - **Select / Insert / Update**: `boolean`
84
+
85
+ **Available filter operators**
86
+
87
+ | Operator | Value type |
88
+ |----------|-----------|
89
+ | `=` | `number` (use `1` for true, `0` for false) |
90
+
91
+ ---
92
+
93
+ ## Date — `DateColumnType<IsRequired>`
94
+
95
+ - **Select / Insert / Update**: `string` (ISO 8601 date string)
96
+
97
+ **Available filter operators**
98
+
99
+ | Operator | Value type |
100
+ |----------|-----------|
101
+ | `=` | `DefaultDateFilterValue` |
102
+ | `!=` | `DefaultDateFilterValue` |
103
+ | `<` | `DefaultDateFilterValue` |
104
+ | `>` | `DefaultDateFilterValue` |
105
+ | `<=` | `DefaultDateFilterValue` |
106
+ | `>=` | `DefaultDateFilterValue` |
107
+ | `isWithIn` | `IsWithinOperatorValue` or `{ value: 'daysAgo' \| 'daysFromNow'; date: number }` |
108
+ | `isEmpty` | _(no value)_ |
109
+ | `isNotEmpty` | _(no value)_ |
110
+
111
+ `DefaultDateFilterValue` can be one of:
112
+ - A named shorthand string: `'today'`, `'tomorrow'`, `'yesterday'`, `'oneWeekAgo'`, `'oneWeekFromNow'`, `'oneMonthAgo'`, `'oneMonthFromNow'`
113
+ - A tuple `['exactDay' | 'exactTimestamp', string]` — pass an ISO date string as the second element
114
+ - A tuple `['daysAgo' | 'daysFromNow', number]`
115
+
116
+ `IsWithinOperatorValue`: `'pastWeek'`, `'pastMonth'`, `'pastYear'`, `'nextWeek'`, `'nextMonth'`, `'nextYear'`, `'daysFromNow'`, `'daysAgo'`, `'currentWeek'`, `'currentMonth'`, `'currentYear'`
117
+
118
+ **Available aggregations**: `empty`, `filled`, `unique`, `percentEmpty`, `percentFilled`, `percentUnique`, `min`, `max`, `daysRange`, `monthRange`
119
+
120
+ ---
121
+
122
+ ## Auto-generated date — `AutoGeneratedDateColumnType`
123
+
124
+ Maps to field types: `createdAt`, `updatedAt`, `modifiedAt`.
125
+
126
+ - **Select**: `string`
127
+ - **Insert / Update**: `never` — cannot be written
128
+
129
+ Same filter operators and aggregations as `DateColumnType`.
130
+
131
+ ---
132
+
133
+ ## Single select — `SingleSelectColumnType<Options, IsRequired>`
134
+
135
+ - **Select / Insert / Update**: `Options[number]` — one of the allowed string values
136
+ - `Options` is the `typeof YourTableFieldOptions` const generated by the CLI
137
+
138
+ **Available filter operators**
139
+
140
+ | Operator | Value type |
141
+ |----------|-----------|
142
+ | `=` | `Options[number]` |
143
+ | `hasAnyOf` | `Options[number][]` |
144
+ | `hasAllOf` | `Options[number][]` |
145
+ | `isExactly` | `Options[number][]` |
146
+ | `hasNoneOf` | `Options[number][]` |
147
+ | `contains` | `string` |
148
+ | `doesNotContain` | `string` |
149
+ | `isEmpty` | _(no value)_ |
150
+ | `isNotEmpty` | _(no value)_ |
151
+
152
+ ---
153
+
154
+ ## Multi select — `MultiSelectColumnType<Options, IsRequired>`
155
+
156
+ - **Select / Insert / Update**: `Options[number][]` — array of allowed string values
157
+
158
+ Same filter operators as `SingleSelectColumnType`.
159
+
160
+ ---
161
+
162
+ ## Link — `LinkColumnType<LinkedTable, IsRequired>`
163
+
164
+ Represents a relationship to another table.
165
+
166
+ - **Select**: `object` (resolved through `.with()`)
167
+ - **Insert**: `number[]` — array of IDs to link
168
+ - **Update**: `number[] | { newIds: number[]; deletedIds: number[] }`
169
+
170
+ **Available filter operators**
171
+
172
+ | Operator | Value type |
173
+ |----------|-----------|
174
+ | `=` | `number` |
175
+ | `hasAnyOf` | `number[]` |
176
+ | `hasAllOf` | `number[]` |
177
+ | `isExactly` | `number[]` |
178
+ | `hasNoneOf` | `number[]` |
179
+ | `contains` | `string` |
180
+ | `doesNotContain` | `string` |
181
+ | `isEmpty` | _(no value)_ |
182
+ | `isNotEmpty` | _(no value)_ |
183
+
184
+ Cross-table filtering is also supported — see [conditions.md](./conditions.md).
185
+
186
+ ---
187
+
188
+ ## Attachment — `AttachmentColumnType<IsRequired>`
189
+
190
+ - **Select**: `string[]` — array of absolute URLs (automatically resolved by the query builder)
191
+ - **Insert**: `Attachment[] | number[]`
192
+ - **Update**: `Attachment[] | number[] | { newIds: number[]; deletedIds: number[] }`
193
+
194
+ For how to produce `Attachment` instances, see [file-upload.md](./file-upload.md).
195
+
196
+ Same filter operators as `LinkColumnType`.
197
+
198
+ ---
199
+
200
+ ## Search — `SearchColumnType`
201
+
202
+ Read-only computed field.
203
+
204
+ - **Select**: `string`
205
+ - **Insert / Update**: `string`
206
+
207
+ **Available filter operators**
208
+
209
+ | Operator | Value type |
210
+ |----------|-----------|
211
+ | `search` | `string` |
212
+ | `contains` | `string` |
213
+ | `containsStrict` | `string` |
214
+ | `isEmpty` | _(no value)_ |
215
+ | `isNotEmpty` | _(no value)_ |
@@ -0,0 +1,85 @@
1
+ # File Upload (Attachments)
2
+
3
+ Attachment fields store files. The upload and the record write are two separate steps.
4
+
5
+ ## Step 1 — upload the files
6
+
7
+ Call `qb.uploadAttachments()` with an array of `{ file, name }` objects. This sends the files to the TaylorDB media service and returns an array of `Attachment` instances.
8
+
9
+ ```ts
10
+ const attachments = await qb.uploadAttachments([
11
+ { file: myBlob, name: 'invoice.pdf' },
12
+ { file: anotherBlob, name: 'receipt.png' },
13
+ ]);
14
+ ```
15
+
16
+ | Parameter | Type | Description |
17
+ |-----------|------|-------------|
18
+ | `file` | `Blob` | The file content (a browser `File` extends `Blob`) |
19
+ | `name` | `string` | The filename including extension |
20
+
21
+ Returns `Attachment[]`. Each `Attachment` object holds metadata: `collectionName`, `fileInformation`, `baseId`, `storageAdaptor`, `_id`.
22
+
23
+ The upload uses your `apiKey` and `baseId` from the query builder config for authentication.
24
+
25
+ ## Step 2 — write the record
26
+
27
+ Pass the `Attachment[]` array as the value for any `AttachmentColumnType` field. The builder automatically calls `.toColumnValue()` on each attachment to convert it to the format the API expects.
28
+
29
+ ```ts
30
+ const attachments = await qb.uploadAttachments([
31
+ { file: invoiceBlob, name: 'invoice.pdf' },
32
+ ]);
33
+
34
+ const record = await qb
35
+ .insertInto('expenses')
36
+ .values({
37
+ title: 'Office supplies',
38
+ receipt: attachments, // AttachmentColumnType field
39
+ })
40
+ .returning(['id', 'title'])
41
+ .executeTakeFirst();
42
+ ```
43
+
44
+ The same works for update:
45
+
46
+ ```ts
47
+ const newAttachments = await qb.uploadAttachments([
48
+ { file: newFileBlob, name: 'updated-receipt.pdf' },
49
+ ]);
50
+
51
+ await qb
52
+ .update('expenses')
53
+ .set({ receipt: newAttachments })
54
+ .where('id', '=', 42)
55
+ .execute();
56
+ ```
57
+
58
+ To replace some attachments while keeping others, use the `{ newIds, deletedIds }` form that `AttachmentColumnType` update accepts:
59
+
60
+ ```ts
61
+ await qb
62
+ .update('expenses')
63
+ .set({
64
+ receipt: {
65
+ newIds: [uploadedAttachment._id], // IDs of newly uploaded files
66
+ deletedIds: [123], // DB IDs of files to remove
67
+ },
68
+ })
69
+ .where('id', '=', 42)
70
+ .execute();
71
+ ```
72
+
73
+ ## Reading attachments
74
+
75
+ When you select an attachment field the query builder automatically converts the raw storage path to a full absolute URL.
76
+
77
+ ```ts
78
+ const record = await qb
79
+ .selectFrom('expenses')
80
+ .select(['id', 'receipt'])
81
+ .executeTakeFirst();
82
+
83
+ // record.receipt is string[] — each entry is a full URL:
84
+ // 'https://media.taylordb.ai/files/...'
85
+ ```
package/docs/insert.md ADDED
@@ -0,0 +1,78 @@
1
+ # Insert
2
+
3
+ Use `insertInto` to create one or more records in a table.
4
+
5
+ ## Basic usage
6
+
7
+ ```ts
8
+ const record = await qb
9
+ .insertInto('users')
10
+ .values({ name: 'Alice', email: 'alice@example.com' })
11
+ .executeTakeFirst();
12
+ // returns { id: number } by default
13
+ ```
14
+
15
+ ## Methods
16
+
17
+ ### `.values(record | record[])`
18
+
19
+ Pass a single object or an array of objects. Each key must match a field name on the table. Required fields (typed as `IsRequired = true`) will cause a TypeScript error if omitted.
20
+
21
+ ```ts
22
+ // single
23
+ .values({ name: 'Alice' })
24
+
25
+ // batch
26
+ .values([{ name: 'Alice' }, { name: 'Bob' }])
27
+ ```
28
+
29
+ Attachment fields expect an `Attachment[]` value. Upload first with `qb.uploadAttachments()`, then pass the returned instances directly — the builder converts them to the correct column format automatically. See [file-upload.md](./file-upload.md).
30
+
31
+ Link fields expect `number[]` — the IDs of the records you want to associate.
32
+
33
+ ### `.returning(fields[])`
34
+
35
+ Specify which fields to include in the response. Without `.returning()` only `id` is returned.
36
+
37
+ ```ts
38
+ const user = await qb
39
+ .insertInto('users')
40
+ .values({ name: 'Alice' })
41
+ .returning(['id', 'name', 'email'])
42
+ .executeTakeFirst();
43
+ // user: { id: number; name: string; email: string }
44
+ ```
45
+
46
+ Only non-link fields can be passed to `.returning()`. To include related records, use an `UpdateQueryBuilder` after the insert.
47
+
48
+ ### `.execute()`
49
+
50
+ Runs the insert and returns an array of records matching the `.returning()` selection.
51
+
52
+ ```ts
53
+ const users = await qb
54
+ .insertInto('users')
55
+ .values([{ name: 'Alice' }, { name: 'Bob' }])
56
+ .returning(['id', 'name'])
57
+ .execute();
58
+ // users: Array<{ id: number; name: string }>
59
+ ```
60
+
61
+ ### `.executeTakeFirst()`
62
+
63
+ Same as `.execute()` but returns the first result or `null`.
64
+
65
+ ```ts
66
+ const user = await qb
67
+ .insertInto('users')
68
+ .values({ name: 'Alice' })
69
+ .returning(['id', 'name'])
70
+ .executeTakeFirst();
71
+ // user: { id: number; name: string } | null
72
+ ```
73
+
74
+ ## Restrictions
75
+
76
+ - `attachmentTable` and `collaborators` are blacklisted and cannot be inserted into.
77
+ - Auto-generated fields (`id`, `createdAt`, `updatedAt`, `autoNumber`) have insert type `never` — passing them causes a TypeScript error.
78
+ - Link fields on insert accept only `number[]` (adding IDs). To selectively add/remove links on an existing record use `.update().set({ field: { newIds: [], deletedIds: [] } })`.
@@ -0,0 +1,88 @@
1
+ # Pagination
2
+
3
+ Three methods control how many records are returned and from where in the result set.
4
+
5
+ ## `.limit(count)`
6
+
7
+ Sets the maximum number of records to return.
8
+
9
+ ```ts
10
+ const users = await qb
11
+ .selectFrom('users')
12
+ .selectAll()
13
+ .limit(20)
14
+ .execute();
15
+ ```
16
+
17
+ ## `.offset(count)`
18
+
19
+ Skips the first `count` records before returning results.
20
+
21
+ ```ts
22
+ const users = await qb
23
+ .selectFrom('users')
24
+ .selectAll()
25
+ .offset(40)
26
+ .execute();
27
+ ```
28
+
29
+ ## `.paginate(page, limit)`
30
+
31
+ Convenience wrapper around `.offset()` and `.limit()`. Pages are 1-indexed.
32
+
33
+ ```ts
34
+ .paginate(page, limit)
35
+ // equivalent to .offset((page - 1) * limit).limit(limit)
36
+ ```
37
+
38
+ ```ts
39
+ const page3 = await qb
40
+ .selectFrom('users')
41
+ .selectAll()
42
+ .orderBy('name', 'asc')
43
+ .paginate(3, 25) // rows 51–75
44
+ .execute();
45
+ ```
46
+
47
+ ## `.count()`
48
+
49
+ Returns the total number of records that match the current filters, ignoring `.limit()` and `.offset()`. Use this to calculate total pages.
50
+
51
+ ```ts
52
+ const total = await qb
53
+ .selectFrom('users')
54
+ .where('role', '=', 'admin')
55
+ .count();
56
+
57
+ const totalPages = Math.ceil(total / pageSize);
58
+ ```
59
+
60
+ `count()` can only be called on a root `selectFrom` query, not on a sub-query inside `.with()`.
61
+
62
+ ## Pagination on sub-queries
63
+
64
+ `.limit()` and `.offset()` are available inside the `.with()` object form to limit how many related records are returned per parent record.
65
+
66
+ ```ts
67
+ const users = await qb
68
+ .selectFrom('users')
69
+ .select(['id', 'name'])
70
+ .with({
71
+ posts: qb => qb.select(['id', 'title']).limit(3),
72
+ })
73
+ .execute();
74
+ // at most 3 posts per user
75
+ ```
76
+
77
+ ## Pagination on aggregation queries
78
+
79
+ All three methods — `.limit()`, `.offset()`, `.paginate()` — are available on `AggregationQueryBuilder` as well.
80
+
81
+ ```ts
82
+ const stats = await qb
83
+ .aggregateFrom('orders')
84
+ .groupBy('status')
85
+ .metrics({ total: count('id') })
86
+ .paginate(1, 10)
87
+ .execute();
88
+ ```
@@ -0,0 +1,75 @@
1
+ # Relationships (Loading Linked Records)
2
+
3
+ Use `.with()` to load related records through link fields. It is available on `selectFrom` queries.
4
+
5
+ ## What can be loaded
6
+
7
+ Only fields typed as `LinkColumnType` can be used with `.with()`. These are the fields the CLI generates when a TaylorDB field is of type `link`, `collaborators`, or `modifiedBy`.
8
+
9
+ Fields that use `AttachmentColumnType` are **not** loaded via `.with()` — attachment URLs are resolved automatically when the attachment field is included in `.select()` or `.selectAll()`.
10
+
11
+ ## Simple form — load all fields
12
+
13
+ Pass a relation name as a string or an array of relation names. All fields of the linked table are fetched.
14
+
15
+ ```ts
16
+ const users = await qb
17
+ .selectFrom('users')
18
+ .select(['id', 'name'])
19
+ .with('posts')
20
+ .execute();
21
+ // each user: { id, name, posts: Array<{ id, title, ... all post fields }> }
22
+ ```
23
+
24
+ Multiple relations at once:
25
+
26
+ ```ts
27
+ .with(['posts', 'team'])
28
+ ```
29
+
30
+ ## Object form — configure the sub-query
31
+
32
+ Pass an object where each key is a relation name and the value is a callback that receives a `QueryBuilder` scoped to the linked table. You can call `.select()`, `.where()`, `.orderBy()`, `.limit()`, `.offset()`, and further `.with()` on the sub-builder.
33
+
34
+ ```ts
35
+ const users = await qb
36
+ .selectFrom('users')
37
+ .select(['id', 'name'])
38
+ .with({
39
+ posts: qb =>
40
+ qb
41
+ .select(['id', 'title', 'createdAt'])
42
+ .where('isPublished', '=', true)
43
+ .orderBy('createdAt', 'desc')
44
+ .limit(5),
45
+ })
46
+ .execute();
47
+ // each user: { id, name, posts: Array<{ id, title, createdAt }> }
48
+ // only published posts, newest first, max 5 per user
49
+ ```
50
+
51
+ ## Nested relationships
52
+
53
+ You can nest `.with()` inside a sub-query to load relationships of related records.
54
+
55
+ ```ts
56
+ const users = await qb
57
+ .selectFrom('users')
58
+ .select(['id', 'name'])
59
+ .with({
60
+ posts: qb =>
61
+ qb
62
+ .select(['id', 'title'])
63
+ .with({
64
+ comments: cqb => cqb.select(['id', 'body']),
65
+ }),
66
+ })
67
+ .execute();
68
+ ```
69
+
70
+ ## What cannot be loaded
71
+
72
+ - **Attachment fields** (`AttachmentColumnType`) are not relationship fields and cannot be used with `.with()`. Select them directly — their URLs are resolved automatically.
73
+ - **Aggregated counts** of related records are not supported through `.with()`. Use `aggregateFrom` with a cross-table filter for that instead.
74
+ - **`returning()` on insert** does not support loading relationships. Perform a follow-up `selectFrom` query if you need related data after an insert.
75
+ - **`update` and `deleteFrom`** do not support `.with()`.
@@ -0,0 +1,63 @@
1
+ # Sorting
2
+
3
+ Use `.orderBy()` to control the order of results returned by a select or aggregation query.
4
+
5
+ ## `.orderBy(field, direction?)`
6
+
7
+ | Parameter | Type | Default | Description |
8
+ |-----------|------|---------|-------------|
9
+ | `field` | `keyof Table` | — | The field to sort by. TypeScript will autocomplete valid field names for the table. |
10
+ | `direction` | `'asc' \| 'desc'` | `'asc'` | Sort direction. |
11
+
12
+ ```ts
13
+ const users = await qb
14
+ .selectFrom('users')
15
+ .select(['id', 'name', 'createdAt'])
16
+ .orderBy('name', 'asc')
17
+ .execute();
18
+ ```
19
+
20
+ ## Multiple sort fields
21
+
22
+ Chain `.orderBy()` multiple times. The sorts are applied in the order they are added.
23
+
24
+ ```ts
25
+ const users = await qb
26
+ .selectFrom('users')
27
+ .selectAll()
28
+ .orderBy('role', 'asc')
29
+ .orderBy('name', 'asc')
30
+ .execute();
31
+ // sorted by role ascending, then by name ascending within each role
32
+ ```
33
+
34
+ ## With pagination
35
+
36
+ Sorting composes cleanly with `.limit()`, `.offset()`, and `.paginate()`.
37
+
38
+ ```ts
39
+ const page2 = await qb
40
+ .selectFrom('posts')
41
+ .select(['id', 'title', 'createdAt'])
42
+ .orderBy('createdAt', 'desc')
43
+ .paginate(2, 20)
44
+ .execute();
45
+ ```
46
+
47
+ ## On aggregation queries
48
+
49
+ `.orderBy()` works the same way on `AggregationQueryBuilder`.
50
+
51
+ ```ts
52
+ const stats = await qb
53
+ .aggregateFrom('orders')
54
+ .groupBy('status')
55
+ .metrics({ total: count('id') })
56
+ .orderBy('status', 'asc')
57
+ .execute();
58
+ ```
59
+
60
+ ## Limitations
61
+
62
+ - Sorting is only available on root select queries and aggregation queries. It is not available when configuring a sub-query inside `.with({ ... })`.
63
+ - You cannot sort by a link field directly. Sort by scalar fields on the table.
package/llm.txt ADDED
@@ -0,0 +1,29 @@
1
+ # @taylordb/query-builder — LLM Documentation Index
2
+
3
+ This package is the TypeScript query builder for TaylorDB. It provides a fluent, type-safe API for reading and writing data. Use `createQueryBuilder<TaylorDatabase>(config)` as the entry point — the `TaylorDatabase` type is generated by `@taylordb/cli`.
4
+
5
+ ## Documentation
6
+
7
+ - [Field Types](./docs/field-types.md)
8
+ All column types (`TextColumnType`, `NumberColumnType`, `DateColumnType`, `CheckboxColumnType`, `SingleSelectColumnType`, `MultiSelectColumnType`, `LinkColumnType`, `AttachmentColumnType`, `SearchColumnType`, auto-generated variants). What value each type accepts on insert/update, what is returned on select, and which filter operators are available per type.
9
+
10
+ - [Insert](./docs/insert.md)
11
+ How to create records using `insertInto().values().returning().execute()`. Single and batch inserts, the default return value, how attachment and link fields are handled on insert, and which tables cannot be inserted into.
12
+
13
+ - [File Upload](./docs/file-upload.md)
14
+ How to upload files with `qb.uploadAttachments()`, how to attach the resulting `Attachment` instances to a record on insert or update, how to add/remove individual attachments on update, and how attachment URLs are resolved when reading.
15
+
16
+ - [Current User](./docs/current-user.md)
17
+ How to retrieve the authenticated user's profile with `qb.auth.getUser()`, what it returns, and what is required for it to work.
18
+
19
+ - [Sorting](./docs/sorting.md)
20
+ How to sort results with `.orderBy(field, direction)`, chaining multiple sort fields, using sorting with pagination, and sorting on aggregation queries.
21
+
22
+ - [Conditions](./docs/conditions.md)
23
+ How to filter records with `.where()` and `.orWhere()`. Simple conditions, AND/OR chaining, grouped/nested conditions via callbacks, cross-table filtering on link fields, isEmpty/isNotEmpty, checkbox and date filter values.
24
+
25
+ - [Relationships](./docs/relationships.md)
26
+ How to load linked records with `.with()`. Simple string/array form, object form with sub-query configuration, nesting relationships, and what cannot be loaded (attachments, aggregated counts, relationships inside insert returning).
27
+
28
+ - [Pagination](./docs/pagination.md)
29
+ How to paginate with `.limit()`, `.offset()`, `.paginate(page, limit)`, how to get the total record count with `.count()`, pagination on sub-queries, and pagination on aggregation queries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taylordb/query-builder",
3
- "version": "0.16.2",
3
+ "version": "0.16.5",
4
4
  "description": "A type-safe query builder for TaylorDB",
5
5
  "private": false,
6
6
  "main": "./dist/cjs/index.js",
@@ -14,7 +14,9 @@
14
14
  }
15
15
  },
16
16
  "files": [
17
- "dist"
17
+ "dist",
18
+ "docs",
19
+ "llm.txt"
18
20
  ],
19
21
  "keywords": [
20
22
  "query-builder",
@@ -29,7 +31,7 @@
29
31
  "lodash": "^4.17.21",
30
32
  "socket.io-client": "^4.8.1",
31
33
  "zod": "^4.1.12",
32
- "@taylordb/shared": "0.16.2"
34
+ "@taylordb/shared": "0.16.5"
33
35
  },
34
36
  "devDependencies": {
35
37
  "@types/eventemitter3": "^2.0.4",