@decaf-ts/core 0.8.26 → 0.8.27
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 +184 -179
- package/dist/core.cjs +1 -1
- package/dist/core.cjs.map +1 -1
- package/dist/core.js +1 -1
- package/dist/core.js.map +1 -1
- package/lib/esm/index.d.ts +1 -1
- package/lib/esm/index.js +1 -1
- package/lib/esm/query/MethodQueryBuilder.d.ts +31 -3
- package/lib/esm/query/MethodQueryBuilder.js +153 -32
- package/lib/esm/query/MethodQueryBuilder.js.map +1 -1
- package/lib/esm/query/Statement.js +135 -7
- package/lib/esm/query/Statement.js.map +1 -1
- package/lib/esm/query/constants.d.ts +8 -1
- package/lib/esm/query/constants.js +7 -0
- package/lib/esm/query/constants.js.map +1 -1
- package/lib/esm/query/decorators.js +60 -6
- package/lib/esm/query/decorators.js.map +1 -1
- package/lib/esm/query/types.d.ts +21 -1
- package/lib/esm/query/types.js +8 -0
- package/lib/esm/query/types.js.map +1 -1
- package/lib/esm/repository/Repository.d.ts +56 -0
- package/lib/esm/repository/Repository.js +129 -0
- package/lib/esm/repository/Repository.js.map +1 -1
- package/lib/index.cjs +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/query/MethodQueryBuilder.cjs +153 -32
- package/lib/query/MethodQueryBuilder.d.ts +31 -3
- package/lib/query/MethodQueryBuilder.js.map +1 -1
- package/lib/query/Statement.cjs +135 -7
- package/lib/query/Statement.js.map +1 -1
- package/lib/query/constants.cjs +7 -0
- package/lib/query/constants.d.ts +8 -1
- package/lib/query/constants.js.map +1 -1
- package/lib/query/decorators.cjs +60 -6
- package/lib/query/decorators.js.map +1 -1
- package/lib/query/types.cjs +8 -0
- package/lib/query/types.d.ts +21 -1
- package/lib/query/types.js.map +1 -1
- package/lib/repository/Repository.cjs +129 -0
- package/lib/repository/Repository.d.ts +56 -0
- package/lib/repository/Repository.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,6 +6,16 @@ Decaf Core provides the foundational building blocks for the Decaf TypeScript ec
|
|
|
6
6
|
|
|
7
7
|
> Release docs refreshed on 2025-11-26. See [workdocs/reports/RELEASE_NOTES.md](./workdocs/reports/RELEASE_NOTES.md) for ticket summaries.
|
|
8
8
|
|
|
9
|
+
### Core Concepts
|
|
10
|
+
|
|
11
|
+
* **`Repository`**: A class that implements the repository pattern, providing a consistent API for CRUD operations and querying.
|
|
12
|
+
* **`Adapter`**: An abstract class that defines the interface for connecting to different database backends.
|
|
13
|
+
* **`Statement`**: A query builder for creating complex database queries in a fluent, type-safe manner.
|
|
14
|
+
* **`TaskEngine`**: A system for managing background jobs and asynchronous operations.
|
|
15
|
+
* **`ModelService` and `PersistenceService`**: Base classes for creating services that encapsulate business logic and data access.
|
|
16
|
+
* **Migrations**: A system for managing database schema changes over time.
|
|
17
|
+
* **RAM Adapter**: An in-memory adapter for testing and development.
|
|
18
|
+
|
|
9
19
|

|
|
10
20
|

|
|
11
21
|

|
|
@@ -29,7 +39,7 @@ Decaf Core provides the foundational building blocks for the Decaf TypeScript ec
|
|
|
29
39
|
|
|
30
40
|
Documentation [here](https://decaf-ts.github.io/injectable-decorators/), Test results [here](https://decaf-ts.github.io/injectable-decorators/workdocs/reports/html/test-report.html) and Coverage [here](https://decaf-ts.github.io/injectable-decorators/workdocs/reports/coverage/lcov-report/index.html)
|
|
31
41
|
|
|
32
|
-
Minimal size:
|
|
42
|
+
Minimal size: 33.6 KB kb gzipped
|
|
33
43
|
|
|
34
44
|
|
|
35
45
|
# Core Package — Detailed Description
|
|
@@ -133,235 +143,230 @@ Design intent
|
|
|
133
143
|
- Enable DI and decorators for ergonomic repository wiring and testing
|
|
134
144
|
|
|
135
145
|
|
|
136
|
-
# How
|
|
146
|
+
# How to Use
|
|
137
147
|
|
|
138
|
-
|
|
148
|
+
This guide provides detailed, real-life examples of how to use the main features of the `@decaf-ts/core` library.
|
|
139
149
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
150
|
+
## Repository and Adapter Interaction
|
|
151
|
+
|
|
152
|
+
The `Repository` and `Adapter` are the core of the persistence layer. The `Repository` provides a high-level API for your application to interact with, while the `Adapter` handles the specific implementation details of your chosen database.
|
|
153
|
+
|
|
154
|
+
### The `prepare` -> `action` -> `revert` Loop
|
|
155
|
+
|
|
156
|
+
This loop is the foundation of the persistence process. It ensures data is correctly transformed, validated, and persisted.
|
|
157
|
+
|
|
158
|
+
```mermaid
|
|
159
|
+
sequenceDiagram
|
|
160
|
+
participant C as Client Code
|
|
161
|
+
participant R as Repository
|
|
162
|
+
participant V as Validators/Decorators
|
|
163
|
+
participant A as Adapter
|
|
164
|
+
participant DB as Database
|
|
165
|
+
|
|
166
|
+
C->>+R: create(model)
|
|
167
|
+
R->>R: 1. createPrefix(model)
|
|
168
|
+
R->>+V: 2. Enforce DB Decorators (ON)
|
|
169
|
+
V-->>-R:
|
|
170
|
+
R->>+A: 3. prepare(model)
|
|
171
|
+
A-->>-R: { record, id, transient }
|
|
172
|
+
R->>+A: 4. create(table, id, record)
|
|
173
|
+
A->>+DB: 5. Database Insert
|
|
174
|
+
DB-->>-A: Result
|
|
175
|
+
A-->>-R: record
|
|
176
|
+
R->>+A: 6. revert(record)
|
|
177
|
+
A-->>-R: model instance
|
|
178
|
+
R->>R: 7. createSuffix(model)
|
|
179
|
+
R->>+V: 8. Enforce DB Decorators (AFTER)
|
|
180
|
+
V-->>-R:
|
|
181
|
+
R-->>-C: created model
|
|
167
182
|
```
|
|
168
183
|
|
|
184
|
+
1. **`createPrefix`**: The `Repository`'s `createPrefix` method is called. This is where you can add logic to be executed before the main `create` operation.
|
|
185
|
+
2. **Decorators (ON)**: Any decorators configured to run `ON` the `CREATE` operation are executed. This is a good place for validation or data transformation.
|
|
186
|
+
3. **`prepare`**: The `Adapter`'s `prepare` method is called to convert the model into a format suitable for the database. This includes separating transient properties.
|
|
187
|
+
4. **`create`**: The `Adapter`'s `create` method is called to persist the data to the database.
|
|
188
|
+
5. **Database Insert**: The `Adapter` communicates with the database to perform the insert operation.
|
|
189
|
+
6. **`revert`**: The `Adapter`'s `revert` method is called to convert the database record back into a model instance.
|
|
190
|
+
7. **`createSuffix`**: The `Repository`'s `createSuffix` method is called. This is where you can add logic to be executed after the main `create` operation.
|
|
191
|
+
8. **Decorators (AFTER)**: Any decorators configured to run `AFTER` the `CREATE` operation are executed.
|
|
169
192
|
|
|
170
|
-
|
|
171
|
-
Description: Create a RamAdapter and a Repository for a model and perform CRUD operations; mirrors core/tests/unit/RamAdapter.test.ts and adapter.test.ts.
|
|
172
|
-
```typescript
|
|
173
|
-
import { NotFoundError } from "@decaf-ts/db-decorators";
|
|
193
|
+
## Core Decorators
|
|
174
194
|
|
|
175
|
-
|
|
176
|
-
const adapter = new RamAdapter();
|
|
177
|
-
const repo: RamRepository<User> = new Repository(adapter, User);
|
|
195
|
+
The library provides a set of powerful decorators for defining models and their behavior.
|
|
178
196
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
197
|
+
* `@table(name)`: Specifies the database table name for a model.
|
|
198
|
+
* `@pk()`: Marks a property as the primary key.
|
|
199
|
+
* `@column(name)`: Maps a property to a database column with a different name.
|
|
200
|
+
* `@createdAt()`: Automatically sets the property to the current timestamp when a model is created.
|
|
201
|
+
* `@updatedAt()`: Automatically sets the property to the current timestamp when a model is created or updated.
|
|
202
|
+
* `@index()`: Creates a database index on a property.
|
|
183
203
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
204
|
+
```typescript
|
|
205
|
+
import { table, pk, column, createdAt, updatedAt, index } from '@decaf-ts/core';
|
|
206
|
+
import { model, Model } from '@decaf-ts/decorator-validation';
|
|
207
|
+
|
|
208
|
+
@table('users')
|
|
209
|
+
@model()
|
|
210
|
+
export class User extends Model {
|
|
211
|
+
@pk()
|
|
212
|
+
id: string;
|
|
187
213
|
|
|
188
|
-
|
|
189
|
-
|
|
214
|
+
@column('user_name')
|
|
215
|
+
@index()
|
|
216
|
+
name: string;
|
|
190
217
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
218
|
+
@createdAt()
|
|
219
|
+
createdAt: Date;
|
|
220
|
+
|
|
221
|
+
@updatedAt()
|
|
222
|
+
updatedAt: Date;
|
|
194
223
|
}
|
|
195
224
|
```
|
|
196
225
|
|
|
226
|
+
## Complex Relations
|
|
227
|
+
|
|
228
|
+
You can model complex relationships between your classes using `@oneToOne`, `@oneToMany`, and `@manyToOne`.
|
|
197
229
|
|
|
198
|
-
- Adapter current and registered models; @repository class decorator
|
|
199
|
-
Description: Show how to set/get current adapter and register a repository via the @repository decorator; mirrors adapter.test.ts.
|
|
200
230
|
```typescript
|
|
231
|
+
import { table, pk, oneToOne, oneToMany, manyToOne } from '@decaf-ts/core';
|
|
232
|
+
import { model, Model } from '@decaf-ts/decorator-validation';
|
|
233
|
+
import { User } from './User';
|
|
201
234
|
|
|
235
|
+
@table('profiles')
|
|
202
236
|
@model()
|
|
203
|
-
class
|
|
237
|
+
export class Profile extends Model {
|
|
238
|
+
@pk()
|
|
239
|
+
id: string;
|
|
204
240
|
|
|
205
|
-
|
|
206
|
-
@uses("ram")
|
|
207
|
-
class ManagedRepository extends Repository<Managed> {
|
|
208
|
-
// Concrete adapter-backed methods would be provided by adapter implementation
|
|
209
|
-
// For quick test or demo, use a RamAdapter
|
|
241
|
+
bio: string;
|
|
210
242
|
}
|
|
211
243
|
|
|
212
|
-
|
|
213
|
-
|
|
244
|
+
@table('posts')
|
|
245
|
+
@model()
|
|
246
|
+
export class Post extends Model {
|
|
247
|
+
@pk()
|
|
248
|
+
id: string;
|
|
214
249
|
|
|
215
|
-
|
|
216
|
-
console.log(Adapter.current === Adapter.get("ram")); // true
|
|
250
|
+
title: string;
|
|
217
251
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
console.log(Array.isArray(managed));
|
|
252
|
+
@manyToOne(() => User)
|
|
253
|
+
author: User;
|
|
221
254
|
}
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
- Query building with select/order and execution
|
|
225
|
-
Description: Build a statement with orderBy and run it, as done in core/tests/unit/Pagination.test.ts.
|
|
226
255
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
// Seed data
|
|
233
|
-
await repo.createAll(
|
|
234
|
-
Array.from({ length: 5 }).map((_, i) =>
|
|
235
|
-
new User({ id: (i + 1).toString(), name: `u${i + 1}`, nif: "123456789" })
|
|
236
|
-
)
|
|
237
|
-
);
|
|
238
|
-
|
|
239
|
-
const results = await repo
|
|
240
|
-
.select()
|
|
241
|
-
.orderBy(["id", OrderDirection.ASC])
|
|
242
|
-
.execute();
|
|
243
|
-
|
|
244
|
-
console.log(results.map((u) => u.id)); // ["1","2","3","4","5"]
|
|
245
|
-
}
|
|
246
|
-
```
|
|
256
|
+
@table('users')
|
|
257
|
+
@model()
|
|
258
|
+
export class User extends Model {
|
|
259
|
+
@pk()
|
|
260
|
+
id: string;
|
|
247
261
|
|
|
248
|
-
|
|
249
|
-
|
|
262
|
+
@oneToOne(() => Profile)
|
|
263
|
+
profile: Profile;
|
|
250
264
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const adapter = new RamAdapter();
|
|
254
|
-
const repo: RamRepository<User> = new Repository(adapter, User);
|
|
255
|
-
|
|
256
|
-
// Seed data
|
|
257
|
-
const size = 25;
|
|
258
|
-
await repo.createAll(
|
|
259
|
-
Array.from({ length: size }).map((_, i) =>
|
|
260
|
-
new User({ id: (i + 1).toString(), name: `u${i + 1}`, nif: "123456789" })
|
|
261
|
-
)
|
|
262
|
-
);
|
|
263
|
-
|
|
264
|
-
const paginator: Paginator<User> = await repo
|
|
265
|
-
.select()
|
|
266
|
-
.orderBy(["id", OrderDirection.DSC])
|
|
267
|
-
.paginate(10);
|
|
268
|
-
|
|
269
|
-
const page1 = await paginator.page(); // first page by default
|
|
270
|
-
const page2 = await paginator.next();
|
|
271
|
-
const page3 = await paginator.next();
|
|
272
|
-
|
|
273
|
-
console.log(page1.length, page2.length, page3.length); // 10, 10, 5
|
|
265
|
+
@oneToMany(() => Post)
|
|
266
|
+
posts: Post[];
|
|
274
267
|
}
|
|
275
268
|
```
|
|
276
269
|
|
|
270
|
+
## Extending the Adapter
|
|
277
271
|
|
|
278
|
-
|
|
279
|
-
Description: Compose conditions with the builder and apply them in a where clause.
|
|
280
|
-
```typescript
|
|
281
|
-
import { Condition } from "@decaf-ts/core";
|
|
272
|
+
You can create your own persistence layer by extending the `Adapter` class.
|
|
282
273
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
const repo: RamRepository<User> = new Repository(adapter, User);
|
|
274
|
+
```typescript
|
|
275
|
+
import { Adapter, Model, Constructor, PrimaryKeyType } from '@decaf-ts/core';
|
|
286
276
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
277
|
+
class MyCustomAdapter extends Adapter<any, any, any, any> {
|
|
278
|
+
constructor() {
|
|
279
|
+
super({}, 'my-custom-adapter');
|
|
280
|
+
}
|
|
291
281
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
282
|
+
async create<M extends Model>(
|
|
283
|
+
clazz: Constructor<M>,
|
|
284
|
+
id: PrimaryKeyType,
|
|
285
|
+
model: Record<string, any>
|
|
286
|
+
): Promise<Record<string, any>> {
|
|
287
|
+
console.log(`Creating in ${Model.tableName(clazz)} with id ${id}`);
|
|
288
|
+
// Your database insert logic here
|
|
289
|
+
return model;
|
|
290
|
+
}
|
|
295
291
|
|
|
296
|
-
|
|
297
|
-
console.log(results.length); // 1
|
|
292
|
+
// Implement other abstract methods: read, update, delete, raw
|
|
298
293
|
}
|
|
299
294
|
```
|
|
300
295
|
|
|
296
|
+
## Services
|
|
301
297
|
|
|
302
|
-
|
|
303
|
-
Description: Convert a model to a storage record and back using Adapter.prepare and Adapter.revert; mirrors adapter.test.ts.
|
|
304
|
-
```typescript
|
|
305
|
-
async function mappingExample() {
|
|
306
|
-
const adapter = new RamAdapter();
|
|
307
|
-
const repo: RamRepository<User> = new Repository(adapter, User);
|
|
298
|
+
The `ModelService` provides a convenient way to interact with your repositories.
|
|
308
299
|
|
|
309
|
-
|
|
300
|
+
```typescript
|
|
301
|
+
import { ModelService, Repository } from '@decaf-ts/core';
|
|
302
|
+
import { User } from './models';
|
|
310
303
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
304
|
+
class UserService extends ModelService<User, Repository<User, any>> {
|
|
305
|
+
constructor() {
|
|
306
|
+
super(User);
|
|
307
|
+
}
|
|
315
308
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
309
|
+
async findActiveUsers(): Promise<User[]> {
|
|
310
|
+
return this.repository.select().where({ status: 'active' }).execute();
|
|
311
|
+
}
|
|
319
312
|
}
|
|
313
|
+
|
|
314
|
+
const userService = new UserService();
|
|
315
|
+
const activeUsers = await userService.findActiveUsers();
|
|
320
316
|
```
|
|
321
317
|
|
|
318
|
+
## Task Engine
|
|
319
|
+
|
|
320
|
+
The `TaskEngine` is a powerful tool for managing background jobs.
|
|
321
|
+
|
|
322
|
+
### Creating a Task Handler
|
|
323
|
+
|
|
324
|
+
A `TaskHandler` defines the logic for a specific task.
|
|
322
325
|
|
|
323
|
-
- Auto-resolving repositories with InjectablesRegistry
|
|
324
|
-
Description: Retrieve a repository by model name or constructor using the DI registry; see repository/injectables.ts flow.
|
|
325
326
|
```typescript
|
|
326
|
-
import {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
async
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
// Resolve by constructor
|
|
335
|
-
const userRepo = Injectables.get<Repository<User>>(User);
|
|
336
|
-
if (userRepo) {
|
|
337
|
-
const u = await userRepo.create(
|
|
338
|
-
new User({ id: "1", name: "A", nif: "123456789" })
|
|
339
|
-
);
|
|
340
|
-
console.log(!!u);
|
|
327
|
+
import { TaskHandler, TaskContext } from '@decaf-ts/core';
|
|
328
|
+
|
|
329
|
+
class MyTaskHandler implements TaskHandler<any, any> {
|
|
330
|
+
async run(input: any, context: TaskContext): Promise<any> {
|
|
331
|
+
console.log('Running my task with input:', input);
|
|
332
|
+
await context.progress({ message: 'Step 1 complete' });
|
|
333
|
+
// ... task logic
|
|
334
|
+
return { result: 'success' };
|
|
341
335
|
}
|
|
342
336
|
}
|
|
343
337
|
```
|
|
344
338
|
|
|
339
|
+
### Using the Task Engine
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
import { TaskEngine, TaskModel, TaskHandlerRegistry } from '@decaf-ts/core';
|
|
343
|
+
import { MyTaskHandler } from './MyTaskHandler';
|
|
344
|
+
|
|
345
|
+
// 1. Register the handler
|
|
346
|
+
const registry = new TaskHandlerRegistry();
|
|
347
|
+
registry.register('my-task', new MyTaskHandler());
|
|
345
348
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
349
|
+
// 2. Create the task engine
|
|
350
|
+
const taskEngine = new TaskEngine({ adapter, registry });
|
|
351
|
+
|
|
352
|
+
// 3. Push a task
|
|
353
|
+
const task = new TaskModel({
|
|
354
|
+
classification: 'my-task',
|
|
355
|
+
input: { some: 'data' },
|
|
356
|
+
});
|
|
357
|
+
const { tracker } = await taskEngine.push(task, true);
|
|
358
|
+
|
|
359
|
+
// 4. Track the task's progress and result
|
|
360
|
+
tracker.on('progress', (payload) => {
|
|
361
|
+
console.log('Task progress:', payload);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
const result = await tracker.resolve();
|
|
365
|
+
console.log('Task result:', result);
|
|
366
|
+
|
|
367
|
+
// 5. Schedule a task
|
|
368
|
+
taskEngine.schedule(task).for(new Date(Date.now() + 5000)); // 5 seconds from now
|
|
369
|
+
```
|
|
365
370
|
|
|
366
371
|
|
|
367
372
|
### Related
|