@wabot-dev/framework 0.9.2 → 0.9.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.
Files changed (37) hide show
  1. package/dist/src/addon/async/pg/PgCronJobRepository.js +7 -5
  2. package/dist/src/addon/async/pg/PgJobRepository.js +7 -5
  3. package/dist/src/addon/async/pg/PgTransactionAdapter.js +4 -4
  4. package/dist/src/addon/auth/api-key/ApiKey.js +4 -4
  5. package/dist/src/addon/auth/api-key/PgApiKeyRepository.js +9 -8
  6. package/dist/src/addon/auth/jwt/JwtRefreshToken.js +4 -4
  7. package/dist/src/addon/auth/jwt/PgJwtRefreshTokenRepository.js +6 -5
  8. package/dist/src/addon/chat-bot/pg/PgChatMemory.js +8 -7
  9. package/dist/src/addon/chat-bot/pg/PgChatRepository.js +7 -6
  10. package/dist/src/addon/chat-controller/cmd/@cmd.js +7 -2
  11. package/dist/src/addon/chat-controller/cmd/CmdChannel.js +85 -61
  12. package/dist/src/addon/chat-controller/cmd/CmdChannelConfig.js +8 -0
  13. package/dist/src/addon/chat-controller/cmd/CmdChannelServer.js +169 -0
  14. package/dist/src/addon/chat-controller/cmd/cmdChannelSocketPath.js +16 -0
  15. package/dist/src/addon/chat-controller/cmd/runCmdClient.js +226 -0
  16. package/dist/src/core/repository/CrudRepository.js +25 -0
  17. package/dist/src/feature/pg/@pgExtension.js +33 -0
  18. package/dist/src/feature/pg/PgJsonRepositoryAdapter.js +50 -0
  19. package/dist/src/feature/pg/index.js +4 -7
  20. package/dist/src/feature/project-runner/ProjectRunner.js +67 -17
  21. package/dist/src/feature/repository/@memoryExtension.js +29 -0
  22. package/dist/src/feature/repository/@query.js +22 -0
  23. package/dist/src/feature/repository/@queryExtension.js +21 -0
  24. package/dist/src/feature/repository/@repository.js +170 -0
  25. package/dist/src/feature/repository/MemoryRepositoryAdapter.js +110 -0
  26. package/dist/src/feature/repository/RepositoryAdapterRegistry.js +27 -0
  27. package/dist/src/feature/repository/RepositoryMetadataStore.js +102 -0
  28. package/dist/src/feature/repository/evaluateQueryAst.js +134 -0
  29. package/dist/src/index.d.ts +197 -47
  30. package/dist/src/index.js +18 -7
  31. package/package.json +4 -2
  32. package/dist/src/feature/pg/query/@pgJsonRepository.js +0 -73
  33. package/dist/src/feature/pg/query/@query.js +0 -14
  34. package/dist/src/feature/pg/query/PgJsonRepository.js +0 -23
  35. package/dist/src/feature/pg/query/PgRepositoryMetadataStore.js +0 -44
  36. /package/dist/src/feature/pg/{query/buildQuerySql.js → buildQuerySql.js} +0 -0
  37. /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 { pgJsonRepository } from './query/@pgJsonRepository.js';
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,9 +30,13 @@ 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)$/;
39
+ const DEFAULT_EXCLUDE = ['run.ts', 'cmd.ts'];
36
40
  const DEFAULT_CHAT_ADAPTERS = [
37
41
  ['../../addon/chat-bot/openia', 'OpenaiChatAdapter'],
38
42
  ['../../addon/chat-bot/openrouter', 'OpenRouterChatAdapter'],
@@ -41,12 +45,14 @@ const DEFAULT_CHAT_ADAPTERS = [
41
45
  ];
42
46
  class ProjectRunner {
43
47
  directories;
48
+ exclude;
44
49
  chatAdapters;
45
50
  connectionString;
46
51
  isPg;
47
52
  pool = null;
48
53
  constructor(config = {}) {
49
54
  this.directories = config.directories ?? ['src'];
55
+ this.exclude = [...DEFAULT_EXCLUDE, ...(config.exclude ?? [])];
50
56
  this.chatAdapters = config.chatAdapters;
51
57
  this.connectionString = this.resolveConnectionString(config.connectionString);
52
58
  this.isPg = this.connectionString != null && isPostgresUrl(this.connectionString);
@@ -83,10 +89,30 @@ class ProjectRunner {
83
89
  seen.add(d);
84
90
  return true;
85
91
  });
86
- const results = await Promise.all(roots.map((dir) => scanDir(dir).catch((err) => {
87
- logger.warn(`Could not read directory ${dir}: ${err.message}`);
88
- return [];
89
- })));
92
+ const excludedNames = new Set();
93
+ const excludedPathsByRoot = new Map();
94
+ for (const entry of this.exclude) {
95
+ if (entry.includes('/') || entry.includes('\\')) {
96
+ for (const root of roots) {
97
+ let paths = excludedPathsByRoot.get(root);
98
+ if (!paths) {
99
+ paths = new Set();
100
+ excludedPathsByRoot.set(root, paths);
101
+ }
102
+ paths.add(resolve(root, entry));
103
+ }
104
+ }
105
+ else {
106
+ excludedNames.add(entry);
107
+ }
108
+ }
109
+ const results = await Promise.all(roots.map((dir) => {
110
+ const excludedPaths = excludedPathsByRoot.get(dir) ?? new Set();
111
+ return scanDir(dir, excludedNames, excludedPaths).catch((err) => {
112
+ logger.warn(`Could not read directory ${dir}: ${err.message}`);
113
+ return [];
114
+ });
115
+ }));
90
116
  return results.flat();
91
117
  }
92
118
  async importFiles(files) {
@@ -97,6 +123,7 @@ class ProjectRunner {
97
123
  const results = await Promise.allSettled(files.map((file) => import(pathToFileURL(file).href)));
98
124
  let imported = 0;
99
125
  let failed = 0;
126
+ const errorGroups = new Map();
100
127
  for (let i = 0; i < results.length; i++) {
101
128
  const result = results[i];
102
129
  if (result.status === 'fulfilled') {
@@ -104,10 +131,21 @@ class ProjectRunner {
104
131
  }
105
132
  else {
106
133
  failed++;
107
- const reason = result.reason;
108
- logger.error(`Failed to import ${files[i]}: ${reason.message}`);
134
+ const message = result.reason.message;
135
+ const group = errorGroups.get(message);
136
+ if (group) {
137
+ group.push(files[i]);
138
+ }
139
+ else {
140
+ errorGroups.set(message, [files[i]]);
141
+ }
109
142
  }
110
143
  }
144
+ for (const [message, affected] of errorGroups) {
145
+ const [first, ...rest] = affected;
146
+ const suffix = rest.length > 0 ? ` (also affects ${rest.length} more file(s))` : '';
147
+ logger.error(`Failed to import ${first}: ${message}${suffix}`);
148
+ }
111
149
  if (failed > 0) {
112
150
  logger.warn(`Imported ${imported}/${files.length} files (${failed} failed)`);
113
151
  }
@@ -143,12 +181,17 @@ class ProjectRunner {
143
181
  import('../../addon/lock/index.js'),
144
182
  needsJobs ? import('../../addon/async/in-memory/index.js') : Promise.resolve(null),
145
183
  ]);
146
- container.registerType(ChatRepository, chatBotMod.InMemoryChatRepository);
147
- container.registerType(Locker, lockMod.InMemoryLocker);
184
+ container.register(ChatRepository, { useToken: chatBotMod.InMemoryChatRepository });
185
+ container.register(Locker, { useToken: lockMod.InMemoryLocker });
186
+ const memoryAdapter = new MemoryRepositoryAdapter();
187
+ container.resolve(RepositoryAdapterRegistry).setDefault(memoryAdapter);
188
+ container.resolve(RepositoryMetadataStore).validateExtensionsRegistered(memoryAdapter.id);
148
189
  if (asyncMod) {
149
- container.registerType(JobRepository, asyncMod.InMemoryJobRepository);
190
+ container.register(JobRepository, { useToken: asyncMod.InMemoryJobRepository });
150
191
  if (components.cronHandlers.length > 0) {
151
- container.registerType(CronJobRepository, asyncMod.InMemoryCronJobRepository);
192
+ container.register(CronJobRepository, {
193
+ useToken: asyncMod.InMemoryCronJobRepository,
194
+ });
152
195
  }
153
196
  }
154
197
  logger.info('Configured with in-memory adapters');
@@ -162,17 +205,20 @@ class ProjectRunner {
162
205
  import('../pg/index.js'),
163
206
  import('../../addon/async/pg/index.js'),
164
207
  ]);
165
- container.registerType(ChatRepository, chatBotMod.PgChatRepository);
166
- container.registerType(Locker, pgMod.PgLocker);
208
+ container.register(ChatRepository, { useToken: chatBotMod.PgChatRepository });
209
+ container.register(Locker, { useToken: pgMod.PgLocker });
210
+ const pgAdapter = new pgMod.PgJsonRepositoryAdapter(this.pool);
211
+ container.resolve(RepositoryAdapterRegistry).setDefault(pgAdapter);
212
+ container.resolve(RepositoryMetadataStore).validateExtensionsRegistered(pgAdapter.id);
167
213
  const transactionStore = container.resolve(TransactionMetadataStore);
168
214
  transactionStore.registerAdapter('default', new asyncMod.PgTransactionAdapter(this.pool));
169
215
  const hasCommandHandlers = components.commandHandlers.length > 0;
170
216
  const hasCronHandlers = components.cronHandlers.length > 0;
171
217
  if (hasCommandHandlers || hasCronHandlers) {
172
- container.registerType(JobRepository, asyncMod.PgJobRepository);
218
+ container.register(JobRepository, { useToken: asyncMod.PgJobRepository });
173
219
  }
174
220
  if (hasCronHandlers) {
175
- container.registerType(CronJobRepository, asyncMod.PgCronJobRepository);
221
+ container.register(CronJobRepository, { useToken: asyncMod.PgCronJobRepository });
176
222
  }
177
223
  logger.info('Configured with PostgreSQL adapters');
178
224
  }
@@ -227,14 +273,19 @@ function run(config) {
227
273
  function isPostgresUrl(cs) {
228
274
  return cs.startsWith('postgres://') || cs.startsWith('postgresql://');
229
275
  }
230
- async function scanDir(dir) {
276
+ async function scanDir(dir, excludedNames, excludedPaths) {
231
277
  const entries = await readdir(dir, { withFileTypes: true });
232
278
  const subResults = await Promise.all(entries.map(async (entry) => {
233
279
  const name = entry.name;
280
+ const fullPath = join(dir, name);
281
+ if (excludedNames.has(name))
282
+ return [];
283
+ if (excludedPaths.has(fullPath))
284
+ return [];
234
285
  if (entry.isDirectory()) {
235
286
  if (name.startsWith('__'))
236
287
  return [];
237
- return scanDir(join(dir, name));
288
+ return scanDir(fullPath, excludedNames, excludedPaths);
238
289
  }
239
290
  if (!entry.isFile())
240
291
  return [];
@@ -242,7 +293,6 @@ async function scanDir(dir) {
242
293
  return [];
243
294
  if (name.endsWith('.d.ts'))
244
295
  return [];
245
- const fullPath = join(dir, name);
246
296
  if (TEST_FILE_PATTERNS.test(fullPath))
247
297
  return [];
248
298
  return [fullPath];
@@ -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 };