@keltoi/hydra 2.0.0 → 2.0.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/README.md +353 -1
- package/index.js +10 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,2 +1,354 @@
|
|
|
1
1
|
# HydraJs
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
HydraJs is a library that provides a set of classes to implement the Repository Pattern in your code. By using these classes, you can abstract away the data storage and retrieval logic, making it easier to switch between different data storage systems. It uses `Knex.js`, a popular SQL query builder for Node.js that allows you to build and execute SQL queries in a safe and efficient way. Knex provides a simple and intuitive API for building queries, and supports a wide range of database management systems, including MySQL, PostgreSQL, SQLite, and more.
|
|
4
|
+
|
|
5
|
+
For more information about Knex.js, including documentation, tutorials, and examples, visit the official Knex.js website.
|
|
6
|
+
|
|
7
|
+
https://knexjs.org/
|
|
8
|
+
|
|
9
|
+
For APIContext, HydraJs uses Axios, a popular JavaScript library used for making HTTP requests in Node.js and web applications. It provides a simple and intuitive way to send HTTP requests and interact with web servers.
|
|
10
|
+
|
|
11
|
+
https://axios-http.com/
|
|
12
|
+
|
|
13
|
+
**Key Features**
|
|
14
|
+
----------------
|
|
15
|
+
|
|
16
|
+
* **Modular Design**: Hydra.JS is designed to be highly modular, allowing developers to easily add or remove features as needed.
|
|
17
|
+
* **Extensible Architecture**: The framework's architecture is designed to be extensible, making it easy to add new features and functionality.
|
|
18
|
+
* **Flexible Database Integration**: Hydra.JS provides a flexible way to integrate with various databases, making it easy to switch between different databases as needed.
|
|
19
|
+
* **Robust Query Execution**: The framework provides a robust way to execute queries, including support for transactions and batch operations.
|
|
20
|
+
* **Efficient Data Management**: Hydra.JS provides an efficient way to manage data, including support for caching and data validation.
|
|
21
|
+
|
|
22
|
+
## Provided Abstractions
|
|
23
|
+
---------
|
|
24
|
+
|
|
25
|
+
### Entity
|
|
26
|
+
-----------
|
|
27
|
+
|
|
28
|
+
The `Entity` class represents a single entity in your data storage system. It is a fundamental class in the HydraJs library, and it plays a crucial role in representing a single entity in your data storage system.
|
|
29
|
+
|
|
30
|
+
**Class Definition**
|
|
31
|
+
|
|
32
|
+
The `Entity` class is defined as follows:
|
|
33
|
+
```javascript
|
|
34
|
+
Class Entity:
|
|
35
|
+
Fields:
|
|
36
|
+
#key
|
|
37
|
+
#data
|
|
38
|
+
constructor(key = {}, data = {}) {
|
|
39
|
+
this.#data = data;
|
|
40
|
+
this.#key = key;
|
|
41
|
+
}
|
|
42
|
+
Methods:
|
|
43
|
+
key()
|
|
44
|
+
data()
|
|
45
|
+
$()
|
|
46
|
+
migrations()
|
|
47
|
+
structMe(db = knex())
|
|
48
|
+
build({ })
|
|
49
|
+
fromResult(result = new Result())
|
|
50
|
+
```
|
|
51
|
+
**Fields**
|
|
52
|
+
|
|
53
|
+
The `Entity` class has two private fields:
|
|
54
|
+
|
|
55
|
+
* `#key`: This field represents the unique identifier of the entity. It is an object that contains the key-value pairs that uniquely identify the entity.
|
|
56
|
+
* `#data`: This field represents the data associated with the entity. It is an object that contains the key-value pairs that represent the entity's data.
|
|
57
|
+
|
|
58
|
+
**Constructor**
|
|
59
|
+
|
|
60
|
+
The `Entity` class has a constructor that takes two optional parameters:
|
|
61
|
+
|
|
62
|
+
* `key`: This parameter is an object that represents the unique identifier of the entity. If not provided, an empty object is used.
|
|
63
|
+
* `data`: This parameter is an object that represents the data associated with the entity. If not provided, an empty object is used.
|
|
64
|
+
|
|
65
|
+
The constructor initializes the `#key` and `#data` fields with the provided values.
|
|
66
|
+
|
|
67
|
+
**Methods**
|
|
68
|
+
|
|
69
|
+
The `Entity` class has several methods that provide various functionalities:
|
|
70
|
+
|
|
71
|
+
* `key()`: This method returns the `#key` field, which represents the unique identifier of the entity.
|
|
72
|
+
* `data()`: This method returns the `#data` field, which represents the data associated with the entity.
|
|
73
|
+
* `$()`: This method returns a new object that contains the merged `#key` and `#data` fields. This method is useful for creating a single object that represents the entity's data and key.
|
|
74
|
+
* `migrations()`: This method is not implemented in the provided code snippet, but it is likely used to handle database migrations for the entity.
|
|
75
|
+
* `structMe(db = knex())`: This method takes a `db` parameter, which is an instance of the `knex` library. The method returns a new object that represents the entity's structure.
|
|
76
|
+
* `build({ })`: This method takes an object with key-value pairs that represent the entity's data. The method returns a new `Entity` instance with the provided data.
|
|
77
|
+
* `fromResult(result = new Result())`: This method takes a `result` parameter, which is an instance of the `Result` class. The method returns a new `Entity` instance with the data from the `result` object.
|
|
78
|
+
|
|
79
|
+
**Usage**
|
|
80
|
+
|
|
81
|
+
The `Entity` class can be used to represent a single entity in your data storage system. For example:
|
|
82
|
+
```javascript
|
|
83
|
+
const entity = new Entity({ id: 1 }, { name: 'John Doe' });
|
|
84
|
+
console.log(entity.key()); // Output: { id: 1 }
|
|
85
|
+
console.log(entity.data()); // Output: { name: 'John Doe' }
|
|
86
|
+
console.log(entity.$()); // Output: { id: 1, name: 'John Doe' }
|
|
87
|
+
```
|
|
88
|
+
Note that this is just a basic example, and you will need to customize the usage to fit your specific use case.
|
|
89
|
+
|
|
90
|
+
### Model
|
|
91
|
+
------------
|
|
92
|
+
|
|
93
|
+
The `Model` class is an abstract class that represents a data model. It has a constructor that takes an object with properties that will be used to initialize the model and an abstract method `validate` that can be override to cover specifics validation scenario
|
|
94
|
+
|
|
95
|
+
### Linking
|
|
96
|
+
------------
|
|
97
|
+
|
|
98
|
+
The `Linking` class represents a relationship between two entities. It has properties for `abscissa` and `ordinate`, which represent the two entities being linked as a cartesian model.
|
|
99
|
+
|
|
100
|
+
### Repository
|
|
101
|
+
-------------
|
|
102
|
+
|
|
103
|
+
The `Repository` class is an abstract class that provides a set of methods for creating, reading, updating, and deleting entities in a data storage system. It plays several roles in the HydraJS library:
|
|
104
|
+
|
|
105
|
+
* **Data Abstraction**: The `Repository` class abstracts the data storage and retrieval logic, making it easier to switch between different data storage systems.
|
|
106
|
+
* **Entity Management**: The `Repository` class is responsible for managing entities in a data storage system, including creating, reading, updating, and deleting entities.
|
|
107
|
+
* **Context Management**: The `Repository` class is responsible for managing the context in which it operates, including database connections, API endpoints, and other data storage systems.
|
|
108
|
+
|
|
109
|
+
**Class Definition**
|
|
110
|
+
--------------------
|
|
111
|
+
|
|
112
|
+
The `Repository` class is defined as follows:
|
|
113
|
+
```javascript
|
|
114
|
+
Class Repository:
|
|
115
|
+
Fields:
|
|
116
|
+
#entity
|
|
117
|
+
#context
|
|
118
|
+
constructor(entity = Entity, context = Context) {
|
|
119
|
+
this.#entity = entity;
|
|
120
|
+
this.#context = context;
|
|
121
|
+
}
|
|
122
|
+
Methods:
|
|
123
|
+
create(entity)
|
|
124
|
+
insert(entity)
|
|
125
|
+
get(entity)
|
|
126
|
+
update(entity)
|
|
127
|
+
delete(entity)
|
|
128
|
+
list()
|
|
129
|
+
```
|
|
130
|
+
**Fields**
|
|
131
|
+
----------
|
|
132
|
+
|
|
133
|
+
The `Repository` class has two private fields:
|
|
134
|
+
|
|
135
|
+
* `#entity`: This field represents the entity class that the repository is responsible for managing.
|
|
136
|
+
* `#context`: This field represents the context in which the repository operates. The context can be a database, an API, or any other data storage system.
|
|
137
|
+
|
|
138
|
+
**Constructor**
|
|
139
|
+
--------------
|
|
140
|
+
|
|
141
|
+
The `Repository` class has a constructor that takes two optional parameters:
|
|
142
|
+
|
|
143
|
+
* `entity`: This parameter is the entity class that the repository is responsible for managing. If not provided, the `Entity` class is used by default.
|
|
144
|
+
* `context`: This parameter is the context in which the repository operates. If not provided, the `Context` class is used by default.
|
|
145
|
+
|
|
146
|
+
The constructor initializes the `#entity` and `#context` fields with the provided values.
|
|
147
|
+
|
|
148
|
+
**Methods**
|
|
149
|
+
------------
|
|
150
|
+
|
|
151
|
+
The `Repository` class provides a set of methods for creating, reading, updating, and deleting entities in a data storage system:
|
|
152
|
+
|
|
153
|
+
* `include(entity)`: This method includes an entity in the data storage system.
|
|
154
|
+
* `create(entity)`: This method includes a new entity in the data storage system and returns the new `key` created in database. The difference between `include` and `create` is that the first one entry a new data in storage system, and is accountable for manages it's ids on database, so it can includes an autoincrement key and retrieve the created id.
|
|
155
|
+
* `get(entity)`: This method retrieves an entity from the data storage system based on its unique identifier, structured by `key` property.
|
|
156
|
+
* `update(entity)`: This method updates an existing entity in the data storage system, structured by `key` property.
|
|
157
|
+
* `delete(entity)`: This method deletes an entity from the data storage system based on its unique identifier, structured by `key` property.
|
|
158
|
+
* `list`: This method retrieves all elements of a data storage entity.
|
|
159
|
+
|
|
160
|
+
**Usage**
|
|
161
|
+
---------
|
|
162
|
+
|
|
163
|
+
The `Repository` class can be used to create a repository for managing entities in a data storage system. For example:
|
|
164
|
+
```javascript
|
|
165
|
+
const repository = new Repository(Entity, Context);
|
|
166
|
+
const entity = new Entity({ id: 'f47ac10b-58cc-43e8-9b9a-8f8f9f9f9f9f' }, { name: 'Cthulhu da Silva' });
|
|
167
|
+
|
|
168
|
+
await repository.create(entity);
|
|
169
|
+
//created {id:'f47ac10b-58cc-43e8-9b9a-8f8f9f9f9f9f', name:'Cthulhu da Silva'} in Entity table
|
|
170
|
+
|
|
171
|
+
const retrievedEntity = await repository.get(entity);
|
|
172
|
+
//retrieves {id:'f47ac10b-58cc-43e8-9b9a-8f8f9f9f9f9f', name:'Cthulhu da Silva'} from Entity table
|
|
173
|
+
|
|
174
|
+
retrievedEntity.changeName('Cthulhu de Souza')
|
|
175
|
+
|
|
176
|
+
await repository.update(entity);
|
|
177
|
+
//Now changes from 'Cthulhu da Silva' name to 'Cthulhu de Souza'
|
|
178
|
+
|
|
179
|
+
await repository.delete(entity);
|
|
180
|
+
//Removes {id:'f47ac10b-58cc-43e8-9b9a-8f8f9f9f9f9f', name:'Cthulhu de Souza'} from database
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
Note that this is just a basic example, and you will need to customize the usage to fit your specific use case.
|
|
184
|
+
|
|
185
|
+
### Context
|
|
186
|
+
------------
|
|
187
|
+
|
|
188
|
+
The `Context` class represents a database context and is responsible for managing the database connection, executing queries, and handling transactions. It can be extended by two classes `ApiContext` and `DbContext`
|
|
189
|
+
|
|
190
|
+
### Api Context ###
|
|
191
|
+
--------------------
|
|
192
|
+
|
|
193
|
+
The `ApiContext` class is defined as follows:
|
|
194
|
+
```javascript
|
|
195
|
+
Class Context:
|
|
196
|
+
Fields:
|
|
197
|
+
#http
|
|
198
|
+
constructor(config = axios.create({})) {
|
|
199
|
+
this.#http = config;
|
|
200
|
+
}
|
|
201
|
+
Methods:
|
|
202
|
+
http()
|
|
203
|
+
```
|
|
204
|
+
**Fields**
|
|
205
|
+
----------
|
|
206
|
+
|
|
207
|
+
The `ApiContext` class has one private field:
|
|
208
|
+
|
|
209
|
+
* `#http`: This field represents the HTTP client instance used to execute queries.
|
|
210
|
+
|
|
211
|
+
**Constructor**
|
|
212
|
+
--------------
|
|
213
|
+
|
|
214
|
+
The `ApiContext` class has a constructor that takes an optional `config` parameter:
|
|
215
|
+
|
|
216
|
+
* `config`: This parameter is an object that configures the HTTP client instance using `axios`. If not provided, a default configuration is used.
|
|
217
|
+
|
|
218
|
+
The constructor initializes the `#http` field with the provided configuration.
|
|
219
|
+
|
|
220
|
+
**Methods**
|
|
221
|
+
------------
|
|
222
|
+
|
|
223
|
+
The `ApiContext` class provides one method:
|
|
224
|
+
|
|
225
|
+
* `http()`: This method returns the HTTP client instance used to execute queries.
|
|
226
|
+
|
|
227
|
+
**Axios Configuration**
|
|
228
|
+
----------------------
|
|
229
|
+
|
|
230
|
+
The `ApiContext` class uses the `axios` library to create an HTTP client instance. You can configure the HTTP client instance by passing a `config` object to the constructor.
|
|
231
|
+
|
|
232
|
+
For example:
|
|
233
|
+
```javascript
|
|
234
|
+
const config = {
|
|
235
|
+
baseURL: 'https://api.example.com',
|
|
236
|
+
headers: {
|
|
237
|
+
'Content-Type': 'application/json',
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
const context = new ApiContext(config);
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### DbContext ###
|
|
244
|
+
------------
|
|
245
|
+
|
|
246
|
+
The `DbContext` class is defined as follows:
|
|
247
|
+
```javascript
|
|
248
|
+
Class DbContext:
|
|
249
|
+
Fields:
|
|
250
|
+
#db
|
|
251
|
+
constructor(database=knex()) {
|
|
252
|
+
this.#db = database
|
|
253
|
+
}
|
|
254
|
+
Methods:
|
|
255
|
+
db()
|
|
256
|
+
unitOfWork(...repositories)
|
|
257
|
+
terraform(models = [Model])
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Constructor**
|
|
261
|
+
--------------
|
|
262
|
+
|
|
263
|
+
The `DbContext` class has a constructor that takes a `database` parameter:
|
|
264
|
+
|
|
265
|
+
* `database`: This parameter is Knex.js instance.
|
|
266
|
+
|
|
267
|
+
**Methods**
|
|
268
|
+
------------
|
|
269
|
+
|
|
270
|
+
The `Context` class provides several methods:
|
|
271
|
+
|
|
272
|
+
* `db()`: This method returns the database instance used to execute queries.
|
|
273
|
+
* `unitOfWork(...repositories)`: This method creates a unit of work that can be used to execute multiple queries in a single transaction.
|
|
274
|
+
* `terraform(models = [Model])`: This method define initial state of database and execute table migrations.
|
|
275
|
+
|
|
276
|
+
**Knex.js Configuration**
|
|
277
|
+
-------------------------
|
|
278
|
+
|
|
279
|
+
The `DbContext` class uses the `knex` library to create a Knex.js instance. You can configure the Knex.js instance by passing a `knexConfig` instance to the constructor of `knex`.
|
|
280
|
+
|
|
281
|
+
For example:
|
|
282
|
+
```javascript
|
|
283
|
+
const knexConfig = {
|
|
284
|
+
client: 'pg',
|
|
285
|
+
connection: 'postgresql://user:password@host:port/database',
|
|
286
|
+
migrations: {
|
|
287
|
+
directory: './migrations',
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const database = knex(knexConfig)
|
|
292
|
+
const context = new DbContext(database);
|
|
293
|
+
```
|
|
294
|
+
This configuration sets the database client, connection string, and migration directory for the Knex.js instance.
|
|
295
|
+
|
|
296
|
+
**UnitOfWork**
|
|
297
|
+
-------------
|
|
298
|
+
|
|
299
|
+
**Method Signature**
|
|
300
|
+
--------------------
|
|
301
|
+
|
|
302
|
+
`unitOfWork(...repositories)`
|
|
303
|
+
|
|
304
|
+
The `unitOfWork` method creates a unit of work that can be used to execute multiple queries in a single transaction. The method takes one or more `Repository` instances as arguments.
|
|
305
|
+
|
|
306
|
+
For example:
|
|
307
|
+
```javascript
|
|
308
|
+
const userRepository = new UserRepository(context);
|
|
309
|
+
const orderRepository = new OrderRepository(context);
|
|
310
|
+
const tx = await context.unitOfWork(userRepository, orderRepository);
|
|
311
|
+
```
|
|
312
|
+
**Parameters**
|
|
313
|
+
--------------
|
|
314
|
+
|
|
315
|
+
* `repositories`: A variable number of repository objects that will be used to execute queries within the unit of work.
|
|
316
|
+
|
|
317
|
+
**Return Type**
|
|
318
|
+
--------------
|
|
319
|
+
|
|
320
|
+
The `unitOfWork` method returns an object with two methods: `done` and `rollback`.
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
**Example Usage**
|
|
324
|
+
-----------------
|
|
325
|
+
|
|
326
|
+
```javascript
|
|
327
|
+
const unitOfWork = context.unitOfWork(repo1, repo2);
|
|
328
|
+
|
|
329
|
+
// Execute queries on the repositories
|
|
330
|
+
repo1.create({ name: 'John Doe' });
|
|
331
|
+
repo2.create({ userId: 1, total: 100 });
|
|
332
|
+
|
|
333
|
+
// Commit the transaction
|
|
334
|
+
unitOfWork.done().then(() => {
|
|
335
|
+
console.log('Transaction committed');
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// Alternatively, roll back the transaction
|
|
339
|
+
unitOfWork.rollback().then(() => {
|
|
340
|
+
console.log('Transaction rolled back');
|
|
341
|
+
});
|
|
342
|
+
```
|
|
343
|
+
**Terraform**
|
|
344
|
+
-------------
|
|
345
|
+
|
|
346
|
+
The `terraform` method creates initial database state of every `Model` type passed by parameters and execute eventual database migrations. The method takes an array of types based on `Model` as an argument.
|
|
347
|
+
|
|
348
|
+
For example:
|
|
349
|
+
```javascript
|
|
350
|
+
|
|
351
|
+
await context.terraform([UserModel, OrderModel]);
|
|
352
|
+
```
|
|
353
|
+
This code creates initial state on database and execute database migrations for the `User` and `Order` tables.
|
|
354
|
+
|
package/index.js
CHANGED
|
@@ -448,13 +448,13 @@ let Repository$1 = class Repository{
|
|
|
448
448
|
|
|
449
449
|
set context(value=knex()){ this.myContext=()=>value(this.#name); }
|
|
450
450
|
|
|
451
|
-
|
|
451
|
+
insert = (entity = new Entity())=>
|
|
452
452
|
this.myContext()
|
|
453
453
|
.insert(entity.$)
|
|
454
454
|
.then(()=>new Result({data:entity}))
|
|
455
455
|
.catch(err=>Promise.reject( new Result({code:500,message:err}) ))
|
|
456
456
|
|
|
457
|
-
|
|
457
|
+
create = (entity = new Entity())=>
|
|
458
458
|
this.myContext()
|
|
459
459
|
.insert(entity.data,Object.keys(entity.key))
|
|
460
460
|
.then(ids=>new Result({
|
|
@@ -633,7 +633,7 @@ class DbLinked {
|
|
|
633
633
|
this.#linking = link;
|
|
634
634
|
}
|
|
635
635
|
|
|
636
|
-
|
|
636
|
+
insertBatch = (linkings=[ new Linking() ])=>
|
|
637
637
|
this.myContext()
|
|
638
638
|
.insert(
|
|
639
639
|
linkings.map(l => (
|
|
@@ -646,7 +646,7 @@ class DbLinked {
|
|
|
646
646
|
.then(()=>new Result({data:linkings}))
|
|
647
647
|
.catch(err=>Promise.reject(new Result({code:500,message:err})));
|
|
648
648
|
|
|
649
|
-
|
|
649
|
+
insert = (linked=new Linking())=>
|
|
650
650
|
this.myContext()
|
|
651
651
|
.insert({
|
|
652
652
|
...linked.key,
|
|
@@ -662,7 +662,7 @@ class DbLinked {
|
|
|
662
662
|
.build({abscissa,ordinate:o})
|
|
663
663
|
);
|
|
664
664
|
|
|
665
|
-
return this.
|
|
665
|
+
return this.insertBatch(batch);
|
|
666
666
|
}
|
|
667
667
|
|
|
668
668
|
insertAbscissasByOrdinate = ({ ordinate=new Entity(),abscissas=[new Entity()] })=>{
|
|
@@ -671,7 +671,7 @@ class DbLinked {
|
|
|
671
671
|
.build({abscissa:a,ordinate})
|
|
672
672
|
);
|
|
673
673
|
|
|
674
|
-
return this.
|
|
674
|
+
return this.insertBatch(batch)
|
|
675
675
|
}
|
|
676
676
|
|
|
677
677
|
delete = (linked=new Linking())=>
|
|
@@ -771,7 +771,7 @@ let Context$1 = class Context{
|
|
|
771
771
|
class Repository {
|
|
772
772
|
#context
|
|
773
773
|
|
|
774
|
-
constructor(
|
|
774
|
+
constructor(context=new Context$1()){
|
|
775
775
|
this.#context = context;
|
|
776
776
|
}
|
|
777
777
|
|
|
@@ -785,8 +785,8 @@ class Repository {
|
|
|
785
785
|
}
|
|
786
786
|
|
|
787
787
|
class RestfulRepository extends Repository{
|
|
788
|
-
constructor(
|
|
789
|
-
super(
|
|
788
|
+
constructor(context=new Context$1()){
|
|
789
|
+
super(context);
|
|
790
790
|
}
|
|
791
791
|
|
|
792
792
|
#keysAsParams(route='',key={}){
|
|
@@ -815,13 +815,6 @@ class RestfulRepository extends Repository{
|
|
|
815
815
|
: Promise.reject(new Result({code:value.status, message:value.statusText}));
|
|
816
816
|
})
|
|
817
817
|
|
|
818
|
-
list=(route='',query={}|undefined)=>this.#httpGet(route,query)
|
|
819
|
-
.then(value=>{
|
|
820
|
-
value.status === 200
|
|
821
|
-
? new Result({data:value.data})
|
|
822
|
-
: Promise.reject(new Result({code:value.status, message:value.statusText}));
|
|
823
|
-
})
|
|
824
|
-
|
|
825
818
|
create=(route='',entity=new Entity())=>this.context
|
|
826
819
|
.http
|
|
827
820
|
.post(route,entity.data)
|
|
@@ -857,7 +850,7 @@ class RestfulRepository extends Repository{
|
|
|
857
850
|
})
|
|
858
851
|
}
|
|
859
852
|
|
|
860
|
-
delete=(
|
|
853
|
+
delete=(route='',entity = new Entity())=>{
|
|
861
854
|
const url = this.#keysAsParams(route,entity.key);
|
|
862
855
|
|
|
863
856
|
return this.context
|