@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.
- package/docs/conditions.md +111 -0
- package/docs/current-user.md +50 -0
- package/docs/field-types.md +215 -0
- package/docs/file-upload.md +85 -0
- package/docs/insert.md +78 -0
- package/docs/pagination.md +88 -0
- package/docs/relationships.md +75 -0
- package/docs/sorting.md +63 -0
- package/llm.txt +29 -0
- package/package.json +5 -3
|
@@ -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()`.
|
package/docs/sorting.md
ADDED
|
@@ -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.
|
|
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.
|
|
34
|
+
"@taylordb/shared": "0.16.5"
|
|
33
35
|
},
|
|
34
36
|
"devDependencies": {
|
|
35
37
|
"@types/eventemitter3": "^2.0.4",
|