accio-orm 0.1.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/LICENSE +21 -0
- package/README.md +482 -0
- package/dist/connection/Connection.d.ts +29 -0
- package/dist/connection/Connection.js +63 -0
- package/dist/connection/types.d.ts +7 -0
- package/dist/connection/types.js +2 -0
- package/dist/decorators/Column.d.ts +23 -0
- package/dist/decorators/Column.js +34 -0
- package/dist/decorators/PrimaryColumn.d.ts +9 -0
- package/dist/decorators/PrimaryColumn.js +26 -0
- package/dist/decorators/Table.d.ts +10 -0
- package/dist/decorators/Table.js +21 -0
- package/dist/examples/test-connection.d.ts +1 -0
- package/dist/examples/test-connection.js +36 -0
- package/dist/examples/test-decorators.d.ts +1 -0
- package/dist/examples/test-decorators.js +40 -0
- package/dist/examples/test-metadata.d.ts +1 -0
- package/dist/examples/test-metadata.js +110 -0
- package/dist/examples/test-querybuilder.d.ts +1 -0
- package/dist/examples/test-querybuilder.js +142 -0
- package/dist/examples/test-repository.d.ts +1 -0
- package/dist/examples/test-repository.js +111 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +23 -0
- package/dist/metadata/MetadataStorage.d.ts +30 -0
- package/dist/metadata/MetadataStorage.js +82 -0
- package/dist/metadata/types.d.ts +7 -0
- package/dist/metadata/types.js +2 -0
- package/dist/query/QueryBuilder.d.ts +64 -0
- package/dist/query/QueryBuilder.js +180 -0
- package/dist/repository/Repository.d.ts +43 -0
- package/dist/repository/Repository.js +156 -0
- package/dist/utils/entityMapper.d.ts +9 -0
- package/dist/utils/entityMapper.js +22 -0
- package/package.json +72 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Adubiagbe Mustapha
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
# Accio 🪄
|
|
2
|
+
|
|
3
|
+
> The summoning charm for Postgres
|
|
4
|
+
|
|
5
|
+
Accio is a lightweight, type-safe TypeScript ORM for PostgreSQL, built from first principles with a focus on simplicity and developer experience.
|
|
6
|
+
|
|
7
|
+
[](https://www.typescriptlang.org/)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- ✨ **Decorator-based** entity definitions
|
|
13
|
+
- 🔒 **Type-safe** queries with TypeScript
|
|
14
|
+
- 🎯 **Simple and intuitive** API
|
|
15
|
+
- 🔗 **Fluent query builder** with method chaining
|
|
16
|
+
- 📦 **Zero dependencies** (except pg and reflect-metadata)
|
|
17
|
+
- 🎨 **Data Mapper pattern** for clean architecture
|
|
18
|
+
- 🚀 **Connection pooling** built-in
|
|
19
|
+
- 💪 **Full CRUD operations** out of the box
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
1. Install the package
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install accio-orm
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
2. Install `pg` (postgres driver)
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install pg
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
3. Install `reflect-metadata`
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install reflect-metadata
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Important: Import reflect-metadata
|
|
42
|
+
|
|
43
|
+
You **must** import `reflect-metadata` once at your application's entry point (before any Accio code runs):
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// src/index.ts or src/main.ts
|
|
47
|
+
import 'reflect-metadata';
|
|
48
|
+
|
|
49
|
+
// Now you can use Accio
|
|
50
|
+
import { connect, Table, Column, PrimaryColumn } from 'accio-orm';
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Why?** TypeScript decorators require `reflect-metadata` to be loaded globally before any decorator-decorated classes are loaded. This enables Accio to read metadata from your `@Table` and `@Column` decorators.
|
|
54
|
+
|
|
55
|
+
**Note:** `pg` (^8.0.0) and `reflect-metadata` (^0.2.2) are peer dependencies and must be installed separately.
|
|
56
|
+
|
|
57
|
+
### Prerequisites
|
|
58
|
+
|
|
59
|
+
- Node.js 16+
|
|
60
|
+
- PostgreSQL 12+
|
|
61
|
+
- TypeScript 5+
|
|
62
|
+
|
|
63
|
+
## Quick Start
|
|
64
|
+
|
|
65
|
+
### 1. Configure TypeScript
|
|
66
|
+
|
|
67
|
+
Add these to your `tsconfig.json`:
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"compilerOptions": {
|
|
72
|
+
"experimentalDecorators": true,
|
|
73
|
+
"emitDecoratorMetadata": true
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 2. Define Your Entity
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import 'reflect-metadata';
|
|
82
|
+
import { Table, Column, PrimaryColumn } from 'accio-orm';
|
|
83
|
+
|
|
84
|
+
@Table('users')
|
|
85
|
+
class User {
|
|
86
|
+
@PrimaryColumn()
|
|
87
|
+
id!: number;
|
|
88
|
+
|
|
89
|
+
@Column()
|
|
90
|
+
name!: string;
|
|
91
|
+
|
|
92
|
+
@Column()
|
|
93
|
+
age!: number;
|
|
94
|
+
|
|
95
|
+
@Column()
|
|
96
|
+
email!: string;
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 3. Connect to Database
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { connect } from 'accio-orm';
|
|
104
|
+
|
|
105
|
+
const db = connect({
|
|
106
|
+
host: 'localhost',
|
|
107
|
+
port: 5432,
|
|
108
|
+
database: 'mydb',
|
|
109
|
+
user: 'postgres',
|
|
110
|
+
password: 'password'
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 4. Use the Repository
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
const userRepo = db.getRepository(User);
|
|
118
|
+
|
|
119
|
+
// Create
|
|
120
|
+
const user = new User();
|
|
121
|
+
user.name = 'Alice';
|
|
122
|
+
user.age = 25;
|
|
123
|
+
user.email = 'alice@example.com';
|
|
124
|
+
await userRepo.save(user);
|
|
125
|
+
|
|
126
|
+
// Read
|
|
127
|
+
const foundUser = await userRepo.findById(1);
|
|
128
|
+
const allUsers = await userRepo.findAll();
|
|
129
|
+
|
|
130
|
+
// Update
|
|
131
|
+
foundUser.age = 26;
|
|
132
|
+
await userRepo.save(foundUser);
|
|
133
|
+
|
|
134
|
+
// Delete
|
|
135
|
+
await userRepo.delete(foundUser);
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## API Documentation
|
|
139
|
+
|
|
140
|
+
### Decorators
|
|
141
|
+
|
|
142
|
+
#### `@Table(tableName: string)`
|
|
143
|
+
|
|
144
|
+
Marks a class as a database entity.
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
@Table('users')
|
|
148
|
+
class User {
|
|
149
|
+
// ...
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
#### `@PrimaryColumn()`
|
|
154
|
+
|
|
155
|
+
Marks a property as the primary key column.
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
@PrimaryColumn()
|
|
159
|
+
id!: number;
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
#### `@Column(options?)`
|
|
163
|
+
|
|
164
|
+
Marks a property as a database column.
|
|
165
|
+
|
|
166
|
+
**Options:**
|
|
167
|
+
|
|
168
|
+
- `name?: string` - Custom column name (default: property name)
|
|
169
|
+
- `nullable?: boolean` - Whether the column can be null (default: true)
|
|
170
|
+
- `type?: string` - Database type hint (optional)
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
@Column()
|
|
174
|
+
name!: string;
|
|
175
|
+
|
|
176
|
+
@Column({ name: 'user_email', nullable: false })
|
|
177
|
+
email!: string;
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Connection
|
|
181
|
+
|
|
182
|
+
#### `connect(config: ConnectionConfig): Connection`
|
|
183
|
+
|
|
184
|
+
Creates a connection to the database.
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
const db = connect({
|
|
188
|
+
host: 'localhost',
|
|
189
|
+
port: 5432,
|
|
190
|
+
database: 'mydb',
|
|
191
|
+
user: 'postgres',
|
|
192
|
+
password: 'password',
|
|
193
|
+
max: 10 // optional: max connections in pool
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
#### `connection.getRepository<T>(entityClass): Repository<T>`
|
|
198
|
+
|
|
199
|
+
Gets a repository for an entity.
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
const userRepo = db.getRepository(User);
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
#### `connection.close(): Promise<void>`
|
|
206
|
+
|
|
207
|
+
Closes all database connections.
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
await db.close();
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Repository
|
|
214
|
+
|
|
215
|
+
#### Basic Operations
|
|
216
|
+
|
|
217
|
+
**`findById(id): Promise<T | null>`**
|
|
218
|
+
|
|
219
|
+
Find an entity by its primary key.
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
const user = await userRepo.findById(1);
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**`findAll(): Promise<T[]>`**
|
|
226
|
+
|
|
227
|
+
Find all entities.
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
const users = await userRepo.findAll();
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**`save(entity): Promise<T>`**
|
|
234
|
+
|
|
235
|
+
Insert or update an entity (smart save).
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
const user = new User();
|
|
239
|
+
user.name = 'Bob';
|
|
240
|
+
await userRepo.save(user); // Insert
|
|
241
|
+
|
|
242
|
+
user.age = 30;
|
|
243
|
+
await userRepo.save(user); // Update
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**`insert(entity): Promise<T>`**
|
|
247
|
+
|
|
248
|
+
Explicitly insert a new entity.
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
await userRepo.insert(user);
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**`update(entity): Promise<T>`**
|
|
255
|
+
|
|
256
|
+
Explicitly update an existing entity.
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
await userRepo.update(user);
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**`delete(entity): Promise<void>`**
|
|
263
|
+
|
|
264
|
+
Delete an entity.
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
await userRepo.delete(user);
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**`deleteById(id): Promise<void>`**
|
|
271
|
+
|
|
272
|
+
Delete by primary key.
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
await userRepo.deleteById(1);
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
**`count(): Promise<number>`**
|
|
279
|
+
|
|
280
|
+
Count all entities.
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
const total = await userRepo.count();
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**`exists(id): Promise<boolean>`**
|
|
287
|
+
|
|
288
|
+
Check if an entity exists by ID.
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
const exists = await userRepo.exists(1);
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Query Builder
|
|
295
|
+
|
|
296
|
+
#### `where(conditions): QueryBuilder<T>`
|
|
297
|
+
|
|
298
|
+
Add WHERE conditions (can be chained, combined with AND).
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
// Single condition
|
|
302
|
+
const users = await userRepo.where({ age: 25 }).find();
|
|
303
|
+
|
|
304
|
+
// Multiple properties (AND)
|
|
305
|
+
const users = await userRepo.where({ age: 25, city: 'NYC' }).find();
|
|
306
|
+
|
|
307
|
+
// Chain multiple where() calls (AND)
|
|
308
|
+
const users = await userRepo.where({ age: 25 }).where({ city: 'NYC' }).find();
|
|
309
|
+
|
|
310
|
+
// Array values (IN clause)
|
|
311
|
+
const users = await userRepo.where({ age: [25, 30, 35] }).find();
|
|
312
|
+
|
|
313
|
+
// NULL values
|
|
314
|
+
const users = await userRepo.where({ middleName: null }).find();
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
#### `orderBy(column, direction?): QueryBuilder<T>`
|
|
318
|
+
|
|
319
|
+
Order results by a column.
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
const users = await userRepo.where({ age: 25 }).orderBy('name', 'ASC').find();
|
|
323
|
+
|
|
324
|
+
// DESC order
|
|
325
|
+
const users = await userRepo.orderBy('age', 'DESC').find();
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
#### `limit(n): QueryBuilder<T>`
|
|
329
|
+
|
|
330
|
+
Limit the number of results.
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
const users = await userRepo.where({ age: 25 }).limit(10).find();
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
#### `offset(n): QueryBuilder<T>`
|
|
337
|
+
|
|
338
|
+
Skip the first N results (for pagination).
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
const users = await userRepo.where({ age: 25 }).offset(20).limit(10).find();
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
#### Terminal Operations
|
|
345
|
+
|
|
346
|
+
**`find(): Promise<T[]>`**
|
|
347
|
+
|
|
348
|
+
Execute the query and return all results.
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
const users = await userRepo.where({ age: 25 }).find();
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
**`findOne(): Promise<T | null>`**
|
|
355
|
+
|
|
356
|
+
Execute the query and return the first result.
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
const user = await userRepo.where({ email: 'alice@example.com' }).findOne();
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**`count(): Promise<number>`**
|
|
363
|
+
|
|
364
|
+
Count matching results.
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
const count = await userRepo.where({ age: 25 }).count();
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
**`exists(): Promise<boolean>`**
|
|
371
|
+
|
|
372
|
+
Check if any results exist.
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
const exists = await userRepo.where({ email: 'alice@example.com' }).exists();
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
**`toSQL(): { sql: string; params: unknown[] }`**
|
|
379
|
+
|
|
380
|
+
Get the SQL that would be executed (for debugging).
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
const { sql, params } = userRepo.where({ age: 25 }).toSQL();
|
|
384
|
+
console.log(sql); // SELECT * FROM users WHERE age = $1
|
|
385
|
+
console.log(params); // [25]
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
## Examples
|
|
389
|
+
|
|
390
|
+
### Pagination
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
const page = 1;
|
|
394
|
+
const pageSize = 10;
|
|
395
|
+
|
|
396
|
+
const users = await userRepo
|
|
397
|
+
.orderBy('name', 'ASC')
|
|
398
|
+
.offset((page - 1) * pageSize)
|
|
399
|
+
.limit(pageSize)
|
|
400
|
+
.find();
|
|
401
|
+
|
|
402
|
+
const total = await userRepo.count();
|
|
403
|
+
const totalPages = Math.ceil(total / pageSize);
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Search
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
const results = await userRepo
|
|
410
|
+
.where({ city: 'NYC' })
|
|
411
|
+
.orderBy('age', 'DESC')
|
|
412
|
+
.limit(20)
|
|
413
|
+
.find();
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### Complex Queries
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
const users = await userRepo
|
|
420
|
+
.where({ age: [25, 30, 35] })
|
|
421
|
+
.where({ city: 'NYC' })
|
|
422
|
+
.orderBy('name', 'ASC')
|
|
423
|
+
.limit(10)
|
|
424
|
+
.find();
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
## Database Setup
|
|
428
|
+
|
|
429
|
+
Create your tables manually (Accio is schema-agnostic):
|
|
430
|
+
|
|
431
|
+
```sql
|
|
432
|
+
CREATE TABLE users (
|
|
433
|
+
id SERIAL PRIMARY KEY,
|
|
434
|
+
name TEXT NOT NULL,
|
|
435
|
+
age INTEGER NOT NULL,
|
|
436
|
+
email TEXT NOT NULL UNIQUE
|
|
437
|
+
);
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
## Design Philosophy
|
|
441
|
+
|
|
442
|
+
Accio follows the **Data Mapper pattern**, keeping your domain models clean and separate from persistence logic. This means:
|
|
443
|
+
|
|
444
|
+
- 📦 **Entities are just classes** - no methods for database operations
|
|
445
|
+
- 🔧 **Repositories handle persistence** - clear separation of concerns
|
|
446
|
+
- 🎯 **Type-safe queries** - TypeScript catches errors at compile time
|
|
447
|
+
- 🪶 **Lightweight** - minimal abstractions, close to SQL
|
|
448
|
+
|
|
449
|
+
## Roadmap
|
|
450
|
+
|
|
451
|
+
- [ ] Relationships (one-to-many, many-to-many)
|
|
452
|
+
- [ ] Transactions support
|
|
453
|
+
- [ ] Advanced query operators (LIKE, >, <, !=, OR)
|
|
454
|
+
- [ ] Schema migrations
|
|
455
|
+
- [ ] Lifecycle hooks (beforeInsert, afterUpdate)
|
|
456
|
+
- [ ] Validation decorators
|
|
457
|
+
- [ ] Query result caching
|
|
458
|
+
- [ ] Soft deletes
|
|
459
|
+
- [ ] Automatic timestamps (createdAt, updatedAt)
|
|
460
|
+
|
|
461
|
+
## Contributing
|
|
462
|
+
|
|
463
|
+
Contributions are welcome! This is a learning project, so feel free to:
|
|
464
|
+
|
|
465
|
+
- Report bugs
|
|
466
|
+
- Suggest features
|
|
467
|
+
- Submit pull requests
|
|
468
|
+
- Improve documentation
|
|
469
|
+
|
|
470
|
+
## License
|
|
471
|
+
|
|
472
|
+
MIT
|
|
473
|
+
|
|
474
|
+
## Acknowledgments
|
|
475
|
+
|
|
476
|
+
Built as a learning project to understand ORM internals, design patterns, and TypeScript decorators.
|
|
477
|
+
|
|
478
|
+
Inspired by TypeORM, Prisma, and other great ORMs in the ecosystem.
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
**Accio!** âš¡ Summon your data with ease.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type QueryResult } from 'pg';
|
|
2
|
+
import { Repository } from '../repository/Repository';
|
|
3
|
+
import type { ConnectionConfig } from './types';
|
|
4
|
+
export declare class Connection {
|
|
5
|
+
private pool;
|
|
6
|
+
constructor(config: ConnectionConfig);
|
|
7
|
+
/**
|
|
8
|
+
* Get a repository for an entity class
|
|
9
|
+
* @param entityClass
|
|
10
|
+
* @returns
|
|
11
|
+
*/
|
|
12
|
+
getRepository<T>(entityClass: new () => T): Repository<T>;
|
|
13
|
+
/**
|
|
14
|
+
* Execute a raw SQL query
|
|
15
|
+
*/
|
|
16
|
+
query(sql: string, params?: any[]): Promise<QueryResult>;
|
|
17
|
+
/**
|
|
18
|
+
* Close all connections in the pool
|
|
19
|
+
*/
|
|
20
|
+
close(): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Test the connection
|
|
23
|
+
*/
|
|
24
|
+
testConnection(): Promise<boolean>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Factory function to create a connection
|
|
28
|
+
*/
|
|
29
|
+
export declare function connect(config: ConnectionConfig): Connection;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Connection = void 0;
|
|
4
|
+
exports.connect = connect;
|
|
5
|
+
const pg_1 = require("pg");
|
|
6
|
+
const Repository_1 = require("../repository/Repository");
|
|
7
|
+
class Connection {
|
|
8
|
+
constructor(config) {
|
|
9
|
+
this.pool = new pg_1.Pool({
|
|
10
|
+
host: config.host,
|
|
11
|
+
port: config.port ?? 5432,
|
|
12
|
+
database: config.database,
|
|
13
|
+
user: config.user,
|
|
14
|
+
password: config.password
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Get a repository for an entity class
|
|
19
|
+
* @param entityClass
|
|
20
|
+
* @returns
|
|
21
|
+
*/
|
|
22
|
+
getRepository(entityClass) {
|
|
23
|
+
return new Repository_1.Repository(entityClass, this);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Execute a raw SQL query
|
|
27
|
+
*/
|
|
28
|
+
async query(sql, params) {
|
|
29
|
+
try {
|
|
30
|
+
return await this.pool.query(sql, params);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
// re-throw with context
|
|
34
|
+
throw new Error(`Query failed: ${sql}\nError: ${error instanceof Error ? error.message : String(error)}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Close all connections in the pool
|
|
39
|
+
*/
|
|
40
|
+
async close() {
|
|
41
|
+
await this.pool.end();
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Test the connection
|
|
45
|
+
*/
|
|
46
|
+
async testConnection() {
|
|
47
|
+
try {
|
|
48
|
+
await this.pool.query('SELECT 1');
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
console.log('Connection test failed:', error);
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
exports.Connection = Connection;
|
|
58
|
+
/**
|
|
59
|
+
* Factory function to create a connection
|
|
60
|
+
*/
|
|
61
|
+
function connect(config) {
|
|
62
|
+
return new Connection(config);
|
|
63
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
export declare const COLUMNS_KEY: unique symbol;
|
|
3
|
+
export interface ColumnOptions {
|
|
4
|
+
name?: string;
|
|
5
|
+
type?: string;
|
|
6
|
+
nullable?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface ColumnMetadata {
|
|
9
|
+
propertyKey: string;
|
|
10
|
+
columnName: string;
|
|
11
|
+
type?: string;
|
|
12
|
+
isNullable?: boolean;
|
|
13
|
+
isPrimary: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Decorator to mark a property as a database column
|
|
17
|
+
*/
|
|
18
|
+
export declare function Column(options?: ColumnOptions): (target: object, // The prototype
|
|
19
|
+
propertyKey: string | symbol) => void;
|
|
20
|
+
/**
|
|
21
|
+
* helper to retrieve all columns from a class
|
|
22
|
+
*/
|
|
23
|
+
export declare function getColumns(target: new (...args: unknown[]) => object): ColumnMetadata[];
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.COLUMNS_KEY = void 0;
|
|
4
|
+
exports.Column = Column;
|
|
5
|
+
exports.getColumns = getColumns;
|
|
6
|
+
require("reflect-metadata");
|
|
7
|
+
exports.COLUMNS_KEY = Symbol('table:columns');
|
|
8
|
+
/**
|
|
9
|
+
* Decorator to mark a property as a database column
|
|
10
|
+
*/
|
|
11
|
+
function Column(options = {}) {
|
|
12
|
+
return function (target, // The prototype
|
|
13
|
+
propertyKey // The property name
|
|
14
|
+
) {
|
|
15
|
+
// target is the prototype, target.constructor is the class
|
|
16
|
+
const constructor = target.constructor;
|
|
17
|
+
const columns = Reflect.getMetadata(exports.COLUMNS_KEY, constructor) ?? [];
|
|
18
|
+
const key = typeof propertyKey === 'symbol' ? propertyKey.toString() : propertyKey;
|
|
19
|
+
columns.push({
|
|
20
|
+
propertyKey: key,
|
|
21
|
+
columnName: options.name ?? key,
|
|
22
|
+
type: options.type,
|
|
23
|
+
isNullable: options.nullable ?? true,
|
|
24
|
+
isPrimary: false
|
|
25
|
+
});
|
|
26
|
+
Reflect.defineMetadata(exports.COLUMNS_KEY, columns, constructor);
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* helper to retrieve all columns from a class
|
|
31
|
+
*/
|
|
32
|
+
function getColumns(target) {
|
|
33
|
+
return (Reflect.getMetadata(exports.COLUMNS_KEY, target) ?? []);
|
|
34
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PrimaryColumn = PrimaryColumn;
|
|
4
|
+
require("reflect-metadata");
|
|
5
|
+
const Column_1 = require("./Column");
|
|
6
|
+
/**
|
|
7
|
+
* Decorator to mark a property as the primary key column
|
|
8
|
+
*
|
|
9
|
+
* Example:
|
|
10
|
+
* @PrimaryColumn
|
|
11
|
+
* id: number
|
|
12
|
+
*/
|
|
13
|
+
function PrimaryColumn() {
|
|
14
|
+
return function (target, propertyKey) {
|
|
15
|
+
const constructor = target.constructor;
|
|
16
|
+
const columns = Reflect.getMetadata(Column_1.COLUMNS_KEY, constructor) ?? [];
|
|
17
|
+
const key = typeof propertyKey === 'symbol' ? propertyKey.toString() : propertyKey;
|
|
18
|
+
columns.push({
|
|
19
|
+
propertyKey: key,
|
|
20
|
+
columnName: key,
|
|
21
|
+
isPrimary: true,
|
|
22
|
+
isNullable: false
|
|
23
|
+
});
|
|
24
|
+
Reflect.defineMetadata(Column_1.COLUMNS_KEY, columns, constructor);
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
/**
|
|
3
|
+
* Decorator to mark a class as a database table
|
|
4
|
+
* @param tableName - The name of the database table
|
|
5
|
+
*/
|
|
6
|
+
export declare function Table(tableName: string): <T extends new (...args: unknown[]) => object>(target: T) => void;
|
|
7
|
+
/**
|
|
8
|
+
* helper to retrieve the table name from a class
|
|
9
|
+
*/
|
|
10
|
+
export declare function getTableName(target: new (...args: unknown[]) => object): string | undefined;
|