onecore 0.6.3 → 0.6.6
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 +562 -184
- package/lib/index.js +84 -0
- package/package.json +1 -1
- package/src/db.ts +2 -2
- package/src/formatter.ts +13 -14
- package/src/index.ts +8 -4
- package/src/io.ts +5 -0
- package/src/mq.ts +7 -7
- package/src/service.ts +1 -1
package/README.md
CHANGED
|
@@ -4,154 +4,140 @@
|
|
|
4
4
|
|
|
5
5
|
`onecore` provides the common contracts, metadata model, and reusable application-layer base classes used throughout the Core TS ecosystem.
|
|
6
6
|
|
|
7
|
-
It
|
|
7
|
+
It is designed to let infrastructure libraries and applications share the same abstractions without coupling application code to a specific database, messaging system, validation implementation, cache, or framework.
|
|
8
8
|
|
|
9
|
-
##
|
|
10
|
-
|
|
11
|
-
* Common interfaces for enterprise applications
|
|
12
|
-
* Unified metadata model (`Attribute`)
|
|
13
|
-
* Reusable CRUD and Search use-case base classes
|
|
14
|
-
* Zero-cost abstractions through TypeScript interfaces
|
|
15
|
-
* Tree-shakeable architecture
|
|
16
|
-
* Works in both backend and frontend applications
|
|
17
|
-
* No framework dependency
|
|
18
|
-
* Fully written in TypeScript
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
# Installation
|
|
9
|
+
## Installation
|
|
23
10
|
|
|
24
11
|
```bash
|
|
25
12
|
npm install onecore
|
|
26
13
|
```
|
|
27
14
|
|
|
28
|
-
|
|
15
|
+
## What is onecore?
|
|
29
16
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
Enterprise applications often use many infrastructure libraries.
|
|
33
|
-
|
|
34
|
-
For example:
|
|
17
|
+
Large applications commonly depend on many infrastructure libraries:
|
|
35
18
|
|
|
36
|
-
* Database
|
|
37
|
-
* Validation
|
|
38
19
|
* Cache
|
|
39
|
-
* Message Queue
|
|
40
20
|
* Logging
|
|
41
21
|
* Health Check
|
|
42
|
-
*
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
Repository
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
`onecore` provides a single common language for the entire ecosystem.
|
|
55
|
-
|
|
56
|
-
```ts
|
|
57
|
-
import {
|
|
58
|
-
Repository,
|
|
59
|
-
Validator,
|
|
60
|
-
Producer,
|
|
61
|
-
CachePort,
|
|
62
|
-
Logger,
|
|
63
|
-
HealthChecker
|
|
64
|
-
} from "onecore";
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
Applications only depend on **onecore**, while implementation libraries implement these contracts.
|
|
68
|
-
|
|
69
|
-
---
|
|
22
|
+
* Validation
|
|
23
|
+
* Database
|
|
24
|
+
* DB
|
|
25
|
+
* Transaction
|
|
26
|
+
* Statement
|
|
27
|
+
* Repository
|
|
28
|
+
* Service
|
|
29
|
+
* Message Queue
|
|
30
|
+
* Import / Export
|
|
31
|
+
* HTTP clients
|
|
32
|
+
* Localization
|
|
70
33
|
|
|
71
|
-
|
|
34
|
+
Without a shared foundation, each library tends to define its own interfaces and metadata.
|
|
72
35
|
|
|
73
|
-
|
|
74
|
-
Applications
|
|
75
|
-
┌───────────────────────────────────────────────────────────────┐
|
|
76
|
-
│ │
|
|
77
|
-
│ React Apps • REST APIs • Microservices • Workers • CLI │
|
|
78
|
-
│ │
|
|
79
|
-
└───────────────────────────────────────────────────────────────┘
|
|
80
|
-
▲
|
|
81
|
-
│
|
|
82
|
-
Reusable Use Cases (Optional)
|
|
83
|
-
CRUD • Search • View • Generic Services
|
|
84
|
-
▲
|
|
85
|
-
│
|
|
86
|
-
┌─────────────────────────────────────────────────────────────────────────────────┐
|
|
87
|
-
│ onecore │
|
|
88
|
-
│ │
|
|
89
|
-
│ • Common Interfaces │
|
|
90
|
-
│ • Unified Attribute Metadata │
|
|
91
|
-
│ • Locale & Currency │
|
|
92
|
-
│ • Shared Models │
|
|
93
|
-
│ • Reusable Service Layer │
|
|
94
|
-
└─────────────────────────────────────────────────────────────────────────────────┘
|
|
95
|
-
▲ ▲ ▲ ▲ ▲
|
|
96
|
-
│ │ │ │ │
|
|
97
|
-
│ │ │ │ │
|
|
98
|
-
┌────────────────┐ ┌────────────────┐ ┌──────────────────┐ ┌────────────────┐ ┌────────────────┐
|
|
99
|
-
│ Database │ │ Validation │ │ Messaging │ │ Import / Export│ │ Cache │
|
|
100
|
-
├────────────────┤ ├────────────────┤ ├──────────────────┤ ├────────────────┤ ├────────────────┤
|
|
101
|
-
│ sql-core │ │validation-core │ │message-processing│ │import-service │ │ cache-plus │
|
|
102
|
-
│ mysql2-core │ │ │ │nats-plus │ │export-kit │ │ redis-plus │
|
|
103
|
-
│ postgres-kit │ │ │ │redis-messaging │ │ │ │ │
|
|
104
|
-
│ oracle-core │ │ │ │rabbitmq-transport│ │ │ │ │
|
|
105
|
-
│ mssql-core │ │ │ │activemq │ │ │ │ │
|
|
106
|
-
│ mongodb-kit │ │ │ │ibmmq-plus │ │ │ │ │
|
|
107
|
-
└────────────────┘ └────────────────┘ └──────────────────┘ └────────────────┘ └────────────────┘
|
|
36
|
+
`onecore` provides a common set of contracts so that these libraries can work together through a consistent API.
|
|
108
37
|
|
|
38
|
+
```text
|
|
39
|
+
Application
|
|
40
|
+
│
|
|
41
|
+
Use Cases
|
|
42
|
+
│
|
|
43
|
+
┌─────────┴─────────┐
|
|
44
|
+
│ onecore │
|
|
45
|
+
│ │
|
|
46
|
+
│ Interfaces │
|
|
47
|
+
│ Attribute │
|
|
48
|
+
│ Transaction │
|
|
49
|
+
│ Repository │
|
|
50
|
+
│ Use Cases │
|
|
51
|
+
└─────────┬─────────┘
|
|
52
|
+
│
|
|
53
|
+
┌──────────┬──────────┼──────────┬──────────┐
|
|
54
|
+
│ │ │ │ │
|
|
55
|
+
Database Validation Cache Messaging Logging
|
|
109
56
|
```
|
|
110
57
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
---
|
|
58
|
+
The implementation packages depend on these contracts, while application code can depend on the contracts rather than concrete infrastructure implementations.
|
|
114
59
|
|
|
115
|
-
|
|
60
|
+
## Features
|
|
116
61
|
|
|
117
|
-
|
|
62
|
+
* Shared TypeScript interfaces for the Core TS ecosystem
|
|
63
|
+
* Unified `Attribute` metadata model
|
|
64
|
+
* `ErrorMessage` for data validation
|
|
65
|
+
* Generic `Filter` and `SearchResult` models
|
|
66
|
+
* Database executor and transaction abstractions
|
|
67
|
+
* Type-safe database parameter abstraction
|
|
68
|
+
* Repository abstractions for CRUD and search
|
|
69
|
+
* Reusable CRUD and Search use-case classes
|
|
118
70
|
|
|
119
|
-
|
|
71
|
+
* Interfaces with no runtime representation after TypeScript compilation
|
|
72
|
+
* No framework dependency
|
|
73
|
+
* Suitable for both backend and frontend projects
|
|
74
|
+
* Tree-shakeable runtime code
|
|
75
|
+
## Unified metadata
|
|
120
76
|
|
|
121
|
-
|
|
77
|
+
The central concept in `onecore` is `Attribute`.
|
|
122
78
|
|
|
123
79
|
```ts
|
|
124
|
-
|
|
80
|
+
export interface Attribute {
|
|
81
|
+
name?: string
|
|
82
|
+
field?: string
|
|
83
|
+
column?: string
|
|
84
|
+
type?: DataType
|
|
85
|
+
format?: FormatType
|
|
86
|
+
required?: boolean
|
|
87
|
+
|
|
88
|
+
key?: boolean
|
|
89
|
+
unique?: boolean
|
|
90
|
+
|
|
91
|
+
enum?: string[] | number[]
|
|
92
|
+
|
|
93
|
+
noinsert?: boolean
|
|
94
|
+
noupdate?: boolean
|
|
95
|
+
nopatch?: boolean
|
|
96
|
+
|
|
97
|
+
version?: boolean
|
|
98
|
+
createdAt?: boolean
|
|
99
|
+
updatedAt?: boolean
|
|
100
|
+
|
|
101
|
+
length?: number
|
|
102
|
+
min?: number | Date | string
|
|
103
|
+
max?: number | Date | string
|
|
104
|
+
gt?: number | Date | string
|
|
105
|
+
lt?: number | Date | string
|
|
106
|
+
|
|
107
|
+
precision?: number
|
|
108
|
+
scale?: number
|
|
109
|
+
}
|
|
125
110
|
```
|
|
126
111
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
## Base Classes
|
|
130
|
-
|
|
131
|
-
If an application extends one of the reusable CRUD or Search use-case classes, only those classes are included in the final bundle.
|
|
132
|
-
|
|
133
|
-
Modern bundlers such as Webpack, Rollup, Vite, and esbuild tree-shake unused code automatically.
|
|
134
|
-
|
|
135
|
-
This allows frontend and backend projects to share the same package while keeping bundles minimal.
|
|
112
|
+
Unlike persistence-specific or validation-specific metadata, `Attribute` is the canonical metadata model shared across the entire ecosystem.
|
|
136
113
|
|
|
137
|
-
|
|
114
|
+
`Attribute` is intended to be shared by multiple parts of an application instead of maintaining separate metadata definitions for every subsystem.
|
|
138
115
|
|
|
139
|
-
|
|
116
|
+
For example, the same metadata can describe:
|
|
140
117
|
|
|
141
|
-
|
|
118
|
+
* Database mapping
|
|
119
|
+
* Validation
|
|
120
|
+
* Search
|
|
121
|
+
* Formatting
|
|
122
|
+
* Import
|
|
123
|
+
* Export
|
|
124
|
+
* Serialization
|
|
142
125
|
|
|
143
|
-
|
|
126
|
+
Individual libraries consume only the properties relevant to them.
|
|
144
127
|
|
|
145
128
|
```
|
|
146
129
|
Attribute
|
|
147
130
|
│
|
|
148
131
|
┌───────────────┼────────────────┐
|
|
149
132
|
│ │ │
|
|
150
|
-
|
|
133
|
+
Validation Database Import/Export
|
|
151
134
|
│ │ │
|
|
152
|
-
validation-core sql-core
|
|
153
|
-
|
|
154
|
-
|
|
135
|
+
validation-core sql-core import-service
|
|
136
|
+
postgres-kit export-kit
|
|
137
|
+
mysql2-core
|
|
138
|
+
oracle-core
|
|
139
|
+
mssql-core
|
|
140
|
+
cassandra-core
|
|
155
141
|
mongodb-kit
|
|
156
142
|
```
|
|
157
143
|
|
|
@@ -190,31 +176,411 @@ The same metadata definition can therefore be used for
|
|
|
190
176
|
|
|
191
177
|
without duplication.
|
|
192
178
|
|
|
193
|
-
|
|
179
|
+
## Data types
|
|
194
180
|
|
|
195
|
-
|
|
181
|
+
`onecore` defines a common `DataType` model:
|
|
196
182
|
|
|
197
|
-
|
|
183
|
+
```ts
|
|
184
|
+
export type DataType =
|
|
185
|
+
| 'ObjectId'
|
|
186
|
+
| 'date'
|
|
187
|
+
| 'datetime'
|
|
188
|
+
| 'time'
|
|
189
|
+
| 'boolean'
|
|
190
|
+
| 'number'
|
|
191
|
+
| 'integer'
|
|
192
|
+
| 'string'
|
|
193
|
+
| 'text'
|
|
194
|
+
| 'object'
|
|
195
|
+
| 'array'
|
|
196
|
+
| 'binary'
|
|
197
|
+
| 'primitives'
|
|
198
|
+
| 'booleans'
|
|
199
|
+
| 'numbers'
|
|
200
|
+
| 'integers'
|
|
201
|
+
| 'strings'
|
|
202
|
+
| 'dates'
|
|
203
|
+
| 'datetimes'
|
|
204
|
+
| 'times'
|
|
205
|
+
```
|
|
198
206
|
|
|
199
|
-
|
|
207
|
+
Scalar and collection types can therefore share the same metadata system.
|
|
200
208
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
209
|
+
Formatting is represented separately:
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
export type FormatType =
|
|
213
|
+
| 'currency'
|
|
214
|
+
| 'percentage'
|
|
215
|
+
| 'email'
|
|
216
|
+
| 'url'
|
|
217
|
+
| 'phone'
|
|
218
|
+
| 'fax'
|
|
219
|
+
| 'ipv4'
|
|
220
|
+
| 'ipv6'
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Database abstraction
|
|
224
|
+
|
|
225
|
+
`onecore` provides a small database-independent execution contract.
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
export interface Executor {
|
|
229
|
+
driver: string
|
|
230
|
+
param(i: number): string
|
|
231
|
+
query<T>(sql: string, args?: any[], m?: StringMap, bools?: Attribute[]): Promise<T[]>
|
|
232
|
+
execute(sql: string, args?: any[]): Promise<number>
|
|
233
|
+
executeBatch(statements: Statement[], requireFirstAffected?: boolean): Promise<number>
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
A database implementation is responsible for adapting this interface to its native driver.
|
|
238
|
+
|
|
239
|
+
### Parameter abstraction
|
|
240
|
+
|
|
241
|
+
Different databases use different parameter syntaxes.
|
|
242
|
+
|
|
243
|
+
For example:
|
|
244
|
+
|
|
245
|
+
```text
|
|
246
|
+
PostgreSQL $1 $2 $3
|
|
247
|
+
MySQL ? ? ?
|
|
248
|
+
Oracle :1 :2 :3
|
|
249
|
+
SQL Server @p1 @p2 @p3
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
`Executor.param()` allows higher-level SQL-building code to generate database-specific parameters without depending on a particular driver.
|
|
253
|
+
|
|
254
|
+
### Statements
|
|
255
|
+
|
|
256
|
+
A batch operation is represented by:
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
export interface Statement {
|
|
260
|
+
query: string
|
|
261
|
+
params?: any[]
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
This keeps batch execution independent of the underlying database implementation.
|
|
266
|
+
|
|
267
|
+
## Transactions
|
|
268
|
+
|
|
269
|
+
Transactions extend the same executor abstraction:
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
export interface Transaction extends Executor {
|
|
273
|
+
commit(): Promise<void>
|
|
274
|
+
rollback(): Promise<void>
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
A database provides transactions through:
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
export interface DB extends Executor {
|
|
282
|
+
beginTransaction(): Promise<Transaction>
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
This allows repository code to use either the normal database executor or a transaction without knowing the underlying connection type.
|
|
287
|
+
|
|
288
|
+
For example:
|
|
289
|
+
|
|
290
|
+
```ts
|
|
291
|
+
const tx = await db.beginTransaction()
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
await userRepository.create(user, tx)
|
|
295
|
+
await accountRepository.create(account, tx)
|
|
296
|
+
|
|
297
|
+
await tx.commit()
|
|
298
|
+
} catch (err) {
|
|
299
|
+
await tx.rollback()
|
|
300
|
+
throw err
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
The transaction abstraction is particularly useful when several repositories participate in the same operation.
|
|
305
|
+
|
|
306
|
+
## Repository abstraction
|
|
307
|
+
|
|
308
|
+
`onecore` defines generic repository contracts for CRUD and search operations.
|
|
309
|
+
|
|
310
|
+
### CRUDRepository
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
export interface CRUDRepository<T, ID> {
|
|
314
|
+
load(id: ID, tx?: Transaction): Promise<T | null>
|
|
315
|
+
create(obj: T, tx?: Transaction): Promise<number>
|
|
316
|
+
update(obj: T, tx?: Transaction): Promise<number>
|
|
317
|
+
patch(obj: Partial<T>, tx?: Transaction): Promise<number>
|
|
318
|
+
delete(id: ID, tx?: Transaction): Promise<number>
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
The optional `Transaction` parameter allows the same repository method to participate in a larger transaction.
|
|
323
|
+
|
|
324
|
+
Normal operation:
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
await repository.create(user)
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Transactional operation:
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
await repository.create(user, tx)
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### SearchRepository
|
|
337
|
+
|
|
338
|
+
```ts
|
|
339
|
+
export interface SearchRepository<T, F extends Filter> {
|
|
340
|
+
search(
|
|
341
|
+
filter: F,
|
|
342
|
+
limit: number,
|
|
343
|
+
offset?: number | string,
|
|
344
|
+
fields?: string[],
|
|
345
|
+
tx?: Transaction
|
|
346
|
+
): Promise<SearchResult<T>>
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
The generic filter model is:
|
|
351
|
+
|
|
352
|
+
```ts
|
|
353
|
+
export interface Filter {
|
|
354
|
+
page?: number
|
|
355
|
+
limit: number
|
|
356
|
+
fields?: string[]
|
|
357
|
+
sort?: string
|
|
358
|
+
q?: string
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Search results are represented as:
|
|
363
|
+
|
|
364
|
+
```ts
|
|
365
|
+
export interface SearchResult<T> {
|
|
366
|
+
list: T[]
|
|
367
|
+
total?: number
|
|
368
|
+
next?: string
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
This allows implementations to support result counts as well as cursor-style pagination.
|
|
373
|
+
|
|
374
|
+
## Repository
|
|
375
|
+
|
|
376
|
+
CRUD and search repositories can be combined:
|
|
377
|
+
|
|
378
|
+
```ts
|
|
379
|
+
export interface Repository<T, ID, F extends Filter>
|
|
380
|
+
extends CRUDRepository<T, ID>, SearchRepository<T, F> {
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
An application can therefore define:
|
|
385
|
+
|
|
386
|
+
```ts
|
|
387
|
+
interface UserFilter extends Filter {
|
|
388
|
+
active?: boolean
|
|
389
|
+
role?: string
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
type UserRepository = Repository<User, string, UserFilter>
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
## Reusable use cases
|
|
396
|
+
|
|
397
|
+
`onecore` includes reusable application-layer classes so applications do not have to repeatedly write trivial forwarding logic.
|
|
398
|
+
|
|
399
|
+
### SearchUseCase
|
|
400
|
+
|
|
401
|
+
```ts
|
|
402
|
+
export class SearchUseCase<T, F extends Filter> {
|
|
403
|
+
constructor(
|
|
404
|
+
protected repository: SearchRepository<T, F>
|
|
405
|
+
) {}
|
|
406
|
+
|
|
407
|
+
search(filter: F, limit: number, page?: number | string,
|
|
408
|
+
fields?: string[]): Promise<SearchResult<T>> {
|
|
409
|
+
return this.repository.search(filter, limit, page, fields)
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### CRUDUseCase
|
|
415
|
+
|
|
416
|
+
```ts
|
|
417
|
+
export class CRUDUseCase<T, ID> {
|
|
418
|
+
constructor(protected repository: CRUDRepository<T, ID>) {}
|
|
419
|
+
|
|
420
|
+
load(id: ID): Promise<T | null> {
|
|
421
|
+
return this.repository.load(id)
|
|
422
|
+
}
|
|
423
|
+
create(obj: T): Promise<number> {
|
|
424
|
+
return this.repository.create(obj)
|
|
425
|
+
}
|
|
426
|
+
update(obj: T): Promise<number> {
|
|
427
|
+
return this.repository.update(obj)
|
|
428
|
+
}
|
|
429
|
+
patch(obj: Partial<T>): Promise<number> {
|
|
430
|
+
return this.repository.patch(obj)
|
|
431
|
+
}
|
|
432
|
+
delete(id: ID): Promise<number> {
|
|
433
|
+
return this.repository.delete(id)
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### UseCase
|
|
439
|
+
|
|
440
|
+
For applications that need both CRUD and search operations:
|
|
441
|
+
|
|
442
|
+
```ts
|
|
443
|
+
export class UseCase<T, ID, F extends Filter> {
|
|
444
|
+
constructor(protected repository: Repository<T, ID, F>) {}
|
|
445
|
+
|
|
446
|
+
search(filter: F, limit: number, page?: number | string,
|
|
447
|
+
fields?: string[]): Promise<SearchResult<T>> {
|
|
448
|
+
return this.repository.search(filter, limit, page, fields)
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
load(id: ID): Promise<T | null> {
|
|
452
|
+
return this.repository.load(id)
|
|
453
|
+
}
|
|
454
|
+
create(obj: T): Promise<number> {
|
|
455
|
+
return this.repository.create(obj)
|
|
456
|
+
}
|
|
457
|
+
update(obj: T): Promise<number> {
|
|
458
|
+
return this.repository.update(obj)
|
|
459
|
+
}
|
|
460
|
+
patch(obj: Partial<T>): Promise<number> {
|
|
461
|
+
return this.repository.patch(obj)
|
|
462
|
+
}
|
|
463
|
+
delete(id: ID): Promise<number> {
|
|
464
|
+
return this.repository.delete(id)
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
Applications can extend these classes when the default repository-to-use-case behavior is sufficient, and override methods when additional business logic is required.
|
|
470
|
+
|
|
471
|
+
## Services and results
|
|
472
|
+
|
|
473
|
+
Service contracts can be defined independently from the repository layer:
|
|
474
|
+
|
|
475
|
+
```ts
|
|
476
|
+
export interface SearchService<T, F extends Filter> {
|
|
477
|
+
search(
|
|
478
|
+
filter: F,
|
|
479
|
+
limit: number,
|
|
480
|
+
page?: number | string,
|
|
481
|
+
fields?: string[]
|
|
482
|
+
): Promise<SearchResult<T>>
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
CRUD services expose application-facing operations:
|
|
487
|
+
|
|
488
|
+
```ts
|
|
489
|
+
export interface CRUDService<T, ID> {
|
|
490
|
+
load(id: ID): Promise<T | null>
|
|
491
|
+
create(obj: T): Promise<Result<T>>
|
|
492
|
+
update(obj: T): Promise<Result<T>>
|
|
493
|
+
patch(obj: Partial<T>): Promise<Result<T>>
|
|
494
|
+
delete(id: ID): Promise<number>
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
Validation or application errors can be represented by:
|
|
499
|
+
|
|
500
|
+
```ts
|
|
501
|
+
export interface ErrorMessage {
|
|
502
|
+
field: string
|
|
503
|
+
code: string
|
|
504
|
+
param?: string | number | Date
|
|
505
|
+
message?: string
|
|
506
|
+
}
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
with:
|
|
510
|
+
|
|
511
|
+
```ts
|
|
512
|
+
export type Result<T> = number | T | ErrorMessage[]
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
This allows an application layer to return structured field-level errors without coupling the service interface to a particular validation library.
|
|
516
|
+
|
|
517
|
+
## Ecosystem
|
|
518
|
+
|
|
519
|
+
`onecore` is intended to be the common foundation for the Core TS packages.
|
|
520
|
+
|
|
521
|
+
Examples include:
|
|
522
|
+
|
|
523
|
+
```text
|
|
524
|
+
Database
|
|
525
|
+
├── sql-core
|
|
526
|
+
├── postgres-kit
|
|
527
|
+
├── mysql2-core
|
|
528
|
+
├── oracle-core
|
|
529
|
+
├── mssql-core
|
|
530
|
+
├── cassandra-core
|
|
531
|
+
└── mongodb-kit
|
|
532
|
+
|
|
533
|
+
Validation
|
|
534
|
+
└── validation-core
|
|
535
|
+
|
|
536
|
+
Messaging
|
|
537
|
+
├── message-processing
|
|
538
|
+
├── nats-plus
|
|
539
|
+
├── redis-messaging
|
|
540
|
+
├── rabbitmq-transport
|
|
541
|
+
├── activemq
|
|
542
|
+
├── kafka-plus
|
|
543
|
+
└── ibmmq-plus
|
|
544
|
+
|
|
545
|
+
Cache
|
|
546
|
+
├── cache-plus
|
|
547
|
+
└── redis-plus
|
|
548
|
+
|
|
549
|
+
Other
|
|
550
|
+
├── import-service
|
|
551
|
+
├── export-kit
|
|
552
|
+
├── logger-core
|
|
553
|
+
├── health-service
|
|
554
|
+
├── locale-service
|
|
555
|
+
└── web-clients
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
These packages can share the same contracts and metadata model while remaining independently implemented.
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
## Shared Interfaces
|
|
562
|
+
|
|
563
|
+
`onecore` defines the common contracts used by the ecosystem.
|
|
564
|
+
|
|
565
|
+
### Database
|
|
205
566
|
* Statement
|
|
206
|
-
*
|
|
567
|
+
* DB
|
|
568
|
+
* Transaction
|
|
569
|
+
* SearchRepository
|
|
570
|
+
* CRUDRepository
|
|
571
|
+
* Repository
|
|
207
572
|
|
|
208
573
|
Implemented by
|
|
209
574
|
|
|
210
575
|
* sql-core
|
|
211
576
|
* mysql2-core
|
|
577
|
+
* oracle-core
|
|
578
|
+
* mssql-core
|
|
212
579
|
* postgres-kit
|
|
580
|
+
* cassandra-core
|
|
213
581
|
* mongodb-kit
|
|
214
582
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
## Validation
|
|
583
|
+
### Validation
|
|
218
584
|
|
|
219
585
|
* Validator<T>
|
|
220
586
|
|
|
@@ -222,9 +588,7 @@ Implemented by
|
|
|
222
588
|
|
|
223
589
|
* validation-core
|
|
224
590
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
## Cache
|
|
591
|
+
### Cache
|
|
228
592
|
|
|
229
593
|
* CachePort
|
|
230
594
|
|
|
@@ -233,9 +597,7 @@ Implemented by
|
|
|
233
597
|
* cache-plus
|
|
234
598
|
* redis-plus
|
|
235
599
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
## Message Queue
|
|
600
|
+
### Message Queue
|
|
239
601
|
|
|
240
602
|
* Producer
|
|
241
603
|
* Consumer
|
|
@@ -248,11 +610,11 @@ Implemented by
|
|
|
248
610
|
* redis-messaging
|
|
249
611
|
* nats-plus
|
|
250
612
|
* rabbitmq-transport
|
|
251
|
-
*
|
|
252
|
-
|
|
253
|
-
|
|
613
|
+
* activemq
|
|
614
|
+
* kafka-plus
|
|
615
|
+
* ibmmq-plus
|
|
254
616
|
|
|
255
|
-
|
|
617
|
+
### Logging
|
|
256
618
|
|
|
257
619
|
* Logger
|
|
258
620
|
|
|
@@ -260,9 +622,7 @@ Implemented by
|
|
|
260
622
|
|
|
261
623
|
* logger-core
|
|
262
624
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
## Health Check
|
|
625
|
+
### Health Check
|
|
266
626
|
|
|
267
627
|
* HealthChecker
|
|
268
628
|
|
|
@@ -270,9 +630,7 @@ Implemented by
|
|
|
270
630
|
|
|
271
631
|
* health-service
|
|
272
632
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
## HTTP
|
|
633
|
+
### HTTP
|
|
276
634
|
|
|
277
635
|
* HttpRequest
|
|
278
636
|
* HttpOptionsService
|
|
@@ -281,9 +639,7 @@ Implemented by
|
|
|
281
639
|
|
|
282
640
|
* web-clients
|
|
283
641
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
## Localization
|
|
642
|
+
### Localization
|
|
287
643
|
|
|
288
644
|
* Locale
|
|
289
645
|
* Currency
|
|
@@ -292,25 +648,42 @@ Implemented by
|
|
|
292
648
|
|
|
293
649
|
* locale-service
|
|
294
650
|
|
|
295
|
-
|
|
651
|
+
## Zero-cost abstractions
|
|
296
652
|
|
|
297
|
-
|
|
653
|
+
One of the design goals of `onecore` is to provide shared APIs without imposing framework-level runtime overhead.
|
|
298
654
|
|
|
299
|
-
|
|
655
|
+
TypeScript interfaces such as:
|
|
300
656
|
|
|
301
|
-
|
|
657
|
+
```ts
|
|
658
|
+
Repository
|
|
659
|
+
Transaction
|
|
660
|
+
Executor
|
|
661
|
+
Validator
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
do not exist in the generated JavaScript.
|
|
302
665
|
|
|
303
|
-
|
|
304
|
-
* Search services
|
|
305
|
-
* View services
|
|
666
|
+
Reusable classes such as `UseCase` are only included when they are actually imported and used, allowing modern bundlers to tree-shake unused code.
|
|
306
667
|
|
|
307
|
-
|
|
668
|
+
This makes the package suitable for both backend and frontend projects.
|
|
308
669
|
|
|
309
|
-
|
|
670
|
+
## Backend Support
|
|
310
671
|
|
|
311
|
-
|
|
672
|
+
Infrastructure services often depend only on interfaces.
|
|
673
|
+
|
|
674
|
+
Examples include
|
|
675
|
+
|
|
676
|
+
* Shared metadata
|
|
677
|
+
* Database transactions
|
|
678
|
+
* Repository contracts
|
|
679
|
+
* Application use cases
|
|
680
|
+
* Message Queue services
|
|
681
|
+
* Import services / Export services
|
|
682
|
+
* Infrastructure interfaces: Logging, Cache, Validation...
|
|
683
|
+
|
|
684
|
+
Since these projects do not use application-layer base classes, the generated JavaScript contains virtually no runtime code from `onecore`.
|
|
312
685
|
|
|
313
|
-
|
|
686
|
+
## Frontend Support
|
|
314
687
|
|
|
315
688
|
`onecore` works in frontend applications.
|
|
316
689
|
|
|
@@ -323,34 +696,52 @@ React applications commonly use
|
|
|
323
696
|
|
|
324
697
|
without importing any backend service classes.
|
|
325
698
|
|
|
699
|
+
For example:
|
|
700
|
+
|
|
701
|
+
```ts
|
|
702
|
+
import type {
|
|
703
|
+
Attribute,
|
|
704
|
+
Filter,
|
|
705
|
+
SearchResult
|
|
706
|
+
} from 'onecore'
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
This allows a frontend and backend to share the same domain metadata and contracts.
|
|
710
|
+
|
|
326
711
|
The final JavaScript bundle remains minimal because interfaces are removed during compilation and unused code is tree-shaken.
|
|
327
712
|
|
|
328
|
-
|
|
713
|
+
## Design principles
|
|
329
714
|
|
|
330
|
-
|
|
715
|
+
### One common contract
|
|
331
716
|
|
|
332
|
-
Infrastructure
|
|
717
|
+
Infrastructure implementations should depend on common interfaces instead of forcing applications to depend on implementation details.
|
|
333
718
|
|
|
334
|
-
|
|
719
|
+
### Metadata reuse
|
|
335
720
|
|
|
336
|
-
|
|
337
|
-
* Import services
|
|
338
|
-
* Export services
|
|
339
|
-
* Background workers
|
|
721
|
+
Define metadata once and reuse it across database mapping, validation, search, serialization, import/export, and formatting.
|
|
340
722
|
|
|
341
|
-
|
|
723
|
+
### Database independence
|
|
724
|
+
|
|
725
|
+
Application and repository contracts should not expose a PostgreSQL client, MySQL connection, Oracle connection, or other driver-specific resource.
|
|
726
|
+
|
|
727
|
+
### Explicit transactions
|
|
728
|
+
|
|
729
|
+
Transactions are passed explicitly to repository operations when multiple operations must participate in the same atomic unit of work.
|
|
342
730
|
|
|
343
|
-
|
|
731
|
+
### Minimal runtime
|
|
344
732
|
|
|
345
|
-
|
|
733
|
+
Keep shared contracts in TypeScript interfaces and keep runtime implementations small and reusable.
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
## Sample Projects
|
|
346
737
|
|
|
347
738
|
The following sample applications demonstrate different ways of using `onecore`.
|
|
348
739
|
|
|
349
740
|
### CRUD Applications
|
|
350
741
|
|
|
351
|
-
* [
|
|
352
|
-
* [
|
|
353
|
-
* [
|
|
742
|
+
* [sql-modular-sample](https://github.com/source-code-template/sql-modular-sample) — SQL modular microservice using MySQL
|
|
743
|
+
* [sql-simple-modular-sample](https://github.com/source-code-template/sql-simple-modular-sample) — SQL modular microservice using PosgreSQL
|
|
744
|
+
* [mongo-simple-modular-sample](https://github.com/source-code-template/mongo-simple-modular-sample) — MongoDB modular microservice
|
|
354
745
|
|
|
355
746
|
These projects extend the reusable CRUD and Search use-case classes.
|
|
356
747
|
|
|
@@ -380,21 +771,8 @@ These projects depend only on interfaces and the shared `Attribute` metadata mod
|
|
|
380
771
|
|
|
381
772
|
These projects depend only on interfaces and the shared `Attribute` metadata model.
|
|
382
773
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
# Design Principles
|
|
774
|
+
## License
|
|
386
775
|
|
|
387
|
-
|
|
388
|
-
* Dependency inversion
|
|
389
|
-
* Clean Architecture
|
|
390
|
-
* Hexagonal Architecture
|
|
391
|
-
* Tree-shakeable design
|
|
392
|
-
* Shared metadata model
|
|
393
|
-
* Framework independent
|
|
394
|
-
* Enterprise ready
|
|
395
|
-
|
|
396
|
-
---
|
|
776
|
+
MIT
|
|
397
777
|
|
|
398
|
-
# License
|
|
399
778
|
|
|
400
|
-
MIT
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export class SearchUseCase {
|
|
2
|
+
constructor(repository) {
|
|
3
|
+
this.repository = repository;
|
|
4
|
+
this.search = this.search.bind(this);
|
|
5
|
+
}
|
|
6
|
+
search(s, limit, page, fields) {
|
|
7
|
+
return this.repository.search(s, limit, page, fields);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export const SearchManager = SearchUseCase;
|
|
11
|
+
export class Writer {
|
|
12
|
+
constructor(repository) {
|
|
13
|
+
this.repository = repository;
|
|
14
|
+
this.create = this.create.bind(this);
|
|
15
|
+
this.update = this.update.bind(this);
|
|
16
|
+
this.patch = this.patch.bind(this);
|
|
17
|
+
}
|
|
18
|
+
create(obj) {
|
|
19
|
+
return this.repository.create(obj);
|
|
20
|
+
}
|
|
21
|
+
update(obj) {
|
|
22
|
+
return this.repository.update(obj);
|
|
23
|
+
}
|
|
24
|
+
patch(obj) {
|
|
25
|
+
return (this.repository.patch ? this.repository.patch(obj) : Promise.resolve(-1));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export class SearchWriter extends Writer {
|
|
29
|
+
constructor(repository) {
|
|
30
|
+
super(repository);
|
|
31
|
+
this.repository = repository;
|
|
32
|
+
this.search = this.search.bind(this);
|
|
33
|
+
}
|
|
34
|
+
search(filter, limit, page, fields) {
|
|
35
|
+
return this.repository.search(filter, limit, page, fields);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export class UseCase extends SearchWriter {
|
|
39
|
+
constructor(repository) {
|
|
40
|
+
super(repository);
|
|
41
|
+
this.repository = repository;
|
|
42
|
+
this.metadata = this.metadata.bind(this);
|
|
43
|
+
this.keys = this.keys.bind(this);
|
|
44
|
+
this.load = this.load.bind(this);
|
|
45
|
+
this.delete = this.delete.bind(this);
|
|
46
|
+
}
|
|
47
|
+
metadata() {
|
|
48
|
+
return (this.repository.metadata ? this.repository.metadata() : undefined);
|
|
49
|
+
}
|
|
50
|
+
keys() {
|
|
51
|
+
return (this.repository.keys ? this.repository.keys() : []);
|
|
52
|
+
}
|
|
53
|
+
load(id) {
|
|
54
|
+
return this.repository.load(id);
|
|
55
|
+
}
|
|
56
|
+
delete(id) {
|
|
57
|
+
return this.repository.delete(id);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export class CRUDUseCase extends Writer {
|
|
61
|
+
constructor(repository) {
|
|
62
|
+
super(repository);
|
|
63
|
+
this.repository = repository;
|
|
64
|
+
this.metadata = this.metadata.bind(this);
|
|
65
|
+
this.keys = this.keys.bind(this);
|
|
66
|
+
this.load = this.load.bind(this);
|
|
67
|
+
this.delete = this.delete.bind(this);
|
|
68
|
+
}
|
|
69
|
+
metadata() {
|
|
70
|
+
return (this.repository.metadata ? this.repository.metadata() : undefined);
|
|
71
|
+
}
|
|
72
|
+
keys() {
|
|
73
|
+
return (this.repository.keys ? this.repository.keys() : []);
|
|
74
|
+
}
|
|
75
|
+
load(id) {
|
|
76
|
+
return this.repository.load(id);
|
|
77
|
+
}
|
|
78
|
+
delete(id) {
|
|
79
|
+
return this.repository.delete(id);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export const GenericUseCase = CRUDUseCase;
|
|
83
|
+
export const GenericManager = CRUDUseCase;
|
|
84
|
+
export const CRUDManager = CRUDUseCase;
|
package/package.json
CHANGED
package/src/db.ts
CHANGED
|
@@ -18,7 +18,7 @@ export interface Executor {
|
|
|
18
18
|
driver: string
|
|
19
19
|
param(i: number): string
|
|
20
20
|
execute(sql: string, args?: any[]): Promise<number>
|
|
21
|
-
executeBatch(statements: Statement[],
|
|
21
|
+
executeBatch(statements: Statement[], requireFirstAffected?: boolean): Promise<number>
|
|
22
22
|
query<T>(sql: string, args?: any[], m?: StringMap, bools?: Attribute[]): Promise<T[]>
|
|
23
23
|
}
|
|
24
24
|
export interface Transaction extends Executor {
|
|
@@ -35,7 +35,7 @@ export interface ViewRepository<T, ID> {
|
|
|
35
35
|
load(id: ID, tx?: Transaction): Promise<T | null>;
|
|
36
36
|
}
|
|
37
37
|
export interface SearchRepository<T, F extends Filter> {
|
|
38
|
-
search(
|
|
38
|
+
search(filter: F, limit: number, offset?: number|string, fields?: string[], tx?: Transaction): Promise<SearchResult<T>>;
|
|
39
39
|
}
|
|
40
40
|
export interface ViewSearchRepository<T, ID, F extends Filter>
|
|
41
41
|
extends ViewRepository<T, ID>, SearchRepository<T, F> {
|
package/src/formatter.ts
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
export interface Locale {
|
|
2
|
-
|
|
3
|
-
countryCode: string
|
|
4
|
-
dateFormat: string
|
|
5
|
-
firstDayOfWeek: number
|
|
6
|
-
decimalSeparator: string
|
|
7
|
-
groupSeparator: string
|
|
8
|
-
decimalDigits: number
|
|
9
|
-
currencyCode: string
|
|
10
|
-
currencySymbol: string
|
|
11
|
-
currencyPattern: number
|
|
12
|
-
currencySample?: string;
|
|
2
|
+
code: string
|
|
3
|
+
countryCode: string
|
|
4
|
+
dateFormat: string
|
|
5
|
+
firstDayOfWeek: number
|
|
6
|
+
decimalSeparator: string
|
|
7
|
+
groupSeparator: string
|
|
8
|
+
decimalDigits: number
|
|
9
|
+
currencyCode: string
|
|
10
|
+
currencySymbol: string
|
|
11
|
+
currencyPattern: number
|
|
13
12
|
}
|
|
14
13
|
export interface Currency {
|
|
15
|
-
code: string
|
|
16
|
-
decimalDigits: number
|
|
17
|
-
symbol: string
|
|
14
|
+
code: string
|
|
15
|
+
decimalDigits: number
|
|
16
|
+
symbol: string
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
export interface StringFormatter<T> {
|
package/src/index.ts
CHANGED
|
@@ -118,11 +118,15 @@ export interface Parser<T> {
|
|
|
118
118
|
export interface Transformer<T> {
|
|
119
119
|
transform: (data: string) => Promise<T>;
|
|
120
120
|
}
|
|
121
|
+
export interface ExportResult {
|
|
122
|
+
total: number
|
|
123
|
+
success: number
|
|
124
|
+
}
|
|
121
125
|
export interface ExportService {
|
|
122
|
-
export(ctx?: any): Promise<
|
|
126
|
+
export(ctx?: any): Promise<ExportResult>;
|
|
123
127
|
}
|
|
124
128
|
export interface Exporter {
|
|
125
|
-
export(ctx?: any): Promise<
|
|
129
|
+
export(ctx?: any): Promise<ExportResult>;
|
|
126
130
|
}
|
|
127
131
|
|
|
128
132
|
// tslint:disable-next-line:max-classes-per-file
|
|
@@ -171,8 +175,8 @@ export class SearchWriter<T, F extends Filter> extends Writer<T> {
|
|
|
171
175
|
super(repository)
|
|
172
176
|
this.search = this.search.bind(this);
|
|
173
177
|
}
|
|
174
|
-
search(
|
|
175
|
-
return this.repository.search(
|
|
178
|
+
search(filter: F, limit: number, page?: number|string, fields?: string[]): Promise<SearchResult<T>> {
|
|
179
|
+
return this.repository.search(filter, limit, page, fields);
|
|
176
180
|
}
|
|
177
181
|
}
|
|
178
182
|
|
package/src/io.ts
CHANGED
package/src/mq.ts
CHANGED
|
@@ -56,27 +56,27 @@ export interface Writer<T, R, ID> {
|
|
|
56
56
|
}
|
|
57
57
|
*/
|
|
58
58
|
export interface SimpleConsumer<T, R> {
|
|
59
|
-
consume(
|
|
59
|
+
consume(process: (data: T, attributes?: StringMap, raw?: R) => Promise<number>): void;
|
|
60
60
|
}
|
|
61
61
|
export interface SimpleSubscriber<T, R> {
|
|
62
|
-
subscribe(
|
|
62
|
+
subscribe(process: (data: T, attributes?: StringMap, raw?: R) => Promise<number>): void;
|
|
63
63
|
}
|
|
64
64
|
export interface SimpleReceiver<T, R> {
|
|
65
|
-
receive(
|
|
65
|
+
receive(process: (data: T, attributes?: StringMap, raw?: R) => Promise<number>): void;
|
|
66
66
|
}
|
|
67
67
|
export interface SimpleReader<T, R> {
|
|
68
|
-
read(
|
|
68
|
+
read(process: (data: T, attributes?: StringMap, raw?: R) => Promise<number>): void;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
export interface MessageConsumer<T, ID, R> {
|
|
72
72
|
consume(data: Message<T, ID, R>): void;
|
|
73
73
|
}
|
|
74
74
|
export interface MessageSubscriber<T, ID, R> {
|
|
75
|
-
subscribe(
|
|
75
|
+
subscribe(process: (data: Message<T, ID, R>) => Promise<number>): void;
|
|
76
76
|
}
|
|
77
77
|
export interface MessageReceiver<T, ID, R> {
|
|
78
|
-
receive(
|
|
78
|
+
receive(process: (data: Message<T, ID, R>) => Promise<number>): void;
|
|
79
79
|
}
|
|
80
80
|
export interface MessageReader<T, ID, R> {
|
|
81
|
-
read(
|
|
81
|
+
read(process: (data: Message<T, ID, R>) => Promise<number>): void;
|
|
82
82
|
}
|
package/src/service.ts
CHANGED
|
@@ -9,7 +9,7 @@ export interface ViewService<T, ID> {
|
|
|
9
9
|
|
|
10
10
|
export interface SearchService<T, F extends Filter> {
|
|
11
11
|
keys?(): string[];
|
|
12
|
-
search(
|
|
12
|
+
search(filter: F, limit: number, page?: number|string, fields?: string[]): Promise<SearchResult<T>>;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export interface Query<T, ID, F extends Filter> extends SearchService<T, F> {
|