@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.
Files changed (91) hide show
  1. package/LICENSE.md +13 -16
  2. package/README.md +252 -604
  3. package/dist/core.cjs +335 -125
  4. package/dist/core.esm.cjs +338 -128
  5. package/lib/esm/identity/decorators.js +1 -1
  6. package/lib/esm/index.d.ts +1 -1
  7. package/lib/esm/index.js +1 -1
  8. package/lib/esm/model/types.d.ts +0 -9
  9. package/lib/esm/model/types.js +1 -1
  10. package/lib/esm/persistence/Adapter.d.ts +61 -51
  11. package/lib/esm/persistence/Adapter.js +120 -47
  12. package/lib/esm/persistence/Dispatch.d.ts +2 -13
  13. package/lib/esm/persistence/Dispatch.js +7 -15
  14. package/lib/esm/persistence/errors.d.ts +2 -2
  15. package/lib/esm/persistence/errors.js +4 -4
  16. package/lib/esm/persistence/types.d.ts +2 -0
  17. package/lib/esm/persistence/types.js +1 -1
  18. package/lib/esm/query/Condition.d.ts +8 -0
  19. package/lib/esm/query/Condition.js +9 -1
  20. package/lib/esm/query/Paginator.d.ts +2 -2
  21. package/lib/esm/query/Paginator.js +1 -1
  22. package/lib/esm/query/Statement.d.ts +4 -3
  23. package/lib/esm/query/Statement.js +4 -2
  24. package/lib/esm/query/errors.d.ts +3 -3
  25. package/lib/esm/query/errors.js +6 -6
  26. package/lib/esm/ram/RamAdapter.d.ts +8 -14
  27. package/lib/esm/ram/RamAdapter.js +44 -33
  28. package/lib/esm/ram/RamContext.d.ts +14 -6
  29. package/lib/esm/ram/RamContext.js +15 -7
  30. package/lib/esm/ram/constants.d.ts +1 -2
  31. package/lib/esm/ram/constants.js +2 -3
  32. package/lib/esm/ram/types.d.ts +3 -0
  33. package/lib/esm/ram/types.js +1 -1
  34. package/lib/esm/repository/Repository.d.ts +8 -7
  35. package/lib/esm/repository/Repository.js +8 -8
  36. package/lib/esm/repository/constants.d.ts +5 -4
  37. package/lib/esm/repository/constants.js +6 -5
  38. package/lib/esm/repository/decorators.d.ts +1 -1
  39. package/lib/esm/repository/decorators.js +12 -9
  40. package/lib/esm/repository/errors.d.ts +2 -2
  41. package/lib/esm/repository/errors.js +4 -4
  42. package/lib/esm/repository/injectables.d.ts +82 -11
  43. package/lib/esm/repository/injectables.js +137 -28
  44. package/lib/esm/repository/utils.d.ts +28 -6
  45. package/lib/esm/repository/utils.js +29 -7
  46. package/lib/esm/utils/errors.d.ts +5 -5
  47. package/lib/esm/utils/errors.js +9 -9
  48. package/lib/identity/decorators.cjs +1 -1
  49. package/lib/index.cjs +1 -1
  50. package/lib/index.d.ts +1 -1
  51. package/lib/model/types.cjs +1 -1
  52. package/lib/model/types.d.ts +0 -9
  53. package/lib/persistence/Adapter.cjs +119 -46
  54. package/lib/persistence/Adapter.d.ts +61 -51
  55. package/lib/persistence/Dispatch.cjs +6 -14
  56. package/lib/persistence/Dispatch.d.ts +2 -13
  57. package/lib/persistence/errors.cjs +3 -3
  58. package/lib/persistence/errors.d.ts +2 -2
  59. package/lib/persistence/types.cjs +1 -1
  60. package/lib/persistence/types.d.ts +2 -0
  61. package/lib/query/Condition.cjs +9 -1
  62. package/lib/query/Condition.d.ts +8 -0
  63. package/lib/query/Paginator.cjs +1 -1
  64. package/lib/query/Paginator.d.ts +2 -2
  65. package/lib/query/Statement.cjs +4 -2
  66. package/lib/query/Statement.d.ts +4 -3
  67. package/lib/query/errors.cjs +5 -5
  68. package/lib/query/errors.d.ts +3 -3
  69. package/lib/ram/RamAdapter.cjs +43 -32
  70. package/lib/ram/RamAdapter.d.ts +8 -14
  71. package/lib/ram/RamContext.cjs +15 -7
  72. package/lib/ram/RamContext.d.ts +14 -6
  73. package/lib/ram/constants.cjs +2 -3
  74. package/lib/ram/constants.d.ts +1 -2
  75. package/lib/ram/types.cjs +1 -1
  76. package/lib/ram/types.d.ts +3 -0
  77. package/lib/repository/Repository.cjs +8 -8
  78. package/lib/repository/Repository.d.ts +8 -7
  79. package/lib/repository/constants.cjs +6 -5
  80. package/lib/repository/constants.d.ts +5 -4
  81. package/lib/repository/decorators.cjs +12 -9
  82. package/lib/repository/decorators.d.ts +1 -1
  83. package/lib/repository/errors.cjs +3 -3
  84. package/lib/repository/errors.d.ts +2 -2
  85. package/lib/repository/injectables.cjs +137 -28
  86. package/lib/repository/injectables.d.ts +82 -11
  87. package/lib/repository/utils.cjs +29 -7
  88. package/lib/repository/utils.d.ts +28 -6
  89. package/lib/utils/errors.cjs +8 -8
  90. package/lib/utils/errors.d.ts +5 -5
  91. package/package.json +3 -2
package/README.md CHANGED
@@ -1,8 +1,6 @@
1
- ![Banner](./workdocs/assets/Banner.png)
1
+ # Decaf TS — Core Package
2
2
 
3
- ## Core Module
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
  ![Licence](https://img.shields.io/github/license/decaf-ts/core.svg?style=plastic)
@@ -26,666 +24,316 @@ The Decaf TypeScript Core Module is a comprehensive framework that provides a ro
26
24
  ![Node Version](https://img.shields.io/badge/dynamic/json.svg?url=https%3A%2F%2Fraw.githubusercontent.com%2Fbadges%2Fshields%2Fmaster%2Fpackage.json&label=Node&query=$.engines.node&colorB=blue)
27
25
  ![NPM Version](https://img.shields.io/badge/dynamic/json.svg?url=https%3A%2F%2Fraw.githubusercontent.com%2Fbadges%2Fshields%2Fmaster%2Fpackage.json&label=NPM&query=$.engines.npm&colorB=purple)
28
26
 
29
- Documentation available [here](https://decaf-ts.github.io/core/)
30
-
31
- ### Description
32
-
33
- The Decaf TypeScript Core Module is a sophisticated framework designed to streamline data persistence and model management in TypeScript applications. Building upon the foundation of `db-decorators`, `decorator-validation`, and `injectable-decorators`, it provides a comprehensive solution for working with data models across various storage mechanisms.
34
-
35
- #### Architecture Overview
36
-
37
- The framework is organized into several key modules:
38
-
39
- 1. **Model System**: At the heart of the framework is the `BaseModel` class, which serves as the foundation for all domain models. It provides automatic timestamp tracking and integrates with the validation system. The model system supports:
40
- - Property decorators for defining model attributes
41
- - Relationship decorators (`@oneToOne`, `@oneToMany`, `@manyToOne`) for defining associations between models
42
- - Table and column mapping through `@table` and `@column` decorators
43
- - Indexing capabilities with the `@index` decorator
44
-
45
- 2. **Identity Management**: The framework includes robust identity handling with:
46
- - Primary key generation through the `@pk` decorator
47
- - Sequence generation for automatic ID assignment
48
- - Utilities for table name resolution and sequence naming
49
-
50
- 3. **Repository Pattern**: The repository module provides a clean abstraction for data access operations:
51
- - CRUD operations (create, read, update, delete)
52
- - Transaction support
53
- - Relationship management with cascade operations
54
- - Custom repository implementations through decorators
55
-
56
- 4. **Query System**: A flexible query builder allows for:
57
- - Condition-based filtering
58
- - Property selection
59
- - Pagination
60
- - Sorting and ordering
61
- - Statement execution
62
-
63
- 5. **Persistence Layer**: The adapter-based persistence system:
64
- - Abstracts away storage implementation details
65
- - Supports multiple storage backends
66
- - Provides sequence management
67
- - Implements the Observer pattern for reactive updates
68
-
69
- 6. **RAM Implementation**: An in-memory implementation of the persistence layer for:
70
- - Testing purposes
71
- - Prototyping
72
- - Caching
73
-
74
- #### Key Features
75
-
76
- - **Type Safety**: Leverages TypeScript's type system to provide compile-time checks
77
- - **Decorator-Based Configuration**: Uses decorators for clean, declarative model definitions
78
- - **Relationship Management**: Handles one-to-one, one-to-many, and many-to-one relationships with automatic cascading
79
- - **Flexible Storage**: Works with any storage mechanism through the adapter pattern
80
- - **Reactive Updates**: Implements the Observer pattern for reactive programming
81
- - **Dependency Injection**: Integrates with dependency injection for flexible component wiring
82
- - **Raw Access**: Provides direct access to the underlying storage when needed
83
- - **Automatic Timestamps**: Tracks creation and update times automatically
84
-
85
- The Core Module is designed to be extensible and developer-friendly, reducing boilerplate code while providing powerful features for data management in TypeScript applications.
86
-
87
-
88
- ### How to Use
89
-
90
- - [Initial Setup](../../workdocs/tutorials/For%20Developers.md#_initial-setup_)
91
- - [Installation](../../workdocs/tutorials/For%20Developers.md#installation)
92
-
93
- ## Table of Contents
94
-
95
- - [Model Definition](#model-definition)
96
- - [Identity Management](#identity-management)
97
- - [Relationships](#relationships)
98
- - [Repository Operations](#repository-operations)
99
- - [Querying](#querying)
100
- - [Persistence](#persistence)
101
- - [Observer Pattern](#observer-pattern)
102
-
103
- ## Model Definition
104
-
105
- ### Creating a Basic Model
106
-
107
- Define a domain model by extending the BaseModel class and using decorators to define properties.
108
-
109
- ```typescript
110
- import { BaseModel, required, email, pk } from '@decaf-ts/core';
111
-
112
- class User extends BaseModel {
113
- @pk()
114
- id!: string;
115
-
116
- @required()
117
- username!: string;
118
-
119
- @email()
120
- email!: string;
121
-
122
- @required()
123
- firstName!: string;
124
-
125
- @required()
126
- lastName!: string;
127
-
128
- constructor(data?: Partial<User>) {
129
- super(data);
130
- }
131
- }
132
-
133
- // Create a new user
134
- const user = new User({
135
- username: 'johndoe',
136
- email: 'john.doe@example.com',
137
- firstName: 'John',
138
- lastName: 'Doe'
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 { BaseModel, required, table, column, pk } from '@decaf-ts/core';
148
-
149
- @table('app_users')
150
- class User extends BaseModel {
151
- @pk()
152
- id!: string;
153
-
154
- @required()
155
- username!: string;
156
-
157
- @required()
158
- @column('user_email')
159
- email!: string;
160
-
161
- @column('first_name')
162
- firstName!: string;
163
-
164
- @column('last_name')
165
- lastName!: string;
166
-
167
- constructor(data?: Partial<User>) {
168
- super(data);
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 { BaseModel, pk, required, oneToMany } from '@decaf-ts/core';
307
-
308
- class Author extends BaseModel {
309
- @pk()
310
- id!: string;
168
+ import { NotFoundError } from "@decaf-ts/db-decorators";
311
169
 
312
- @required()
313
- name!: string;
170
+ async function crudExample() {
171
+ const adapter = new RamAdapter();
172
+ const repo: RamRepository<User> = new Repository(adapter, User);
314
173
 
315
- @oneToMany(Book)
316
- books?: Book[];
317
-
318
- constructor(data?: Partial<Author>) {
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
- @required()
328
- title!: string;
179
+ // READ
180
+ const read = await repo.read(created.id);
181
+ console.log(read.equals(created)); // true (same data, different instance)
329
182
 
330
- @required()
331
- authorId!: string;
183
+ // UPDATE
184
+ const updated = await repo.update(Object.assign(read, {name: "Alice 2" }));
332
185
 
333
- constructor(data?: Partial<Book>) {
334
- super(data);
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
- @required()
354
- authorId!: string;
197
+ @model()
198
+ class Managed extends Model { constructor(arg?: ModelArg<Managed>) { super(arg); } }
355
199
 
356
- @manyToOne(Author)
357
- author?: Author;
358
-
359
- constructor(data?: Partial<Book>) {
360
- super(data);
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
- class Author extends BaseModel {
365
- @pk()
366
- id!: string;
207
+ async function adapterRegistryExample() {
208
+ const adapter = new RamAdapter();
367
209
 
368
- @required()
369
- name!: string;
210
+ Adapter.setCurrent("ram"); // set current flavour
211
+ console.log(Adapter.current === Adapter.get("ram")); // true
370
212
 
371
- constructor(data?: Partial<Author>) {
372
- super(data);
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
- ## Repository Operations
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
- import { Repository, BaseModel, pk, required } from '@decaf-ts/core';
385
-
386
- class User extends BaseModel {
387
- @pk({ type: 'Number' })
388
- id!: string;
389
-
390
- @required()
391
- username!: string;
392
-
393
- @required()
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
- // Update a user
424
- async function updateUser(user: User) {
425
- user.email = 'new.email@example.com';
426
- const updatedUser = await userRepository.update(user);
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
- // Delete a user
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
- ## Querying
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
- import { Repository, BaseModel, pk, required, Condition } from '@decaf-ts/core';
446
-
447
- class Product extends BaseModel {
448
- @pk()
449
- id!: string;
450
-
451
- @required()
452
- name!: string;
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
- constructor(data?: Partial<Product>) {
516
- super(data);
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
- // Get paginated results
523
- async function getProductsPage(pageNumber: number, pageSize: number) {
524
- const result = await productRepository.select()
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(`Page ${pageNumber}: ${result.length} products`);
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 { Repository, BaseModel, pk, required } from '@decaf-ts/core';
543
-
544
- class User extends BaseModel {
545
- @pk()
546
- id!: string;
276
+ import { Condition } from "@decaf-ts/core";
547
277
 
548
- @required()
549
- username!: string;
278
+ async function conditionExample() {
279
+ const adapter = new RamAdapter();
280
+ const repo: RamRepository<User> = new Repository(adapter, User);
550
281
 
551
- @required()
552
- email!: string;
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
- @required()
555
- password!: string;
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
- // The returned objects will only have id, username, and email properties
571
- console.log('Users public info:', users);
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
- ### Using Different Adapters
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
- Configure and use different persistence adapters.
304
+ const toCreate = new User({ id: "abc", name: "Test", nif: "123456789" });
581
305
 
582
- ```typescript
583
- import {
584
- Adapter,
585
- RamAdapter,
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
- // Use RAM adapter for in-memory storage (useful for testing)
605
- const ramAdapter = new RamAdapter();
606
- const userRepository = new Repository(User, { adapter: ramAdapter });
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 { Observer, Observable } from '@decaf-ts/core';
637
-
638
- // Create a custom observer
639
- class LoggingObserver implements Observer {
640
- async refresh(...args: any[]): Promise<void> {
641
- console.log('Observable was updated with args:', args);
642
- }
643
- }
644
-
645
- // Example usage with a hypothetical observable repository
646
- class ObservableRepository implements Observable {
647
- private observers: Observer[] = [];
648
-
649
- observe(observer: Observer): void {
650
- this.observers.push(observer);
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 [MIT License](./LICENSE.md).
380
+ This project is released under the [AGPL-3.0-or-later License](./LICENSE.md).
733
381
 
734
382
  By developers, for developers...