litedbmodel 0.22.2 → 1.0.0
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/README.md +114 -79
- package/dist/Column.d.ts +17 -9
- package/dist/Column.d.ts.map +1 -1
- package/dist/Column.js +39 -4
- package/dist/Column.js.map +1 -1
- package/dist/DBModel.d.ts +39 -16
- package/dist/DBModel.d.ts.map +1 -1
- package/dist/DBModel.js +69 -41
- package/dist/DBModel.js.map +1 -1
- package/dist/SqlFragment.d.ts +118 -0
- package/dist/SqlFragment.d.ts.map +1 -0
- package/dist/SqlFragment.js +237 -0
- package/dist/SqlFragment.js.map +1 -0
- package/dist/decorators.d.ts.map +1 -1
- package/dist/decorators.js +5 -1
- package/dist/decorators.js.map +1 -1
- package/dist/drivers/PostgresSqlBuilder.d.ts.map +1 -1
- package/dist/drivers/PostgresSqlBuilder.js +2 -0
- package/dist/drivers/PostgresSqlBuilder.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/docs/COMPARISON.md +25 -20
- package/docs/sql-tagged-template.md +259 -0
- package/package.json +1 -1
package/docs/COMPARISON.md
CHANGED
|
@@ -12,7 +12,7 @@ A detailed comparison of litedbmodel with popular TypeScript/JavaScript ORMs.
|
|
|
12
12
|
| | Migrations | Manual | Manual | Kit (optional) | Built-in | Built-in | Built-in | Built-in | Knex |
|
|
13
13
|
| **Query API** | Primary query style | Tuple array | Fluent QB | Fluent QB | QB / Find | Fluent client | QB / EM | Fluent | Fluent |
|
|
14
14
|
| | Raw SQL escape hatch† | ✅ `query()` | ⚠️ `sql` | ⚠️ `sql` | ⚠️ `query()` | ⚠️ `$queryRaw` | ⚠️ `execute()` | ⚠️ `query()` | ⚠️ `raw()` |
|
|
15
|
-
| **Type Safety**†† | Compile-time type safety |
|
|
15
|
+
| **Type Safety**†† | Compile-time type safety | ✅ | ✅ | ✅ | ⚠️ partial | ✅ | ⚠️ partial | ❌ runtime-heavy | ⚠️ partial |
|
|
16
16
|
| **Column Refs**‡ | How columns are referenced | **Symbols** | String literals | Column objects | Strings/decorators | Object keys | Strings | Strings | Strings |
|
|
17
17
|
| **IDE Support**§ | Refactoring safety (IDE) | ✅ Full | ❌ | ⚠️ Partial | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
18
18
|
| **Updates** | Declarative partial update | ✅ **SKIP** | ❌ manual | ❌ manual | ❌ manual | ⚠️ `undefined` | ❌ manual | ❌ manual | ❌ manual |
|
|
@@ -52,7 +52,8 @@ Whether the *tool's standard relation API* fetches nested relations in a single
|
|
|
52
52
|
|
|
53
53
|
**†† Type safety (litedbmodel):**
|
|
54
54
|
- `[Column, value]` tuples: **Compile-time** type checking via Column symbols
|
|
55
|
-
-
|
|
55
|
+
- ``sql`` tagged template conditions (``[sql`${Col} > ?`, val]``): **Compile-time** type checking — the ``sql`` tag preserves the Column's value type
|
|
56
|
+
- ESLint plugin provides additional SQL syntax and cross-model validation
|
|
56
57
|
|
|
57
58
|
**\*\* DB portability:**
|
|
58
59
|
- **Multi-DB** = databases the tool can talk to
|
|
@@ -106,6 +107,7 @@ class UserModel extends DBModel {
|
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
// Migrations: Manual SQL files
|
|
110
|
+
// Code generation: litedbmodel-gen generates @column() from schema.sql (optional)
|
|
109
111
|
```
|
|
110
112
|
|
|
111
113
|
### Prisma
|
|
@@ -156,7 +158,7 @@ drizzle-kit generate:pg
|
|
|
156
158
|
|---------|-------------|--------|---------|---------|--------|
|
|
157
159
|
| **Schema Format** | TS Decorators | Prisma DSL | TS Decorators | TS Schema | TS Interface |
|
|
158
160
|
| **Migration Tool** | Manual | Built-in | Built-in | drizzle-kit | Manual |
|
|
159
|
-
| **Auto-Generate** |
|
|
161
|
+
| **Auto-Generate** | ⚠️ [litedbmodel-gen](https://www.npmjs.com/package/litedbmodel-gen) | ✅ | ✅ | ✅ | ❌ |
|
|
160
162
|
| **Reversible** | Manual | ✅ | ✅ | ⚠️ | Manual |
|
|
161
163
|
|
|
162
164
|
---
|
|
@@ -175,7 +177,7 @@ const users = await User.find([
|
|
|
175
177
|
[User.is_active, true],
|
|
176
178
|
User.or(
|
|
177
179
|
[[User.role, 'admin']],
|
|
178
|
-
[[User.role, 'moderator'], [`${User.level} >= ?`, 5]],
|
|
180
|
+
[[User.role, 'moderator'], [sql`${User.level} >= ?`, 5]],
|
|
179
181
|
),
|
|
180
182
|
]);
|
|
181
183
|
|
|
@@ -352,7 +354,7 @@ const users = await User.query(`
|
|
|
352
354
|
| Scenario | litedbmodel | Query Builder ORMs |
|
|
353
355
|
|----------|-------------|-------------------|
|
|
354
356
|
| **Simple CRUD** | Tuple API: `[[User.name, 'John']]` | Object: `{ name: 'John' }` |
|
|
355
|
-
| **Complex WHERE** |
|
|
357
|
+
| **Complex WHERE** | `sql` tag: `` [sql`${User.age} > ?`, 18] `` | DSL methods: `.where('age', '>', 18)` |
|
|
356
358
|
| **JOINs, CTEs** | Real SQL via `query()` or Query-Based Models | Limited DSL or raw escape |
|
|
357
359
|
| **Debugging** | See actual SQL | Translate DSL → SQL mentally |
|
|
358
360
|
| **Learning** | Know SQL = Ready | Learn DSL + SQL |
|
|
@@ -369,9 +371,9 @@ const users = await User.query(`
|
|
|
369
371
|
|
|
370
372
|
## 5. Type Safety
|
|
371
373
|
|
|
372
|
-
### litedbmodel - Compile-time (Column Symbols)
|
|
374
|
+
### litedbmodel - Compile-time (Column Symbols + `sql` Tag)
|
|
373
375
|
|
|
374
|
-
Type derived from decorators, checked via Column symbol references.
|
|
376
|
+
Type derived from decorators, checked via Column symbol references and `sql` tagged template.
|
|
375
377
|
|
|
376
378
|
```typescript
|
|
377
379
|
// Compile-time validation via Column symbols
|
|
@@ -383,8 +385,9 @@ await User.create([
|
|
|
383
385
|
|
|
384
386
|
// Conditions also type-checked
|
|
385
387
|
await User.find([
|
|
386
|
-
[User.id, 1],
|
|
387
|
-
[`${User.age} > ?`, 18],
|
|
388
|
+
[User.id, 1], // ✅ Compile-time (Column symbol)
|
|
389
|
+
[sql`${User.age} > ?`, 18], // ✅ Compile-time (sql tag preserves Column value type)
|
|
390
|
+
// [sql`${User.age} > ?`, 'x'], // ❌ Compile error (string not assignable to number)
|
|
388
391
|
]);
|
|
389
392
|
```
|
|
390
393
|
|
|
@@ -475,13 +478,14 @@ const users = await db
|
|
|
475
478
|
| Feature | litedbmodel | Prisma | TypeORM | Drizzle | Kysely |
|
|
476
479
|
|---------|-------------|--------|---------|---------|--------|
|
|
477
480
|
| **Type Source** | Decorators → Symbols | .prisma → Generated | Decorators | TS Schema | TS Interface |
|
|
478
|
-
| **Condition Check** | ✅ Full
|
|
481
|
+
| **Condition Check** | ✅ Full | ✅ Full | ⚠️ Partial | ✅ Full | ✅ Full |
|
|
479
482
|
| **Result Types** | ✅ Model types | ✅ Generated | ✅ Entity types | ✅ Inferred | ✅ Inferred |
|
|
480
|
-
| **Compile-Time** |
|
|
483
|
+
| **Compile-Time** | ✅ Full† | ✅ | ⚠️ | ✅ | ✅ |
|
|
481
484
|
|
|
482
|
-
*† litedbmodel's type safety
|
|
485
|
+
*† litedbmodel's compile-time type safety covers both patterns:*
|
|
483
486
|
- *`[Column, value]` tuples: Compile-time type checking via Column symbols*
|
|
484
|
-
-
|
|
487
|
+
- *`[sql\`${Col} > ?\`, val]` tuples: Compile-time type checking — the `sql` tag preserves the Column's value type*
|
|
488
|
+
- *ESLint plugin provides additional SQL syntax validation and cross-model column checking*
|
|
485
489
|
|
|
486
490
|
---
|
|
487
491
|
|
|
@@ -1152,13 +1156,14 @@ For detailed benchmark results comparing litedbmodel with Prisma, Kysely, Drizzl
|
|
|
1152
1156
|
### litedbmodel's Characteristics
|
|
1153
1157
|
|
|
1154
1158
|
1. **Symbol-Based Columns**: IDE "Find References" and "Rename Symbol" work
|
|
1155
|
-
2.
|
|
1156
|
-
3. **
|
|
1157
|
-
4. **
|
|
1158
|
-
5. **
|
|
1159
|
-
6. **
|
|
1160
|
-
7. **
|
|
1161
|
-
8. **
|
|
1159
|
+
2. **`sql` Tagged Template**: Type-safe operator conditions and parameterized queries
|
|
1160
|
+
3. **SKIP Sentinel**: Declarative conditional fields without if-statements
|
|
1161
|
+
4. **Minimal Footprint**: 60KB minified, no binary dependencies
|
|
1162
|
+
5. **Middleware**: Cross-cutting concern support (logging, auth, metrics)
|
|
1163
|
+
6. **Auto N+1 Prevention**: Batch loading enabled automatically for multiple records
|
|
1164
|
+
7. **Composite Key Support**: Full support for composite keys in relations
|
|
1165
|
+
8. **Fixed SQL Parameters**: `ANY()` and `unnest()` keep parameter count constant
|
|
1166
|
+
9. **Readable SQL**: Simple, debuggable queries (no hash aliases or deep nesting)
|
|
1162
1167
|
|
|
1163
1168
|
### Trade-offs
|
|
1164
1169
|
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# `sql` Tagged Template Literal
|
|
2
|
+
|
|
3
|
+
The `sql` tagged template provides type-safe, parameterized SQL fragments for conditions, `withQuery`, `QUERY`, and `execute`.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { sql } from 'litedbmodel';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Conditions
|
|
12
|
+
|
|
13
|
+
### Column Tuples with `sql` Tag (Pattern A)
|
|
14
|
+
|
|
15
|
+
Use `[sql\`...\`, value]` tuples for operator conditions. The `sql` tag extracts the Column reference and preserves its value type, so the tuple's second element is type-checked:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
await User.find([
|
|
19
|
+
[sql`${User.age} > ?`, 18], // ✅ number
|
|
20
|
+
[sql`${User.age} BETWEEN ? AND ?`, [18, 65]], // ✅ number[]
|
|
21
|
+
[sql`${User.name} LIKE ?`, '%test%'], // ✅ string
|
|
22
|
+
[sql`${User.status} IN (?)`, ['active', 'pending']], // ✅ string[]
|
|
23
|
+
sql`${User.deleted_at} IS NULL`, // ✅ no value needed
|
|
24
|
+
]);
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Type errors are caught at compile time:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
[sql`${User.age} > ?`, 'hello'] // ❌ Type error: string is not assignable to number
|
|
31
|
+
[sql`${User.name} LIKE ?`, 42] // ❌ Type error: number is not assignable to string
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Embedded Values (Pattern B)
|
|
35
|
+
|
|
36
|
+
Values can be interpolated directly in the template. The `sql` tag automatically converts them to `?` placeholders and extracts them as parameters:
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
await User.find([
|
|
40
|
+
sql`${User.age} > ${18}`,
|
|
41
|
+
sql`${User.age} BETWEEN ${18} AND ${65}`,
|
|
42
|
+
sql`${User.name} LIKE ${'%test%'}`,
|
|
43
|
+
sql`${User.status} IN ${['active', 'pending']}`,
|
|
44
|
+
sql`${User.deleted_at} IS NULL`,
|
|
45
|
+
]);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Both patterns produce the same internal representation and can be mixed freely.
|
|
49
|
+
|
|
50
|
+
### SKIP Pattern
|
|
51
|
+
|
|
52
|
+
Both patterns work with `SKIP` for conditional conditions:
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { SKIP } from 'litedbmodel';
|
|
56
|
+
|
|
57
|
+
await User.find([
|
|
58
|
+
[User.deleted, false],
|
|
59
|
+
query.name ? [sql`${User.name} LIKE ?`, `%${query.name}%`] : SKIP,
|
|
60
|
+
[User.status, query.status ?? SKIP],
|
|
61
|
+
]);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### OR Conditions
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
await User.find([
|
|
68
|
+
[sql`${User.age} >= ?`, 18],
|
|
69
|
+
User.or(
|
|
70
|
+
[[sql`${User.role} = ?`, 'admin']],
|
|
71
|
+
[[sql`${User.role} = ?`, 'moderator']],
|
|
72
|
+
),
|
|
73
|
+
]);
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Conditions Builder
|
|
77
|
+
|
|
78
|
+
The `Conditions` class provides an `addSql()` method for dynamic condition building:
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
const where = new Conditions<User>();
|
|
82
|
+
where.add(User.is_active, true);
|
|
83
|
+
where.addSql(sql`${User.age} > ?`, 18);
|
|
84
|
+
where.addSql(sql`${User.deleted_at} IS NULL`); // value-free
|
|
85
|
+
const users = await User.find(where.build());
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## `withQuery` — Parameterized Query-Based Models
|
|
91
|
+
|
|
92
|
+
The `sql` tag eliminates manual `$1`/`$2` placeholder numbering and DB dialect differences. Parameters are co-located with their SQL usage:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
@model('sales_report')
|
|
96
|
+
class SalesReportModel extends DBModel {
|
|
97
|
+
@column() product_id?: number;
|
|
98
|
+
@column() total_revenue?: number;
|
|
99
|
+
|
|
100
|
+
static forPeriod(startDate: string, endDate: string) {
|
|
101
|
+
return this.withQuery(sql`
|
|
102
|
+
SELECT p.id AS product_id, SUM(oi.price) AS total_revenue
|
|
103
|
+
FROM products p
|
|
104
|
+
INNER JOIN order_items oi ON p.id = oi.product_id
|
|
105
|
+
INNER JOIN orders o ON oi.order_id = o.id
|
|
106
|
+
WHERE o.status = 'completed'
|
|
107
|
+
AND o.created_at >= ${startDate}
|
|
108
|
+
AND o.created_at < ${endDate}
|
|
109
|
+
GROUP BY p.id
|
|
110
|
+
`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const Q1Report = SalesReport.forPeriod('2024-01-01', '2024-04-01');
|
|
115
|
+
const results = await Q1Report.find([
|
|
116
|
+
[sql`${SalesReport.total_revenue} > ?`, 10000],
|
|
117
|
+
]);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The `sql` tag always uses `?` placeholders internally. The dialect conversion (`?` → `$1`/`$2` for PostgreSQL) is handled transparently at execution time.
|
|
121
|
+
|
|
122
|
+
The `{ sql, params }` object form is still supported for backward compatibility.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## `QUERY` — Static Query-Based Models
|
|
127
|
+
|
|
128
|
+
Use the `sql` tag for `QUERY` to get Column and table name references that are refactoring-safe:
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
@model('user_activity')
|
|
132
|
+
class UserActivityModel extends DBModel {
|
|
133
|
+
@column() user_id?: number;
|
|
134
|
+
@column() user_name?: string;
|
|
135
|
+
@column() total_posts?: number;
|
|
136
|
+
|
|
137
|
+
static QUERY = sql`
|
|
138
|
+
SELECT
|
|
139
|
+
${User.id} AS user_id,
|
|
140
|
+
${User.name} AS user_name,
|
|
141
|
+
COUNT(${Post.id}) AS total_posts
|
|
142
|
+
FROM ${User.TABLE_NAME}
|
|
143
|
+
LEFT JOIN ${Post.TABLE_NAME} ON ${User.id} = ${Post.user_id}
|
|
144
|
+
GROUP BY ${User.id}, ${User.name}
|
|
145
|
+
`;
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
When only Column references and `TABLE_NAME` objects are interpolated (no runtime values), the `sql` tag embeds them directly in the SQL string with no parameters.
|
|
150
|
+
|
|
151
|
+
String `QUERY` is still supported for backward compatibility.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## `execute` — Raw SQL Execution
|
|
156
|
+
|
|
157
|
+
`DBModel.execute()` accepts `sql` tagged templates:
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
await DBModel.execute(sql`SELECT process_daily_aggregates(${targetDate})`);
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
String + params form is still supported:
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
await DBModel.execute('SELECT process_daily_aggregates($1)', [targetDate]);
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Interpolation Rules
|
|
172
|
+
|
|
173
|
+
The `sql` tag processes interpolated values according to their type:
|
|
174
|
+
|
|
175
|
+
| Interpolated Value | Processing | Example Output |
|
|
176
|
+
|---|---|---|
|
|
177
|
+
| `Column` | Embedded as column name | `age`, `name` |
|
|
178
|
+
| `{ TABLE_NAME }` | Embedded as table name | `users`, `posts` |
|
|
179
|
+
| `sql.raw(str)` | Embedded as raw SQL (no parameterization) | `ASC`, `DISTINCT` |
|
|
180
|
+
| `sql.ref(col)` | Embedded as `table.column` | `users.id` |
|
|
181
|
+
| `parentRef(col)` | Embedded as `table.column` | `users.id` |
|
|
182
|
+
| Nested `sql` fragment | SQL expanded, params merged | See below |
|
|
183
|
+
| Array | Expanded to `?, ?, ?` + params extracted | `?, ?, ?` |
|
|
184
|
+
| Primitive / Date | Replaced with `?` + param extracted | `?` |
|
|
185
|
+
| `null` / `undefined` | Embedded as `NULL` literal | `NULL` |
|
|
186
|
+
|
|
187
|
+
### `sql.raw()` — Raw SQL Embedding
|
|
188
|
+
|
|
189
|
+
Use for dynamic SQL keywords that should not be parameterized:
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
const direction = sql.raw(isAsc ? 'ASC' : 'DESC');
|
|
193
|
+
const results = await Ranking.query(
|
|
194
|
+
sql`SELECT * FROM rankings ORDER BY score ${direction} LIMIT ${limit}`
|
|
195
|
+
);
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### `sql.ref()` — Table-Qualified Column References
|
|
199
|
+
|
|
200
|
+
Use for JOINs where table disambiguation is needed:
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
sql`SELECT ${sql.ref(User.id)}, ${sql.ref(Post.title)}
|
|
204
|
+
FROM users
|
|
205
|
+
JOIN posts ON ${sql.ref(User.id)} = ${sql.ref(Post.user_id)}`
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
| Interpolation | Output |
|
|
209
|
+
|---|---|
|
|
210
|
+
| `${User.id}` | `id` |
|
|
211
|
+
| `${sql.ref(User.id)}` | `users.id` |
|
|
212
|
+
|
|
213
|
+
### Nested `sql` Fragments
|
|
214
|
+
|
|
215
|
+
A `sql` fragment can be interpolated inside another. The inner SQL is expanded in place and parameters are merged:
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
const sub = sql`SELECT user_id FROM orders WHERE status = ${'paid'}`;
|
|
219
|
+
// → { sql: 'SELECT user_id FROM orders WHERE status = ?', params: ['paid'] }
|
|
220
|
+
|
|
221
|
+
sql`SELECT * FROM users WHERE id IN (${sub}) AND age > ${18}`;
|
|
222
|
+
// → {
|
|
223
|
+
// sql: 'SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE status = ?) AND age > ?',
|
|
224
|
+
// params: ['paid', 18]
|
|
225
|
+
// }
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Return Types
|
|
231
|
+
|
|
232
|
+
The `sql` tag returns different types based on the interpolated values, enabling type-safe usage in different contexts:
|
|
233
|
+
|
|
234
|
+
| Interpolation Pattern | Return Type | Usage |
|
|
235
|
+
|---|---|---|
|
|
236
|
+
| Single `Column` only | `SqlTypedFragment<V, M>` | Condition tuple first element, or value-free condition (IS NULL) |
|
|
237
|
+
| Single `Column` + value(s) | `SqlCondition<M>` | Direct condition element (Pattern B) |
|
|
238
|
+
| Multiple Columns / TABLE_NAMEs | `SqlFragment` | `QUERY`, `withQuery`, `execute` |
|
|
239
|
+
| Mixed (Columns + values) | `SqlFragment` | `withQuery`, `execute` |
|
|
240
|
+
|
|
241
|
+
- `SqlTypedFragment<V, M>` carries the Column's value type `V` and model type `M`, enabling type checking on the tuple's second element.
|
|
242
|
+
- `SqlCondition<M>` carries the model type `M` for model validation.
|
|
243
|
+
- `SqlFragment` is the general type for arbitrary SQL with parameters.
|
|
244
|
+
|
|
245
|
+
All three types have `sql: string` and `params: readonly unknown[]` properties.
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Backward Compatibility
|
|
250
|
+
|
|
251
|
+
The `sql` tag is a purely additive feature. All existing patterns continue to work:
|
|
252
|
+
|
|
253
|
+
| Existing Pattern | Status |
|
|
254
|
+
|---|---|
|
|
255
|
+
| `[Column, value]` tuples | Recommended for equality conditions |
|
|
256
|
+
| `[string, value]` tuples (template literals) | Supported (backward compatible) |
|
|
257
|
+
| `{ sql, params }` for `withQuery` | Supported (backward compatible) |
|
|
258
|
+
| `string` for `QUERY` | Supported (backward compatible) |
|
|
259
|
+
| `execute(sql, params)` | Supported (backward compatible) |
|