orange-orm 4.10.0-beta.1 β†’ 5.0.0-beta.1

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 CHANGED
@@ -51,7 +51,7 @@ Watch the [tutorial video on YouTube](https://youtu.be/1IwwjPr2lMs)
51
51
  ![Relations diagram](./docs/diagram.svg)
52
52
 
53
53
  <sub>πŸ“„ map.ts</sub>
54
- ```javascript
54
+ ```ts
55
55
  import orange from 'orange-orm';
56
56
 
57
57
  const map = orange.map(x => ({
@@ -88,7 +88,7 @@ const map = orange.map(x => ({
88
88
  street: column('street').string(),
89
89
  postalCode: column('postalCode').string(),
90
90
  postalPlace: column('postalPlace').string(),
91
- countryCode: column('countryCode').string(),
91
+ countryCode: column('countryCode').string().enum(['NO', 'SE', 'DK', 'FI', 'IS', 'DE', 'FR', 'NL', 'ES', 'IT']),
92
92
  }))
93
93
 
94
94
  })).map(x => ({
@@ -106,7 +106,7 @@ export default map;
106
106
  ```
107
107
  <sub>πŸ“„ update.ts</sub>
108
108
 
109
- ```javascript
109
+ ```ts
110
110
  import map from './map';
111
111
  const db = map.sqlite('demo.db');
112
112
 
@@ -127,14 +127,14 @@ async function updateRow() {
127
127
  ```
128
128
  <sub>πŸ“„ filter.ts</sub>
129
129
 
130
- ```javascript
130
+ ```ts
131
131
  import map from './map';
132
132
  const db = map.sqlite('demo.db');
133
133
 
134
134
  getRows();
135
135
 
136
136
  async function getRows() {
137
- const orders = await db.order.getAll({
137
+ const orders = await db.order.getMany({
138
138
  where: x => x.lines.any(line => line.product.contains('broomstick'))
139
139
  .and(x.customer.name.startsWith('Harry')),
140
140
  lines: {
@@ -157,7 +157,8 @@ Each column within your database table is designated by using the <strong><i>col
157
157
  Relationships between tables can also be outlined. By using methods like <strong><i>hasOne</i></strong>, <strong><i>hasMany</i></strong>, and <strong><i>references</i></strong>, you can establish connections that reflect the relationships in your data schema. In the example below, an 'order' is linked to a 'customer' reference, a 'deliveryAddress', and multiple 'lines'. The hasMany and hasOne relations represents ownership - the tables 'deliveryAddress' and 'orderLine' are owned by the 'order' table, and therefore, they contain the 'orderId' column referring to their parent table, which is 'order'. The similar relationship exists between orderLine and package - hence the packages are owned by the orderLine. Conversely, the customer table is independent and can exist without any knowledge of the 'order' table. Therefore we say that the order table <i>references</i> the customer table - necessitating the existence of a 'customerId' column in the 'order' table.</p>
158
158
 
159
159
  <sub>πŸ“„ map.ts</sub>
160
- ```javascript
160
+
161
+ ```ts
161
162
  import orange from 'orange-orm';
162
163
 
163
164
  const map = orange.map(x => ({
@@ -193,7 +194,7 @@ const map = orange.map(x => ({
193
194
  street: column('street').string(),
194
195
  postalCode: column('postalCode').string(),
195
196
  postalPlace: column('postalPlace').string(),
196
- countryCode: column('countryCode').string(),
197
+ countryCode: column('countryCode').string().enum(['NO', 'SE', 'DK', 'FI', 'IS', 'DE', 'FR', 'NL', 'ES', 'IT']),
197
198
  }))
198
199
 
199
200
  })).map(x => ({
@@ -218,7 +219,8 @@ Then, we define a SQL string. This string outlines the structure of our SQLite d
218
219
  Because of a peculiarity in SQLite, which only allows one statement execution at a time, we split this SQL string into separate statements. We do this using the split() method, which breaks up the string at every semicolon.
219
220
 
220
221
  <sub>πŸ“„ init.ts</sub>
221
- ```javascript
222
+
223
+ ```ts
222
224
  import map from './map';
223
225
  const db = map.sqlite('demo.db');
224
226
 
@@ -310,34 +312,13 @@ __Why close ?__
310
312
  In serverless environments (e.g. AWS Lambda, Vercel, Cloudflare Workers) execution contexts are frequently frozen and resumed. Explicitly closing the client or pool ensures that file handles are released promptly and prevents β€œdatabase locked” errors between invocations.
311
313
 
312
314
  __SQLite user-defined functions__
313
- You can register custom SQLite functions on the connection using `db.function(name, fn)`.
314
-
315
- ```javascript
316
- import map from './map';
317
- const db = map.sqlite('demo.db');
318
-
319
- db.function('add_prefix', (text, prefix) => `${prefix}${text}`);
320
-
321
- const rows = await db.query(
322
- "select id, name, add_prefix(name, '[VIP] ') as prefixedName from customer"
323
- );
324
- ```
325
-
326
- If you need the function inside a transaction, register it within the transaction callback to ensure it is available on that connection.
327
-
328
- ```javascript
329
- await db.transaction(async (db) => {
330
- db.function('add_prefix', (text, prefix) => `${prefix}${text}`);
331
- return db.query(
332
- "select id, name, add_prefix(name, '[VIP] ') as prefixedName from customer"
333
- );
334
- });
335
- ```
315
+ You can register custom SQLite functions using `db.function(name, fn)`.
316
+ For full behavior, runtime caveats, and examples, see [SQLite user-defined functions](#sqlite-user-defined-functions).
336
317
 
337
318
  __From the browser__
338
319
  You can securely use Orange from the browser by utilizing the Express plugin, which serves to safeguard sensitive database credentials from exposure at the client level. This technique bypasses the need to transmit raw SQL queries directly from the client to the server. Instead, it logs method calls initiated by the client, which are later replayed and authenticated on the server. This not only reinforces security by preventing the disclosure of raw SQL queries on the client side but also facilitates a smoother operation. Essentially, this method mirrors a traditional REST API, augmented with advanced TypeScript tooling for enhanced functionality. You can read more about it in the section called [In the browser](#user-content-in-the-browser)
339
320
  <sub>πŸ“„ server.ts</sub>
340
- ```javascript
321
+ ```ts
341
322
  import map from './map';
342
323
  import { json } from 'body-parser';
343
324
  import express from 'express';
@@ -354,7 +335,7 @@ express().disable('x-powered-by')
354
335
  ```
355
336
 
356
337
  <sub>πŸ“„ browser.ts</sub>
357
- ```javascript
338
+ ```ts
358
339
  import map from './map';
359
340
 
360
341
  const db = map.http('http://localhost:3000/orange');
@@ -432,7 +413,7 @@ database_id = "<your-guid-for-the-database>"
432
413
  ```
433
414
 
434
415
  <sub>πŸ“„ src/index.ts</sub>
435
- ```javascript
416
+ ```ts
436
417
  import map from './map';
437
418
 
438
419
  export interface Env {
@@ -443,7 +424,7 @@ export interface Env {
443
424
  export default {
444
425
  async fetch(request, env): Promise<Response> {
445
426
  const db = map.d1(env.DB);
446
- const customers = await db.customer.getAll();
427
+ const customers = await db.customer.getMany();
447
428
  return Response.json(customers);
448
429
  },
449
430
  } satisfies ExportedHandler<Env>;
@@ -611,7 +592,7 @@ const db = map.sqlite('demo.db');
611
592
  getRows();
612
593
 
613
594
  async function getRows() {
614
- const orders = await db.order.getAll({
595
+ const orders = await db.order.getMany({
615
596
  customer: true,
616
597
  deliveryAddress: true,
617
598
  lines: {
@@ -630,7 +611,7 @@ const db = map.sqlite('demo.db');
630
611
  getRows();
631
612
 
632
613
  async function getRows() {
633
- const orders = await db.order.getAll({
614
+ const orders = await db.order.getMany({
634
615
  offset: 1,
635
616
  orderBy: ['orderDate desc', 'id'],
636
617
  limit: 10,
@@ -662,7 +643,7 @@ const db = map.sqlite('demo.db');
662
643
  getRows();
663
644
 
664
645
  async function getRows() {
665
- const orders = await db.order.getAll({
646
+ const orders = await db.order.getMany({
666
647
  numberOfLines: x => x.count(x => x.lines.id),
667
648
  totalAmount: x => x.sum(x => lines.amount),
668
649
  balance: x => x.customer.balance
@@ -679,7 +660,7 @@ const db = map.sqlite('demo.db');
679
660
  getRows();
680
661
 
681
662
  async function getRows() {
682
- const orders = await db.order.getAll({
663
+ const orders = await db.order.getMany({
683
664
  where: x => x.lines.any(line => line.product.contains('i'))
684
665
  .and(x.customer.balance.greaterThan(180)),
685
666
  customer: true,
@@ -688,14 +669,13 @@ async function getRows() {
688
669
  });
689
670
  }
690
671
  ```
691
- You can also use the alternative syntax for the `where-filter`. This way, the filter can be constructed independently from the fetching strategy. Keep in mind that you must use the `getMany` method instead of the `getAll` method.
692
- It is also possible to combine `where-filter` with the independent filter when using the `getMany` method.
672
+ You can also use the alternative syntax for the `where` filter. This way, the filter can be constructed independently from the fetching strategy and passed in via the `where` clause.
693
673
  ```javascript
694
674
  async function getRows() {
695
675
  const filter = db.order.lines.any(line => line.product.contains('i'))
696
676
  .and(db.order.customer.balance.greaterThan(180));
697
- const orders = await db.order.getMany(filter, {
698
- //where: x => ... can be combined as well
677
+ const orders = await db.order.getMany({
678
+ where: filter,
699
679
  customer: true,
700
680
  deliveryAddress: true,
701
681
  lines: true
@@ -810,7 +790,7 @@ const db = map.sqlite('demo.db');
810
790
  update();
811
791
 
812
792
  async function update() {
813
- let orders = await db.order.getAll({
793
+ let orders = await db.order.getMany({
814
794
  orderBy: 'id',
815
795
  lines: true,
816
796
  deliveryAddress: true,
@@ -1043,7 +1023,7 @@ const db = map.sqlite('demo.db');
1043
1023
  updateInsertDelete();
1044
1024
 
1045
1025
  async function updateInsertDelete() {
1046
- const orders = await db.order.getAll({
1026
+ const orders = await db.order.getMany({
1047
1027
  customer: true,
1048
1028
  deliveryAddress: true,
1049
1029
  lines: true
@@ -1090,7 +1070,7 @@ const db = map.sqlite('demo.db');
1090
1070
  deleteRows();
1091
1071
 
1092
1072
  async function deleteRows() {
1093
- let orders = await db.order.getAll({
1073
+ let orders = await db.order.getMany({
1094
1074
  where: x => x.customer.name.eq('George')
1095
1075
  });
1096
1076
 
@@ -1106,7 +1086,7 @@ const db = map.sqlite('demo.db');
1106
1086
  deleteRows();
1107
1087
 
1108
1088
  async function deleteRows() {
1109
- let orders = await db.order.getAll({
1089
+ let orders = await db.order.getMany({
1110
1090
  where: x => x.deliveryAddress.name.eq('George'),
1111
1091
  customer: true,
1112
1092
  deliveryAddress: true,
@@ -1174,7 +1154,7 @@ Raw sql queries, raw sql filters and transactions are disabled at the http clien
1174
1154
 
1175
1155
  <sub>πŸ“„ server.ts</sub>
1176
1156
 
1177
- ```javascript
1157
+ ```ts
1178
1158
  import map from './map';
1179
1159
  import { json } from 'body-parser';
1180
1160
  import express from 'express';
@@ -1191,7 +1171,7 @@ express().disable('x-powered-by')
1191
1171
  ```
1192
1172
 
1193
1173
  <sub>πŸ“„ browser.ts</sub>
1194
- ```javascript
1174
+ ```ts
1195
1175
  import map from './map';
1196
1176
 
1197
1177
  const db = map.http('http://localhost:3000/orange');
@@ -1222,7 +1202,7 @@ One notable side effect compared to the previous example, is that only the order
1222
1202
 
1223
1203
  <sub>πŸ“„ server.ts</sub>
1224
1204
 
1225
- ```javascript
1205
+ ```ts
1226
1206
  import map from './map';
1227
1207
  import { json } from 'body-parser';
1228
1208
  import express from 'express';
@@ -1257,7 +1237,7 @@ function validateToken(req, res, next) {
1257
1237
 
1258
1238
  <sub>πŸ“„ browser.ts</sub>
1259
1239
 
1260
- ```javascript
1240
+ ```ts
1261
1241
  import map from './map';
1262
1242
 
1263
1243
  const db = map.http('http://localhost:3000/orange');
@@ -1301,7 +1281,7 @@ async function updateRows() {
1301
1281
 
1302
1282
  ```
1303
1283
 
1304
- __Row Level Security (Postgres)__
1284
+ __Row Level Security__
1305
1285
  You can enforce tenant isolation at the database level by combining Postgres RLS with Express hooks. The example below mirrors the β€œInterceptors and base filter” style by putting the tenant id in a (fake) token on the client, then extracting it on the server and setting it inside the transaction. This is convenient for a demo because we can seed data and prove rows are filtered. In a real application you must validate signatures and derive tenant id from a trusted identity source, not from arbitrary client input.
1306
1286
 
1307
1287
  <sub>πŸ“„ setup.sql</sub>
@@ -1329,7 +1309,7 @@ insert into tenant_data (tenant_id, value) values
1329
1309
 
1330
1310
  <sub>πŸ“„ server.ts</sub>
1331
1311
 
1332
- ```javascript
1312
+ ```ts
1333
1313
  import map from './map';
1334
1314
  import { json } from 'body-parser';
1335
1315
  import express from 'express';
@@ -1344,6 +1324,7 @@ express().disable('x-powered-by')
1344
1324
  .use('/orange', db.express({
1345
1325
  hooks: {
1346
1326
  transaction: {
1327
+ //beforeBegin: async (db, req) => ...,
1347
1328
  afterBegin: async (db, req) => {
1348
1329
  const tenantId = Number.parseInt(String(req.user?.tenantId ?? ''), 10);
1349
1330
  if (!Number.isFinite(tenantId)) throw new Error('Missing tenant id');
@@ -1352,7 +1333,12 @@ express().disable('x-powered-by')
1352
1333
  sql: 'select set_config(\'app.tenant_id\', ?, true)',
1353
1334
  parameters: [String(tenantId)]
1354
1335
  });
1355
- }
1336
+ },
1337
+ //beforeCommit: async (db, req) => ...,
1338
+ //afterCommit: async (db, req) => ...,
1339
+ // afterRollback: async (db, req, error) => {
1340
+ // console.dir(error);
1341
+ // }
1356
1342
  }
1357
1343
  }
1358
1344
  }))
@@ -1381,7 +1367,7 @@ function decodeFakeJwt(token) {
1381
1367
 
1382
1368
  <sub>πŸ“„ browser.ts</sub>
1383
1369
 
1384
- ```javascript
1370
+ ```ts
1385
1371
  import map from './map';
1386
1372
 
1387
1373
  const db = map.http('http://localhost:3000/orange');
@@ -1411,7 +1397,7 @@ const db = map.sqlite('demo.db');
1411
1397
  getRows();
1412
1398
 
1413
1399
  async function getRows() {
1414
- const rows = await db.order.getAll({
1400
+ const rows = await db.order.getMany({
1415
1401
  deliveryAddress: true
1416
1402
  });
1417
1403
  }
@@ -1427,7 +1413,7 @@ const db = map.sqlite('demo.db');
1427
1413
  getRows();
1428
1414
 
1429
1415
  async function getRows() {
1430
- const rows = await db.order.getAll({
1416
+ const rows = await db.order.getMany({
1431
1417
  orderDate: false,
1432
1418
  deliveryAddress: {
1433
1419
  countryCode: true,
@@ -1451,7 +1437,7 @@ const db = map.sqlite('demo.db');
1451
1437
  getRows();
1452
1438
 
1453
1439
  async function getRows() {
1454
- const rows = await db.customer.getAll({
1440
+ const rows = await db.customer.getMany({
1455
1441
  where x => x.name.equal('Harry')
1456
1442
  });
1457
1443
  }
@@ -1464,7 +1450,7 @@ const db = map.sqlite('demo.db');
1464
1450
  getRows();
1465
1451
 
1466
1452
  async function getRows() {
1467
- const rows = await db.customer.getAll({
1453
+ const rows = await db.customer.getMany({
1468
1454
  where x => x.name.notEqual('Harry')
1469
1455
  });
1470
1456
  }
@@ -1477,7 +1463,7 @@ const db = map.sqlite('demo.db');
1477
1463
  getRows();
1478
1464
 
1479
1465
  async function getRows() {
1480
- const rows = await db.customer.getAll({
1466
+ const rows = await db.customer.getMany({
1481
1467
  where: x => x.name.contains('arr')
1482
1468
  });
1483
1469
  }
@@ -1492,7 +1478,7 @@ getRows();
1492
1478
  async function getRows() {
1493
1479
  const filter = db.customer.name.startsWith('Harr');
1494
1480
 
1495
- const rows = await db.customer.getAll({
1481
+ const rows = await db.customer.getMany({
1496
1482
  where: x => x.name.startsWith('Harr')
1497
1483
  });
1498
1484
  }
@@ -1505,7 +1491,7 @@ const db = map.sqlite('demo.db');
1505
1491
  getRows();
1506
1492
 
1507
1493
  async function getRows() {
1508
- const rows = await db.customer.getAll({
1494
+ const rows = await db.customer.getMany({
1509
1495
  where: x => x.name.endsWith('arry')
1510
1496
  });
1511
1497
  }
@@ -1518,7 +1504,7 @@ const db = map.sqlite('demo.db');
1518
1504
  getRows();
1519
1505
 
1520
1506
  async function getRows() {
1521
- const rows = await db.order.getAll({
1507
+ const rows = await db.order.getMany({
1522
1508
  where: x => x.orderDate.greaterThan('2023-07-14T12:00:00')
1523
1509
  });
1524
1510
  }
@@ -1531,7 +1517,7 @@ const db = map.sqlite('demo.db');
1531
1517
  getRows();
1532
1518
 
1533
1519
  async function getRows() {
1534
- const rows = await db.order.getAll({
1520
+ const rows = await db.order.getMany({
1535
1521
  where: x => x.orderDate.greaterThanOrEqual('2023-07-14T12:00:00')
1536
1522
  });
1537
1523
  }
@@ -1544,7 +1530,7 @@ const db = map.sqlite('demo.db');
1544
1530
  getRows();
1545
1531
 
1546
1532
  async function getRows() {
1547
- const rows = await db.order.getAll({
1533
+ const rows = await db.order.getMany({
1548
1534
  where: x => x.orderDate.lessThan('2023-07-14T12:00:00')
1549
1535
  });
1550
1536
  }
@@ -1557,7 +1543,7 @@ const db = map.sqlite('demo.db');
1557
1543
  getRows();
1558
1544
 
1559
1545
  async function getRows() {
1560
- const rows = await db.order.getAll({
1546
+ const rows = await db.order.getMany({
1561
1547
  where: x => x.orderDate.lessThanOrEqual('2023-07-14T12:00:00')
1562
1548
  });
1563
1549
  }
@@ -1570,7 +1556,7 @@ const db = map.sqlite('demo.db');
1570
1556
  getRows();
1571
1557
 
1572
1558
  async function getRows() {
1573
- const rows = await db.order.getAll({
1559
+ const rows = await db.order.getMany({
1574
1560
  where: x => x.orderDate.between('2023-07-14T12:00:00', '2024-07-14T12:00:00')
1575
1561
  });
1576
1562
  }
@@ -1583,7 +1569,7 @@ const db = map.sqlite('demo.db');
1583
1569
  getRows();
1584
1570
 
1585
1571
  async function getRows() {
1586
- const rows = await db.order.getAll({
1572
+ const rows = await db.order.getMany({
1587
1573
  where: x => x.customer.name.in('George', 'Harry')
1588
1574
  });
1589
1575
 
@@ -1605,11 +1591,11 @@ async function getRows() {
1605
1591
  parameters: ['%arry']
1606
1592
  };
1607
1593
 
1608
- const rowsWithRaw = await db.customer.getAll({
1594
+ const rowsWithRaw = await db.customer.getMany({
1609
1595
  where: () => rawFilter
1610
1596
  });
1611
1597
 
1612
- const rowsWithCombined = await db.customer.getAll({
1598
+ const rowsWithCombined = await db.customer.getMany({
1613
1599
  where: x => x.balance.greaterThan(100).and(rawFilter)
1614
1600
  });
1615
1601
  }
@@ -1627,7 +1613,7 @@ const db = map.sqlite('demo.db');
1627
1613
  getRows();
1628
1614
 
1629
1615
  async function getRows() {
1630
- const orders = await db.order.getAll({
1616
+ const orders = await db.order.getMany({
1631
1617
  lines: {
1632
1618
  where: x => x.product.contains('broomstick')
1633
1619
  },
@@ -1649,7 +1635,7 @@ const db = map.sqlite('demo.db');
1649
1635
  getRows();
1650
1636
 
1651
1637
  async function getRows() {
1652
- const rows = await db.order.getAll({
1638
+ const rows = await db.order.getMany({
1653
1639
  where: x => x.customer.name.equal('Harry')
1654
1640
  .and(x.orderDate.greaterThan('2023-07-14T12:00:00'))
1655
1641
  });
@@ -1664,7 +1650,7 @@ getRows();
1664
1650
 
1665
1651
  async function getRows() {
1666
1652
 
1667
- const rows = await db.order.getAll({
1653
+ const rows = await db.order.getMany({
1668
1654
  where: y => y.customer( x => x.name.equal('George')
1669
1655
  .or(x.name.equal('Harry')))
1670
1656
  });
@@ -1679,7 +1665,7 @@ getRows();
1679
1665
 
1680
1666
  async function getRows() {
1681
1667
  //Neither George nor Harry
1682
- const rows = await db.order.getAll({
1668
+ const rows = await db.order.getMany({
1683
1669
  where: y => y.customer(x => x.name.equal('George')
1684
1670
  .or(x.name.equal('Harry')))
1685
1671
  .not()
@@ -1694,7 +1680,7 @@ const db = map.sqlite('demo.db');
1694
1680
  getRows();
1695
1681
 
1696
1682
  async function getRows() {
1697
- const rows = await db.order.getAll({
1683
+ const rows = await db.order.getMany({
1698
1684
  where: x => x.deliveryAddress.exists()
1699
1685
  });
1700
1686
  }
@@ -1715,7 +1701,7 @@ const db = map.sqlite('demo.db');
1715
1701
  getRows();
1716
1702
 
1717
1703
  async function getRows() {
1718
- const rows = await db.order.getAll({
1704
+ const rows = await db.order.getMany({
1719
1705
  where: y => y.lines.any(x => x.product.contains('guitar'))
1720
1706
  //equivalent syntax:
1721
1707
  //where: x => x.lines.product.contains('guitar')
@@ -1731,7 +1717,7 @@ const db = map.sqlite('demo.db');
1731
1717
  getRows();
1732
1718
 
1733
1719
  async function getRows() {
1734
- const rows = await db.order.getAll({
1720
+ const rows = await db.order.getMany({
1735
1721
  where: y => y.lines.all(x => x.product.contains('a'))
1736
1722
  });
1737
1723
  }
@@ -1745,7 +1731,7 @@ const db = map.sqlite('demo.db');
1745
1731
  getRows();
1746
1732
 
1747
1733
  async function getRows() {
1748
- const rows = await db.order.getAll({
1734
+ const rows = await db.order.getMany({
1749
1735
  where: y => y.lines.none(x => x.product.equal('Magic wand'))
1750
1736
  });
1751
1737
  }
@@ -1794,7 +1780,8 @@ async function execute() {
1794
1780
  - **`json`** and **`jsonOf<T>`** are represented as an object or array in javascript and maps to JSON, JSONB, NVARCHAR(max) or TEXT (sqlite) in sql.
1795
1781
 
1796
1782
  <sub>πŸ“„ map.ts</sub>
1797
- ```javascript
1783
+
1784
+ ```ts
1798
1785
  import orange from 'orange-orm';
1799
1786
 
1800
1787
  interface Pet {
@@ -1817,7 +1804,8 @@ const map = orange.map(x => ({
1817
1804
  }));
1818
1805
  ```
1819
1806
  <sub>πŸ“„ map.js</sub>
1820
- ```javascript
1807
+
1808
+ ```js
1821
1809
  import orange from 'orange-orm';
1822
1810
 
1823
1811
  /**
@@ -1844,6 +1832,138 @@ const map = orange.map(x => ({
1844
1832
  ```
1845
1833
  </details>
1846
1834
 
1835
+ <details id="enums"><summary><strong>Enums</strong></summary>
1836
+ <p>Enums can be defined using object literals, arrays, or TypeScript enums. The <strong><i>enum(...)</i></strong> method uses literal types when possible, so prefer patterns that keep literals (inline objects, <code>as const</code>, or <code>Object.freeze</code> in JS).</p>
1837
+
1838
+ <sub>πŸ“„ map.ts (TypeScript enum)</sub>
1839
+
1840
+ ```ts
1841
+ enum CountryCode {
1842
+ NORWAY = 'NO',
1843
+ SWEDEN = 'SE',
1844
+ DENMARK = 'DK',
1845
+ FINLAND = 'FI',
1846
+ ICELAND = 'IS',
1847
+ GERMANY = 'DE',
1848
+ FRANCE = 'FR',
1849
+ NETHERLANDS = 'NL',
1850
+ SPAIN = 'ES',
1851
+ ITALY = 'IT',
1852
+ }
1853
+
1854
+ const map = orange.map(x => ({
1855
+ deliveryAddress: x.table('deliveryAddress').map(({ column }) => ({
1856
+ id: column('id').numeric().primary(),
1857
+ name: column('name').string(),
1858
+ street: column('street').string(),
1859
+ postalCode: column('postalCode').string(),
1860
+ postalPlace: column('postalPlace').string(),
1861
+ countryCode: column('countryCode').string().enum(CountryCode),
1862
+ }))
1863
+ }));
1864
+ ```
1865
+
1866
+
1867
+ <sub>πŸ“„ map.ts (as const)</sub>
1868
+
1869
+ ```ts
1870
+ const Countries = {
1871
+ NORWAY: 'NO',
1872
+ SWEDEN: 'SE',
1873
+ DENMARK: 'DK',
1874
+ FINLAND: 'FI',
1875
+ ICELAND: 'IS',
1876
+ GERMANY: 'DE',
1877
+ FRANCE: 'FR',
1878
+ NETHERLANDS: 'NL',
1879
+ SPAIN: 'ES',
1880
+ ITALY: 'IT',
1881
+ } as const;
1882
+
1883
+ const map = orange.map(x => ({
1884
+ deliveryAddress: x.table('deliveryAddress').map(({ column }) => ({
1885
+ id: column('id').numeric().primary(),
1886
+ name: column('name').string(),
1887
+ street: column('street').string(),
1888
+ postalCode: column('postalCode').string(),
1889
+ postalPlace: column('postalPlace').string(),
1890
+ countryCode: column('countryCode').string().enum(Countries),
1891
+ }))
1892
+ }));
1893
+ ```
1894
+
1895
+ <sub>πŸ“„ map.ts (array)</sub>
1896
+
1897
+ ```ts
1898
+ const map = orange.map(x => ({
1899
+ deliveryAddress: x.table('deliveryAddress').map(({ column }) => ({
1900
+ id: column('id').numeric().primary(),
1901
+ name: column('name').string(),
1902
+ street: column('street').string(),
1903
+ postalCode: column('postalCode').string(),
1904
+ postalPlace: column('postalPlace').string(),
1905
+ countryCode: column('countryCode').string().enum(
1906
+ ['NO', 'SE', 'DK', 'FI', 'IS', 'DE', 'FR', 'NL', 'ES', 'IT']
1907
+ ),
1908
+ }))
1909
+ }));
1910
+ ```
1911
+
1912
+ <sub>πŸ“„ map.ts (literal object)</sub>
1913
+
1914
+ ```ts
1915
+ const map = orange.map(x => ({
1916
+ deliveryAddress: x.table('deliveryAddress').map(({ column }) => ({
1917
+ id: column('id').numeric().primary(),
1918
+ name: column('name').string(),
1919
+ street: column('street').string(),
1920
+ postalCode: column('postalCode').string(),
1921
+ postalPlace: column('postalPlace').string(),
1922
+ countryCode: column('countryCode').string().enum({
1923
+ NORWAY: 'NO',
1924
+ SWEDEN: 'SE',
1925
+ DENMARK: 'DK',
1926
+ FINLAND: 'FI',
1927
+ ICELAND: 'IS',
1928
+ GERMANY: 'DE',
1929
+ FRANCE: 'FR',
1930
+ NETHERLANDS: 'NL',
1931
+ SPAIN: 'ES',
1932
+ ITALY: 'IT',
1933
+ }),
1934
+ }))
1935
+ }));
1936
+ ```
1937
+
1938
+ <sub>πŸ“„ map.js (Object.freeze)</sub>
1939
+
1940
+ ```js
1941
+ const Countries = Object.freeze({
1942
+ NORWAY: 'NO',
1943
+ SWEDEN: 'SE',
1944
+ DENMARK: 'DK',
1945
+ FINLAND: 'FI',
1946
+ ICELAND: 'IS',
1947
+ GERMANY: 'DE',
1948
+ FRANCE: 'FR',
1949
+ NETHERLANDS: 'NL',
1950
+ SPAIN: 'ES',
1951
+ ITALY: 'IT',
1952
+ });
1953
+
1954
+ const map = orange.map(x => ({
1955
+ deliveryAddress: x.table('deliveryAddress').map(({ column }) => ({
1956
+ id: column('id').numeric().primary(),
1957
+ name: column('name').string(),
1958
+ street: column('street').string(),
1959
+ postalCode: column('postalCode').string(),
1960
+ postalPlace: column('postalPlace').string(),
1961
+ countryCode: column('countryCode').string().enum(Countries),
1962
+ }))
1963
+ }));
1964
+ ```
1965
+ </details>
1966
+
1847
1967
  <details id="default-values"><summary><strong>Default values</strong></summary>
1848
1968
  <p>Utilizing default values can be especially useful for automatically populating these fields when the underlying database doesn't offer native support for default value generation.
1849
1969
 
@@ -1870,7 +1990,7 @@ export default map;
1870
1990
  <p>In the previous sections you have already seen the <strong><i>notNull()</i></strong> validator being used on some columns. This will not only generate correct typescript mapping, but also throw an error if value is set to null or undefined. However, sometimes we do not want the notNull-validator to be run on inserts. Typically, when we have an autoincremental key or server generated uuid, it does not make sense to check for null on insert. This is where <strong><i>notNullExceptInsert()</strong></i> comes to rescue. You can also create your own custom validator as shown below. The last kind of validator, is the <a href="https://ajv.js.org/json-schema.html">ajv JSON schema validator</a>. This can be used on json columns as well as any other column type.</p>
1871
1991
 
1872
1992
  <sub>πŸ“„ map.ts</sub>
1873
- ```javascript
1993
+ ```ts
1874
1994
  import orange from 'orange-orm';
1875
1995
 
1876
1996
  interface Pet {
@@ -1901,7 +2021,7 @@ const map = orange.map(x => ({
1901
2021
  export default map;
1902
2022
  ```
1903
2023
  <sub>πŸ“„ map.js</sub>
1904
- ```javascript
2024
+ ```js
1905
2025
  import orange from 'orange-orm';
1906
2026
 
1907
2027
  /**
@@ -2039,6 +2159,43 @@ async function getRows() {
2039
2159
  ```
2040
2160
  </details>
2041
2161
 
2162
+ <details id="sqlite-user-defined-functions"><summary><strong>SQLite user-defined functions</strong></summary>
2163
+
2164
+ You can register custom SQLite functions on the connection using `db.function(name, fn)`.
2165
+
2166
+ The `fn` argument is your user-defined callback:
2167
+ - It is invoked by SQLite every time the SQL function is called.
2168
+ - Callback arguments are positional and match the SQL call (for example, `my_fn(a, b)` maps to `(a, b)`).
2169
+ - Return a SQLite-compatible scalar value (for example text, number, or `null`).
2170
+ - Throwing inside the callback fails the SQL statement.
2171
+
2172
+ `db.function(...)` is sync-only in Node and Deno, but can be async or sync in Bun.
2173
+
2174
+ ```javascript
2175
+ import map from './map';
2176
+ const db = map.sqlite('demo.db');
2177
+
2178
+ await db.function('add_prefix', (text, prefix) => `${prefix}${text}`);
2179
+
2180
+ const rows = await db.query(
2181
+ "select id, name, add_prefix(name, '[VIP] ') as prefixedName from customer"
2182
+ );
2183
+ ```
2184
+
2185
+ If you need the function inside a transaction, register it within the transaction callback to ensure it is available on that connection.
2186
+
2187
+ ```javascript
2188
+ await db.transaction(async (db) => {
2189
+ await db.function('add_prefix', (text, prefix) => `${prefix}${text}`);
2190
+ return db.query(
2191
+ "select id, name, add_prefix(name, '[VIP] ') as prefixedName from customer"
2192
+ );
2193
+ });
2194
+ ```
2195
+
2196
+ `db.function(...)` is available on direct SQLite connections (for example `map.sqlite(...)`) and not through `map.http(...)`.
2197
+ </details>
2198
+
2042
2199
  <details id="aggregates"><summary><strong>Aggregate functions</strong></summary>
2043
2200
 
2044
2201
  You can count records and aggregate numerical columns. This can either be done across rows or separately for each row.
@@ -2060,7 +2217,7 @@ const db = map.sqlite('demo.db');
2060
2217
  getRows();
2061
2218
 
2062
2219
  async function getRows() {
2063
- const orders = await db.order.getAll({
2220
+ const orders = await db.order.getMany({
2064
2221
  numberOfLines: x => x.count(x => x.lines.id),
2065
2222
  totalAmount: x => x.sum(x => lines.amount),
2066
2223
  balance: x => x.customer.balance
@@ -2111,7 +2268,7 @@ async function getCount() {
2111
2268
 
2112
2269
  <sub>πŸ“„ map.ts</sub>
2113
2270
 
2114
- ```javascript
2271
+ ```ts
2115
2272
  import orange from 'orange-orm';
2116
2273
 
2117
2274
  const map = orange.map(x => ({
@@ -2127,7 +2284,7 @@ export default map;
2127
2284
  ```
2128
2285
  <sub>πŸ“„ sensitive.ts</sub>
2129
2286
 
2130
- ```javascript
2287
+ ```ts
2131
2288
  import map from './map';
2132
2289
  const db = map.sqlite('demo.db');
2133
2290