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 +242 -86
- package/dist/index.browser.mjs +36 -3
- package/dist/index.mjs +36 -3
- package/docs/changelog.md +5 -0
- package/package.json +1 -1
- package/src/emptyFilter.js +1 -1
- package/src/map.d.ts +46 -4
- package/src/map2.d.ts +19 -9
- package/src/table/column.js +32 -2
- package/src/table.js +4 -1
package/README.md
CHANGED
|
@@ -51,7 +51,7 @@ Watch the [tutorial video on YouTube](https://youtu.be/1IwwjPr2lMs)
|
|
|
51
51
|

|
|
52
52
|
|
|
53
53
|
<sub>π map.ts</sub>
|
|
54
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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(
|
|
698
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
1594
|
+
const rowsWithRaw = await db.customer.getMany({
|
|
1610
1595
|
where: () => rawFilter
|
|
1611
1596
|
});
|
|
1612
1597
|
|
|
1613
|
-
const rowsWithCombined = await db.customer.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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.
|
|
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
|
-
```
|
|
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
|
-
```
|
|
2287
|
+
```ts
|
|
2132
2288
|
import map from './map';
|
|
2133
2289
|
const db = map.sqlite('demo.db');
|
|
2134
2290
|
|
package/dist/index.browser.mjs
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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
package/src/emptyFilter.js
CHANGED
|
@@ -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
|
|
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
|
|
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?:
|
|
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
|
|
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?:
|
|
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?:
|
|
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
|
|
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:
|
|
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:
|
|
713
|
+
opts: { where: WhereClause<M, K> },
|
|
704
714
|
strategy: strategy
|
|
705
715
|
): Promise<WithArrayActiveRecord<Array<DeepExpand<Selection<M, K, strategy>>>, M, K>>;
|
|
706
716
|
|
package/src/table/column.js
CHANGED
|
@@ -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 =
|
|
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);
|