orange-orm 4.9.0 → 4.10.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +120 -0
- package/deno.lock +76 -0
- package/dist/index.browser.mjs +192 -56
- package/dist/index.mjs +292 -179
- package/docs/changelog.md +2 -0
- package/other.db +0 -0
- package/package.json +1 -1
- package/src/bunPg/newDatabase.js +3 -14
- package/src/bunPg/newTransaction.js +1 -0
- package/src/bunSqlite/newDatabase.js +22 -13
- package/src/bunSqlite/newTransaction.js +1 -0
- package/src/client/index.js +8 -1
- package/src/client/netAdapter.js +13 -2
- package/src/d1/newDatabase.js +3 -13
- package/src/d1/newTransaction.js +1 -0
- package/src/getTSDefinition.js +14 -1
- package/src/hostExpress.js +8 -3
- package/src/hostLocal.js +66 -6
- package/src/map2.d.ts +18 -1
- package/src/mssql/newDatabase.js +3 -13
- package/src/mssql/newTransaction.js +1 -0
- package/src/mySql/newDatabase.js +3 -13
- package/src/mySql/newTransaction.js +1 -0
- package/src/nodeSqlite/newDatabase.js +29 -18
- package/src/nodeSqlite/newTransaction.js +1 -0
- package/src/oracle/newDatabase.js +3 -13
- package/src/oracle/newTransaction.js +1 -0
- package/src/pg/newDatabase.js +4 -16
- package/src/pg/newTransaction.js +1 -0
- package/src/pglite/newDatabase.js +3 -14
- package/src/pglite/newTransaction.js +1 -0
- package/src/sap/newDatabase.js +3 -13
- package/src/sap/newTransaction.js +1 -0
- package/src/sqlite3/newDatabase.js +22 -13
- package/src/sqlite3/newTransaction.js +1 -0
- package/src/sqliteFunction.js +20 -0
- package/src/table/begin.js +0 -1
- package/src/table/commit.js +21 -1
- package/src/table/query/singleQuery/joinSql/newShallowJoinSqlCore.js +6 -4
- package/src/table/rollback.js +22 -2
- package/src/tedious/newDatabase.js +3 -13
- package/src/tedious/newTransaction.js +1 -0
package/README.md
CHANGED
|
@@ -309,6 +309,31 @@ await pool.close(); // closes all pooled connections
|
|
|
309
309
|
__Why close ?__
|
|
310
310
|
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
311
|
|
|
312
|
+
__SQLite user-defined functions__
|
|
313
|
+
You can register custom SQLite functions on the connection using `db.function(name, fn)`.
|
|
314
|
+
|
|
315
|
+
```javascript
|
|
316
|
+
import map from './map';
|
|
317
|
+
const db = map.sqlite('demo.db');
|
|
318
|
+
|
|
319
|
+
db.function('add_prefix', (text, prefix) => `${prefix}${text}`);
|
|
320
|
+
|
|
321
|
+
const rows = await db.query(
|
|
322
|
+
"select id, name, add_prefix(name, '[VIP] ') as prefixedName from customer"
|
|
323
|
+
);
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
If you need the function inside a transaction, register it within the transaction callback to ensure it is available on that connection.
|
|
327
|
+
|
|
328
|
+
```javascript
|
|
329
|
+
await db.transaction(async (db) => {
|
|
330
|
+
db.function('add_prefix', (text, prefix) => `${prefix}${text}`);
|
|
331
|
+
return db.query(
|
|
332
|
+
"select id, name, add_prefix(name, '[VIP] ') as prefixedName from customer"
|
|
333
|
+
);
|
|
334
|
+
});
|
|
335
|
+
```
|
|
336
|
+
|
|
312
337
|
__From the browser__
|
|
313
338
|
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)
|
|
314
339
|
<sub>📄 server.ts</sub>
|
|
@@ -1273,6 +1298,101 @@ async function updateRows() {
|
|
|
1273
1298
|
|
|
1274
1299
|
}
|
|
1275
1300
|
|
|
1301
|
+
```
|
|
1302
|
+
|
|
1303
|
+
__Row Level Security (Postgres)__
|
|
1304
|
+
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.
|
|
1305
|
+
|
|
1306
|
+
<sub>📄 setup.sql</sub>
|
|
1307
|
+
|
|
1308
|
+
```sql
|
|
1309
|
+
create role rls_app_user nologin;
|
|
1310
|
+
|
|
1311
|
+
create table tenant_data (
|
|
1312
|
+
id serial primary key,
|
|
1313
|
+
tenant_id int not null,
|
|
1314
|
+
value text not null
|
|
1315
|
+
);
|
|
1316
|
+
|
|
1317
|
+
alter table tenant_data enable row level security;
|
|
1318
|
+
create policy tenant_data_tenant on tenant_data
|
|
1319
|
+
using (tenant_id = current_setting('app.tenant_id', true)::int);
|
|
1320
|
+
|
|
1321
|
+
grant select, insert, update, delete on tenant_data to rls_app_user;
|
|
1322
|
+
|
|
1323
|
+
insert into tenant_data (tenant_id, value) values
|
|
1324
|
+
(1, 'alpha'),
|
|
1325
|
+
(1, 'beta'),
|
|
1326
|
+
(2, 'gamma');
|
|
1327
|
+
```
|
|
1328
|
+
|
|
1329
|
+
<sub>📄 server.ts</sub>
|
|
1330
|
+
|
|
1331
|
+
```javascript
|
|
1332
|
+
import map from './map';
|
|
1333
|
+
import { json } from 'body-parser';
|
|
1334
|
+
import express from 'express';
|
|
1335
|
+
import cors from 'cors';
|
|
1336
|
+
|
|
1337
|
+
const db = map.postgres('postgres://postgres:postgres@localhost/postgres');
|
|
1338
|
+
|
|
1339
|
+
express().disable('x-powered-by')
|
|
1340
|
+
.use(json({ limit: '100mb' }))
|
|
1341
|
+
.use(cors())
|
|
1342
|
+
.use('/orange', validateToken)
|
|
1343
|
+
.use('/orange', db.express({
|
|
1344
|
+
hooks: {
|
|
1345
|
+
transaction: {
|
|
1346
|
+
afterBegin: async (db, req) => {
|
|
1347
|
+
const tenantId = Number.parseInt(String(req.user?.tenantId ?? ''), 10);
|
|
1348
|
+
if (!Number.isFinite(tenantId)) throw new Error('Missing tenant id');
|
|
1349
|
+
await db.query('set local role rls_app_user');
|
|
1350
|
+
await db.query({
|
|
1351
|
+
sql: 'select set_config(\'app.tenant_id\', ?, true)',
|
|
1352
|
+
parameters: [String(tenantId)]
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
}))
|
|
1358
|
+
.listen(3000, () => console.log('Example app listening on port 3000!'));
|
|
1359
|
+
|
|
1360
|
+
function validateToken(req, res, next) {
|
|
1361
|
+
const authHeader = req.headers.authorization;
|
|
1362
|
+
if (!authHeader) return res.status(401).json({ error: 'Authorization header missing' });
|
|
1363
|
+
try {
|
|
1364
|
+
const token = authHeader.replace(/^Bearer\s+/i, '');
|
|
1365
|
+
const payload = decodeFakeJwt(token); // demo-only, do not use in production
|
|
1366
|
+
req.user = { tenantId: String(payload.tenantId) };
|
|
1367
|
+
return next();
|
|
1368
|
+
} catch (error) {
|
|
1369
|
+
return res.status(401).json({ error: 'Invalid token' });
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
function decodeFakeJwt(token) {
|
|
1374
|
+
// Demo-only format: "tenant:<id>"
|
|
1375
|
+
const match = /^tenant:(\d+)$/.exec(token);
|
|
1376
|
+
if (!match) throw new Error('Invalid demo token');
|
|
1377
|
+
return { tenantId: Number(match[1]) };
|
|
1378
|
+
}
|
|
1379
|
+
```
|
|
1380
|
+
|
|
1381
|
+
<sub>📄 browser.ts</sub>
|
|
1382
|
+
|
|
1383
|
+
```javascript
|
|
1384
|
+
import map from './map';
|
|
1385
|
+
|
|
1386
|
+
const db = map.http('http://localhost:3000/orange');
|
|
1387
|
+
|
|
1388
|
+
db.interceptors.request.use((config) => {
|
|
1389
|
+
// Demo-only token: payload carries the tenant id so we can verify filtering
|
|
1390
|
+
config.headers.Authorization = 'Bearer tenant:1';
|
|
1391
|
+
return config;
|
|
1392
|
+
});
|
|
1393
|
+
|
|
1394
|
+
const rows = await db.tenant_data.getMany();
|
|
1395
|
+
// rows => [{ id: 1, tenant_id: 1, value: 'alpha' }, { id: 2, tenant_id: 1, value: 'beta' }]
|
|
1276
1396
|
```
|
|
1277
1397
|
</details>
|
|
1278
1398
|
|
package/deno.lock
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "5",
|
|
3
|
+
"specifiers": {
|
|
4
|
+
"jsr:@std/assert@*": "1.0.17",
|
|
5
|
+
"jsr:@std/assert@^1.0.17": "1.0.17",
|
|
6
|
+
"jsr:@std/internal@^1.0.12": "1.0.12",
|
|
7
|
+
"jsr:@std/path@*": "1.1.4",
|
|
8
|
+
"jsr:@std/testing@*": "1.0.17"
|
|
9
|
+
},
|
|
10
|
+
"jsr": {
|
|
11
|
+
"@std/assert@1.0.17": {
|
|
12
|
+
"integrity": "df5ebfffe77c03b3fa1401e11c762cc8f603d51021c56c4d15a8c7ab45e90dbe",
|
|
13
|
+
"dependencies": [
|
|
14
|
+
"jsr:@std/internal"
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
"@std/internal@1.0.12": {
|
|
18
|
+
"integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027"
|
|
19
|
+
},
|
|
20
|
+
"@std/path@1.1.4": {
|
|
21
|
+
"integrity": "1d2d43f39efb1b42f0b1882a25486647cb851481862dc7313390b2bb044314b5",
|
|
22
|
+
"dependencies": [
|
|
23
|
+
"jsr:@std/internal"
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
"@std/testing@1.0.17": {
|
|
27
|
+
"integrity": "87bdc2700fa98249d48a17cd72413352d3d3680dcfbdb64947fd0982d6bbf681",
|
|
28
|
+
"dependencies": [
|
|
29
|
+
"jsr:@std/assert@^1.0.17",
|
|
30
|
+
"jsr:@std/internal"
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"workspace": {
|
|
35
|
+
"packageJson": {
|
|
36
|
+
"dependencies": [
|
|
37
|
+
"npm:@cloudflare/workers-types@^4.20241106.0",
|
|
38
|
+
"npm:@electric-sql/pglite@0.3",
|
|
39
|
+
"npm:@lroal/on-change@^4.0.2",
|
|
40
|
+
"npm:@rollup/plugin-commonjs@^28.0.2",
|
|
41
|
+
"npm:@rollup/plugin-json@^6.1.0",
|
|
42
|
+
"npm:@rollup/plugin-node-resolve@13",
|
|
43
|
+
"npm:@tediousjs/connection-string@~0.4.1",
|
|
44
|
+
"npm:@types/express@^4.17.13",
|
|
45
|
+
"npm:@types/oracledb@^6.0.4",
|
|
46
|
+
"npm:@types/tedious@^4.0.14",
|
|
47
|
+
"npm:@typescript-eslint/eslint-plugin@6",
|
|
48
|
+
"npm:@typescript-eslint/parser@6",
|
|
49
|
+
"npm:@vitest/coverage-v8@^3.2.4",
|
|
50
|
+
"npm:ajv@^8.17.1",
|
|
51
|
+
"npm:axios@^1.6.2",
|
|
52
|
+
"npm:better-sqlite3@^11.8.1",
|
|
53
|
+
"npm:cors@^2.8.5",
|
|
54
|
+
"npm:eslint-plugin-jest@^27.1.7",
|
|
55
|
+
"npm:eslint@^8.57.0",
|
|
56
|
+
"npm:express@^4.18.2",
|
|
57
|
+
"npm:fast-json-patch@^3.1.1",
|
|
58
|
+
"npm:findup-sync@5",
|
|
59
|
+
"npm:glob@^10.3.4 || ^11.0.2",
|
|
60
|
+
"npm:module-definition@4 || 5 || * || 6",
|
|
61
|
+
"npm:msnodesqlv8@^4.1.0",
|
|
62
|
+
"npm:mysql2@^3.9.4",
|
|
63
|
+
"npm:oracledb@^6.3.0",
|
|
64
|
+
"npm:owasp-dependency-check@^0.0.21",
|
|
65
|
+
"npm:pg-query-stream@^3.3.2",
|
|
66
|
+
"npm:pg@^8.5.1",
|
|
67
|
+
"npm:rfdc@^1.2.0",
|
|
68
|
+
"npm:rollup@^2.52.7",
|
|
69
|
+
"npm:tedious@19",
|
|
70
|
+
"npm:typescript@^5.4.5",
|
|
71
|
+
"npm:uuid@^8.3.2 || 9 || 10 || ^11.1.0",
|
|
72
|
+
"npm:vitest@^3.2.4"
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|