@decaf-ts/core 0.5.20 → 0.5.22
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.md +13 -16
- package/README.md +252 -604
- package/dist/core.cjs +335 -125
- package/dist/core.esm.cjs +338 -128
- package/lib/esm/identity/decorators.js +1 -1
- package/lib/esm/index.d.ts +1 -1
- package/lib/esm/index.js +1 -1
- package/lib/esm/model/types.d.ts +0 -9
- package/lib/esm/model/types.js +1 -1
- package/lib/esm/persistence/Adapter.d.ts +61 -51
- package/lib/esm/persistence/Adapter.js +120 -47
- package/lib/esm/persistence/Dispatch.d.ts +2 -13
- package/lib/esm/persistence/Dispatch.js +7 -15
- package/lib/esm/persistence/errors.d.ts +2 -2
- package/lib/esm/persistence/errors.js +4 -4
- package/lib/esm/persistence/types.d.ts +2 -0
- package/lib/esm/persistence/types.js +1 -1
- package/lib/esm/query/Condition.d.ts +8 -0
- package/lib/esm/query/Condition.js +9 -1
- package/lib/esm/query/Paginator.d.ts +2 -2
- package/lib/esm/query/Paginator.js +1 -1
- package/lib/esm/query/Statement.d.ts +4 -3
- package/lib/esm/query/Statement.js +4 -2
- package/lib/esm/query/errors.d.ts +3 -3
- package/lib/esm/query/errors.js +6 -6
- package/lib/esm/ram/RamAdapter.d.ts +8 -14
- package/lib/esm/ram/RamAdapter.js +44 -33
- package/lib/esm/ram/RamContext.d.ts +14 -6
- package/lib/esm/ram/RamContext.js +15 -7
- package/lib/esm/ram/constants.d.ts +1 -2
- package/lib/esm/ram/constants.js +2 -3
- package/lib/esm/ram/types.d.ts +3 -0
- package/lib/esm/ram/types.js +1 -1
- package/lib/esm/repository/Repository.d.ts +8 -7
- package/lib/esm/repository/Repository.js +8 -8
- package/lib/esm/repository/constants.d.ts +5 -4
- package/lib/esm/repository/constants.js +6 -5
- package/lib/esm/repository/decorators.d.ts +1 -1
- package/lib/esm/repository/decorators.js +12 -9
- package/lib/esm/repository/errors.d.ts +2 -2
- package/lib/esm/repository/errors.js +4 -4
- package/lib/esm/repository/injectables.d.ts +82 -11
- package/lib/esm/repository/injectables.js +137 -28
- package/lib/esm/repository/utils.d.ts +28 -6
- package/lib/esm/repository/utils.js +29 -7
- package/lib/esm/utils/errors.d.ts +5 -5
- package/lib/esm/utils/errors.js +9 -9
- package/lib/identity/decorators.cjs +1 -1
- package/lib/index.cjs +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/model/types.cjs +1 -1
- package/lib/model/types.d.ts +0 -9
- package/lib/persistence/Adapter.cjs +119 -46
- package/lib/persistence/Adapter.d.ts +61 -51
- package/lib/persistence/Dispatch.cjs +6 -14
- package/lib/persistence/Dispatch.d.ts +2 -13
- package/lib/persistence/errors.cjs +3 -3
- package/lib/persistence/errors.d.ts +2 -2
- package/lib/persistence/types.cjs +1 -1
- package/lib/persistence/types.d.ts +2 -0
- package/lib/query/Condition.cjs +9 -1
- package/lib/query/Condition.d.ts +8 -0
- package/lib/query/Paginator.cjs +1 -1
- package/lib/query/Paginator.d.ts +2 -2
- package/lib/query/Statement.cjs +4 -2
- package/lib/query/Statement.d.ts +4 -3
- package/lib/query/errors.cjs +5 -5
- package/lib/query/errors.d.ts +3 -3
- package/lib/ram/RamAdapter.cjs +43 -32
- package/lib/ram/RamAdapter.d.ts +8 -14
- package/lib/ram/RamContext.cjs +15 -7
- package/lib/ram/RamContext.d.ts +14 -6
- package/lib/ram/constants.cjs +2 -3
- package/lib/ram/constants.d.ts +1 -2
- package/lib/ram/types.cjs +1 -1
- package/lib/ram/types.d.ts +3 -0
- package/lib/repository/Repository.cjs +8 -8
- package/lib/repository/Repository.d.ts +8 -7
- package/lib/repository/constants.cjs +6 -5
- package/lib/repository/constants.d.ts +5 -4
- package/lib/repository/decorators.cjs +12 -9
- package/lib/repository/decorators.d.ts +1 -1
- package/lib/repository/errors.cjs +3 -3
- package/lib/repository/errors.d.ts +2 -2
- package/lib/repository/injectables.cjs +137 -28
- package/lib/repository/injectables.d.ts +82 -11
- package/lib/repository/utils.cjs +29 -7
- package/lib/repository/utils.d.ts +28 -6
- package/lib/utils/errors.cjs +8 -8
- package/lib/utils/errors.d.ts +5 -5
- package/package.json +3 -2
package/README.md
CHANGED
@@ -1,8 +1,6 @@
|
|
1
|
-
|
1
|
+
# Decaf TS — Core Package
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
The Decaf TypeScript Core Module is a comprehensive framework that provides a robust foundation for building TypeScript applications with data persistence capabilities. It offers a flexible model-repository architecture with support for various storage mechanisms, relationship management, querying capabilities, and reactive programming through the Observer pattern. The framework simplifies data handling with decorators for model definition, identity management, and persistence operations while maintaining type safety throughout.
|
3
|
+
Decaf Core provides the foundational building blocks for the Decaf TypeScript ecosystem: strongly-typed models, repository pattern, pluggable persistence adapters, a composable query DSL, and pagination/observer utilities. With decorators and an injectable registry, it wires models to repositories and adapters so you can build data access that is framework-agnostic yet fully typed.
|
6
4
|
|
7
5
|
|
8
6
|

|
@@ -26,666 +24,316 @@ The Decaf TypeScript Core Module is a comprehensive framework that provides a ro
|
|
26
24
|

|
27
25
|

|
28
26
|
|
29
|
-
Documentation
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
-
|
77
|
-
-
|
78
|
-
-
|
79
|
-
-
|
80
|
-
-
|
81
|
-
-
|
82
|
-
-
|
83
|
-
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
-
|
91
|
-
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
-
|
96
|
-
-
|
97
|
-
-
|
98
|
-
-
|
99
|
-
-
|
100
|
-
-
|
101
|
-
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
```
|
141
|
-
|
142
|
-
### Customizing Table and Column Names
|
143
|
-
|
144
|
-
Use the `@table` and `@column` decorators to customize database table and column names.
|
145
|
-
|
27
|
+
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)
|
28
|
+
|
29
|
+
|
30
|
+
# Core Package — Detailed Description
|
31
|
+
|
32
|
+
The Decaf Core package provides a cohesive set of primitives for building strongly-typed data-access layers in TypeScript. It centers around:
|
33
|
+
|
34
|
+
- Models (from @decaf-ts/decorator-validation) enhanced with identity and persistence metadata
|
35
|
+
- A Repository abstraction that encapsulates CRUD, querying, and observation
|
36
|
+
- Adapters that bridge repositories to underlying storage (in-memory, HTTP, TypeORM, etc.)
|
37
|
+
- A fluent Query DSL (Statement/Condition) with pagination
|
38
|
+
- Lightweight dependency injection utilities to auto-resolve repositories
|
39
|
+
|
40
|
+
Below is an overview of the main modules and their public APIs exposed by core.
|
41
|
+
|
42
|
+
1) Repository module
|
43
|
+
- Repository<M>
|
44
|
+
- Constructor: new Repository(adapter: Adapter, clazz: Constructor<M>, ...)
|
45
|
+
- CRUD: create, read, update, delete
|
46
|
+
- Bulk ops: createAll, readAll, updateAll, deleteAll
|
47
|
+
- Hooks: createPrefix/createSuffix, updateAllPrefix, readAllPrefix, deleteAllPrefix (internal orchestration helpers)
|
48
|
+
- Query: select(...selectors?), query(condition?, orderBy?, order?, limit?, skip?)
|
49
|
+
- Observation: observe(observer, filter?), unObserve(observer), updateObservers(...), refresh(...)
|
50
|
+
- Repository registry helpers:
|
51
|
+
- static for(config, ...args): Proxy factory for building repositories with specific adapter config
|
52
|
+
- static forModel(model, alias?, ...args): returns a Repository instance or repository constructor registered for the model
|
53
|
+
- static get(model, alias?): low-level retrieval of a registered repository constructor
|
54
|
+
- static register(model, repoCtor, alias?)
|
55
|
+
- static getMetadata/setMetadata/removeMetadata(model)
|
56
|
+
- static getSequenceOptions(model)
|
57
|
+
- static indexes(model): reads index definitions for model
|
58
|
+
- static relations(model)
|
59
|
+
- static table(model), static column(model, attribute)
|
60
|
+
- Decorators (repository/decorators)
|
61
|
+
- repository(modelCtor, flavour?):
|
62
|
+
- As property decorator: injects the repository instance for the annotated model
|
63
|
+
- As class decorator: registers the annotated class as the repository for the model; integrates with Injectables
|
64
|
+
- Injectables registry (repository/injectables)
|
65
|
+
- InjectablesRegistry extends InjectableRegistryImp
|
66
|
+
- get<T>(name | token | ctor, flavour?): resolves a registered injectable; if not registered, attempts to infer the model and construct or fetch the appropriate repository based on adapter flavour or metadata (falling back to current adapter)
|
67
|
+
- Types/utilities (repository/types, repository/utils)
|
68
|
+
- IndexMetadata, OrderDirection, generateInjectableNameForRepository, and other helpers/constants
|
69
|
+
|
70
|
+
2) Persistence module
|
71
|
+
- Adapter<N = any, Q = any, R = any, Ctx = Context>
|
72
|
+
- Base bridge between repository and the back-end. Offers:
|
73
|
+
- initialize(...), flags(...), context(...)
|
74
|
+
- prepare(model, pk): model -> record mapping using model metadata
|
75
|
+
- revert(record, clazz, pk, id, transient?): record -> model mapping
|
76
|
+
- CRUD: create, createAll, read, readAll, update, updateAll, delete, deleteAll
|
77
|
+
- raw(rawInput): pass-through for back-end specific commands
|
78
|
+
- Observation: observe/unObserve, updateObservers, refresh
|
79
|
+
- Flavour/alias management: current(), get(flavour), setCurrent(flavour), alias(), models(flavour), flavourOf(model)
|
80
|
+
- Factory helpers: Statement(), Dispatch(), ObserverHandler(), Sequence(options)
|
81
|
+
- for(config, ...args): proxy-bound adapter for a given configuration
|
82
|
+
- Dispatch: batching/dispatch helpers used by Adapter
|
83
|
+
- Sequence: provides identity/sequence generation based on SequenceOptions (see interfaces)
|
84
|
+
- ObserverHandler: internal observer list and filtering logic used by repositories/adapters
|
85
|
+
- constants, errors, types: PersistenceKeys, EventIds, ObserverFilter, etc.
|
86
|
+
|
87
|
+
3) Query module
|
88
|
+
- Statement<M extends Model>
|
89
|
+
- Fluent DSL to build and execute queries via the configured Adapter
|
90
|
+
- Methods:
|
91
|
+
- select(...keys?), distinct(key), count(key), max(key), min(key)
|
92
|
+
- from(modelCtor), where(Condition), orderBy([key, OrderDirection]), groupBy(key)
|
93
|
+
- limit(n), offset(n), execute(), raw(input), paginate(size)
|
94
|
+
- Condition<M extends Model>
|
95
|
+
- Composable condition tree with a builder API and logical combinators
|
96
|
+
- Methods:
|
97
|
+
- and(cond), or(cond), not(cond)
|
98
|
+
- attribute/attr(name): switch attribute under construction
|
99
|
+
- hasErrors(exceptions?): validation helper
|
100
|
+
- group(cond1, GroupOperator, cond2)
|
101
|
+
- builder(): ConditionBuilder
|
102
|
+
- ConditionBuilder methods: eq, dif, gt, lt, gte, lte, in, regexp, build
|
103
|
+
- Paginator<M>
|
104
|
+
- Abstract pagination helper returned by Statement.paginate(size)
|
105
|
+
- Properties: current, total, count, size
|
106
|
+
- Methods: page(n?), next(), previous(); requires an Adapter-specific concrete implementation
|
107
|
+
|
108
|
+
4) Interfaces module
|
109
|
+
- Observable<T>, Observer<T>: basic observer pattern primitives
|
110
|
+
- Executor, RawExecutor: contracts for query execution
|
111
|
+
- Queriable: minimal interface for types that can return a Statement
|
112
|
+
- Paginatable: marks types that can paginate
|
113
|
+
- SequenceOptions and defaults: sequence/generator configuration presets
|
114
|
+
|
115
|
+
5) Model & Identity modules
|
116
|
+
- BaseModel and supporting types: base class all models extend from
|
117
|
+
- identity/decorators and identity/utils: helpers to derive table names, etc.
|
118
|
+
- model/decorators: e.g., @model and other persistence-related metadata (provided by @decaf-ts/decorator-validation and enriched here)
|
119
|
+
|
120
|
+
6) RAM runtime (core/src/ram)
|
121
|
+
- RamAdapter, RamRepository, RamStatement, RamPaginator (in-memory implementations used by tests and examples)
|
122
|
+
- Useful for local testing and reference behavior of the core abstractions.
|
123
|
+
|
124
|
+
Design intent
|
125
|
+
- Provide a consistent, typed data access layer decoupled from any particular storage or framework
|
126
|
+
- Allow adapters to plug into multiple backends while preserving a uniform repository and query API
|
127
|
+
- Make querying expressive but type-safe through fluent builders and model metadata
|
128
|
+
- Enable DI and decorators for ergonomic repository wiring and testing
|
129
|
+
|
130
|
+
|
131
|
+
# How To Use — Core Package
|
132
|
+
|
133
|
+
Below are practical, focused examples for the public APIs exposed by the Core package. Each example includes a short description and valid TypeScript code. Examples are inspired by and aligned with the unit tests under core/tests.
|
134
|
+
|
135
|
+
Prerequisites used across examples:
|
136
|
+
- Ensure your model builder is set for tests/dev scenarios: Model.setBuilder(Model.fromModel)
|
137
|
+
- Use the RAM adapter for quick in-memory demos
|
146
138
|
```typescript
|
147
|
-
import {
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
}
|
170
|
-
}
|
171
|
-
```
|
172
|
-
|
173
|
-
### Creating Indexes
|
174
|
-
|
175
|
-
Use the `@index` decorator to create database indexes for better query performance.
|
176
|
-
|
177
|
-
```typescript
|
178
|
-
import { BaseModel, required, index, pk, OrderDirection } from '@decaf-ts/core';
|
179
|
-
|
180
|
-
class Product extends BaseModel {
|
181
|
-
@pk()
|
182
|
-
id!: string;
|
183
|
-
|
184
|
-
@required()
|
185
|
-
@index([OrderDirection.ASC])
|
186
|
-
name!: string;
|
187
|
-
|
188
|
-
@required()
|
189
|
-
@index([OrderDirection.ASC, OrderDirection.DSC])
|
190
|
-
price!: number;
|
191
|
-
|
192
|
-
@required()
|
193
|
-
category!: string;
|
194
|
-
|
195
|
-
constructor(data?: Partial<Product>) {
|
196
|
-
super(data);
|
197
|
-
}
|
198
|
-
}
|
199
|
-
```
|
200
|
-
|
201
|
-
## Identity Management
|
202
|
-
|
203
|
-
### Using Primary Keys with Automatic Sequence Generation
|
204
|
-
|
205
|
-
The `@pk` decorator marks a property as the primary key and sets up automatic sequence generation.
|
206
|
-
|
207
|
-
```typescript
|
208
|
-
import { BaseModel, pk, required } from '@decaf-ts/core';
|
209
|
-
|
210
|
-
class Order extends BaseModel {
|
211
|
-
@pk({ type: 'Number' })
|
212
|
-
id!: number;
|
213
|
-
|
214
|
-
@required()
|
215
|
-
customerId!: string;
|
216
|
-
|
217
|
-
@required()
|
218
|
-
totalAmount!: number;
|
219
|
-
|
220
|
-
constructor(data?: Partial<Order>) {
|
221
|
-
super(data);
|
222
|
-
}
|
223
|
-
}
|
224
|
-
|
225
|
-
// The id will be automatically generated when the order is saved
|
226
|
-
const order = new Order({
|
227
|
-
customerId: 'cust123',
|
228
|
-
totalAmount: 99.99
|
229
|
-
});
|
230
|
-
```
|
231
|
-
|
232
|
-
### Custom Sequence Options
|
233
|
-
|
234
|
-
Customize the sequence generation for primary keys.
|
235
|
-
|
236
|
-
```typescript
|
237
|
-
import { BaseModel, pk, required } from '@decaf-ts/core';
|
238
|
-
|
239
|
-
class Invoice extends BaseModel {
|
240
|
-
@pk({
|
241
|
-
type: 'BigInt',
|
242
|
-
name: 'invoice_sequence',
|
243
|
-
startWith: 1000,
|
244
|
-
incrementBy: 1
|
245
|
-
})
|
246
|
-
invoiceNumber!: bigint;
|
247
|
-
|
248
|
-
@required()
|
249
|
-
orderId!: number;
|
250
|
-
|
251
|
-
@required()
|
252
|
-
amount!: number;
|
253
|
-
|
254
|
-
constructor(data?: Partial<Invoice>) {
|
255
|
-
super(data);
|
256
|
-
}
|
257
|
-
}
|
258
|
-
```
|
259
|
-
|
260
|
-
## Relationships
|
261
|
-
|
262
|
-
### One-to-One Relationships
|
263
|
-
|
264
|
-
Define a one-to-one relationship between models.
|
265
|
-
|
266
|
-
```typescript
|
267
|
-
import { BaseModel, pk, required, oneToOne } from '@decaf-ts/core';
|
268
|
-
|
269
|
-
class User extends BaseModel {
|
270
|
-
@pk()
|
271
|
-
id!: string;
|
272
|
-
|
273
|
-
@required()
|
274
|
-
username!: string;
|
275
|
-
|
276
|
-
@oneToOne(Profile)
|
277
|
-
profile?: Profile;
|
278
|
-
|
279
|
-
constructor(data?: Partial<User>) {
|
280
|
-
super(data);
|
281
|
-
}
|
282
|
-
}
|
283
|
-
|
284
|
-
class Profile extends BaseModel {
|
285
|
-
@pk()
|
286
|
-
id!: string;
|
287
|
-
|
288
|
-
@required()
|
289
|
-
userId!: string;
|
290
|
-
|
291
|
-
bio?: string;
|
292
|
-
|
293
|
-
avatarUrl?: string;
|
294
|
-
|
295
|
-
constructor(data?: Partial<Profile>) {
|
296
|
-
super(data);
|
297
|
-
}
|
139
|
+
import { Model, model } from "@decaf-ts/decorator-validation";
|
140
|
+
import type { ModelArg } from "@decaf-ts/decorator-validation";
|
141
|
+
import {
|
142
|
+
Adapter,
|
143
|
+
OrderDirection,
|
144
|
+
Paginator,
|
145
|
+
Repository,
|
146
|
+
repository,
|
147
|
+
uses,
|
148
|
+
pk,
|
149
|
+
column,
|
150
|
+
table,
|
151
|
+
} from "@decaf-ts/core";
|
152
|
+
import { RamAdapter, RamRepository } from "@decaf-ts/core/ram";
|
153
|
+
|
154
|
+
@table("tst_user")
|
155
|
+
@model()
|
156
|
+
class User extends Model {
|
157
|
+
@pk() id!: string;
|
158
|
+
@column("tst_name") name!: string;
|
159
|
+
@column("tst_nif") nif!: string;
|
160
|
+
constructor(arg?: ModelArg<User>) { super(arg); }
|
298
161
|
}
|
299
162
|
```
|
300
163
|
|
301
|
-
### One-to-Many Relationships
|
302
|
-
|
303
|
-
Define a one-to-many relationship between models.
|
304
164
|
|
165
|
+
- Repository + RAM adapter: basic CRUD
|
166
|
+
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.
|
305
167
|
```typescript
|
306
|
-
import {
|
307
|
-
|
308
|
-
class Author extends BaseModel {
|
309
|
-
@pk()
|
310
|
-
id!: string;
|
168
|
+
import { NotFoundError } from "@decaf-ts/db-decorators";
|
311
169
|
|
312
|
-
|
313
|
-
|
170
|
+
async function crudExample() {
|
171
|
+
const adapter = new RamAdapter();
|
172
|
+
const repo: RamRepository<User> = new Repository(adapter, User);
|
314
173
|
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
super(data);
|
320
|
-
}
|
321
|
-
}
|
322
|
-
|
323
|
-
class Book extends BaseModel {
|
324
|
-
@pk()
|
325
|
-
id!: string;
|
174
|
+
// CREATE
|
175
|
+
const created = await repo.create(
|
176
|
+
new User({ id: Date.now().toString(), name: "Alice", nif: "123456789" })
|
177
|
+
);
|
326
178
|
|
327
|
-
|
328
|
-
|
179
|
+
// READ
|
180
|
+
const read = await repo.read(created.id);
|
181
|
+
console.log(read.equals(created)); // true (same data, different instance)
|
329
182
|
|
330
|
-
|
331
|
-
|
183
|
+
// UPDATE
|
184
|
+
const updated = await repo.update(Object.assign(read, {name: "Alice 2" }));
|
332
185
|
|
333
|
-
|
334
|
-
|
335
|
-
|
186
|
+
// DELETE
|
187
|
+
const deleted = await repo.delete(created.id);
|
188
|
+
console.log(deleted.equals(updated)); // true
|
336
189
|
}
|
337
190
|
```
|
338
191
|
|
339
|
-
### Many-to-One Relationships
|
340
|
-
|
341
|
-
Define a many-to-one relationship between models.
|
342
192
|
|
193
|
+
- Adapter current and registered models; @repository class decorator
|
194
|
+
Description: Show how to set/get current adapter and register a repository via the @repository decorator; mirrors adapter.test.ts.
|
343
195
|
```typescript
|
344
|
-
import { BaseModel, pk, required, manyToOne } from '@decaf-ts/core';
|
345
|
-
|
346
|
-
class Book extends BaseModel {
|
347
|
-
@pk()
|
348
|
-
id!: string;
|
349
|
-
|
350
|
-
@required()
|
351
|
-
title!: string;
|
352
196
|
|
353
|
-
|
354
|
-
|
197
|
+
@model()
|
198
|
+
class Managed extends Model { constructor(arg?: ModelArg<Managed>) { super(arg); } }
|
355
199
|
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
}
|
200
|
+
@repository(Managed)
|
201
|
+
@uses("ram")
|
202
|
+
class ManagedRepository extends Repository<Managed> {
|
203
|
+
// Concrete adapter-backed methods would be provided by adapter implementation
|
204
|
+
// For quick test or demo, use a RamAdapter
|
362
205
|
}
|
363
206
|
|
364
|
-
|
365
|
-
|
366
|
-
id!: string;
|
207
|
+
async function adapterRegistryExample() {
|
208
|
+
const adapter = new RamAdapter();
|
367
209
|
|
368
|
-
|
369
|
-
|
210
|
+
Adapter.setCurrent("ram"); // set current flavour
|
211
|
+
console.log(Adapter.current === Adapter.get("ram")); // true
|
370
212
|
|
371
|
-
|
372
|
-
|
373
|
-
|
213
|
+
// Models managed by current or specific adapter flavour
|
214
|
+
const managed = Adapter.models("ram");
|
215
|
+
console.log(Array.isArray(managed));
|
374
216
|
}
|
375
217
|
```
|
376
218
|
|
377
|
-
|
378
|
-
|
379
|
-
### Basic CRUD Operations
|
380
|
-
|
381
|
-
Perform basic CRUD operations using a repository.
|
219
|
+
- Query building with select/order and execution
|
220
|
+
Description: Build a statement with orderBy and run it, as done in core/tests/unit/Pagination.test.ts.
|
382
221
|
|
383
222
|
```typescript
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
email!: string;
|
395
|
-
|
396
|
-
constructor(data?: Partial<User>) {
|
397
|
-
super(data);
|
398
|
-
}
|
399
|
-
}
|
400
|
-
|
401
|
-
// Create a repository for the User model
|
402
|
-
const userRepository = new Repository(User);
|
403
|
-
|
404
|
-
// Create a new user
|
405
|
-
async function createUser() {
|
406
|
-
const user = new User({
|
407
|
-
username: 'johndoe',
|
408
|
-
email: 'john.doe@example.com'
|
409
|
-
});
|
410
|
-
|
411
|
-
const createdUser = await userRepository.create(user);
|
412
|
-
console.log('User created with ID:', createdUser.id);
|
413
|
-
return createdUser;
|
414
|
-
}
|
415
|
-
|
416
|
-
// Read a user by ID
|
417
|
-
async function getUserById(id: string) {
|
418
|
-
const user = await userRepository.read(id);
|
419
|
-
console.log('User found:', user);
|
420
|
-
return user;
|
421
|
-
}
|
223
|
+
async function queryExample() {
|
224
|
+
const adapter = new RamAdapter();
|
225
|
+
const repo: RamRepository<User> = new Repository(adapter, User);
|
226
|
+
|
227
|
+
// Seed data
|
228
|
+
await repo.createAll(
|
229
|
+
Array.from({ length: 5 }).map((_, i) =>
|
230
|
+
new User({ id: (i + 1).toString(), name: `u${i + 1}`, nif: "123456789" })
|
231
|
+
)
|
232
|
+
);
|
422
233
|
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
console.log('User updated');
|
428
|
-
return updatedUser;
|
429
|
-
}
|
234
|
+
const results = await repo
|
235
|
+
.select()
|
236
|
+
.orderBy(["id", OrderDirection.ASC])
|
237
|
+
.execute();
|
430
238
|
|
431
|
-
|
432
|
-
async function deleteUser(user: User) {
|
433
|
-
await userRepository.delete(user);
|
434
|
-
console.log('User deleted');
|
239
|
+
console.log(results.map((u) => u.id)); // ["1","2","3","4","5"]
|
435
240
|
}
|
436
241
|
```
|
437
242
|
|
438
|
-
|
439
|
-
|
440
|
-
### Basic Queries
|
441
|
-
|
442
|
-
Perform basic queries using conditions.
|
243
|
+
- Pagination with Paginator
|
244
|
+
Description: Paginate query results using Statement.paginate(size), then page through results; mirrors Pagination.test.ts.
|
443
245
|
|
444
246
|
```typescript
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
@required()
|
455
|
-
price!: number;
|
456
|
-
|
457
|
-
@required()
|
458
|
-
category!: string;
|
459
|
-
|
460
|
-
constructor(data?: Partial<Product>) {
|
461
|
-
super(data);
|
462
|
-
}
|
463
|
-
}
|
464
|
-
|
465
|
-
const productRepository = new Repository(Product);
|
466
|
-
|
467
|
-
// Find products by category
|
468
|
-
async function findProductsByCategory(category: string) {
|
469
|
-
const condition = Condition.eq('category', category);
|
470
|
-
const products = await productRepository.find(condition);
|
471
|
-
console.log(`Found ${products.length} products in category ${category}`);
|
472
|
-
return products;
|
473
|
-
}
|
474
|
-
|
475
|
-
// Find products with price greater than a value
|
476
|
-
async function findExpensiveProducts(minPrice: number) {
|
477
|
-
const condition = Condition.gt('price', minPrice);
|
478
|
-
const products = await productRepository.find(condition);
|
479
|
-
console.log(`Found ${products.length} products with price > ${minPrice}`);
|
480
|
-
return products;
|
481
|
-
}
|
482
|
-
|
483
|
-
// Find products with complex conditions
|
484
|
-
async function findSpecificProducts() {
|
485
|
-
const condition = Condition.and(
|
486
|
-
Condition.eq('category', 'electronics'),
|
487
|
-
Condition.or(
|
488
|
-
Condition.lt('price', 500),
|
489
|
-
Condition.gt('price', 1000)
|
247
|
+
async function paginationExample() {
|
248
|
+
const adapter = new RamAdapter();
|
249
|
+
const repo: RamRepository<User> = new Repository(adapter, User);
|
250
|
+
|
251
|
+
// Seed data
|
252
|
+
const size = 25;
|
253
|
+
await repo.createAll(
|
254
|
+
Array.from({ length: size }).map((_, i) =>
|
255
|
+
new User({ id: (i + 1).toString(), name: `u${i + 1}`, nif: "123456789" })
|
490
256
|
)
|
491
257
|
);
|
492
|
-
const products = await productRepository.find(condition);
|
493
|
-
console.log(`Found ${products.length} specific products`);
|
494
|
-
return products;
|
495
|
-
}
|
496
|
-
```
|
497
|
-
|
498
|
-
### Pagination
|
499
|
-
|
500
|
-
Use pagination to handle large result sets.
|
501
|
-
|
502
|
-
```typescript
|
503
|
-
import { Repository, BaseModel, pk, required, OrderDirection } from '@decaf-ts/core';
|
504
|
-
|
505
|
-
class Product extends BaseModel {
|
506
|
-
@pk()
|
507
|
-
id!: string;
|
508
|
-
|
509
|
-
@required()
|
510
|
-
name!: string;
|
511
|
-
|
512
|
-
@required()
|
513
|
-
price!: number;
|
514
258
|
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
const productRepository = new Repository(Product);
|
259
|
+
const paginator: Paginator<User> = await repo
|
260
|
+
.select()
|
261
|
+
.orderBy(["id", OrderDirection.DSC])
|
262
|
+
.paginate(10);
|
521
263
|
|
522
|
-
//
|
523
|
-
|
524
|
-
const
|
525
|
-
.orderBy('name', OrderDirection.ASC)
|
526
|
-
.paginate(pageSize)
|
527
|
-
.page()
|
264
|
+
const page1 = await paginator.page(); // first page by default
|
265
|
+
const page2 = await paginator.next();
|
266
|
+
const page3 = await paginator.next();
|
528
267
|
|
529
|
-
console.log(
|
530
|
-
console.log(`Total pages: ${result.totalPages}`);
|
531
|
-
console.log(`Total items: ${result.totalItems}`);
|
532
|
-
|
533
|
-
return result;
|
268
|
+
console.log(page1.length, page2.length, page3.length); // 10, 10, 5
|
534
269
|
}
|
535
270
|
```
|
536
271
|
|
537
|
-
### Property Selection
|
538
|
-
|
539
|
-
Select specific properties from models.
|
540
272
|
|
273
|
+
- Conditions: building filters
|
274
|
+
Description: Compose conditions with the builder and apply them in a where clause.
|
541
275
|
```typescript
|
542
|
-
import {
|
543
|
-
|
544
|
-
class User extends BaseModel {
|
545
|
-
@pk()
|
546
|
-
id!: string;
|
276
|
+
import { Condition } from "@decaf-ts/core";
|
547
277
|
|
548
|
-
|
549
|
-
|
278
|
+
async function conditionExample() {
|
279
|
+
const adapter = new RamAdapter();
|
280
|
+
const repo: RamRepository<User> = new Repository(adapter, User);
|
550
281
|
|
551
|
-
|
552
|
-
|
282
|
+
await repo.createAll([
|
283
|
+
new User({ id: "1", name: "Alice", nif: "111111111" }),
|
284
|
+
new User({ id: "2", name: "Bob", nif: "222222222" }),
|
285
|
+
]);
|
553
286
|
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
constructor(data?: Partial<User>) {
|
558
|
-
super(data);
|
559
|
-
}
|
560
|
-
}
|
561
|
-
|
562
|
-
const userRepository = new Repository(User);
|
563
|
-
|
564
|
-
// Select only specific properties
|
565
|
-
async function getUsersPublicInfo() {
|
566
|
-
const users = await userRepository
|
567
|
-
.select(['id', 'username', 'email'])
|
568
|
-
.execute();
|
287
|
+
const cond = Condition.attr<User>("name")
|
288
|
+
.eq("Alice")
|
289
|
+
.build();
|
569
290
|
|
570
|
-
|
571
|
-
console.log(
|
572
|
-
return users;
|
291
|
+
const results = await repo.select().where(cond).execute();
|
292
|
+
console.log(results.length); // 1
|
573
293
|
}
|
574
294
|
```
|
575
295
|
|
576
|
-
## Persistence
|
577
296
|
|
578
|
-
|
297
|
+
- Adapter mapping: prepare and revert
|
298
|
+
Description: Convert a model to a storage record and back using Adapter.prepare and Adapter.revert; mirrors adapter.test.ts.
|
299
|
+
```typescript
|
300
|
+
async function mappingExample() {
|
301
|
+
const adapter = new RamAdapter();
|
302
|
+
const repo: RamRepository<User> = new Repository(adapter, User);
|
579
303
|
|
580
|
-
|
304
|
+
const toCreate = new User({ id: "abc", name: "Test", nif: "123456789" });
|
581
305
|
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
Repository,
|
587
|
-
BaseModel,
|
588
|
-
pk,
|
589
|
-
required
|
590
|
-
} from '@decaf-ts/core';
|
591
|
-
|
592
|
-
class User extends BaseModel {
|
593
|
-
@pk()
|
594
|
-
id!: string;
|
595
|
-
|
596
|
-
@required()
|
597
|
-
username!: string;
|
598
|
-
|
599
|
-
constructor(data?: Partial<User>) {
|
600
|
-
super(data);
|
601
|
-
}
|
602
|
-
}
|
306
|
+
// prepare: model -> record
|
307
|
+
const pk = "id"; // infer with findPrimaryKey(toCreate).id if available
|
308
|
+
const { record, id } = adapter.prepare(toCreate, pk);
|
309
|
+
console.log(id === toCreate.id); // true
|
603
310
|
|
604
|
-
//
|
605
|
-
const
|
606
|
-
|
607
|
-
|
608
|
-
// Example with a hypothetical SQL adapter
|
609
|
-
// const sqlAdapter = new SqlAdapter({
|
610
|
-
// host: 'localhost',
|
611
|
-
// port: 5432,
|
612
|
-
// database: 'myapp',
|
613
|
-
// username: 'user',
|
614
|
-
// password: 'password'
|
615
|
-
// });
|
616
|
-
// const userRepository = new Repository(User, { adapter: sqlAdapter });
|
617
|
-
|
618
|
-
async function testRepository() {
|
619
|
-
// Create a user
|
620
|
-
const user = new User({ username: 'testuser' });
|
621
|
-
await userRepository.create(user);
|
622
|
-
|
623
|
-
// Read the user
|
624
|
-
const retrievedUser = await userRepository.read(user.id);
|
625
|
-
console.log('Retrieved user:', retrievedUser);
|
311
|
+
// revert: record -> model instance
|
312
|
+
const model = adapter.revert(record, User, pk, id) as User;
|
313
|
+
console.log(model instanceof User); // true
|
626
314
|
}
|
627
315
|
```
|
628
316
|
|
629
|
-
## Observer Pattern
|
630
|
-
|
631
|
-
### Implementing an Observer
|
632
|
-
|
633
|
-
Create an observer to react to changes in observable objects.
|
634
317
|
|
318
|
+
- Auto-resolving repositories with InjectablesRegistry
|
319
|
+
Description: Retrieve a repository by model name or constructor using the DI registry; see repository/injectables.ts flow.
|
635
320
|
```typescript
|
636
|
-
import {
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
console.log('Observer registered');
|
321
|
+
import { Injectables } from "@decaf-ts/injectable-decorators";
|
322
|
+
import { InjectablesRegistry } from "@decaf-ts/core";
|
323
|
+
|
324
|
+
async function injectablesExample() {
|
325
|
+
// Register current adapter so repositories can be created
|
326
|
+
new RamAdapter();
|
327
|
+
Adapter.setCurrent("ram");
|
328
|
+
|
329
|
+
// Resolve by constructor
|
330
|
+
const userRepo = Injectables.get<Repository<User>>(User);
|
331
|
+
if (userRepo) {
|
332
|
+
const u = await userRepo.create(
|
333
|
+
new User({ id: "1", name: "A", nif: "123456789" })
|
334
|
+
);
|
335
|
+
console.log(!!u);
|
652
336
|
}
|
653
|
-
|
654
|
-
unObserve(observer: Observer): void {
|
655
|
-
this.observers = this.observers.filter(obs => obs !== observer);
|
656
|
-
console.log('Observer unregistered');
|
657
|
-
}
|
658
|
-
|
659
|
-
async updateObservers(...args: any[]): Promise<void> {
|
660
|
-
console.log('Notifying observers...');
|
661
|
-
for (const observer of this.observers) {
|
662
|
-
await observer.refresh(...args);
|
663
|
-
}
|
664
|
-
}
|
665
|
-
|
666
|
-
// Example method that triggers an update
|
667
|
-
async performAction(action: string): Promise<void> {
|
668
|
-
console.log(`Performing action: ${action}`);
|
669
|
-
await this.updateObservers(action, new Date());
|
670
|
-
}
|
671
|
-
}
|
672
|
-
|
673
|
-
// Usage
|
674
|
-
async function demonstrateObserverPattern() {
|
675
|
-
const repository = new ObservableRepository();
|
676
|
-
const logger = new LoggingObserver();
|
677
|
-
|
678
|
-
// Register the observer
|
679
|
-
repository.observe(logger);
|
680
|
-
|
681
|
-
// Perform an action that will notify the observer
|
682
|
-
await repository.performAction('save');
|
683
|
-
|
684
|
-
// Unregister the observer
|
685
|
-
repository.unObserve(logger);
|
686
|
-
|
687
|
-
// This action won't be logged by the observer
|
688
|
-
await repository.performAction('delete');
|
689
337
|
}
|
690
338
|
```
|
691
339
|
|
@@ -729,6 +377,6 @@ So if you can, if this project in any way. either by learning something or simpl
|
|
729
377
|
|
730
378
|
## License
|
731
379
|
|
732
|
-
This project is released under the [
|
380
|
+
This project is released under the [AGPL-3.0-or-later License](./LICENSE.md).
|
733
381
|
|
734
382
|
By developers, for developers...
|