@wabot-dev/framework 0.9.2 → 0.9.5
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/dist/src/addon/async/pg/PgCronJobRepository.js +7 -5
- package/dist/src/addon/async/pg/PgJobRepository.js +7 -5
- package/dist/src/addon/async/pg/PgTransactionAdapter.js +4 -4
- package/dist/src/addon/auth/api-key/ApiKey.js +4 -4
- package/dist/src/addon/auth/api-key/PgApiKeyRepository.js +9 -8
- package/dist/src/addon/auth/jwt/JwtRefreshToken.js +4 -4
- package/dist/src/addon/auth/jwt/PgJwtRefreshTokenRepository.js +6 -5
- package/dist/src/addon/chat-bot/pg/PgChatMemory.js +8 -7
- package/dist/src/addon/chat-bot/pg/PgChatRepository.js +7 -6
- package/dist/src/addon/chat-controller/cmd/@cmd.js +7 -2
- package/dist/src/addon/chat-controller/cmd/CmdChannel.js +85 -61
- package/dist/src/addon/chat-controller/cmd/CmdChannelConfig.js +8 -0
- package/dist/src/addon/chat-controller/cmd/CmdChannelServer.js +169 -0
- package/dist/src/addon/chat-controller/cmd/cmdChannelSocketPath.js +16 -0
- package/dist/src/addon/chat-controller/cmd/runCmdClient.js +226 -0
- package/dist/src/core/repository/CrudRepository.js +25 -0
- package/dist/src/feature/pg/@pgExtension.js +33 -0
- package/dist/src/feature/pg/PgJsonRepositoryAdapter.js +50 -0
- package/dist/src/feature/pg/index.js +4 -7
- package/dist/src/feature/project-runner/ProjectRunner.js +33 -10
- package/dist/src/feature/repository/@memoryExtension.js +29 -0
- package/dist/src/feature/repository/@query.js +22 -0
- package/dist/src/feature/repository/@queryExtension.js +21 -0
- package/dist/src/feature/repository/@repository.js +170 -0
- package/dist/src/feature/repository/MemoryRepositoryAdapter.js +110 -0
- package/dist/src/feature/repository/RepositoryAdapterRegistry.js +27 -0
- package/dist/src/feature/repository/RepositoryMetadataStore.js +102 -0
- package/dist/src/feature/repository/evaluateQueryAst.js +134 -0
- package/dist/src/index.d.ts +195 -47
- package/dist/src/index.js +18 -7
- package/package.json +4 -2
- package/dist/src/feature/pg/query/@pgJsonRepository.js +0 -73
- package/dist/src/feature/pg/query/@query.js +0 -14
- package/dist/src/feature/pg/query/PgJsonRepository.js +0 -23
- package/dist/src/feature/pg/query/PgRepositoryMetadataStore.js +0 -44
- /package/dist/src/feature/pg/{query/buildQuerySql.js → buildQuerySql.js} +0 -0
- /package/dist/src/feature/{pg/query → repository}/parseQueryMethodName.js +0 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import * as net from 'node:net';
|
|
2
|
+
import * as readline from 'node:readline';
|
|
3
|
+
import { cmdChannelSocketPath } from './cmdChannelSocketPath.js';
|
|
4
|
+
|
|
5
|
+
const useColor = process.stdout.isTTY && !process.env.NO_COLOR && process.env.TERM !== 'dumb';
|
|
6
|
+
const ansi = (code) => (text) => useColor ? `\x1b[${code}m${text}\x1b[0m` : text;
|
|
7
|
+
const bold = ansi('1');
|
|
8
|
+
const dim = ansi('2');
|
|
9
|
+
const cyan = ansi('1;36');
|
|
10
|
+
const green = ansi('1;32');
|
|
11
|
+
const greenText = ansi('32');
|
|
12
|
+
const red = ansi('1;31');
|
|
13
|
+
const yellow = ansi('33');
|
|
14
|
+
const COMMANDS = ['/channels', '/help', '/exit'];
|
|
15
|
+
const HELP_LINES = [
|
|
16
|
+
'Commands:',
|
|
17
|
+
' /channels list channels and switch',
|
|
18
|
+
' /help show this help',
|
|
19
|
+
' /exit quit',
|
|
20
|
+
];
|
|
21
|
+
const RECONNECT_DELAY_MS = 1000;
|
|
22
|
+
function runCmdClient() {
|
|
23
|
+
const socketPath = cmdChannelSocketPath();
|
|
24
|
+
let socket = null;
|
|
25
|
+
let buffer = '';
|
|
26
|
+
let routes = [];
|
|
27
|
+
let state = 'disconnected';
|
|
28
|
+
let selected = null;
|
|
29
|
+
let reconnectTimer = null;
|
|
30
|
+
let waitingNoticeShown = false;
|
|
31
|
+
let exiting = false;
|
|
32
|
+
let restoring = false;
|
|
33
|
+
const completer = (line) => {
|
|
34
|
+
if (line.startsWith('/')) {
|
|
35
|
+
const hits = COMMANDS.filter((c) => c.startsWith(line));
|
|
36
|
+
return [hits.length > 0 ? hits : [...COMMANDS], line];
|
|
37
|
+
}
|
|
38
|
+
if (state === 'choosing') {
|
|
39
|
+
const hits = routes
|
|
40
|
+
.map((r) => r.route)
|
|
41
|
+
.filter((r) => r.toLowerCase().startsWith(line.toLowerCase()));
|
|
42
|
+
return [hits, line];
|
|
43
|
+
}
|
|
44
|
+
return [[], line];
|
|
45
|
+
};
|
|
46
|
+
const rl = readline.createInterface({
|
|
47
|
+
input: process.stdin,
|
|
48
|
+
output: process.stdout,
|
|
49
|
+
completer,
|
|
50
|
+
});
|
|
51
|
+
const send = (msg) => {
|
|
52
|
+
if (!socket) {
|
|
53
|
+
process.stderr.write(red('not connected to framework — waiting for server...') + '\n');
|
|
54
|
+
rl.prompt();
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
socket.write(JSON.stringify(msg) + '\n');
|
|
58
|
+
return true;
|
|
59
|
+
};
|
|
60
|
+
const printChannels = (list) => {
|
|
61
|
+
routes = list;
|
|
62
|
+
if (list.length === 0) {
|
|
63
|
+
process.stdout.write(yellow('No cmd channels are registered on the server yet.') + '\n');
|
|
64
|
+
rl.prompt();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (list.length === 1) {
|
|
68
|
+
send({ type: 'select', route: list[0].route });
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
process.stdout.write(bold('Available cmd channels:') + '\n');
|
|
72
|
+
list.forEach((entry, i) => {
|
|
73
|
+
const marker = entry.route === selected ? ' ' + yellow('(active)') : '';
|
|
74
|
+
process.stdout.write(` ${dim(`${i + 1}.`)} ${cyan(entry.route)}${marker}\n`);
|
|
75
|
+
});
|
|
76
|
+
state = 'choosing';
|
|
77
|
+
rl.setPrompt(bold('select # ') + dim('> '));
|
|
78
|
+
rl.prompt();
|
|
79
|
+
};
|
|
80
|
+
const handleServerMessage = (msg) => {
|
|
81
|
+
switch (msg.type) {
|
|
82
|
+
case 'channels':
|
|
83
|
+
printChannels(msg.list);
|
|
84
|
+
return;
|
|
85
|
+
case 'selected':
|
|
86
|
+
selected = msg.route;
|
|
87
|
+
state = 'chatting';
|
|
88
|
+
if (!restoring) {
|
|
89
|
+
process.stdout.write(green(`[connected to ${msg.route}]`) + '\n');
|
|
90
|
+
}
|
|
91
|
+
restoring = false;
|
|
92
|
+
rl.setPrompt(cyan(msg.route) + dim(' > '));
|
|
93
|
+
rl.prompt();
|
|
94
|
+
return;
|
|
95
|
+
case 'reply':
|
|
96
|
+
process.stdout.write(`\n${green(`[${msg.senderName ?? 'bot'}]:`)} ${greenText(msg.text)}\n\n`);
|
|
97
|
+
rl.prompt();
|
|
98
|
+
return;
|
|
99
|
+
case 'error':
|
|
100
|
+
process.stderr.write(red('error: ') + red(msg.message) + '\n');
|
|
101
|
+
rl.prompt();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
const scheduleReconnect = () => {
|
|
106
|
+
if (exiting || reconnectTimer)
|
|
107
|
+
return;
|
|
108
|
+
reconnectTimer = setTimeout(() => {
|
|
109
|
+
reconnectTimer = null;
|
|
110
|
+
connect();
|
|
111
|
+
}, RECONNECT_DELAY_MS);
|
|
112
|
+
};
|
|
113
|
+
const connect = () => {
|
|
114
|
+
if (exiting)
|
|
115
|
+
return;
|
|
116
|
+
buffer = '';
|
|
117
|
+
socket = net.connect(socketPath);
|
|
118
|
+
socket.on('connect', () => {
|
|
119
|
+
waitingNoticeShown = false;
|
|
120
|
+
// Silent reconnect when we're restoring a prior selection;
|
|
121
|
+
// otherwise announce the connection and ask for channels.
|
|
122
|
+
if (selected) {
|
|
123
|
+
restoring = true;
|
|
124
|
+
socket.write(JSON.stringify({ type: 'select', route: selected }) + '\n');
|
|
125
|
+
state = 'chatting';
|
|
126
|
+
rl.setPrompt(cyan(selected) + dim(' > '));
|
|
127
|
+
rl.prompt();
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
process.stdout.write(green('[connected to framework]') + '\n');
|
|
131
|
+
socket.write(JSON.stringify({ type: 'hello' }) + '\n');
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
socket.on('data', (chunk) => {
|
|
135
|
+
buffer += chunk.toString();
|
|
136
|
+
let idx;
|
|
137
|
+
while ((idx = buffer.indexOf('\n')) !== -1) {
|
|
138
|
+
const line = buffer.slice(0, idx);
|
|
139
|
+
buffer = buffer.slice(idx + 1);
|
|
140
|
+
if (!line)
|
|
141
|
+
continue;
|
|
142
|
+
try {
|
|
143
|
+
handleServerMessage(JSON.parse(line));
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
process.stderr.write(red(`invalid server message: ${err.message}`) + '\n');
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
socket.on('error', (err) => {
|
|
151
|
+
const transient = err.code === 'ENOENT' ||
|
|
152
|
+
err.code === 'ECONNREFUSED' ||
|
|
153
|
+
err.code === 'ECONNRESET' ||
|
|
154
|
+
err.code === 'EPIPE';
|
|
155
|
+
if (!transient) {
|
|
156
|
+
process.stderr.write(red(`socket error: ${err.message}`) + '\n');
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
socket.on('close', () => {
|
|
160
|
+
const wasConnected = state !== 'disconnected';
|
|
161
|
+
state = 'disconnected';
|
|
162
|
+
socket = null;
|
|
163
|
+
if (exiting)
|
|
164
|
+
return;
|
|
165
|
+
if (wasConnected) {
|
|
166
|
+
process.stdout.write(yellow('\n[disconnected — waiting for framework to come back...]') + '\n');
|
|
167
|
+
}
|
|
168
|
+
else if (!waitingNoticeShown && !selected) {
|
|
169
|
+
waitingNoticeShown = true;
|
|
170
|
+
process.stdout.write(dim('Waiting for framework...') + '\n');
|
|
171
|
+
}
|
|
172
|
+
scheduleReconnect();
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
const cleanup = (code) => {
|
|
176
|
+
exiting = true;
|
|
177
|
+
if (reconnectTimer) {
|
|
178
|
+
clearTimeout(reconnectTimer);
|
|
179
|
+
reconnectTimer = null;
|
|
180
|
+
}
|
|
181
|
+
rl.close();
|
|
182
|
+
if (socket)
|
|
183
|
+
socket.end();
|
|
184
|
+
process.exit(code);
|
|
185
|
+
};
|
|
186
|
+
rl.on('line', (input) => {
|
|
187
|
+
const trimmed = input.trim();
|
|
188
|
+
if (!trimmed) {
|
|
189
|
+
rl.prompt();
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (trimmed === '/exit' || trimmed.toLowerCase() === 'exit') {
|
|
193
|
+
cleanup(0);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (trimmed === '/help') {
|
|
197
|
+
process.stdout.write(HELP_LINES.join('\n') + '\n');
|
|
198
|
+
rl.prompt();
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (trimmed === '/channels') {
|
|
202
|
+
send({ type: 'hello' });
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (state === 'disconnected') {
|
|
206
|
+
process.stderr.write(red('not connected to framework — waiting for server...') + '\n');
|
|
207
|
+
rl.prompt();
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (state === 'choosing') {
|
|
211
|
+
const num = Number.parseInt(trimmed, 10);
|
|
212
|
+
if (!Number.isInteger(num) || num < 1 || num > routes.length) {
|
|
213
|
+
process.stderr.write(red(`Invalid selection. Enter a number between 1 and ${routes.length}.`) + '\n');
|
|
214
|
+
rl.prompt();
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
send({ type: 'select', route: routes[num - 1].route });
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
send({ type: 'message', text: trimmed });
|
|
221
|
+
});
|
|
222
|
+
rl.on('close', () => cleanup(0));
|
|
223
|
+
connect();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export { runCmdClient };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
class CrudRepository {
|
|
2
|
+
find(id) {
|
|
3
|
+
throw new Error("Method not implemented.");
|
|
4
|
+
}
|
|
5
|
+
findOrThrow(id) {
|
|
6
|
+
throw new Error("Method not implemented.");
|
|
7
|
+
}
|
|
8
|
+
findByIds(ids) {
|
|
9
|
+
throw new Error("Method not implemented.");
|
|
10
|
+
}
|
|
11
|
+
findAll(id) {
|
|
12
|
+
throw new Error("Method not implemented.");
|
|
13
|
+
}
|
|
14
|
+
create(item) {
|
|
15
|
+
throw new Error("Method not implemented.");
|
|
16
|
+
}
|
|
17
|
+
update(item) {
|
|
18
|
+
throw new Error("Method not implemented.");
|
|
19
|
+
}
|
|
20
|
+
delete(item) {
|
|
21
|
+
throw new Error("Method not implemented.");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export { CrudRepository };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { container } from '../../core/injection/index.js';
|
|
2
|
+
import 'short-uuid';
|
|
3
|
+
import '../../core/error/setupErrorHandlers.js';
|
|
4
|
+
import { RepositoryMetadataStore } from '../repository/RepositoryMetadataStore.js';
|
|
5
|
+
import '../repository/RepositoryAdapterRegistry.js';
|
|
6
|
+
import { PgRepositoryBase } from './PgRepositoryBase.js';
|
|
7
|
+
|
|
8
|
+
const PG_ADAPTER_ID = Symbol('wabot:pg-adapter');
|
|
9
|
+
function inheritsFrom(ctor, base) {
|
|
10
|
+
let proto = ctor.prototype;
|
|
11
|
+
while (proto) {
|
|
12
|
+
if (proto === base.prototype)
|
|
13
|
+
return true;
|
|
14
|
+
proto = Object.getPrototypeOf(proto);
|
|
15
|
+
}
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
function pgExtension(repositoryClass) {
|
|
19
|
+
if (typeof repositoryClass !== 'function') {
|
|
20
|
+
throw new Error(`@pgExtension: repository argument must be a class, ` +
|
|
21
|
+
`got ${typeof repositoryClass}`);
|
|
22
|
+
}
|
|
23
|
+
return function (target) {
|
|
24
|
+
if (!inheritsFrom(target, PgRepositoryBase)) {
|
|
25
|
+
throw new Error(`@pgExtension on ${target.name}: extension class must extend ` +
|
|
26
|
+
`PgRepositoryExtension.`);
|
|
27
|
+
}
|
|
28
|
+
const store = container.resolve(RepositoryMetadataStore);
|
|
29
|
+
store.saveExtension(repositoryClass, PG_ADAPTER_ID, target);
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { PG_ADAPTER_ID, pgExtension };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { PgCrudRepository } from './PgCrudRepository.js';
|
|
2
|
+
import { PG_ADAPTER_ID } from './@pgExtension.js';
|
|
3
|
+
import { buildQuerySql } from './buildQuerySql.js';
|
|
4
|
+
import { withPgClient } from './withPgClient.js';
|
|
5
|
+
|
|
6
|
+
class PgJsonRepositoryRuntime extends PgCrudRepository {
|
|
7
|
+
async runQuery(ast, args) {
|
|
8
|
+
const built = buildQuerySql(ast, this.table, this.columns);
|
|
9
|
+
const params = built.buildParams(args);
|
|
10
|
+
return this.query(built.sql, params);
|
|
11
|
+
}
|
|
12
|
+
async runCount(ast, args) {
|
|
13
|
+
const built = buildQuerySql(ast, this.table, this.columns);
|
|
14
|
+
const params = built.buildParams(args);
|
|
15
|
+
return withPgClient(this.pool, async (client) => {
|
|
16
|
+
await this.ensureTable(client);
|
|
17
|
+
const result = await client.query(built.sql, params);
|
|
18
|
+
return result.rows[0]?.count ?? 0;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
async runExists(ast, args) {
|
|
22
|
+
const built = buildQuerySql(ast, this.table, this.columns);
|
|
23
|
+
const params = built.buildParams(args);
|
|
24
|
+
return withPgClient(this.pool, async (client) => {
|
|
25
|
+
await this.ensureTable(client);
|
|
26
|
+
const result = await client.query(built.sql, params);
|
|
27
|
+
return Boolean(result.rows[0]?.exists);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
async runDelete(ast, args) {
|
|
31
|
+
const built = buildQuerySql(ast, this.table, this.columns);
|
|
32
|
+
const params = built.buildParams(args);
|
|
33
|
+
await this.exec(built.sql, params);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
class PgJsonRepositoryAdapter {
|
|
37
|
+
pool;
|
|
38
|
+
id = PG_ADAPTER_ID;
|
|
39
|
+
constructor(pool) {
|
|
40
|
+
this.pool = pool;
|
|
41
|
+
}
|
|
42
|
+
build(config) {
|
|
43
|
+
return new PgJsonRepositoryRuntime(this.pool, config);
|
|
44
|
+
}
|
|
45
|
+
buildExtension(config, ExtensionCtor) {
|
|
46
|
+
return new ExtensionCtor(this.pool, config);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { PgJsonRepositoryAdapter };
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
+
export { PG_ADAPTER_ID, pgExtension } from './@pgExtension.js';
|
|
1
2
|
export { PgCrudRepository } from './PgCrudRepository.js';
|
|
3
|
+
export { PgJsonRepositoryAdapter } from './PgJsonRepositoryAdapter.js';
|
|
2
4
|
export { PgLocker } from './PgLocker.js';
|
|
3
5
|
export { PgLockKey } from './PgLockKey.js';
|
|
4
|
-
export { PgRepositoryBase } from './PgRepositoryBase.js';
|
|
6
|
+
export { PgRepositoryBase, PgRepositoryBase as PgRepositoryExtension } from './PgRepositoryBase.js';
|
|
5
7
|
export { getClientMap, pgStorage } from './pgStorage.js';
|
|
6
|
-
export {
|
|
7
|
-
export { query } from './query/@query.js';
|
|
8
|
-
export { PgJsonRepository } from './query/PgJsonRepository.js';
|
|
9
|
-
export { PgRepositoryMetadataStore } from './query/PgRepositoryMetadataStore.js';
|
|
10
|
-
export { buildQuerySql } from './query/buildQuerySql.js';
|
|
11
|
-
export { parseQueryMethodName } from './query/parseQueryMethodName.js';
|
|
8
|
+
export { buildQuerySql } from './buildQuerySql.js';
|
|
12
9
|
export { getPgClient, withPgClient } from './withPgClient.js';
|
|
13
10
|
export { withPgTransaction } from './withPgTransaction.js';
|
|
@@ -30,6 +30,9 @@ import { runCommandHandlers } from '../async/runCommandHandlers.js';
|
|
|
30
30
|
import { runCronHandlers } from '../async/runCronHandlers.js';
|
|
31
31
|
import { SocketControllerMetadataStore } from '../socket-controller/metadata/SocketControllerMetadataStore.js';
|
|
32
32
|
import { runSocketControllers } from '../socket-controller/runSocketControllers.js';
|
|
33
|
+
import { MemoryRepositoryAdapter } from '../repository/MemoryRepositoryAdapter.js';
|
|
34
|
+
import { RepositoryMetadataStore } from '../repository/RepositoryMetadataStore.js';
|
|
35
|
+
import { RepositoryAdapterRegistry } from '../repository/RepositoryAdapterRegistry.js';
|
|
33
36
|
|
|
34
37
|
const logger = new Logger('wabot:project-runner');
|
|
35
38
|
const TEST_FILE_PATTERNS = /\.(test|spec|unit|integration|e2e|multiprocess)\.(ts|js)$/;
|
|
@@ -97,6 +100,7 @@ class ProjectRunner {
|
|
|
97
100
|
const results = await Promise.allSettled(files.map((file) => import(pathToFileURL(file).href)));
|
|
98
101
|
let imported = 0;
|
|
99
102
|
let failed = 0;
|
|
103
|
+
const errorGroups = new Map();
|
|
100
104
|
for (let i = 0; i < results.length; i++) {
|
|
101
105
|
const result = results[i];
|
|
102
106
|
if (result.status === 'fulfilled') {
|
|
@@ -104,10 +108,21 @@ class ProjectRunner {
|
|
|
104
108
|
}
|
|
105
109
|
else {
|
|
106
110
|
failed++;
|
|
107
|
-
const
|
|
108
|
-
|
|
111
|
+
const message = result.reason.message;
|
|
112
|
+
const group = errorGroups.get(message);
|
|
113
|
+
if (group) {
|
|
114
|
+
group.push(files[i]);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
errorGroups.set(message, [files[i]]);
|
|
118
|
+
}
|
|
109
119
|
}
|
|
110
120
|
}
|
|
121
|
+
for (const [message, affected] of errorGroups) {
|
|
122
|
+
const [first, ...rest] = affected;
|
|
123
|
+
const suffix = rest.length > 0 ? ` (also affects ${rest.length} more file(s))` : '';
|
|
124
|
+
logger.error(`Failed to import ${first}: ${message}${suffix}`);
|
|
125
|
+
}
|
|
111
126
|
if (failed > 0) {
|
|
112
127
|
logger.warn(`Imported ${imported}/${files.length} files (${failed} failed)`);
|
|
113
128
|
}
|
|
@@ -143,12 +158,17 @@ class ProjectRunner {
|
|
|
143
158
|
import('../../addon/lock/index.js'),
|
|
144
159
|
needsJobs ? import('../../addon/async/in-memory/index.js') : Promise.resolve(null),
|
|
145
160
|
]);
|
|
146
|
-
container.
|
|
147
|
-
container.
|
|
161
|
+
container.register(ChatRepository, { useToken: chatBotMod.InMemoryChatRepository });
|
|
162
|
+
container.register(Locker, { useToken: lockMod.InMemoryLocker });
|
|
163
|
+
const memoryAdapter = new MemoryRepositoryAdapter();
|
|
164
|
+
container.resolve(RepositoryAdapterRegistry).setDefault(memoryAdapter);
|
|
165
|
+
container.resolve(RepositoryMetadataStore).validateExtensionsRegistered(memoryAdapter.id);
|
|
148
166
|
if (asyncMod) {
|
|
149
|
-
container.
|
|
167
|
+
container.register(JobRepository, { useToken: asyncMod.InMemoryJobRepository });
|
|
150
168
|
if (components.cronHandlers.length > 0) {
|
|
151
|
-
container.
|
|
169
|
+
container.register(CronJobRepository, {
|
|
170
|
+
useToken: asyncMod.InMemoryCronJobRepository,
|
|
171
|
+
});
|
|
152
172
|
}
|
|
153
173
|
}
|
|
154
174
|
logger.info('Configured with in-memory adapters');
|
|
@@ -162,17 +182,20 @@ class ProjectRunner {
|
|
|
162
182
|
import('../pg/index.js'),
|
|
163
183
|
import('../../addon/async/pg/index.js'),
|
|
164
184
|
]);
|
|
165
|
-
container.
|
|
166
|
-
container.
|
|
185
|
+
container.register(ChatRepository, { useToken: chatBotMod.PgChatRepository });
|
|
186
|
+
container.register(Locker, { useToken: pgMod.PgLocker });
|
|
187
|
+
const pgAdapter = new pgMod.PgJsonRepositoryAdapter(this.pool);
|
|
188
|
+
container.resolve(RepositoryAdapterRegistry).setDefault(pgAdapter);
|
|
189
|
+
container.resolve(RepositoryMetadataStore).validateExtensionsRegistered(pgAdapter.id);
|
|
167
190
|
const transactionStore = container.resolve(TransactionMetadataStore);
|
|
168
191
|
transactionStore.registerAdapter('default', new asyncMod.PgTransactionAdapter(this.pool));
|
|
169
192
|
const hasCommandHandlers = components.commandHandlers.length > 0;
|
|
170
193
|
const hasCronHandlers = components.cronHandlers.length > 0;
|
|
171
194
|
if (hasCommandHandlers || hasCronHandlers) {
|
|
172
|
-
container.
|
|
195
|
+
container.register(JobRepository, { useToken: asyncMod.PgJobRepository });
|
|
173
196
|
}
|
|
174
197
|
if (hasCronHandlers) {
|
|
175
|
-
container.
|
|
198
|
+
container.register(CronJobRepository, { useToken: asyncMod.PgCronJobRepository });
|
|
176
199
|
}
|
|
177
200
|
logger.info('Configured with PostgreSQL adapters');
|
|
178
201
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { container } from '../../core/injection/index.js';
|
|
2
|
+
import { MEMORY_ADAPTER_ID, MemoryRepositoryExtension } from './MemoryRepositoryAdapter.js';
|
|
3
|
+
import { RepositoryMetadataStore } from './RepositoryMetadataStore.js';
|
|
4
|
+
|
|
5
|
+
function inheritsFrom(ctor, base) {
|
|
6
|
+
let proto = ctor.prototype;
|
|
7
|
+
while (proto) {
|
|
8
|
+
if (proto === base.prototype)
|
|
9
|
+
return true;
|
|
10
|
+
proto = Object.getPrototypeOf(proto);
|
|
11
|
+
}
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
function memoryExtension(repositoryClass) {
|
|
15
|
+
if (typeof repositoryClass !== 'function') {
|
|
16
|
+
throw new Error(`@memoryExtension: repository argument must be a class, ` +
|
|
17
|
+
`got ${typeof repositoryClass}`);
|
|
18
|
+
}
|
|
19
|
+
return function (target) {
|
|
20
|
+
if (!inheritsFrom(target, MemoryRepositoryExtension)) {
|
|
21
|
+
throw new Error(`@memoryExtension on ${target.name}: extension class must extend ` +
|
|
22
|
+
`MemoryRepositoryExtension.`);
|
|
23
|
+
}
|
|
24
|
+
const store = container.resolve(RepositoryMetadataStore);
|
|
25
|
+
store.saveExtension(repositoryClass, MEMORY_ADAPTER_ID, target);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export { memoryExtension };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { container } from '../../core/injection/index.js';
|
|
2
|
+
import { parseQueryMethodName } from './parseQueryMethodName.js';
|
|
3
|
+
import { RepositoryMetadataStore } from './RepositoryMetadataStore.js';
|
|
4
|
+
|
|
5
|
+
function query() {
|
|
6
|
+
return function (target, propertyKey, descriptor) {
|
|
7
|
+
const functionName = propertyKey.toString();
|
|
8
|
+
const ctor = target.constructor;
|
|
9
|
+
parseQueryMethodName(functionName);
|
|
10
|
+
const value = descriptor ? descriptor.value : target[propertyKey];
|
|
11
|
+
if (value !== undefined && typeof value !== 'function') {
|
|
12
|
+
throw new Error(`@query() on ${ctor.name}.${functionName}: decorated property must be a function`);
|
|
13
|
+
}
|
|
14
|
+
const store = container.resolve(RepositoryMetadataStore);
|
|
15
|
+
store.saveQueryMethodMetadata({
|
|
16
|
+
repositoryConstructor: ctor,
|
|
17
|
+
functionName,
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { query };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { container } from '../../core/injection/index.js';
|
|
2
|
+
import { RepositoryMetadataStore } from './RepositoryMetadataStore.js';
|
|
3
|
+
|
|
4
|
+
function queryExtension() {
|
|
5
|
+
return function (target, propertyKey, descriptor) {
|
|
6
|
+
const functionName = propertyKey.toString();
|
|
7
|
+
const ctor = target.constructor;
|
|
8
|
+
const value = descriptor ? descriptor.value : target[propertyKey];
|
|
9
|
+
if (value !== undefined && typeof value !== 'function') {
|
|
10
|
+
throw new Error(`@queryExtension() on ${ctor.name}.${functionName}: ` +
|
|
11
|
+
`decorated property must be a function (typically a declare).`);
|
|
12
|
+
}
|
|
13
|
+
const store = container.resolve(RepositoryMetadataStore);
|
|
14
|
+
store.saveExtensionMethodMetadata({
|
|
15
|
+
repositoryConstructor: ctor,
|
|
16
|
+
functionName,
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { queryExtension };
|