tshex-cli 1.0.28 → 1.0.29
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/build/main.js +1 -158
- package/docs/shared/application/data.md +13 -1
- package/package.json +7 -8
- package/source/main.ts +34 -11
- package/templates/ctx/example-ports.ts +1 -0
- package/templates/lib/shared/application/data/capabilities.ts +56 -0
- package/templates/lib/shared/application/data/managers.ts +0 -57
- package/templates/lib/shared/application/data/repositories.ts +7 -18
- package/templates/lib/shared/application/loggers.ts +0 -21
- package/templates/lib/shared/domain/entities.ts +1 -1
- package/templates/lib/shared/application/http/handlers.ts +0 -13
- package/templates/lib/shared/application/http/json-api.ts +0 -611
- package/templates/lib/shared/application/http/json-web-token.ts +0 -980
- package/templates/lib/shared/application/http/opengraph.ts +0 -533
package/build/main.js
CHANGED
|
@@ -1,159 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import fs from 'node:fs';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import readline from 'node:readline/promises';
|
|
6
|
-
import { stdin as input, stdout as output } from 'node:process';
|
|
7
|
-
function readPackageJson() {
|
|
8
|
-
const filePath = path.join(import.meta.dirname, '..', 'package.json');
|
|
9
|
-
const fileContents = fs.readFileSync(filePath, 'utf-8');
|
|
10
|
-
return JSON.parse(fileContents);
|
|
11
|
-
}
|
|
12
|
-
function executeCreateProject(templatesDir, projectDir) {
|
|
13
|
-
try {
|
|
14
|
-
fs.cpSync(path.join(templatesDir, 'lib'), projectDir, {
|
|
15
|
-
recursive: true,
|
|
16
|
-
filter: (src) => src.endsWith('.gitkeep') === false
|
|
17
|
-
});
|
|
18
|
-
console.log('Project created successfully');
|
|
19
|
-
}
|
|
20
|
-
catch (err) {
|
|
21
|
-
console.error(err);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
function executeCreateContext(templatesDir, contextDir) {
|
|
25
|
-
try {
|
|
26
|
-
fs.cpSync(path.join(templatesDir, 'ctx'), contextDir, {
|
|
27
|
-
recursive: true,
|
|
28
|
-
filter: (src) => src.endsWith('.gitkeep') === false
|
|
29
|
-
});
|
|
30
|
-
console.log('Context created successfully');
|
|
31
|
-
}
|
|
32
|
-
catch (err) {
|
|
33
|
-
console.error(err);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
function executeCreateReactContext(templatesDir, contextDir) {
|
|
37
|
-
try {
|
|
38
|
-
fs.cpSync(path.join(templatesDir, 'ctx-react'), contextDir, {
|
|
39
|
-
recursive: true,
|
|
40
|
-
filter: (src) => src.endsWith('.gitkeep') === false
|
|
41
|
-
});
|
|
42
|
-
console.log('React context created successfully');
|
|
43
|
-
}
|
|
44
|
-
catch (err) {
|
|
45
|
-
console.error(err);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
function executeCreateTests(sourceDir, destinationDir, fileContents, ignoredSourceDir, rootSourceDir = sourceDir) {
|
|
49
|
-
const entries = fs.readdirSync(sourceDir, { withFileTypes: true });
|
|
50
|
-
if (fs.existsSync(destinationDir) === false) {
|
|
51
|
-
fs.mkdirSync(destinationDir, { recursive: true });
|
|
52
|
-
}
|
|
53
|
-
for (const entry of entries) {
|
|
54
|
-
const sourcePath = path.join(sourceDir, entry.name);
|
|
55
|
-
const destinationPath = path.join(destinationDir, entry.name);
|
|
56
|
-
if (entry.isDirectory()) {
|
|
57
|
-
if (entry.name === 'shared' && sourceDir === rootSourceDir) {
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
if (ignoredSourceDir !== undefined && sourcePath.startsWith(ignoredSourceDir)) {
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
if (fs.existsSync(destinationPath) && fs.statSync(destinationPath).isDirectory() === false) {
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
executeCreateTests(sourcePath, destinationPath, fileContents, ignoredSourceDir, rootSourceDir);
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
if (entry.isFile() && entry.name.endsWith('.ts') && fs.existsSync(destinationPath) === false) {
|
|
70
|
-
fs.writeFileSync(destinationPath, fileContents);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
async function ensureTestsDirectory(testsRootDir) {
|
|
75
|
-
if (fs.existsSync(testsRootDir)) {
|
|
76
|
-
if (fs.statSync(testsRootDir).isDirectory() === false) {
|
|
77
|
-
throw new Error(`Tests path exists but is not a directory: ${testsRootDir}`);
|
|
78
|
-
}
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
const rl = readline.createInterface({ input, output });
|
|
82
|
-
try {
|
|
83
|
-
const answer = await rl.question(`Tests directory does not exist at ${testsRootDir}. Create it? (y/N) `);
|
|
84
|
-
if (answer.trim().toLowerCase() !== 'y') {
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
finally {
|
|
89
|
-
rl.close();
|
|
90
|
-
}
|
|
91
|
-
fs.mkdirSync(testsRootDir, { recursive: true });
|
|
92
|
-
return true;
|
|
93
|
-
}
|
|
94
|
-
async function main(program) {
|
|
95
|
-
const templatesDir = path.join(import.meta.dirname, '..', 'templates');
|
|
96
|
-
const options = program.opts();
|
|
97
|
-
let targetDir = path.resolve(options.dir ?? process.cwd());
|
|
98
|
-
if (Object.keys(options).length === 0) {
|
|
99
|
-
program.help();
|
|
100
|
-
}
|
|
101
|
-
if (fs.existsSync(targetDir) === false) {
|
|
102
|
-
fs.mkdirSync(targetDir, { recursive: true });
|
|
103
|
-
}
|
|
104
|
-
if (options.project !== undefined) {
|
|
105
|
-
targetDir = path.join(targetDir, options.project);
|
|
106
|
-
executeCreateProject(templatesDir, targetDir);
|
|
107
|
-
}
|
|
108
|
-
if (options.react === true && options.context === undefined) {
|
|
109
|
-
program.error('Option --react requires --context <name>');
|
|
110
|
-
}
|
|
111
|
-
if (options.context !== undefined) {
|
|
112
|
-
targetDir = path.join(targetDir, options.context);
|
|
113
|
-
if (options.react === true) {
|
|
114
|
-
executeCreateReactContext(templatesDir, targetDir);
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
executeCreateContext(templatesDir, targetDir);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
if (options.tests !== undefined) {
|
|
121
|
-
const sourceDir = path.resolve(options.tests);
|
|
122
|
-
const testsTemplateFile = path.join(templatesDir, 'tests', 'content.ts');
|
|
123
|
-
if (fs.existsSync(sourceDir) === false) {
|
|
124
|
-
program.error(`Tests source directory does not exist: ${sourceDir}`);
|
|
125
|
-
}
|
|
126
|
-
if (fs.statSync(sourceDir).isDirectory() === false) {
|
|
127
|
-
program.error(`Tests source path is not a directory: ${sourceDir}`);
|
|
128
|
-
}
|
|
129
|
-
if (fs.existsSync(testsTemplateFile) === false) {
|
|
130
|
-
program.error(`Tests template file does not exist: ${testsTemplateFile}`);
|
|
131
|
-
}
|
|
132
|
-
const testsTemplateContents = fs.readFileSync(testsTemplateFile, 'utf-8');
|
|
133
|
-
const testsRootDir = path.join(targetDir, 'tests');
|
|
134
|
-
const testsDirectoryCreated = await ensureTestsDirectory(testsRootDir);
|
|
135
|
-
if (testsDirectoryCreated === false) {
|
|
136
|
-
program.error('Tests directory creation cancelled');
|
|
137
|
-
}
|
|
138
|
-
const testsDir = path.join(testsRootDir, path.basename(sourceDir));
|
|
139
|
-
if (testsDir === sourceDir) {
|
|
140
|
-
program.error('Tests destination directory cannot be the same as the source directory');
|
|
141
|
-
}
|
|
142
|
-
executeCreateTests(sourceDir, testsDir, testsTemplateContents, testsRootDir);
|
|
143
|
-
console.log('Tests structure created successfully');
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
const packageJson = readPackageJson();
|
|
147
|
-
program
|
|
148
|
-
.name('tshex')
|
|
149
|
-
.version(packageJson.version)
|
|
150
|
-
.option('-P, --project <name>', "creates a new project with it's shared directory")
|
|
151
|
-
.option('-C, --context <name>', 'creates a new context')
|
|
152
|
-
.option('-R, --react', 'creates a React context with --context')
|
|
153
|
-
.option('-T, --tests <path>', 'creates a .ts tests structure from an existing directory')
|
|
154
|
-
.option('--dir <path>', 'sets the directory to create the new item')
|
|
155
|
-
.parse(process.argv);
|
|
156
|
-
void main(program).catch((err) => {
|
|
157
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
158
|
-
program.error(message);
|
|
159
|
-
});
|
|
2
|
+
import{program as l}from"commander";import r from"node:fs";import n from"node:path";import p from"node:readline/promises";import{stdin as m,stdout as x}from"node:process";function h(){let e=n.join(import.meta.dirname,"..","package.json"),s=r.readFileSync(e,"utf-8");return JSON.parse(s)}function g(e,s){try{r.cpSync(n.join(e,"lib"),s,{recursive:!0,filter:t=>t.endsWith(".gitkeep")===!1}),console.log("Project created successfully")}catch(t){console.error(t)}}function j(e,s){try{r.cpSync(n.join(e,"ctx"),s,{recursive:!0,filter:t=>t.endsWith(".gitkeep")===!1}),console.log("Context created successfully")}catch(t){console.error(t)}}function S(e,s){try{r.cpSync(n.join(e,"ctx-react"),s,{recursive:!0,filter:t=>t.endsWith(".gitkeep")===!1}),console.log("React context created successfully")}catch(t){console.error(t)}}function d(e,s,t,i,c=e){let u=r.readdirSync(e,{withFileTypes:!0});r.existsSync(s)===!1&&r.mkdirSync(s,{recursive:!0});for(let o of u){let a=n.join(e,o.name),f=n.join(s,o.name);if(o.isDirectory()){if(o.name==="shared"&&e===c||i!==void 0&&a.startsWith(i)||r.existsSync(f)&&r.statSync(f).isDirectory()===!1)continue;d(a,f,t,i,c);continue}o.isFile()&&o.name.endsWith(".ts")&&r.existsSync(f)===!1&&r.writeFileSync(f,t)}}async function w(e){if(r.existsSync(e)){if(r.statSync(e).isDirectory()===!1)throw new Error(`Tests path exists but is not a directory: ${e}`);return}let s=p.createInterface({input:m,output:x});try{if((await s.question(`Tests directory does not exist at ${e}. Create it? (y/N) `)).trim().toLowerCase()!=="y")return!1}finally{s.close()}return r.mkdirSync(e,{recursive:!0}),!0}async function T(e){let s=n.join(import.meta.dirname,"..","templates"),t=e.opts(),i=n.resolve(t.dir??process.cwd());if(Object.keys(t).length===0&&e.help(),r.existsSync(i)===!1&&r.mkdirSync(i,{recursive:!0}),t.project!==void 0&&(i=n.join(i,t.project),g(s,i)),t.react===!0&&t.context===void 0&&e.error("Option --react requires --context <name>"),t.context!==void 0&&(i=n.join(i,t.context),t.react===!0?S(s,i):j(s,i)),t.tests!==void 0){let c=n.resolve(t.tests),u=n.join(s,"tests","content.ts");r.existsSync(c)===!1&&e.error(`Tests source directory does not exist: ${c}`),r.statSync(c).isDirectory()===!1&&e.error(`Tests source path is not a directory: ${c}`),r.existsSync(u)===!1&&e.error(`Tests template file does not exist: ${u}`);let o=r.readFileSync(u,"utf-8"),a=n.join(i,"tests");await w(a)===!1&&e.error("Tests directory creation cancelled");let y=n.join(a,n.basename(c));y===c&&e.error("Tests destination directory cannot be the same as the source directory"),d(c,y,o,a),console.log("Tests structure created successfully")}}var v=h();l.name("tshex").version(v.version).option("-P, --project <name>","creates a new project with it's shared directory").option("-C, --context <name>","creates a new context").option("-R, --react","creates a React context with --context").option("-T, --tests <path>","creates a .ts tests structure from an existing directory").option("--dir <path>","sets the directory to create the new item").parse(process.argv);T(l).catch(e=>{let s=e instanceof Error?e.message:String(e);l.error(s)});
|
|
@@ -49,7 +49,7 @@ The base class provides `none()` as an explicit empty result and requires
|
|
|
49
49
|
contracts such as `Filterable`, `Creatable`, and `Updatable`, plus the
|
|
50
50
|
`DatasetManager` extension for set operations.
|
|
51
51
|
|
|
52
|
-
####
|
|
52
|
+
#### Implementation
|
|
53
53
|
|
|
54
54
|
In the following example we implement an in-memory manager and its driver.
|
|
55
55
|
|
|
@@ -92,6 +92,18 @@ export class MemoryUsersDriver extends DriverAdapter<MemoryUsersManager> {
|
|
|
92
92
|
raw records. The application layer can use both without knowing whether the
|
|
93
93
|
source is memory, SQL, or an HTTP-backed adapter.
|
|
94
94
|
|
|
95
|
+
Put all your complex data operations in `DataManager`. `Repository` should only handle the transformation of raw records into domain representations. For example, if you need to relate users to their posts, implement that in a manager:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
class ComplexUsersManager extends DataManager<EnrichedUserRecord> {
|
|
99
|
+
...
|
|
100
|
+
|
|
101
|
+
public async findAllAndRelate(): Promise<Array<EnrichedUserRecord>> {
|
|
102
|
+
...
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
95
107
|
#### Repository
|
|
96
108
|
|
|
97
109
|
`Repository` is responsible for transforming raw records into domain-oriented
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tshex-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.29",
|
|
4
4
|
"author": "https://github.com/virtualitems/",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Typescript Hexagonal Architecture CLI",
|
|
@@ -41,15 +41,14 @@
|
|
|
41
41
|
"bin": {
|
|
42
42
|
"tshex": "./build/main.js"
|
|
43
43
|
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "deno run --allow-all npm:esbuild --bundle --minify --platform=node --external:commander --format=esm --outfile=./build/main.js ./source/main.ts",
|
|
46
|
+
"test": "deno test --allow-all tests/"
|
|
47
|
+
},
|
|
44
48
|
"dependencies": {
|
|
45
|
-
"commander": "^
|
|
49
|
+
"commander": "^15.0.0"
|
|
46
50
|
},
|
|
47
51
|
"devDependencies": {
|
|
48
|
-
"@
|
|
49
|
-
"@types/node": "^22.5.4",
|
|
50
|
-
"typescript": "^5.4.5"
|
|
51
|
-
},
|
|
52
|
-
"scripts": {
|
|
53
|
-
"build": "tsc"
|
|
52
|
+
"@types/node": "^18"
|
|
54
53
|
}
|
|
55
54
|
}
|
package/source/main.ts
CHANGED
|
@@ -9,12 +9,12 @@ import { program } from 'commander'
|
|
|
9
9
|
import fs from 'node:fs'
|
|
10
10
|
import path from 'node:path'
|
|
11
11
|
import readline from 'node:readline/promises'
|
|
12
|
-
import { stdin
|
|
12
|
+
import { stdin, stdout } from 'node:process'
|
|
13
13
|
|
|
14
14
|
// FUNCTIONS
|
|
15
15
|
|
|
16
16
|
function readPackageJson() {
|
|
17
|
-
const filePath = path.join(import.meta.dirname
|
|
17
|
+
const filePath = path.join(import.meta.dirname!, '..', 'package.json')
|
|
18
18
|
const fileContents = fs.readFileSync(filePath, 'utf-8')
|
|
19
19
|
return JSON.parse(fileContents)
|
|
20
20
|
}
|
|
@@ -77,19 +77,35 @@ function executeCreateTests(
|
|
|
77
77
|
continue
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
if (
|
|
80
|
+
if (
|
|
81
|
+
ignoredSourceDir !== undefined &&
|
|
82
|
+
sourcePath.startsWith(ignoredSourceDir)
|
|
83
|
+
) {
|
|
81
84
|
continue
|
|
82
85
|
}
|
|
83
86
|
|
|
84
|
-
if (
|
|
87
|
+
if (
|
|
88
|
+
fs.existsSync(destinationPath) &&
|
|
89
|
+
fs.statSync(destinationPath).isDirectory() === false
|
|
90
|
+
) {
|
|
85
91
|
continue
|
|
86
92
|
}
|
|
87
93
|
|
|
88
|
-
executeCreateTests(
|
|
94
|
+
executeCreateTests(
|
|
95
|
+
sourcePath,
|
|
96
|
+
destinationPath,
|
|
97
|
+
fileContents,
|
|
98
|
+
ignoredSourceDir,
|
|
99
|
+
rootSourceDir
|
|
100
|
+
)
|
|
89
101
|
continue
|
|
90
102
|
}
|
|
91
103
|
|
|
92
|
-
if (
|
|
104
|
+
if (
|
|
105
|
+
entry.isFile() &&
|
|
106
|
+
entry.name.endsWith('.ts') &&
|
|
107
|
+
fs.existsSync(destinationPath) === false
|
|
108
|
+
) {
|
|
93
109
|
fs.writeFileSync(destinationPath, fileContents)
|
|
94
110
|
}
|
|
95
111
|
}
|
|
@@ -104,10 +120,12 @@ async function ensureTestsDirectory(testsRootDir: string) {
|
|
|
104
120
|
return
|
|
105
121
|
}
|
|
106
122
|
|
|
107
|
-
const rl = readline.createInterface({ input, output })
|
|
123
|
+
const rl = readline.createInterface({ input: stdin, output: stdout })
|
|
108
124
|
|
|
109
125
|
try {
|
|
110
|
-
const answer = await rl.question(
|
|
126
|
+
const answer = await rl.question(
|
|
127
|
+
`Tests directory does not exist at ${testsRootDir}. Create it? (y/N) `
|
|
128
|
+
)
|
|
111
129
|
|
|
112
130
|
if (answer.trim().toLowerCase() !== 'y') {
|
|
113
131
|
return false
|
|
@@ -121,7 +139,7 @@ async function ensureTestsDirectory(testsRootDir: string) {
|
|
|
121
139
|
}
|
|
122
140
|
|
|
123
141
|
async function main(program: typeof import('commander').program) {
|
|
124
|
-
const templatesDir = path.join(import.meta.dirname
|
|
142
|
+
const templatesDir = path.join(import.meta.dirname!, '..', 'templates')
|
|
125
143
|
|
|
126
144
|
const options = program.opts()
|
|
127
145
|
|
|
@@ -182,7 +200,9 @@ async function main(program: typeof import('commander').program) {
|
|
|
182
200
|
const testsDir = path.join(testsRootDir, path.basename(sourceDir))
|
|
183
201
|
|
|
184
202
|
if (testsDir === sourceDir) {
|
|
185
|
-
program.error(
|
|
203
|
+
program.error(
|
|
204
|
+
'Tests destination directory cannot be the same as the source directory'
|
|
205
|
+
)
|
|
186
206
|
}
|
|
187
207
|
|
|
188
208
|
executeCreateTests(sourceDir, testsDir, testsTemplateContents, testsRootDir)
|
|
@@ -198,7 +218,10 @@ program
|
|
|
198
218
|
.option('-P, --project <name>', "creates a new project with it's shared directory")
|
|
199
219
|
.option('-C, --context <name>', 'creates a new context')
|
|
200
220
|
.option('-R, --react', 'creates a React context with --context')
|
|
201
|
-
.option(
|
|
221
|
+
.option(
|
|
222
|
+
'-T, --tests <path>',
|
|
223
|
+
'creates a .ts tests structure from an existing directory'
|
|
224
|
+
)
|
|
202
225
|
.option('--dir <path>', 'sets the directory to create the new item')
|
|
203
226
|
.parse(process.argv)
|
|
204
227
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
type Generic = Record<string, unknown>
|
|
2
|
+
|
|
3
|
+
export interface Listable {
|
|
4
|
+
all(): Generic | Promise<Generic[]>
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @description Declares a filtering operation over plain source records.
|
|
9
|
+
*/
|
|
10
|
+
export interface Filterable {
|
|
11
|
+
filter(selector: unknown): Generic | Promise<Generic[]>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @description Declares a sorting operation over plain source records.
|
|
16
|
+
*/
|
|
17
|
+
export interface Sortable {
|
|
18
|
+
sort(selector: unknown): Generic | Promise<Generic[]>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @description Declares a creation operation for plain source records.
|
|
23
|
+
*/
|
|
24
|
+
export interface Creatable {
|
|
25
|
+
create(data: unknown): unknown
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @description Declares an update operation that selects source records and applies new plain data.
|
|
30
|
+
*/
|
|
31
|
+
export interface Updatable {
|
|
32
|
+
update(selector: unknown, data: unknown): unknown
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @description Declares a deletion operation over source records selected by plain criteria.
|
|
37
|
+
*/
|
|
38
|
+
export interface Deletable {
|
|
39
|
+
delete(selector: unknown): unknown
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @description Declares an aggregation operation over source records.
|
|
44
|
+
*/
|
|
45
|
+
export interface Aggregatable {
|
|
46
|
+
aggregate(selector: unknown): unknown
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @description Declares operations for selecting or preloading relationships from a data source.
|
|
51
|
+
*/
|
|
52
|
+
export interface Relatable {
|
|
53
|
+
selectRelated(...args: unknown[]): unknown
|
|
54
|
+
|
|
55
|
+
prefetchRelated(...args: unknown[]): unknown
|
|
56
|
+
}
|
|
@@ -1,66 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @description Declares a filtering operation over plain source records.
|
|
3
|
-
*/
|
|
4
|
-
export interface Filterable<S = Record<string, unknown>> {
|
|
5
|
-
filter(selector: S): Promise<Array<S>>
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @description Declares a sorting operation over plain source records.
|
|
10
|
-
*/
|
|
11
|
-
export interface Sortable<S = Record<string, unknown>> {
|
|
12
|
-
sort(selector: S): Promise<Array<S>>
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* @description Declares a creation operation for plain source records.
|
|
17
|
-
*/
|
|
18
|
-
export interface Creatable<D = Record<string, unknown>> {
|
|
19
|
-
create(data: D): Promise<unknown>
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* @description Declares an update operation that selects source records and applies new plain data.
|
|
24
|
-
*/
|
|
25
|
-
export interface Updatable<S = Record<string, unknown>, D = Record<string, unknown>> {
|
|
26
|
-
update(selector: S, data: D): Promise<unknown>
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* @description Declares a deletion operation over source records selected by plain criteria.
|
|
31
|
-
*/
|
|
32
|
-
export interface Deletable<S = Record<string, unknown>> {
|
|
33
|
-
delete(selector: S): Promise<unknown>
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* @description Declares an aggregation operation over source records.
|
|
38
|
-
*/
|
|
39
|
-
export interface Aggregatable<S = Record<string, unknown>> {
|
|
40
|
-
aggregate(selector: S): Promise<S>
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* @description Declares operations for selecting or preloading relationships from a data source.
|
|
45
|
-
*/
|
|
46
|
-
export interface Relatable {
|
|
47
|
-
selectRelated(...args: unknown[]): unknown
|
|
48
|
-
|
|
49
|
-
prefetchRelated(...args: unknown[]): unknown
|
|
50
|
-
}
|
|
51
|
-
|
|
52
1
|
/**
|
|
53
2
|
* @description Operates on a data source using plain objects and arrays.
|
|
54
3
|
* It exposes the raw data without transforming it.
|
|
55
4
|
*/
|
|
56
5
|
export abstract class DataManager<T = Record<string, unknown>> {
|
|
57
6
|
[property: string]: unknown
|
|
58
|
-
|
|
59
|
-
public none(): Array<T> {
|
|
60
|
-
return []
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
public abstract all(): Promise<Array<T>>
|
|
64
7
|
} //:: class
|
|
65
8
|
|
|
66
9
|
/**
|
|
@@ -1,29 +1,18 @@
|
|
|
1
1
|
import { type DataManager } from './managers.js'
|
|
2
2
|
import { type DriverAdapter } from './drivers.js'
|
|
3
3
|
|
|
4
|
+
type Generic = Record<string, unknown>
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
7
|
* @description Acts as an intermediary between plain source data and domain objects.
|
|
6
8
|
* It transforms records into domain representations and can translate them back when needed.
|
|
7
9
|
*/
|
|
8
|
-
export abstract class Repository<
|
|
9
|
-
DataShape extends Record<string, unknown> = Record<string, unknown>,
|
|
10
|
-
EntityShape extends Record<string, unknown> = Record<string, unknown>
|
|
11
|
-
> {
|
|
10
|
+
export abstract class Repository<RawDataShape = Generic, EntityShape = Generic> {
|
|
12
11
|
[property: string]: unknown
|
|
13
12
|
|
|
14
|
-
public constructor(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const connection = await this.driver.connect()
|
|
18
|
-
const raw = await connection.all()
|
|
19
|
-
const entities = this.transformList(raw)
|
|
20
|
-
await this.driver.disconnect()
|
|
21
|
-
return entities
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
protected transformList(data: Array<DataShape>): Array<EntityShape> {
|
|
25
|
-
return data.map(this.transform)
|
|
26
|
-
}
|
|
13
|
+
public constructor(
|
|
14
|
+
public readonly driver: DriverAdapter<DataManager<RawDataShape>>
|
|
15
|
+
) {}
|
|
27
16
|
|
|
28
|
-
protected abstract transform(data:
|
|
17
|
+
protected abstract transform(data: RawDataShape): EntityShape
|
|
29
18
|
} //:: class
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { type TimeZone } from '../../types/timezones'
|
|
2
|
-
import { type Locale } from '../../types/locales'
|
|
3
|
-
|
|
4
1
|
export const DEBUG = 10
|
|
5
2
|
|
|
6
3
|
export const INFO = 20
|
|
@@ -22,20 +19,6 @@ export abstract class Logger {
|
|
|
22
19
|
|
|
23
20
|
public level: number = 0
|
|
24
21
|
|
|
25
|
-
public datetimeLocales: Locale[] = ['en-GB']
|
|
26
|
-
|
|
27
|
-
public datetimeFormatOptions: Intl.DateTimeFormatOptions & { timeZone: TimeZone } = {
|
|
28
|
-
timeZone: 'UTC',
|
|
29
|
-
year: 'numeric',
|
|
30
|
-
month: '2-digit',
|
|
31
|
-
day: '2-digit',
|
|
32
|
-
hour: '2-digit',
|
|
33
|
-
minute: '2-digit',
|
|
34
|
-
second: '2-digit',
|
|
35
|
-
fractionalSecondDigits: 3,
|
|
36
|
-
hourCycle: 'h23'
|
|
37
|
-
}
|
|
38
|
-
|
|
39
22
|
public abstract debug(data: unknown): void
|
|
40
23
|
|
|
41
24
|
public abstract info(data: unknown): void
|
|
@@ -45,8 +28,4 @@ export abstract class Logger {
|
|
|
45
28
|
public abstract error(data: unknown): void
|
|
46
29
|
|
|
47
30
|
public abstract critical(data: unknown): void
|
|
48
|
-
|
|
49
|
-
protected getCurrentDatetime(): string {
|
|
50
|
-
return new Date().toLocaleString(this.datetimeLocales, this.datetimeFormatOptions)
|
|
51
|
-
}
|
|
52
31
|
} //:: class
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @description Http request handler to process incoming requests and generate responses.
|
|
3
|
-
*/
|
|
4
|
-
export interface HttpRequestHandler {
|
|
5
|
-
handle(request: Request): Response | Promise<Response>
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @description An HTTP middleware that pipes requests through handlers.
|
|
10
|
-
*/
|
|
11
|
-
export interface HttpMiddleware {
|
|
12
|
-
process(request: Request, handler: HttpRequestHandler): Response | Promise<Response>
|
|
13
|
-
}
|