orange-orm 4.10.0-beta.1 β 5.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +242 -85
- package/dist/index.browser.mjs +43 -4
- package/dist/index.mjs +43 -5
- package/package.json +1 -1
- package/src/client/index.js +8 -0
- package/src/map.d.ts +46 -4
- package/src/map2.d.ts +18 -20
- package/src/nodeSqlite/newDatabase.js +0 -1
- package/src/table/column/utils.js +3 -2
- package/src/table/column.js +32 -2
- package/src/table/relatedTable/where.js +2 -2
- package/src/table/where.js +2 -2
- package/deno.lock +0 -76
- package/other.db +0 -0
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,7 +1281,7 @@ 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
1286
|
|
|
1307
1287
|
<sub>π setup.sql</sub>
|
|
@@ -1329,7 +1309,7 @@ insert into tenant_data (tenant_id, value) values
|
|
|
1329
1309
|
|
|
1330
1310
|
<sub>π server.ts</sub>
|
|
1331
1311
|
|
|
1332
|
-
```
|
|
1312
|
+
```ts
|
|
1333
1313
|
import map from './map';
|
|
1334
1314
|
import { json } from 'body-parser';
|
|
1335
1315
|
import express from 'express';
|
|
@@ -1344,6 +1324,7 @@ express().disable('x-powered-by')
|
|
|
1344
1324
|
.use('/orange', db.express({
|
|
1345
1325
|
hooks: {
|
|
1346
1326
|
transaction: {
|
|
1327
|
+
//beforeBegin: async (db, req) => ...,
|
|
1347
1328
|
afterBegin: async (db, req) => {
|
|
1348
1329
|
const tenantId = Number.parseInt(String(req.user?.tenantId ?? ''), 10);
|
|
1349
1330
|
if (!Number.isFinite(tenantId)) throw new Error('Missing tenant id');
|
|
@@ -1352,7 +1333,12 @@ express().disable('x-powered-by')
|
|
|
1352
1333
|
sql: 'select set_config(\'app.tenant_id\', ?, true)',
|
|
1353
1334
|
parameters: [String(tenantId)]
|
|
1354
1335
|
});
|
|
1355
|
-
}
|
|
1336
|
+
},
|
|
1337
|
+
//beforeCommit: async (db, req) => ...,
|
|
1338
|
+
//afterCommit: async (db, req) => ...,
|
|
1339
|
+
// afterRollback: async (db, req, error) => {
|
|
1340
|
+
// console.dir(error);
|
|
1341
|
+
// }
|
|
1356
1342
|
}
|
|
1357
1343
|
}
|
|
1358
1344
|
}))
|
|
@@ -1381,7 +1367,7 @@ function decodeFakeJwt(token) {
|
|
|
1381
1367
|
|
|
1382
1368
|
<sub>π browser.ts</sub>
|
|
1383
1369
|
|
|
1384
|
-
```
|
|
1370
|
+
```ts
|
|
1385
1371
|
import map from './map';
|
|
1386
1372
|
|
|
1387
1373
|
const db = map.http('http://localhost:3000/orange');
|
|
@@ -1411,7 +1397,7 @@ const db = map.sqlite('demo.db');
|
|
|
1411
1397
|
getRows();
|
|
1412
1398
|
|
|
1413
1399
|
async function getRows() {
|
|
1414
|
-
const rows = await db.order.
|
|
1400
|
+
const rows = await db.order.getMany({
|
|
1415
1401
|
deliveryAddress: true
|
|
1416
1402
|
});
|
|
1417
1403
|
}
|
|
@@ -1427,7 +1413,7 @@ const db = map.sqlite('demo.db');
|
|
|
1427
1413
|
getRows();
|
|
1428
1414
|
|
|
1429
1415
|
async function getRows() {
|
|
1430
|
-
const rows = await db.order.
|
|
1416
|
+
const rows = await db.order.getMany({
|
|
1431
1417
|
orderDate: false,
|
|
1432
1418
|
deliveryAddress: {
|
|
1433
1419
|
countryCode: true,
|
|
@@ -1451,7 +1437,7 @@ const db = map.sqlite('demo.db');
|
|
|
1451
1437
|
getRows();
|
|
1452
1438
|
|
|
1453
1439
|
async function getRows() {
|
|
1454
|
-
const rows = await db.customer.
|
|
1440
|
+
const rows = await db.customer.getMany({
|
|
1455
1441
|
where x => x.name.equal('Harry')
|
|
1456
1442
|
});
|
|
1457
1443
|
}
|
|
@@ -1464,7 +1450,7 @@ const db = map.sqlite('demo.db');
|
|
|
1464
1450
|
getRows();
|
|
1465
1451
|
|
|
1466
1452
|
async function getRows() {
|
|
1467
|
-
const rows = await db.customer.
|
|
1453
|
+
const rows = await db.customer.getMany({
|
|
1468
1454
|
where x => x.name.notEqual('Harry')
|
|
1469
1455
|
});
|
|
1470
1456
|
}
|
|
@@ -1477,7 +1463,7 @@ const db = map.sqlite('demo.db');
|
|
|
1477
1463
|
getRows();
|
|
1478
1464
|
|
|
1479
1465
|
async function getRows() {
|
|
1480
|
-
const rows = await db.customer.
|
|
1466
|
+
const rows = await db.customer.getMany({
|
|
1481
1467
|
where: x => x.name.contains('arr')
|
|
1482
1468
|
});
|
|
1483
1469
|
}
|
|
@@ -1492,7 +1478,7 @@ getRows();
|
|
|
1492
1478
|
async function getRows() {
|
|
1493
1479
|
const filter = db.customer.name.startsWith('Harr');
|
|
1494
1480
|
|
|
1495
|
-
const rows = await db.customer.
|
|
1481
|
+
const rows = await db.customer.getMany({
|
|
1496
1482
|
where: x => x.name.startsWith('Harr')
|
|
1497
1483
|
});
|
|
1498
1484
|
}
|
|
@@ -1505,7 +1491,7 @@ const db = map.sqlite('demo.db');
|
|
|
1505
1491
|
getRows();
|
|
1506
1492
|
|
|
1507
1493
|
async function getRows() {
|
|
1508
|
-
const rows = await db.customer.
|
|
1494
|
+
const rows = await db.customer.getMany({
|
|
1509
1495
|
where: x => x.name.endsWith('arry')
|
|
1510
1496
|
});
|
|
1511
1497
|
}
|
|
@@ -1518,7 +1504,7 @@ const db = map.sqlite('demo.db');
|
|
|
1518
1504
|
getRows();
|
|
1519
1505
|
|
|
1520
1506
|
async function getRows() {
|
|
1521
|
-
const rows = await db.order.
|
|
1507
|
+
const rows = await db.order.getMany({
|
|
1522
1508
|
where: x => x.orderDate.greaterThan('2023-07-14T12:00:00')
|
|
1523
1509
|
});
|
|
1524
1510
|
}
|
|
@@ -1531,7 +1517,7 @@ const db = map.sqlite('demo.db');
|
|
|
1531
1517
|
getRows();
|
|
1532
1518
|
|
|
1533
1519
|
async function getRows() {
|
|
1534
|
-
const rows = await db.order.
|
|
1520
|
+
const rows = await db.order.getMany({
|
|
1535
1521
|
where: x => x.orderDate.greaterThanOrEqual('2023-07-14T12:00:00')
|
|
1536
1522
|
});
|
|
1537
1523
|
}
|
|
@@ -1544,7 +1530,7 @@ const db = map.sqlite('demo.db');
|
|
|
1544
1530
|
getRows();
|
|
1545
1531
|
|
|
1546
1532
|
async function getRows() {
|
|
1547
|
-
const rows = await db.order.
|
|
1533
|
+
const rows = await db.order.getMany({
|
|
1548
1534
|
where: x => x.orderDate.lessThan('2023-07-14T12:00:00')
|
|
1549
1535
|
});
|
|
1550
1536
|
}
|
|
@@ -1557,7 +1543,7 @@ const db = map.sqlite('demo.db');
|
|
|
1557
1543
|
getRows();
|
|
1558
1544
|
|
|
1559
1545
|
async function getRows() {
|
|
1560
|
-
const rows = await db.order.
|
|
1546
|
+
const rows = await db.order.getMany({
|
|
1561
1547
|
where: x => x.orderDate.lessThanOrEqual('2023-07-14T12:00:00')
|
|
1562
1548
|
});
|
|
1563
1549
|
}
|
|
@@ -1570,7 +1556,7 @@ const db = map.sqlite('demo.db');
|
|
|
1570
1556
|
getRows();
|
|
1571
1557
|
|
|
1572
1558
|
async function getRows() {
|
|
1573
|
-
const rows = await db.order.
|
|
1559
|
+
const rows = await db.order.getMany({
|
|
1574
1560
|
where: x => x.orderDate.between('2023-07-14T12:00:00', '2024-07-14T12:00:00')
|
|
1575
1561
|
});
|
|
1576
1562
|
}
|
|
@@ -1583,7 +1569,7 @@ const db = map.sqlite('demo.db');
|
|
|
1583
1569
|
getRows();
|
|
1584
1570
|
|
|
1585
1571
|
async function getRows() {
|
|
1586
|
-
const rows = await db.order.
|
|
1572
|
+
const rows = await db.order.getMany({
|
|
1587
1573
|
where: x => x.customer.name.in('George', 'Harry')
|
|
1588
1574
|
});
|
|
1589
1575
|
|
|
@@ -1605,11 +1591,11 @@ async function getRows() {
|
|
|
1605
1591
|
parameters: ['%arry']
|
|
1606
1592
|
};
|
|
1607
1593
|
|
|
1608
|
-
const rowsWithRaw = await db.customer.
|
|
1594
|
+
const rowsWithRaw = await db.customer.getMany({
|
|
1609
1595
|
where: () => rawFilter
|
|
1610
1596
|
});
|
|
1611
1597
|
|
|
1612
|
-
const rowsWithCombined = await db.customer.
|
|
1598
|
+
const rowsWithCombined = await db.customer.getMany({
|
|
1613
1599
|
where: x => x.balance.greaterThan(100).and(rawFilter)
|
|
1614
1600
|
});
|
|
1615
1601
|
}
|
|
@@ -1627,7 +1613,7 @@ const db = map.sqlite('demo.db');
|
|
|
1627
1613
|
getRows();
|
|
1628
1614
|
|
|
1629
1615
|
async function getRows() {
|
|
1630
|
-
const orders = await db.order.
|
|
1616
|
+
const orders = await db.order.getMany({
|
|
1631
1617
|
lines: {
|
|
1632
1618
|
where: x => x.product.contains('broomstick')
|
|
1633
1619
|
},
|
|
@@ -1649,7 +1635,7 @@ const db = map.sqlite('demo.db');
|
|
|
1649
1635
|
getRows();
|
|
1650
1636
|
|
|
1651
1637
|
async function getRows() {
|
|
1652
|
-
const rows = await db.order.
|
|
1638
|
+
const rows = await db.order.getMany({
|
|
1653
1639
|
where: x => x.customer.name.equal('Harry')
|
|
1654
1640
|
.and(x.orderDate.greaterThan('2023-07-14T12:00:00'))
|
|
1655
1641
|
});
|
|
@@ -1664,7 +1650,7 @@ getRows();
|
|
|
1664
1650
|
|
|
1665
1651
|
async function getRows() {
|
|
1666
1652
|
|
|
1667
|
-
const rows = await db.order.
|
|
1653
|
+
const rows = await db.order.getMany({
|
|
1668
1654
|
where: y => y.customer( x => x.name.equal('George')
|
|
1669
1655
|
.or(x.name.equal('Harry')))
|
|
1670
1656
|
});
|
|
@@ -1679,7 +1665,7 @@ getRows();
|
|
|
1679
1665
|
|
|
1680
1666
|
async function getRows() {
|
|
1681
1667
|
//Neither George nor Harry
|
|
1682
|
-
const rows = await db.order.
|
|
1668
|
+
const rows = await db.order.getMany({
|
|
1683
1669
|
where: y => y.customer(x => x.name.equal('George')
|
|
1684
1670
|
.or(x.name.equal('Harry')))
|
|
1685
1671
|
.not()
|
|
@@ -1694,7 +1680,7 @@ const db = map.sqlite('demo.db');
|
|
|
1694
1680
|
getRows();
|
|
1695
1681
|
|
|
1696
1682
|
async function getRows() {
|
|
1697
|
-
const rows = await db.order.
|
|
1683
|
+
const rows = await db.order.getMany({
|
|
1698
1684
|
where: x => x.deliveryAddress.exists()
|
|
1699
1685
|
});
|
|
1700
1686
|
}
|
|
@@ -1715,7 +1701,7 @@ const db = map.sqlite('demo.db');
|
|
|
1715
1701
|
getRows();
|
|
1716
1702
|
|
|
1717
1703
|
async function getRows() {
|
|
1718
|
-
const rows = await db.order.
|
|
1704
|
+
const rows = await db.order.getMany({
|
|
1719
1705
|
where: y => y.lines.any(x => x.product.contains('guitar'))
|
|
1720
1706
|
//equivalent syntax:
|
|
1721
1707
|
//where: x => x.lines.product.contains('guitar')
|
|
@@ -1731,7 +1717,7 @@ const db = map.sqlite('demo.db');
|
|
|
1731
1717
|
getRows();
|
|
1732
1718
|
|
|
1733
1719
|
async function getRows() {
|
|
1734
|
-
const rows = await db.order.
|
|
1720
|
+
const rows = await db.order.getMany({
|
|
1735
1721
|
where: y => y.lines.all(x => x.product.contains('a'))
|
|
1736
1722
|
});
|
|
1737
1723
|
}
|
|
@@ -1745,7 +1731,7 @@ const db = map.sqlite('demo.db');
|
|
|
1745
1731
|
getRows();
|
|
1746
1732
|
|
|
1747
1733
|
async function getRows() {
|
|
1748
|
-
const rows = await db.order.
|
|
1734
|
+
const rows = await db.order.getMany({
|
|
1749
1735
|
where: y => y.lines.none(x => x.product.equal('Magic wand'))
|
|
1750
1736
|
});
|
|
1751
1737
|
}
|
|
@@ -1794,7 +1780,8 @@ async function execute() {
|
|
|
1794
1780
|
- **`json`** and **`jsonOf<T>`** are represented as an object or array in javascript and maps to JSON, JSONB, NVARCHAR(max) or TEXT (sqlite) in sql.
|
|
1795
1781
|
|
|
1796
1782
|
<sub>π map.ts</sub>
|
|
1797
|
-
|
|
1783
|
+
|
|
1784
|
+
```ts
|
|
1798
1785
|
import orange from 'orange-orm';
|
|
1799
1786
|
|
|
1800
1787
|
interface Pet {
|
|
@@ -1817,7 +1804,8 @@ const map = orange.map(x => ({
|
|
|
1817
1804
|
}));
|
|
1818
1805
|
```
|
|
1819
1806
|
<sub>π map.js</sub>
|
|
1820
|
-
|
|
1807
|
+
|
|
1808
|
+
```js
|
|
1821
1809
|
import orange from 'orange-orm';
|
|
1822
1810
|
|
|
1823
1811
|
/**
|
|
@@ -1844,6 +1832,138 @@ const map = orange.map(x => ({
|
|
|
1844
1832
|
```
|
|
1845
1833
|
</details>
|
|
1846
1834
|
|
|
1835
|
+
<details id="enums"><summary><strong>Enums</strong></summary>
|
|
1836
|
+
<p>Enums can be defined using object literals, arrays, or TypeScript enums. The <strong><i>enum(...)</i></strong> method uses literal types when possible, so prefer patterns that keep literals (inline objects, <code>as const</code>, or <code>Object.freeze</code> in JS).</p>
|
|
1837
|
+
|
|
1838
|
+
<sub>π map.ts (TypeScript enum)</sub>
|
|
1839
|
+
|
|
1840
|
+
```ts
|
|
1841
|
+
enum CountryCode {
|
|
1842
|
+
NORWAY = 'NO',
|
|
1843
|
+
SWEDEN = 'SE',
|
|
1844
|
+
DENMARK = 'DK',
|
|
1845
|
+
FINLAND = 'FI',
|
|
1846
|
+
ICELAND = 'IS',
|
|
1847
|
+
GERMANY = 'DE',
|
|
1848
|
+
FRANCE = 'FR',
|
|
1849
|
+
NETHERLANDS = 'NL',
|
|
1850
|
+
SPAIN = 'ES',
|
|
1851
|
+
ITALY = 'IT',
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
const map = orange.map(x => ({
|
|
1855
|
+
deliveryAddress: x.table('deliveryAddress').map(({ column }) => ({
|
|
1856
|
+
id: column('id').numeric().primary(),
|
|
1857
|
+
name: column('name').string(),
|
|
1858
|
+
street: column('street').string(),
|
|
1859
|
+
postalCode: column('postalCode').string(),
|
|
1860
|
+
postalPlace: column('postalPlace').string(),
|
|
1861
|
+
countryCode: column('countryCode').string().enum(CountryCode),
|
|
1862
|
+
}))
|
|
1863
|
+
}));
|
|
1864
|
+
```
|
|
1865
|
+
|
|
1866
|
+
|
|
1867
|
+
<sub>π map.ts (as const)</sub>
|
|
1868
|
+
|
|
1869
|
+
```ts
|
|
1870
|
+
const Countries = {
|
|
1871
|
+
NORWAY: 'NO',
|
|
1872
|
+
SWEDEN: 'SE',
|
|
1873
|
+
DENMARK: 'DK',
|
|
1874
|
+
FINLAND: 'FI',
|
|
1875
|
+
ICELAND: 'IS',
|
|
1876
|
+
GERMANY: 'DE',
|
|
1877
|
+
FRANCE: 'FR',
|
|
1878
|
+
NETHERLANDS: 'NL',
|
|
1879
|
+
SPAIN: 'ES',
|
|
1880
|
+
ITALY: 'IT',
|
|
1881
|
+
} as const;
|
|
1882
|
+
|
|
1883
|
+
const map = orange.map(x => ({
|
|
1884
|
+
deliveryAddress: x.table('deliveryAddress').map(({ column }) => ({
|
|
1885
|
+
id: column('id').numeric().primary(),
|
|
1886
|
+
name: column('name').string(),
|
|
1887
|
+
street: column('street').string(),
|
|
1888
|
+
postalCode: column('postalCode').string(),
|
|
1889
|
+
postalPlace: column('postalPlace').string(),
|
|
1890
|
+
countryCode: column('countryCode').string().enum(Countries),
|
|
1891
|
+
}))
|
|
1892
|
+
}));
|
|
1893
|
+
```
|
|
1894
|
+
|
|
1895
|
+
<sub>π map.ts (array)</sub>
|
|
1896
|
+
|
|
1897
|
+
```ts
|
|
1898
|
+
const map = orange.map(x => ({
|
|
1899
|
+
deliveryAddress: x.table('deliveryAddress').map(({ column }) => ({
|
|
1900
|
+
id: column('id').numeric().primary(),
|
|
1901
|
+
name: column('name').string(),
|
|
1902
|
+
street: column('street').string(),
|
|
1903
|
+
postalCode: column('postalCode').string(),
|
|
1904
|
+
postalPlace: column('postalPlace').string(),
|
|
1905
|
+
countryCode: column('countryCode').string().enum(
|
|
1906
|
+
['NO', 'SE', 'DK', 'FI', 'IS', 'DE', 'FR', 'NL', 'ES', 'IT']
|
|
1907
|
+
),
|
|
1908
|
+
}))
|
|
1909
|
+
}));
|
|
1910
|
+
```
|
|
1911
|
+
|
|
1912
|
+
<sub>π map.ts (literal object)</sub>
|
|
1913
|
+
|
|
1914
|
+
```ts
|
|
1915
|
+
const map = orange.map(x => ({
|
|
1916
|
+
deliveryAddress: x.table('deliveryAddress').map(({ column }) => ({
|
|
1917
|
+
id: column('id').numeric().primary(),
|
|
1918
|
+
name: column('name').string(),
|
|
1919
|
+
street: column('street').string(),
|
|
1920
|
+
postalCode: column('postalCode').string(),
|
|
1921
|
+
postalPlace: column('postalPlace').string(),
|
|
1922
|
+
countryCode: column('countryCode').string().enum({
|
|
1923
|
+
NORWAY: 'NO',
|
|
1924
|
+
SWEDEN: 'SE',
|
|
1925
|
+
DENMARK: 'DK',
|
|
1926
|
+
FINLAND: 'FI',
|
|
1927
|
+
ICELAND: 'IS',
|
|
1928
|
+
GERMANY: 'DE',
|
|
1929
|
+
FRANCE: 'FR',
|
|
1930
|
+
NETHERLANDS: 'NL',
|
|
1931
|
+
SPAIN: 'ES',
|
|
1932
|
+
ITALY: 'IT',
|
|
1933
|
+
}),
|
|
1934
|
+
}))
|
|
1935
|
+
}));
|
|
1936
|
+
```
|
|
1937
|
+
|
|
1938
|
+
<sub>π map.js (Object.freeze)</sub>
|
|
1939
|
+
|
|
1940
|
+
```js
|
|
1941
|
+
const Countries = Object.freeze({
|
|
1942
|
+
NORWAY: 'NO',
|
|
1943
|
+
SWEDEN: 'SE',
|
|
1944
|
+
DENMARK: 'DK',
|
|
1945
|
+
FINLAND: 'FI',
|
|
1946
|
+
ICELAND: 'IS',
|
|
1947
|
+
GERMANY: 'DE',
|
|
1948
|
+
FRANCE: 'FR',
|
|
1949
|
+
NETHERLANDS: 'NL',
|
|
1950
|
+
SPAIN: 'ES',
|
|
1951
|
+
ITALY: 'IT',
|
|
1952
|
+
});
|
|
1953
|
+
|
|
1954
|
+
const map = orange.map(x => ({
|
|
1955
|
+
deliveryAddress: x.table('deliveryAddress').map(({ column }) => ({
|
|
1956
|
+
id: column('id').numeric().primary(),
|
|
1957
|
+
name: column('name').string(),
|
|
1958
|
+
street: column('street').string(),
|
|
1959
|
+
postalCode: column('postalCode').string(),
|
|
1960
|
+
postalPlace: column('postalPlace').string(),
|
|
1961
|
+
countryCode: column('countryCode').string().enum(Countries),
|
|
1962
|
+
}))
|
|
1963
|
+
}));
|
|
1964
|
+
```
|
|
1965
|
+
</details>
|
|
1966
|
+
|
|
1847
1967
|
<details id="default-values"><summary><strong>Default values</strong></summary>
|
|
1848
1968
|
<p>Utilizing default values can be especially useful for automatically populating these fields when the underlying database doesn't offer native support for default value generation.
|
|
1849
1969
|
|
|
@@ -1870,7 +1990,7 @@ export default map;
|
|
|
1870
1990
|
<p>In the previous sections you have already seen the <strong><i>notNull()</i></strong> validator being used on some columns. This will not only generate correct typescript mapping, but also throw an error if value is set to null or undefined. However, sometimes we do not want the notNull-validator to be run on inserts. Typically, when we have an autoincremental key or server generated uuid, it does not make sense to check for null on insert. This is where <strong><i>notNullExceptInsert()</strong></i> comes to rescue. You can also create your own custom validator as shown below. The last kind of validator, is the <a href="https://ajv.js.org/json-schema.html">ajv JSON schema validator</a>. This can be used on json columns as well as any other column type.</p>
|
|
1871
1991
|
|
|
1872
1992
|
<sub>π map.ts</sub>
|
|
1873
|
-
```
|
|
1993
|
+
```ts
|
|
1874
1994
|
import orange from 'orange-orm';
|
|
1875
1995
|
|
|
1876
1996
|
interface Pet {
|
|
@@ -1901,7 +2021,7 @@ const map = orange.map(x => ({
|
|
|
1901
2021
|
export default map;
|
|
1902
2022
|
```
|
|
1903
2023
|
<sub>π map.js</sub>
|
|
1904
|
-
```
|
|
2024
|
+
```js
|
|
1905
2025
|
import orange from 'orange-orm';
|
|
1906
2026
|
|
|
1907
2027
|
/**
|
|
@@ -2039,6 +2159,43 @@ async function getRows() {
|
|
|
2039
2159
|
```
|
|
2040
2160
|
</details>
|
|
2041
2161
|
|
|
2162
|
+
<details id="sqlite-user-defined-functions"><summary><strong>SQLite user-defined functions</strong></summary>
|
|
2163
|
+
|
|
2164
|
+
You can register custom SQLite functions on the connection using `db.function(name, fn)`.
|
|
2165
|
+
|
|
2166
|
+
The `fn` argument is your user-defined callback:
|
|
2167
|
+
- It is invoked by SQLite every time the SQL function is called.
|
|
2168
|
+
- Callback arguments are positional and match the SQL call (for example, `my_fn(a, b)` maps to `(a, b)`).
|
|
2169
|
+
- Return a SQLite-compatible scalar value (for example text, number, or `null`).
|
|
2170
|
+
- Throwing inside the callback fails the SQL statement.
|
|
2171
|
+
|
|
2172
|
+
`db.function(...)` is sync-only in Node and Deno, but can be async or sync in Bun.
|
|
2173
|
+
|
|
2174
|
+
```javascript
|
|
2175
|
+
import map from './map';
|
|
2176
|
+
const db = map.sqlite('demo.db');
|
|
2177
|
+
|
|
2178
|
+
await db.function('add_prefix', (text, prefix) => `${prefix}${text}`);
|
|
2179
|
+
|
|
2180
|
+
const rows = await db.query(
|
|
2181
|
+
"select id, name, add_prefix(name, '[VIP] ') as prefixedName from customer"
|
|
2182
|
+
);
|
|
2183
|
+
```
|
|
2184
|
+
|
|
2185
|
+
If you need the function inside a transaction, register it within the transaction callback to ensure it is available on that connection.
|
|
2186
|
+
|
|
2187
|
+
```javascript
|
|
2188
|
+
await db.transaction(async (db) => {
|
|
2189
|
+
await db.function('add_prefix', (text, prefix) => `${prefix}${text}`);
|
|
2190
|
+
return db.query(
|
|
2191
|
+
"select id, name, add_prefix(name, '[VIP] ') as prefixedName from customer"
|
|
2192
|
+
);
|
|
2193
|
+
});
|
|
2194
|
+
```
|
|
2195
|
+
|
|
2196
|
+
`db.function(...)` is available on direct SQLite connections (for example `map.sqlite(...)`) and not through `map.http(...)`.
|
|
2197
|
+
</details>
|
|
2198
|
+
|
|
2042
2199
|
<details id="aggregates"><summary><strong>Aggregate functions</strong></summary>
|
|
2043
2200
|
|
|
2044
2201
|
You can count records and aggregate numerical columns. This can either be done across rows or separately for each row.
|
|
@@ -2060,7 +2217,7 @@ const db = map.sqlite('demo.db');
|
|
|
2060
2217
|
getRows();
|
|
2061
2218
|
|
|
2062
2219
|
async function getRows() {
|
|
2063
|
-
const orders = await db.order.
|
|
2220
|
+
const orders = await db.order.getMany({
|
|
2064
2221
|
numberOfLines: x => x.count(x => x.lines.id),
|
|
2065
2222
|
totalAmount: x => x.sum(x => lines.amount),
|
|
2066
2223
|
balance: x => x.customer.balance
|
|
@@ -2111,7 +2268,7 @@ async function getCount() {
|
|
|
2111
2268
|
|
|
2112
2269
|
<sub>π map.ts</sub>
|
|
2113
2270
|
|
|
2114
|
-
```
|
|
2271
|
+
```ts
|
|
2115
2272
|
import orange from 'orange-orm';
|
|
2116
2273
|
|
|
2117
2274
|
const map = orange.map(x => ({
|
|
@@ -2127,7 +2284,7 @@ export default map;
|
|
|
2127
2284
|
```
|
|
2128
2285
|
<sub>π sensitive.ts</sub>
|
|
2129
2286
|
|
|
2130
|
-
```
|
|
2287
|
+
```ts
|
|
2131
2288
|
import map from './map';
|
|
2132
2289
|
const db = map.sqlite('demo.db');
|
|
2133
2290
|
|