orange-orm 5.0.0-beta.3 β†’ 5.0.0-beta.4

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,9 +1281,8 @@ 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
- Available transaction hooks are `beforeBegin`, `afterBegin`, `beforeCommit`, `afterCommit`, and `afterRollback`.
1307
1286
 
1308
1287
  <sub>πŸ“„ setup.sql</sub>
1309
1288
 
@@ -1330,7 +1309,7 @@ insert into tenant_data (tenant_id, value) values
1330
1309
 
1331
1310
  <sub>πŸ“„ server.ts</sub>
1332
1311
 
1333
- ```javascript
1312
+ ```ts
1334
1313
  import map from './map';
1335
1314
  import { json } from 'body-parser';
1336
1315
  import express from 'express';
@@ -1345,6 +1324,7 @@ express().disable('x-powered-by')
1345
1324
  .use('/orange', db.express({
1346
1325
  hooks: {
1347
1326
  transaction: {
1327
+ //beforeBegin: async (db, req) => ...,
1348
1328
  afterBegin: async (db, req) => {
1349
1329
  const tenantId = Number.parseInt(String(req.user?.tenantId ?? ''), 10);
1350
1330
  if (!Number.isFinite(tenantId)) throw new Error('Missing tenant id');
@@ -1353,7 +1333,12 @@ express().disable('x-powered-by')
1353
1333
  sql: 'select set_config(\'app.tenant_id\', ?, true)',
1354
1334
  parameters: [String(tenantId)]
1355
1335
  });
1356
- }
1336
+ },
1337
+ //beforeCommit: async (db, req) => ...,
1338
+ //afterCommit: async (db, req) => ...,
1339
+ // afterRollback: async (db, req, error) => {
1340
+ // console.dir(error);
1341
+ // }
1357
1342
  }
1358
1343
  }
1359
1344
  }))
@@ -1382,7 +1367,7 @@ function decodeFakeJwt(token) {
1382
1367
 
1383
1368
  <sub>πŸ“„ browser.ts</sub>
1384
1369
 
1385
- ```javascript
1370
+ ```ts
1386
1371
  import map from './map';
1387
1372
 
1388
1373
  const db = map.http('http://localhost:3000/orange');
@@ -1412,7 +1397,7 @@ const db = map.sqlite('demo.db');
1412
1397
  getRows();
1413
1398
 
1414
1399
  async function getRows() {
1415
- const rows = await db.order.getAll({
1400
+ const rows = await db.order.getMany({
1416
1401
  deliveryAddress: true
1417
1402
  });
1418
1403
  }
@@ -1428,7 +1413,7 @@ const db = map.sqlite('demo.db');
1428
1413
  getRows();
1429
1414
 
1430
1415
  async function getRows() {
1431
- const rows = await db.order.getAll({
1416
+ const rows = await db.order.getMany({
1432
1417
  orderDate: false,
1433
1418
  deliveryAddress: {
1434
1419
  countryCode: true,
@@ -1452,7 +1437,7 @@ const db = map.sqlite('demo.db');
1452
1437
  getRows();
1453
1438
 
1454
1439
  async function getRows() {
1455
- const rows = await db.customer.getAll({
1440
+ const rows = await db.customer.getMany({
1456
1441
  where x => x.name.equal('Harry')
1457
1442
  });
1458
1443
  }
@@ -1465,7 +1450,7 @@ const db = map.sqlite('demo.db');
1465
1450
  getRows();
1466
1451
 
1467
1452
  async function getRows() {
1468
- const rows = await db.customer.getAll({
1453
+ const rows = await db.customer.getMany({
1469
1454
  where x => x.name.notEqual('Harry')
1470
1455
  });
1471
1456
  }
@@ -1478,7 +1463,7 @@ const db = map.sqlite('demo.db');
1478
1463
  getRows();
1479
1464
 
1480
1465
  async function getRows() {
1481
- const rows = await db.customer.getAll({
1466
+ const rows = await db.customer.getMany({
1482
1467
  where: x => x.name.contains('arr')
1483
1468
  });
1484
1469
  }
@@ -1493,7 +1478,7 @@ getRows();
1493
1478
  async function getRows() {
1494
1479
  const filter = db.customer.name.startsWith('Harr');
1495
1480
 
1496
- const rows = await db.customer.getAll({
1481
+ const rows = await db.customer.getMany({
1497
1482
  where: x => x.name.startsWith('Harr')
1498
1483
  });
1499
1484
  }
@@ -1506,7 +1491,7 @@ const db = map.sqlite('demo.db');
1506
1491
  getRows();
1507
1492
 
1508
1493
  async function getRows() {
1509
- const rows = await db.customer.getAll({
1494
+ const rows = await db.customer.getMany({
1510
1495
  where: x => x.name.endsWith('arry')
1511
1496
  });
1512
1497
  }
@@ -1519,7 +1504,7 @@ const db = map.sqlite('demo.db');
1519
1504
  getRows();
1520
1505
 
1521
1506
  async function getRows() {
1522
- const rows = await db.order.getAll({
1507
+ const rows = await db.order.getMany({
1523
1508
  where: x => x.orderDate.greaterThan('2023-07-14T12:00:00')
1524
1509
  });
1525
1510
  }
@@ -1532,7 +1517,7 @@ const db = map.sqlite('demo.db');
1532
1517
  getRows();
1533
1518
 
1534
1519
  async function getRows() {
1535
- const rows = await db.order.getAll({
1520
+ const rows = await db.order.getMany({
1536
1521
  where: x => x.orderDate.greaterThanOrEqual('2023-07-14T12:00:00')
1537
1522
  });
1538
1523
  }
@@ -1545,7 +1530,7 @@ const db = map.sqlite('demo.db');
1545
1530
  getRows();
1546
1531
 
1547
1532
  async function getRows() {
1548
- const rows = await db.order.getAll({
1533
+ const rows = await db.order.getMany({
1549
1534
  where: x => x.orderDate.lessThan('2023-07-14T12:00:00')
1550
1535
  });
1551
1536
  }
@@ -1558,7 +1543,7 @@ const db = map.sqlite('demo.db');
1558
1543
  getRows();
1559
1544
 
1560
1545
  async function getRows() {
1561
- const rows = await db.order.getAll({
1546
+ const rows = await db.order.getMany({
1562
1547
  where: x => x.orderDate.lessThanOrEqual('2023-07-14T12:00:00')
1563
1548
  });
1564
1549
  }
@@ -1571,7 +1556,7 @@ const db = map.sqlite('demo.db');
1571
1556
  getRows();
1572
1557
 
1573
1558
  async function getRows() {
1574
- const rows = await db.order.getAll({
1559
+ const rows = await db.order.getMany({
1575
1560
  where: x => x.orderDate.between('2023-07-14T12:00:00', '2024-07-14T12:00:00')
1576
1561
  });
1577
1562
  }
@@ -1584,7 +1569,7 @@ const db = map.sqlite('demo.db');
1584
1569
  getRows();
1585
1570
 
1586
1571
  async function getRows() {
1587
- const rows = await db.order.getAll({
1572
+ const rows = await db.order.getMany({
1588
1573
  where: x => x.customer.name.in('George', 'Harry')
1589
1574
  });
1590
1575
 
@@ -1606,11 +1591,11 @@ async function getRows() {
1606
1591
  parameters: ['%arry']
1607
1592
  };
1608
1593
 
1609
- const rowsWithRaw = await db.customer.getAll({
1594
+ const rowsWithRaw = await db.customer.getMany({
1610
1595
  where: () => rawFilter
1611
1596
  });
1612
1597
 
1613
- const rowsWithCombined = await db.customer.getAll({
1598
+ const rowsWithCombined = await db.customer.getMany({
1614
1599
  where: x => x.balance.greaterThan(100).and(rawFilter)
1615
1600
  });
1616
1601
  }
@@ -1628,7 +1613,7 @@ const db = map.sqlite('demo.db');
1628
1613
  getRows();
1629
1614
 
1630
1615
  async function getRows() {
1631
- const orders = await db.order.getAll({
1616
+ const orders = await db.order.getMany({
1632
1617
  lines: {
1633
1618
  where: x => x.product.contains('broomstick')
1634
1619
  },
@@ -1650,7 +1635,7 @@ const db = map.sqlite('demo.db');
1650
1635
  getRows();
1651
1636
 
1652
1637
  async function getRows() {
1653
- const rows = await db.order.getAll({
1638
+ const rows = await db.order.getMany({
1654
1639
  where: x => x.customer.name.equal('Harry')
1655
1640
  .and(x.orderDate.greaterThan('2023-07-14T12:00:00'))
1656
1641
  });
@@ -1665,7 +1650,7 @@ getRows();
1665
1650
 
1666
1651
  async function getRows() {
1667
1652
 
1668
- const rows = await db.order.getAll({
1653
+ const rows = await db.order.getMany({
1669
1654
  where: y => y.customer( x => x.name.equal('George')
1670
1655
  .or(x.name.equal('Harry')))
1671
1656
  });
@@ -1680,7 +1665,7 @@ getRows();
1680
1665
 
1681
1666
  async function getRows() {
1682
1667
  //Neither George nor Harry
1683
- const rows = await db.order.getAll({
1668
+ const rows = await db.order.getMany({
1684
1669
  where: y => y.customer(x => x.name.equal('George')
1685
1670
  .or(x.name.equal('Harry')))
1686
1671
  .not()
@@ -1695,7 +1680,7 @@ const db = map.sqlite('demo.db');
1695
1680
  getRows();
1696
1681
 
1697
1682
  async function getRows() {
1698
- const rows = await db.order.getAll({
1683
+ const rows = await db.order.getMany({
1699
1684
  where: x => x.deliveryAddress.exists()
1700
1685
  });
1701
1686
  }
@@ -1716,7 +1701,7 @@ const db = map.sqlite('demo.db');
1716
1701
  getRows();
1717
1702
 
1718
1703
  async function getRows() {
1719
- const rows = await db.order.getAll({
1704
+ const rows = await db.order.getMany({
1720
1705
  where: y => y.lines.any(x => x.product.contains('guitar'))
1721
1706
  //equivalent syntax:
1722
1707
  //where: x => x.lines.product.contains('guitar')
@@ -1732,7 +1717,7 @@ const db = map.sqlite('demo.db');
1732
1717
  getRows();
1733
1718
 
1734
1719
  async function getRows() {
1735
- const rows = await db.order.getAll({
1720
+ const rows = await db.order.getMany({
1736
1721
  where: y => y.lines.all(x => x.product.contains('a'))
1737
1722
  });
1738
1723
  }
@@ -1746,7 +1731,7 @@ const db = map.sqlite('demo.db');
1746
1731
  getRows();
1747
1732
 
1748
1733
  async function getRows() {
1749
- const rows = await db.order.getAll({
1734
+ const rows = await db.order.getMany({
1750
1735
  where: y => y.lines.none(x => x.product.equal('Magic wand'))
1751
1736
  });
1752
1737
  }
@@ -1795,7 +1780,8 @@ async function execute() {
1795
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.
1796
1781
 
1797
1782
  <sub>πŸ“„ map.ts</sub>
1798
- ```javascript
1783
+
1784
+ ```ts
1799
1785
  import orange from 'orange-orm';
1800
1786
 
1801
1787
  interface Pet {
@@ -1818,7 +1804,8 @@ const map = orange.map(x => ({
1818
1804
  }));
1819
1805
  ```
1820
1806
  <sub>πŸ“„ map.js</sub>
1821
- ```javascript
1807
+
1808
+ ```js
1822
1809
  import orange from 'orange-orm';
1823
1810
 
1824
1811
  /**
@@ -1845,6 +1832,138 @@ const map = orange.map(x => ({
1845
1832
  ```
1846
1833
  </details>
1847
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
+
1848
1967
  <details id="default-values"><summary><strong>Default values</strong></summary>
1849
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.
1850
1969
 
@@ -1871,7 +1990,7 @@ export default map;
1871
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>
1872
1991
 
1873
1992
  <sub>πŸ“„ map.ts</sub>
1874
- ```javascript
1993
+ ```ts
1875
1994
  import orange from 'orange-orm';
1876
1995
 
1877
1996
  interface Pet {
@@ -1902,7 +2021,7 @@ const map = orange.map(x => ({
1902
2021
  export default map;
1903
2022
  ```
1904
2023
  <sub>πŸ“„ map.js</sub>
1905
- ```javascript
2024
+ ```js
1906
2025
  import orange from 'orange-orm';
1907
2026
 
1908
2027
  /**
@@ -2040,6 +2159,43 @@ async function getRows() {
2040
2159
  ```
2041
2160
  </details>
2042
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
+
2043
2199
  <details id="aggregates"><summary><strong>Aggregate functions</strong></summary>
2044
2200
 
2045
2201
  You can count records and aggregate numerical columns. This can either be done across rows or separately for each row.
@@ -2061,7 +2217,7 @@ const db = map.sqlite('demo.db');
2061
2217
  getRows();
2062
2218
 
2063
2219
  async function getRows() {
2064
- const orders = await db.order.getAll({
2220
+ const orders = await db.order.getMany({
2065
2221
  numberOfLines: x => x.count(x => x.lines.id),
2066
2222
  totalAmount: x => x.sum(x => lines.amount),
2067
2223
  balance: x => x.customer.balance
@@ -2112,7 +2268,7 @@ async function getCount() {
2112
2268
 
2113
2269
  <sub>πŸ“„ map.ts</sub>
2114
2270
 
2115
- ```javascript
2271
+ ```ts
2116
2272
  import orange from 'orange-orm';
2117
2273
 
2118
2274
  const map = orange.map(x => ({
@@ -2128,7 +2284,7 @@ export default map;
2128
2284
  ```
2129
2285
  <sub>πŸ“„ sensitive.ts</sub>
2130
2286
 
2131
- ```javascript
2287
+ ```ts
2132
2288
  import map from './map';
2133
2289
  const db = map.sqlite('demo.db');
2134
2290
 
@@ -1197,7 +1197,7 @@ function requireEmptyFilter () {
1197
1197
  return emptyFilter.and.apply(null, arguments);
1198
1198
  }
1199
1199
 
1200
- emptyFilter.sql = parameterized.sql;
1200
+ emptyFilter.sql = parameterized.sql.bind(parameterized);
1201
1201
  emptyFilter.parameters = parameterized.parameters;
1202
1202
 
1203
1203
  emptyFilter.and = function(context, other) {
@@ -5888,6 +5888,37 @@ function requireColumn () {
5888
5888
  return c;
5889
5889
  };
5890
5890
 
5891
+ c.enum = function(values) {
5892
+ let list = values;
5893
+ if (Array.isArray(values))
5894
+ list = values;
5895
+ else if (values && typeof values === 'object') {
5896
+ const keys = Object.keys(values);
5897
+ const nonNumericKeys = keys.filter((key) => !/^-?\d+$/.test(key));
5898
+ list = (nonNumericKeys.length ? nonNumericKeys : keys).map((key) => values[key]);
5899
+ }
5900
+ else
5901
+ throw new Error('enum values must be an array');
5902
+ const allowed = new Set(list);
5903
+ column.enum = list;
5904
+ function validate(value) {
5905
+ if (value === undefined || value === null)
5906
+ return;
5907
+ if (!allowed.has(value)) {
5908
+ const formatted = list.map((v) => JSON.stringify(v)).join(', ');
5909
+ throw new Error(`Column ${column.alias} must be one of: ${formatted}`);
5910
+ }
5911
+ }
5912
+ return c.validate(validate);
5913
+ };
5914
+
5915
+ c.enum2 = function(...values) {
5916
+ const list = values.length === 1 && Array.isArray(values[0])
5917
+ ? values[0]
5918
+ : values;
5919
+ return c.enum(list);
5920
+ };
5921
+
5891
5922
  c.default = function(value) {
5892
5923
  column.default = value;
5893
5924
  return c;
@@ -5903,7 +5934,6 @@ function requireColumn () {
5903
5934
  var oldAlias = column.alias;
5904
5935
  table._aliases.delete(oldAlias);
5905
5936
  table._aliases.add(alias);
5906
- delete table[oldAlias];
5907
5937
  table[alias] = column;
5908
5938
  column.alias = alias;
5909
5939
  return c;
@@ -12745,7 +12775,10 @@ function requireTable () {
12745
12775
  row[property] = value;
12746
12776
  };
12747
12777
 
12748
- table.delete = _delete.bind(null, table);
12778
+ table.delete = function(context, ...rest) {
12779
+ const args = [context, table, ...rest];
12780
+ return _delete.apply(null, args);
12781
+ };
12749
12782
  table.cascadeDelete = function(context, ...rest) {
12750
12783
  const args = [context, table, ...rest];
12751
12784
  return cascadeDelete.apply(null, args);
package/dist/index.mjs CHANGED
@@ -1198,7 +1198,7 @@ function requireEmptyFilter () {
1198
1198
  return emptyFilter.and.apply(null, arguments);
1199
1199
  }
1200
1200
 
1201
- emptyFilter.sql = parameterized.sql;
1201
+ emptyFilter.sql = parameterized.sql.bind(parameterized);
1202
1202
  emptyFilter.parameters = parameterized.parameters;
1203
1203
 
1204
1204
  emptyFilter.and = function(context, other) {
@@ -5889,6 +5889,37 @@ function requireColumn () {
5889
5889
  return c;
5890
5890
  };
5891
5891
 
5892
+ c.enum = function(values) {
5893
+ let list = values;
5894
+ if (Array.isArray(values))
5895
+ list = values;
5896
+ else if (values && typeof values === 'object') {
5897
+ const keys = Object.keys(values);
5898
+ const nonNumericKeys = keys.filter((key) => !/^-?\d+$/.test(key));
5899
+ list = (nonNumericKeys.length ? nonNumericKeys : keys).map((key) => values[key]);
5900
+ }
5901
+ else
5902
+ throw new Error('enum values must be an array');
5903
+ const allowed = new Set(list);
5904
+ column.enum = list;
5905
+ function validate(value) {
5906
+ if (value === undefined || value === null)
5907
+ return;
5908
+ if (!allowed.has(value)) {
5909
+ const formatted = list.map((v) => JSON.stringify(v)).join(', ');
5910
+ throw new Error(`Column ${column.alias} must be one of: ${formatted}`);
5911
+ }
5912
+ }
5913
+ return c.validate(validate);
5914
+ };
5915
+
5916
+ c.enum2 = function(...values) {
5917
+ const list = values.length === 1 && Array.isArray(values[0])
5918
+ ? values[0]
5919
+ : values;
5920
+ return c.enum(list);
5921
+ };
5922
+
5892
5923
  c.default = function(value) {
5893
5924
  column.default = value;
5894
5925
  return c;
@@ -5904,7 +5935,6 @@ function requireColumn () {
5904
5935
  var oldAlias = column.alias;
5905
5936
  table._aliases.delete(oldAlias);
5906
5937
  table._aliases.add(alias);
5907
- delete table[oldAlias];
5908
5938
  table[alias] = column;
5909
5939
  column.alias = alias;
5910
5940
  return c;
@@ -12746,7 +12776,10 @@ function requireTable () {
12746
12776
  row[property] = value;
12747
12777
  };
12748
12778
 
12749
- table.delete = _delete.bind(null, table);
12779
+ table.delete = function(context, ...rest) {
12780
+ const args = [context, table, ...rest];
12781
+ return _delete.apply(null, args);
12782
+ };
12750
12783
  table.cascadeDelete = function(context, ...rest) {
12751
12784
  const args = [context, table, ...rest];
12752
12785
  return cascadeDelete.apply(null, args);
package/docs/changelog.md CHANGED
@@ -1,4 +1,9 @@
1
1
  ## Changelog
2
+ __5.0.0__
3
+ Breaking: `getAll` was removed. Use `getMany` instead (now with the same signature as the removed `getAll`).
4
+ ExpressJS: Before/after hooks to facilitate row-level security [#135](https://github.com/alfateam/orange-orm/issues/135)
5
+ SQLite: Support for invoking user-defined functions [#145](https://github.com/alfateam/orange-orm/issues/145)
6
+ Support for enums [#100](https://github.com/alfateam/orange-orm/issues/100)
2
7
  __4.9.1__
3
8
  Fix: Avoid double-quoting aliases in discriminator join SQL [#144](https://github.com/alfateam/orange-orm/issues/144)
4
9
  __4.9.0__
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orange-orm",
3
- "version": "5.0.0-beta.3",
3
+ "version": "5.0.0-beta.4",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -4,7 +4,7 @@ function emptyFilter() {
4
4
  return emptyFilter.and.apply(null, arguments);
5
5
  }
6
6
 
7
- emptyFilter.sql = parameterized.sql;
7
+ emptyFilter.sql = parameterized.sql.bind(parameterized);
8
8
  emptyFilter.parameters = parameterized.parameters;
9
9
 
10
10
  emptyFilter.and = function(context, other) {
package/src/map.d.ts CHANGED
@@ -96,7 +96,9 @@ type ReturnArrayOrObj<W, V1, V2> =
96
96
  V1;
97
97
 
98
98
 
99
- type ColumnToType<T> = T extends UuidColumnSymbol
99
+ type ColumnToType<T> = T extends EnumOf<infer E>
100
+ ? E
101
+ : T extends UuidColumnSymbol
100
102
  ? string
101
103
  : T extends StringColumnSymbol
102
104
  ? string
@@ -625,6 +627,10 @@ type NotNullExceptInsert = {
625
627
  [' notNullExceptInsert']: boolean;
626
628
  };
627
629
 
630
+ type EnumOf<T> = {
631
+ [' enum']: T;
632
+ };
633
+
628
634
  type JsonOf<T> = {
629
635
  [' isjsonOf']: boolean;
630
636
  type: T;
@@ -734,6 +740,11 @@ type DateWithTimeZoneValidator<M> = M extends NotNull
734
740
  };
735
741
 
736
742
  type StringColumnTypeDef<M> = StringValidator<M> & {
743
+ enum<const V extends readonly string[]>(values: V): StringColumnTypeDef<M & EnumOf<V[number]>> & EnumOf<V[number]>;
744
+ enum<const V extends Record<string, string>>(values: V): StringColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
745
+ enum<TEnum>(values: Record<string, TEnum>): StringColumnTypeDef<M & EnumOf<TEnum>> & EnumOf<TEnum>;
746
+ enum<E extends string>(values: readonly E[]): StringColumnTypeDef<M & EnumOf<E>> & EnumOf<E>;
747
+ enum<E, V extends Record<string, E>>(values: V): StringColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
737
748
  primary(): StringColumnTypeDef<M & IsPrimary> & IsPrimary;
738
749
  notNull(): StringColumnTypeDef<M & NotNull> & NotNull;
739
750
  notNullExceptInsert(): StringColumnTypeDef<M & NotNull & NotNullExceptInsert> & NotNull & NotNullExceptInsert;
@@ -745,6 +756,10 @@ type StringColumnTypeDef<M> = StringValidator<M> & {
745
756
  M;
746
757
 
747
758
  type NumericColumnTypeDef<M> = NumericValidator<M> & {
759
+ enum<const V extends Record<string, number>>(values: V): NumericColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
760
+ enum<TEnum>(values: Record<string, TEnum>): NumericColumnTypeDef<M & EnumOf<TEnum>> & EnumOf<TEnum>;
761
+ enum<E extends number>(values: readonly E[]): NumericColumnTypeDef<M & EnumOf<E>> & EnumOf<E>;
762
+ enum<E, V extends Record<string, E>>(values: V): NumericColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
748
763
  primary(): NumericColumnTypeDef<M & IsPrimary> & IsPrimary;
749
764
  notNull(): NumericColumnTypeDef<M & NotNull> & NotNull;
750
765
  notNullExceptInsert(): NumericColumnTypeDef<M & NotNull & NotNullExceptInsert> & NotNull & NotNullExceptInsert;
@@ -756,6 +771,10 @@ type NumericColumnTypeDef<M> = NumericValidator<M> & {
756
771
  M;
757
772
 
758
773
  type BigIntColumnTypeDef<M> = BigIntValidator<M> & {
774
+ enum<const V extends Record<string, bigint>>(values: V): BigIntColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
775
+ enum<TEnum>(values: Record<string, TEnum>): BigIntColumnTypeDef<M & EnumOf<TEnum>> & EnumOf<TEnum>;
776
+ enum<E extends bigint>(values: readonly E[]): BigIntColumnTypeDef<M & EnumOf<E>> & EnumOf<E>;
777
+ enum<E, V extends Record<string, E>>(values: V): BigIntColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
759
778
  primary(): BigIntColumnTypeDef<M & IsPrimary> & IsPrimary;
760
779
  notNull(): BigIntColumnTypeDef<M & NotNull> & NotNull;
761
780
  notNullExceptInsert(): BigIntColumnTypeDef<M & NotNull & NotNullExceptInsert> & NotNull & NotNullExceptInsert;
@@ -767,6 +786,10 @@ type BigIntColumnTypeDef<M> = BigIntValidator<M> & {
767
786
  M;
768
787
 
769
788
  type UuidColumnTypeDef<M> = UuidValidator<M> & {
789
+ enum<const V extends Record<string, string>>(values: V): UuidColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
790
+ enum<TEnum>(values: Record<string, TEnum>): UuidColumnTypeDef<M & EnumOf<TEnum>> & EnumOf<TEnum>;
791
+ enum<E extends string>(values: readonly E[]): UuidColumnTypeDef<M & EnumOf<E>> & EnumOf<E>;
792
+ enum<E, V extends Record<string, E>>(values: V): UuidColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
770
793
  primary(): UuidColumnTypeDef<M & IsPrimary> & IsPrimary;
771
794
  notNull(): UuidColumnTypeDef<M & NotNull> & NotNull;
772
795
  notNullExceptInsert(): UuidColumnTypeDef<M & NotNull & NotNullExceptInsert> & NotNull & NotNullExceptInsert;
@@ -789,6 +812,10 @@ type JSONColumnTypeDef<M> = JSONValidator<M> & {
789
812
  M;
790
813
 
791
814
  type BinaryColumnTypeDef<M> = BinaryValidator<M> & {
815
+ enum<const V extends Record<string, string>>(values: V): BinaryColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
816
+ enum<TEnum>(values: Record<string, TEnum>): BinaryColumnTypeDef<M & EnumOf<TEnum>> & EnumOf<TEnum>;
817
+ enum<E extends string>(values: readonly E[]): BinaryColumnTypeDef<M & EnumOf<E>> & EnumOf<E>;
818
+ enum<E, V extends Record<string, E>>(values: V): BinaryColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
792
819
  primary(): BinaryColumnTypeDef<M & IsPrimary> & IsPrimary;
793
820
  notNull(): BinaryColumnTypeDef<M & NotNull> & NotNull;
794
821
  notNullExceptInsert(): BinaryColumnTypeDef<M & NotNull & NotNullExceptInsert> & NotNull & NotNullExceptInsert;
@@ -800,6 +827,10 @@ type BinaryColumnTypeDef<M> = BinaryValidator<M> & {
800
827
  M;
801
828
 
802
829
  type BooleanColumnTypeDef<M> = BooleanValidator<M> & {
830
+ enum<const V extends Record<string, boolean>>(values: V): BooleanColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
831
+ enum<TEnum>(values: Record<string, TEnum>): BooleanColumnTypeDef<M & EnumOf<TEnum>> & EnumOf<TEnum>;
832
+ enum<E extends boolean>(values: readonly E[]): BooleanColumnTypeDef<M & EnumOf<E>> & EnumOf<E>;
833
+ enum<E, V extends Record<string, E>>(values: V): BooleanColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
803
834
  primary(): BooleanColumnTypeDef<M & IsPrimary> & IsPrimary;
804
835
  notNull(): BooleanColumnTypeDef<M & NotNull> & NotNull;
805
836
  notNullExceptInsert(): BooleanColumnTypeDef<M & NotNull & NotNullExceptInsert> & NotNull & NotNullExceptInsert;
@@ -811,6 +842,10 @@ type BooleanColumnTypeDef<M> = BooleanValidator<M> & {
811
842
  M;
812
843
 
813
844
  type DateColumnTypeDef<M> = DateValidator<M> & {
845
+ enum<const V extends Record<string, string | Date>>(values: V): DateColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
846
+ enum<TEnum>(values: Record<string, TEnum>): DateColumnTypeDef<M & EnumOf<TEnum>> & EnumOf<TEnum>;
847
+ enum<E extends string | Date>(values: readonly E[]): DateColumnTypeDef<M & EnumOf<E>> & EnumOf<E>;
848
+ enum<E, V extends Record<string, E>>(values: V): DateColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
814
849
  primary(): DateColumnTypeDef<M & IsPrimary> & IsPrimary;
815
850
  notNull(): DateColumnTypeDef<M & NotNull> & NotNull;
816
851
  notNullExceptInsert(): DateColumnTypeDef<M & NotNull & NotNullExceptInsert> & NotNull & NotNullExceptInsert;
@@ -822,6 +857,10 @@ type DateColumnTypeDef<M> = DateValidator<M> & {
822
857
  M;
823
858
 
824
859
  type DateWithTimeZoneColumnTypeDef<M> = DateValidator<M> & {
860
+ enum<const V extends Record<string, string | Date>>(values: V): DateWithTimeZoneColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
861
+ enum<TEnum>(values: Record<string, TEnum>): DateWithTimeZoneColumnTypeDef<M & EnumOf<TEnum>> & EnumOf<TEnum>;
862
+ enum<E extends string | Date>(values: readonly E[]): DateWithTimeZoneColumnTypeDef<M & EnumOf<E>> & EnumOf<E>;
863
+ enum<E, V extends Record<string, E>>(values: V): DateWithTimeZoneColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
825
864
  primary(): DateWithTimeZoneColumnTypeDef<M & IsPrimary> & IsPrimary;
826
865
  notNull(): DateWithTimeZoneColumnTypeDef<M & NotNull> & NotNull;
827
866
  notNullExceptInsert(): DateWithTimeZoneColumnTypeDef<M & NotNull & NotNullExceptInsert> & NotNull & NotNullExceptInsert;
@@ -1000,7 +1039,9 @@ type ExtractPrimaryKeyNames<T> =
1000
1039
  type RelationTarget<T> =
1001
1040
  T extends { __tableAlias: infer S } ? Extract<S, string> : string;
1002
1041
 
1003
- type ColumnToSchemaType<T> =
1042
+ type EnumSchema<T> = T extends EnumOf<infer E> ? { ' enum': readonly E[] } : {};
1043
+
1044
+ type ColumnToSchemaType<T> = (
1004
1045
  T extends JsonOf<infer U>
1005
1046
  ? { ' type': 'json'; ' tsType': U }
1006
1047
  & (T extends NotNullExceptInsert ? { ' notNull': true; ' notNullExceptInsert': true }
@@ -1046,7 +1087,8 @@ type ColumnToSchemaType<T> =
1046
1087
  & (T extends NotNullExceptInsert ? { ' notNull': true; ' notNullExceptInsert': true }
1047
1088
  : T extends NotNull ? { ' notNull': true }
1048
1089
  : {}) :
1049
- never;
1090
+ never
1091
+ ) & EnumSchema<T>;
1050
1092
 
1051
1093
  export type MappedDbDef<T> = {
1052
1094
  map<V extends AllowedDbMap<V>>(
@@ -1059,4 +1101,4 @@ export type MappedDbDef<T> = {
1059
1101
  * Usage: type Schema = ReturnType<typeof db.toSchema>
1060
1102
  */
1061
1103
  toSchema: <U = T>() => SchemaFromMappedDb<U>;
1062
- } & T & DbConnectable<T>;
1104
+ } & T & DbConnectable<T>;
package/src/map2.d.ts CHANGED
@@ -10,6 +10,7 @@ export type ORMColumnType = 'string' | 'bigint' | 'uuid' | 'date' | 'numeric' |
10
10
  // Base column definition with space-prefixed required properties
11
11
  export type ORMColumnDefinition = {
12
12
  ' type': ORMColumnType;
13
+ ' enum'?: readonly (string | number | boolean | Date)[] | Record<string, string | number | boolean | Date>;
13
14
  ' notNull'?: boolean;
14
15
  ' notNullExceptInsert'?: boolean;
15
16
  };
@@ -24,7 +25,7 @@ export type ORMJsonColumnDefinition<T = any> = {
24
25
 
25
26
  type NormalizeColumn<T> =
26
27
  T extends ORMColumnType
27
- ? { ' type': T; ' notNull'?: boolean; ' notNullExceptInsert'?: boolean }
28
+ ? { ' type': T; ' enum'?: readonly (string | number | boolean | Date)[] | Record<string, string | number | boolean | Date>; ' notNull'?: boolean; ' notNullExceptInsert'?: boolean }
28
29
  : T extends { ' type': ORMColumnType }
29
30
  ? { ' notNull'?: boolean; ' notNullExceptInsert'?: boolean } & T
30
31
  : T extends { ' type': 'json'; ' tsType': any }
@@ -41,6 +42,8 @@ type IsRequiredInsert<CT> =
41
42
  : false; // Otherwise, it's optional
42
43
 
43
44
  type ColumnTypeToTS<CT> =
45
+ NormalizeColumn<CT> extends { ' enum': readonly (infer E)[] } ? E :
46
+ NormalizeColumn<CT> extends { ' enum': Record<string, infer E> } ? E :
44
47
  NormalizeColumn<CT>[' type'] extends 'numeric' ? number :
45
48
  NormalizeColumn<CT>[' type'] extends 'boolean' ? boolean :
46
49
  NormalizeColumn<CT>[' type'] extends 'json'
@@ -163,7 +166,7 @@ type BaseFetchStrategy<M extends Record<string, TableDefinition<M>>, K extends k
163
166
  orderBy?: OrderBy<M, K>;
164
167
  limit?: number;
165
168
  offset?: number;
166
- where?: WhereFunc<M, K>;
169
+ where?: WhereClause<M, K>;
167
170
  };
168
171
 
169
172
  export type PrimaryKeyObject<M extends Record<string, TableDefinition<M>>, K extends keyof M> =
@@ -341,18 +344,25 @@ export type AggregateStrategy<M extends Record<string, TableDefinition<M>>, K ex
341
344
  BaseAggregateStrategy<M, K>
342
345
  | CustomAggregateSelectors<M, K>;
343
346
 
344
- type WhereFunc<M extends Record<string, TableDefinition<M>>, K extends keyof M> = (row: RootTableRefs<M, K>) => RawFilter | Array<PrimaryKeyObject<M, K>>;
347
+ type WhereFilter<M extends Record<string, TableDefinition<M>>, K extends keyof M> =
348
+ RawFilter | Array<PrimaryKeyObject<M, K>>;
349
+
350
+ type WhereFunc<M extends Record<string, TableDefinition<M>>, K extends keyof M> =
351
+ (row: RootTableRefs<M, K>) => WhereFilter<M, K>;
352
+
353
+ type WhereClause<M extends Record<string, TableDefinition<M>>, K extends keyof M> =
354
+ WhereFilter<M, K> | WhereFunc<M, K> | (() => WhereFilter<M, K>);
345
355
 
346
356
  type BaseAggregateStrategy<M extends Record<string, TableDefinition<M>>, K extends keyof M> = {
347
357
  limit?: number;
348
358
  offset?: number;
349
- where?: WhereFunc<M, K>;
359
+ where?: WhereClause<M, K>;
350
360
  };
351
361
 
352
362
  type CustomAggregateSelectors<M extends Record<string, TableDefinition<M>>, K extends keyof M> = {
353
363
  [key: string]: (row: RootSelectionRefs<M, K>) => ValidSelectorReturnTypes<M, K>;
354
364
  } & {
355
- where?: WhereFunc<M, K>;
365
+ where?: WhereClause<M, K>;
356
366
  } & {
357
367
  // Explicitly prevent limit/offset in selectors
358
368
  limit?: never;
@@ -674,13 +684,13 @@ type AggregateCustomSelectorProperties<M extends Record<string, TableDefinition<
674
684
 
675
685
  export type TableClient<M extends Record<string, TableDefinition<M>>, K extends keyof M> = {
676
686
  // Array methods - return arrays with array-level active record methods, but individual items are plain
677
- getMany<strategy extends FetchStrategy<M, K> = {}>(strategy: strategy): Promise<WithArrayActiveRecord<Array<DeepExpand<Selection<M, K, strategy>>>, M, K>>;
687
+ getMany<strategy extends FetchStrategy<M, K>>(strategy?: strategy): Promise<WithArrayActiveRecord<Array<DeepExpand<Selection<M, K, strategy>>>, M, K>>;
678
688
 
679
689
  // Aggregate methods - return plain objects (no active record methods)
680
690
  aggregate<strategy extends AggregateStrategy<M, K>>(strategy: strategy): Promise<Array<DeepExpand<AggregateCustomSelectorProperties<M, K, strategy>>>>;
681
691
 
682
692
  // Single item methods - return individual objects with individual active record methods
683
- getOne<strategy extends FetchStrategy<M, K> = {}>(
693
+ getOne<strategy extends FetchStrategy<M, K>>(
684
694
  strategy?: strategy
685
695
  ): Promise<WithActiveRecord<DeepExpand<Selection<M, K, strategy>>, M, K> | undefined>;
686
696
 
@@ -695,12 +705,12 @@ export type TableClient<M extends Record<string, TableDefinition<M>>, K extends
695
705
  // UPDATED: Bulk update methods with relations support
696
706
  update(
697
707
  row: UpdateRowWithRelations<M, K>,
698
- opts: { where: (row: RootTableRefs<M, K>) => RawFilter | Array<PrimaryKeyObject<M, K>> }
708
+ opts: { where: WhereClause<M, K> }
699
709
  ): Promise<void>;
700
710
 
701
711
  update<strategy extends FetchStrategy<M, K>>(
702
712
  row: UpdateRowWithRelations<M, K>,
703
- opts: { where: (row: RootTableRefs<M, K>) => RawFilter | Array<PrimaryKeyObject<M, K>> },
713
+ opts: { where: WhereClause<M, K> },
704
714
  strategy: strategy
705
715
  ): Promise<WithArrayActiveRecord<Array<DeepExpand<Selection<M, K, strategy>>>, M, K>>;
706
716
 
@@ -54,6 +54,37 @@ function defineColumn(column, table) {
54
54
  return c;
55
55
  };
56
56
 
57
+ c.enum = function(values) {
58
+ let list = values;
59
+ if (Array.isArray(values))
60
+ list = values;
61
+ else if (values && typeof values === 'object') {
62
+ const keys = Object.keys(values);
63
+ const nonNumericKeys = keys.filter((key) => !/^-?\d+$/.test(key));
64
+ list = (nonNumericKeys.length ? nonNumericKeys : keys).map((key) => values[key]);
65
+ }
66
+ else
67
+ throw new Error('enum values must be an array');
68
+ const allowed = new Set(list);
69
+ column.enum = list;
70
+ function validate(value) {
71
+ if (value === undefined || value === null)
72
+ return;
73
+ if (!allowed.has(value)) {
74
+ const formatted = list.map((v) => JSON.stringify(v)).join(', ');
75
+ throw new Error(`Column ${column.alias} must be one of: ${formatted}`);
76
+ }
77
+ }
78
+ return c.validate(validate);
79
+ };
80
+
81
+ c.enum2 = function(...values) {
82
+ const list = values.length === 1 && Array.isArray(values[0])
83
+ ? values[0]
84
+ : values;
85
+ return c.enum(list);
86
+ };
87
+
57
88
  c.default = function(value) {
58
89
  column.default = value;
59
90
  return c;
@@ -69,7 +100,6 @@ function defineColumn(column, table) {
69
100
  var oldAlias = column.alias;
70
101
  table._aliases.delete(oldAlias);
71
102
  table._aliases.add(alias);
72
- delete table[oldAlias];
73
103
  table[alias] = column;
74
104
  column.alias = alias;
75
105
  return c;
@@ -160,4 +190,4 @@ function inspect(obj) {
160
190
  }
161
191
 
162
192
 
163
- module.exports = defineColumn;
193
+ module.exports = defineColumn;
package/src/table.js CHANGED
@@ -192,7 +192,10 @@ function _new(tableName) {
192
192
  row[property] = value;
193
193
  };
194
194
 
195
- table.delete = _delete.bind(null, table);
195
+ table.delete = function(context, ...rest) {
196
+ const args = [context, table, ...rest];
197
+ return _delete.apply(null, args);
198
+ };
196
199
  table.cascadeDelete = function(context, ...rest) {
197
200
  const args = [context, table, ...rest];
198
201
  return cascadeDelete.apply(null, args);