@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.
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 +33 -10
  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 +195 -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,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 reason = result.reason;
108
- logger.error(`Failed to import ${files[i]}: ${reason.message}`);
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.registerType(ChatRepository, chatBotMod.InMemoryChatRepository);
147
- container.registerType(Locker, lockMod.InMemoryLocker);
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.registerType(JobRepository, asyncMod.InMemoryJobRepository);
167
+ container.register(JobRepository, { useToken: asyncMod.InMemoryJobRepository });
150
168
  if (components.cronHandlers.length > 0) {
151
- container.registerType(CronJobRepository, asyncMod.InMemoryCronJobRepository);
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.registerType(ChatRepository, chatBotMod.PgChatRepository);
166
- container.registerType(Locker, pgMod.PgLocker);
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.registerType(JobRepository, asyncMod.PgJobRepository);
195
+ container.register(JobRepository, { useToken: asyncMod.PgJobRepository });
173
196
  }
174
197
  if (hasCronHandlers) {
175
- container.registerType(CronJobRepository, asyncMod.PgCronJobRepository);
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 };