bigal 15.10.1 → 15.10.2
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/CHANGELOG.md +2 -0
- package/docs/views-and-readonly-repositories.md +153 -0
- package/eslint.config.mjs +0 -8
- package/package.json +11 -17
package/CHANGELOG.md
CHANGED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Views and Readonly Repositories
|
|
2
|
+
|
|
3
|
+
This guide covers how to use BigAl with PostgreSQL views by creating readonly repositories.
|
|
4
|
+
Readonly repositories expose only read operations (`findOne`, `find`, `count`),
|
|
5
|
+
making them a natural fit for database views.
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Overview](#overview)
|
|
10
|
+
- [Defining a Model for a View](#defining-a-model-for-a-view)
|
|
11
|
+
- [Standalone Model](#standalone-model)
|
|
12
|
+
- [Inheriting from an Existing Model](#inheriting-from-an-existing-model)
|
|
13
|
+
- [Using a Schema](#using-a-schema)
|
|
14
|
+
- [Initializing the Repository](#initializing-the-repository)
|
|
15
|
+
- [Querying](#querying)
|
|
16
|
+
- [Available Methods](#available-methods)
|
|
17
|
+
|
|
18
|
+
## Overview
|
|
19
|
+
|
|
20
|
+
BigAl does not distinguish between tables and views at the ORM level. Both use the `@table()` decorator.
|
|
21
|
+
Setting `readonly: true` in the decorator options causes `initialize()` to return a `ReadonlyRepository`
|
|
22
|
+
instead of a `Repository`. The `ReadonlyRepository` type does not include `create`, `update`, or `destroy` methods,
|
|
23
|
+
so TypeScript will catch accidental write attempts at compile time.
|
|
24
|
+
|
|
25
|
+
BigAl does not create or manage database schemas, so you are responsible for creating the view in PostgreSQL (e.g. via a migration tool).
|
|
26
|
+
|
|
27
|
+
## Defining a Model for a View
|
|
28
|
+
|
|
29
|
+
### Standalone Model
|
|
30
|
+
|
|
31
|
+
Define a model class that maps to the view's columns, and set `readonly: true` in the `@table()` decorator:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { column, primaryColumn, table, Entity } from 'bigal';
|
|
35
|
+
|
|
36
|
+
@table({
|
|
37
|
+
name: 'product_summaries',
|
|
38
|
+
readonly: true,
|
|
39
|
+
})
|
|
40
|
+
export class ProductSummary extends Entity {
|
|
41
|
+
@primaryColumn({ type: 'integer' })
|
|
42
|
+
public id!: number;
|
|
43
|
+
|
|
44
|
+
@column({ type: 'string', required: true })
|
|
45
|
+
public name!: string;
|
|
46
|
+
|
|
47
|
+
@column({ type: 'string', required: true, name: 'store_name' })
|
|
48
|
+
public storeName!: string;
|
|
49
|
+
|
|
50
|
+
@column({ type: 'integer', required: true, name: 'category_count' })
|
|
51
|
+
public categoryCount!: number;
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This maps to a view created in PostgreSQL:
|
|
56
|
+
|
|
57
|
+
```sql
|
|
58
|
+
CREATE VIEW product_summaries AS
|
|
59
|
+
SELECT
|
|
60
|
+
p.id,
|
|
61
|
+
p.name,
|
|
62
|
+
s.name AS store_name,
|
|
63
|
+
COUNT(pc.category_id) AS category_count
|
|
64
|
+
FROM products p
|
|
65
|
+
JOIN stores s ON s.id = p.store_id
|
|
66
|
+
LEFT JOIN product_categories pc ON pc.product_id = p.id
|
|
67
|
+
GROUP BY p.id, p.name, s.name;
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Inheriting from an Existing Model
|
|
71
|
+
|
|
72
|
+
If your view has the same columns as an existing table (or a subset), you can extend the existing model to reuse its column definitions:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { table } from 'bigal';
|
|
76
|
+
import { Product } from './Product';
|
|
77
|
+
|
|
78
|
+
@table({
|
|
79
|
+
name: 'readonly_products',
|
|
80
|
+
readonly: true,
|
|
81
|
+
})
|
|
82
|
+
export class ReadonlyProduct extends Product {}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
This avoids duplicating `@column()` definitions. The `ReadonlyProduct` model inherits all column metadata from `Product` but maps to the `readonly_products` view and produces a `ReadonlyRepository`.
|
|
86
|
+
|
|
87
|
+
### Using a Schema
|
|
88
|
+
|
|
89
|
+
If the view lives in a non-default schema, specify it with the `schema` option:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
@table({
|
|
93
|
+
schema: 'reporting',
|
|
94
|
+
name: 'product_summaries',
|
|
95
|
+
readonly: true,
|
|
96
|
+
})
|
|
97
|
+
export class ProductSummary extends Entity {
|
|
98
|
+
// ...
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Initializing the Repository
|
|
103
|
+
|
|
104
|
+
Include the model in the `initialize()` call. BigAl automatically creates a `ReadonlyRepository` for models marked with `readonly: true`.
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { initialize, ReadonlyRepository } from 'bigal';
|
|
108
|
+
import { Product, Store, ProductSummary } from './models';
|
|
109
|
+
|
|
110
|
+
const repositoriesByName = initialize({
|
|
111
|
+
models: [Product, Store, ProductSummary],
|
|
112
|
+
pool,
|
|
113
|
+
readonlyPool,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const productSummaryRepository = repositoriesByName.ProductSummary as ReadonlyRepository<ProductSummary>;
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Querying
|
|
120
|
+
|
|
121
|
+
Readonly repositories support the same query methods and chaining as regular repositories:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
// Find multiple records with filtering, sorting, and pagination
|
|
125
|
+
const summaries = await productSummaryRepository
|
|
126
|
+
.find()
|
|
127
|
+
.where({
|
|
128
|
+
storeName: { contains: 'Acme' },
|
|
129
|
+
})
|
|
130
|
+
.sort('categoryCount desc')
|
|
131
|
+
.limit(10);
|
|
132
|
+
|
|
133
|
+
// Find a single record
|
|
134
|
+
const summary = await productSummaryRepository.findOne().where({ id: 42 });
|
|
135
|
+
|
|
136
|
+
// Count matching records
|
|
137
|
+
const count = await productSummaryRepository.count().where({
|
|
138
|
+
categoryCount: { '>': 5 },
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
All query features documented in the [README](../README.md) work with readonly repositories, including `where` operators, `sort`, `skip`, `limit`, `paginate`, `populate`, `join`, `withCount`, and `distinctOn`.
|
|
143
|
+
|
|
144
|
+
## Available Methods
|
|
145
|
+
|
|
146
|
+
| Method | `Repository` | `ReadonlyRepository` |
|
|
147
|
+
| ----------- | ------------ | -------------------- |
|
|
148
|
+
| `findOne()` | Yes | Yes |
|
|
149
|
+
| `find()` | Yes | Yes |
|
|
150
|
+
| `count()` | Yes | Yes |
|
|
151
|
+
| `create()` | Yes | No |
|
|
152
|
+
| `update()` | Yes | No |
|
|
153
|
+
| `destroy()` | Yes | No |
|
package/eslint.config.mjs
CHANGED
|
@@ -3,8 +3,6 @@ import { config } from 'eslint-config-decent';
|
|
|
3
3
|
export default [
|
|
4
4
|
...config({
|
|
5
5
|
tsconfigRootDir: import.meta.dirname,
|
|
6
|
-
enableJest: false,
|
|
7
|
-
enableVitest: false,
|
|
8
6
|
enableTestingLibrary: false,
|
|
9
7
|
}),
|
|
10
8
|
{
|
|
@@ -13,10 +11,4 @@ export default [
|
|
|
13
11
|
'@typescript-eslint/no-invalid-void-type': 'off',
|
|
14
12
|
},
|
|
15
13
|
},
|
|
16
|
-
{
|
|
17
|
-
files: ['tests/**/*.ts'],
|
|
18
|
-
rules: {
|
|
19
|
-
'@typescript-eslint/no-non-null-assertion': 'off',
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
14
|
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bigal",
|
|
3
|
-
"version": "15.10.
|
|
3
|
+
"version": "15.10.2",
|
|
4
4
|
"description": "A fast and lightweight orm for postgres and node.js, written in typescript.",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -32,40 +32,34 @@
|
|
|
32
32
|
"engines": {
|
|
33
33
|
"node": ">=20.11.0"
|
|
34
34
|
},
|
|
35
|
-
"dependencies": {},
|
|
36
35
|
"devDependencies": {
|
|
37
|
-
"@faker-js/faker": "10.
|
|
36
|
+
"@faker-js/faker": "10.3.0",
|
|
38
37
|
"@semantic-release/changelog": "6.0.3",
|
|
39
38
|
"@semantic-release/commit-analyzer": "13.0.1",
|
|
40
39
|
"@semantic-release/git": "10.0.1",
|
|
41
|
-
"@semantic-release/github": "12.0.
|
|
42
|
-
"@semantic-release/npm": "13.1.
|
|
40
|
+
"@semantic-release/github": "12.0.6",
|
|
41
|
+
"@semantic-release/npm": "13.1.4",
|
|
43
42
|
"@semantic-release/release-notes-generator": "14.1.0",
|
|
44
|
-
"@types/
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"chai": "6.2.2",
|
|
48
|
-
"eslint": "9.39.2",
|
|
49
|
-
"eslint-config-decent": "3.1.115",
|
|
43
|
+
"@types/node": ">=22",
|
|
44
|
+
"eslint": "10.0.2",
|
|
45
|
+
"eslint-config-decent": "4.1.0",
|
|
50
46
|
"husky": "9.1.7",
|
|
51
47
|
"lint-staged": "16.2.7",
|
|
52
48
|
"markdownlint-cli": "0.47.0",
|
|
53
|
-
"mocha": "11.7.5",
|
|
54
49
|
"npm-run-all2": "8.0.4",
|
|
55
50
|
"pinst": "3.0.0",
|
|
56
51
|
"prettier": "3.8.1",
|
|
57
52
|
"semantic-release": "25.0.3",
|
|
58
53
|
"strict-event-emitter-types": "2.0.0",
|
|
59
|
-
"postgres-pool": "11.0.
|
|
60
|
-
"ts-mockito": "2.6.1",
|
|
61
|
-
"ts-node": "10.9.2",
|
|
54
|
+
"postgres-pool": "11.0.3",
|
|
62
55
|
"typescript": "5.9.3",
|
|
63
|
-
"unbuild": "3.6.1"
|
|
56
|
+
"unbuild": "3.6.1",
|
|
57
|
+
"vitest": "4.0.18"
|
|
64
58
|
},
|
|
65
59
|
"scripts": {
|
|
66
60
|
"build": "unbuild",
|
|
67
61
|
"check:types": "tsc --noEmit --skipLibCheck",
|
|
68
|
-
"test": "
|
|
62
|
+
"test": "npm run check:types && vitest run",
|
|
69
63
|
"lint:markdown": "prettier --write '**/*.md' '!**/node_modules/**' '!**/dist/**' && markdownlint '**/*.md' --ignore '**/node_modules/**' --ignore '**/dist/**' --config=.github/linters/.markdown-lint.yml --fix",
|
|
70
64
|
"lint:code": "eslint --fix",
|
|
71
65
|
"lint": "run-p lint:*",
|