@wabot-dev/framework 0.8.3 → 0.9.0

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 (48) hide show
  1. package/dist/src/addon/async/pg/PgCronJobRepository.js +2 -0
  2. package/dist/src/addon/async/pg/PgJobRepository.js +2 -0
  3. package/dist/src/addon/auth/api-key/@apiKeyGuard.js +2 -2
  4. package/dist/src/addon/auth/api-key/PgApiKeyRepository.js +3 -1
  5. package/dist/src/addon/auth/jwt/@jwtGuard.js +2 -2
  6. package/dist/src/addon/auth/jwt/PgJwtRefreshTokenRepository.js +2 -0
  7. package/dist/src/addon/chat-bot/anthropic/AnthropicChatAdapter.js +38 -14
  8. package/dist/src/addon/chat-bot/deepseek/DeepSeekChatAdapter.js +42 -13
  9. package/dist/src/addon/chat-bot/google/GoogleChatAdapter.js +77 -26
  10. package/dist/src/addon/chat-bot/openia/OpenaiChatAdapter.js +31 -9
  11. package/dist/src/addon/chat-bot/openrouter/OpenRouterChatAdapter.js +23 -4
  12. package/dist/src/addon/chat-bot/pg/PgChatMemory.js +6 -1
  13. package/dist/src/addon/chat-bot/pg/PgChatRepository.js +5 -0
  14. package/dist/src/addon/chat-bot/ram/RamChatRepository.js +3 -0
  15. package/dist/src/addon/chat-bot/wabot/WabotChatAdapter.js +9 -0
  16. package/dist/src/addon/chat-controller/cmd/CmdChannel.js +3 -3
  17. package/dist/src/addon/chat-controller/wasender/WasenderWebhookController.js +1 -1
  18. package/dist/src/addon/chat-controller/whatsapp/PgWhatsAppRepository.js +2 -0
  19. package/dist/src/addon/chat-controller/whatsapp/WhatsAppSender.js +3 -0
  20. package/dist/src/addon/chat-controller/whatsapp/cloud-api/WhatsAppSenderByCloudApi.js +3 -0
  21. package/dist/src/addon/chat-controller/whatsapp/proxy/WhatsAppSenderByWabotProxy.js +3 -0
  22. package/dist/src/feature/chat-bot/Chat.js +2 -1
  23. package/dist/src/feature/chat-bot/ChatAdapterRegistry.js +26 -0
  24. package/dist/src/feature/chat-bot/ChatBot.js +9 -6
  25. package/dist/src/feature/chat-bot/UnionChatAdapter.js +64 -0
  26. package/dist/src/feature/chat-bot/isChatMessageEmpty.js +1 -1
  27. package/dist/src/feature/chat-bot/isRetryableError.js +63 -0
  28. package/dist/src/feature/chat-bot/metadata/@chatAdapter.js +11 -0
  29. package/dist/src/feature/chat-bot/metadata/ChatAdapterMetadataStore.js +17 -0
  30. package/dist/src/feature/chat-bot/runChatAdapters.js +22 -0
  31. package/dist/src/feature/chat-controller/ChatResolver.js +3 -0
  32. package/dist/src/feature/chat-controller/runChatControllers.js +3 -0
  33. package/dist/src/feature/http/HttpServerProvider.js +1 -1
  34. package/dist/src/feature/mindset/IMindset.js +4 -0
  35. package/dist/src/feature/mindset/MindsetOperator.js +30 -2
  36. package/dist/src/feature/pg/pgStorage.js +1 -1
  37. package/dist/src/feature/pg/query/@pgJsonRepository.js +73 -0
  38. package/dist/src/feature/pg/query/@query.js +14 -0
  39. package/dist/src/feature/pg/query/PgJsonRepository.js +23 -0
  40. package/dist/src/feature/pg/query/PgRepositoryMetadataStore.js +44 -0
  41. package/dist/src/feature/pg/query/buildQuerySql.js +164 -0
  42. package/dist/src/feature/pg/query/parseQueryMethodName.js +151 -0
  43. package/dist/src/feature/pg/withPgClient.js +1 -1
  44. package/dist/src/feature/rest-controller/runRestControllers.js +2 -2
  45. package/dist/src/index.d.ts +134 -17
  46. package/dist/src/index.js +14 -2
  47. package/dist/src/node_modules/cron-parser/dist/CronFileParser.js +2 -2
  48. package/package.json +1 -1
@@ -4,6 +4,14 @@ import { CustomError } from '../../../core/error/CustomError.js';
4
4
  import '../../../core/error/setupErrorHandlers.js';
5
5
  import { singleton } from '../../../core/injection/index.js';
6
6
  import { Logger } from '../../../core/logger/Logger.js';
7
+ import '../../../feature/chat-bot/ChatAdapterRegistry.js';
8
+ import '../../../feature/chat-bot/ChatBot.js';
9
+ import '../../../feature/chat-bot/ChatOperator.js';
10
+ import '../../../feature/chat-bot/UnionChatAdapter.js';
11
+ import { chatAdapter } from '../../../feature/chat-bot/metadata/@chatAdapter.js';
12
+ import 'uuid';
13
+ import '../../../feature/chat-bot/metadata/ChatBotMetadataStore.js';
14
+ import '../../../feature/chat-bot/metadata/ChatAdapterMetadataStore.js';
7
15
 
8
16
  let WabotChatAdapter = class WabotChatAdapter {
9
17
  apiKey;
@@ -35,6 +43,7 @@ let WabotChatAdapter = class WabotChatAdapter {
35
43
  }
36
44
  };
37
45
  WabotChatAdapter = __decorate([
46
+ chatAdapter({ provider: 'wabot' }),
38
47
  singleton(),
39
48
  __metadata("design:paramtypes", [Env])
40
49
  ], WabotChatAdapter);
@@ -2,9 +2,9 @@ import { __decorate, __metadata } from 'tslib';
2
2
  import { injectable } from '../../../core/injection/index.js';
3
3
  import { Logger } from '../../../core/logger/Logger.js';
4
4
  import { cmdChannelName } from './cmdChannelName.js';
5
- import * as readline from 'readline';
6
- import * as fs from 'fs';
7
- import * as path from 'path';
5
+ import * as readline from 'node:readline';
6
+ import * as fs from 'node:fs';
7
+ import * as path from 'node:path';
8
8
  import { Random } from '../../../core/random/Random.js';
9
9
  import { Auth } from '../../../core/auth/Auth.js';
10
10
 
@@ -4,7 +4,7 @@ import '../../../core/injection/index.js';
4
4
  import '../../../feature/rest-controller/metadata/RestControllerMetadataStore.js';
5
5
  import { onPost } from '../../../feature/rest-controller/metadata/@onPost.js';
6
6
  import { extractNumberFromWasenderMessageKey } from './extractNumberFromWasenderKey.js';
7
- import { IncomingMessage } from 'http';
7
+ import { IncomingMessage } from 'node:http';
8
8
 
9
9
  class WasenderWebhookController {
10
10
  wasender;
@@ -8,6 +8,8 @@ import 'debug';
8
8
  import 'node:crypto';
9
9
  import '../../../feature/pg/withPgClient.js';
10
10
  import '../../../feature/pg/pgStorage.js';
11
+ import '../../../feature/pg/query/PgJsonRepository.js';
12
+ import '../../../feature/pg/query/PgRepositoryMetadataStore.js';
11
13
 
12
14
  let PgWhatsAppRepository = class PgWhatsAppRepository extends PgCrudRepository {
13
15
  constructor(pool) {
@@ -1,7 +1,10 @@
1
+ import '../../../feature/chat-bot/ChatAdapterRegistry.js';
1
2
  import '../../../feature/chat-bot/ChatBot.js';
2
3
  import { ChatItem } from '../../../feature/chat-bot/ChatItem.js';
3
4
  import '../../../feature/chat-bot/ChatOperator.js';
5
+ import '../../../feature/chat-bot/UnionChatAdapter.js';
4
6
  import '../../../core/injection/index.js';
7
+ import '../../../feature/chat-bot/metadata/ChatAdapterMetadataStore.js';
5
8
  import 'uuid';
6
9
  import '../../../feature/chat-bot/metadata/ChatBotMetadataStore.js';
7
10
  import '../../../core/error/setupErrorHandlers.js';
@@ -5,9 +5,12 @@ import { singleton } from '../../../../core/injection/index.js';
5
5
  import '../../../../feature/chat-controller/metadata/ControllerMetadataStore.js';
6
6
  import { ChatResolver } from '../../../../feature/chat-controller/ChatResolver.js';
7
7
  import '../../../../feature/chat-controller/runChatControllers.js';
8
+ import '../../../../feature/chat-bot/ChatAdapterRegistry.js';
8
9
  import '../../../../feature/chat-bot/ChatBot.js';
9
10
  import '../../../../feature/chat-bot/ChatOperator.js';
10
11
  import { ChatRepository } from '../../../../feature/chat-bot/ChatRepository.js';
12
+ import '../../../../feature/chat-bot/UnionChatAdapter.js';
13
+ import '../../../../feature/chat-bot/metadata/ChatAdapterMetadataStore.js';
11
14
  import 'uuid';
12
15
  import '../../../../feature/chat-bot/metadata/ChatBotMetadataStore.js';
13
16
  import '../../../../core/error/setupErrorHandlers.js';
@@ -2,9 +2,12 @@ import { __decorate, __metadata } from 'tslib';
2
2
  import { WhatsAppSender } from '../WhatsAppSender.js';
3
3
  import { singleton } from '../../../../core/injection/index.js';
4
4
  import { Logger } from '../../../../core/logger/Logger.js';
5
+ import '../../../../feature/chat-bot/ChatAdapterRegistry.js';
5
6
  import '../../../../feature/chat-bot/ChatBot.js';
6
7
  import '../../../../feature/chat-bot/ChatOperator.js';
7
8
  import { ChatRepository } from '../../../../feature/chat-bot/ChatRepository.js';
9
+ import '../../../../feature/chat-bot/UnionChatAdapter.js';
10
+ import '../../../../feature/chat-bot/metadata/ChatAdapterMetadataStore.js';
8
11
  import 'uuid';
9
12
  import '../../../../feature/chat-bot/metadata/ChatBotMetadataStore.js';
10
13
  import '../../../../core/error/setupErrorHandlers.js';
@@ -41,7 +41,8 @@ class Chat extends Entity {
41
41
  this.data.connections.splice(index, 1);
42
42
  }
43
43
  hasAssociation(association) {
44
- return this.data.associations?.some((a) => a.type === association.type && a.id === association.id) ?? false;
44
+ return (this.data.associations?.some((a) => a.type === association.type && a.id === association.id) ??
45
+ false);
45
46
  }
46
47
  hasAssociations(type) {
47
48
  return this.data.associations?.some((a) => a.type === type) ?? false;
@@ -0,0 +1,26 @@
1
+ import { __decorate } from 'tslib';
2
+ import { singleton } from '../../core/injection/index.js';
3
+
4
+ let ChatAdapterRegistry = class ChatAdapterRegistry {
5
+ adapters = new Map();
6
+ order = [];
7
+ register(provider, adapter) {
8
+ if (!this.adapters.has(provider))
9
+ this.order.push(provider);
10
+ this.adapters.set(provider, adapter);
11
+ }
12
+ get(provider) {
13
+ return this.adapters.get(provider);
14
+ }
15
+ defaultProvider() {
16
+ return this.order[0];
17
+ }
18
+ size() {
19
+ return this.adapters.size;
20
+ }
21
+ };
22
+ ChatAdapterRegistry = __decorate([
23
+ singleton()
24
+ ], ChatAdapterRegistry);
25
+
26
+ export { ChatAdapterRegistry };
@@ -35,14 +35,17 @@ let ChatBot = class ChatBot {
35
35
  const systemPrompt = await this.mindset.systemPrompt();
36
36
  const tools = this.mindset.tools();
37
37
  const identity = await this.mindset.identity();
38
- const llms = await this.mindset.llms();
39
- if (llms.length === 0) {
40
- throw new Error(`Invalid ${this.mindset.constructor.name} - llms not found`);
38
+ const needsVision = prevItems.some((item) => {
39
+ const data = item.getData();
40
+ return data.type === 'humanMessage' && (data.humanMessage.images?.length ?? 0) > 0;
41
+ });
42
+ const kind = needsVision ? 'visionLlm' : 'llm';
43
+ const candidates = await this.mindset.resolveModels(kind);
44
+ if (candidates.length === 0) {
45
+ throw new Error(`Invalid ${this.mindset.constructor.name} - no model resolved for kind '${kind}'`);
41
46
  }
42
- const llm = llms[0];
43
47
  const { nextItems: newItemsData } = await this.adapter.nextItems({
44
- model: llm.model,
45
- provider: llm.provider,
48
+ models: candidates,
46
49
  systemPrompt,
47
50
  tools,
48
51
  prevItems: prevItems.map((x) => x.getData()),
@@ -0,0 +1,64 @@
1
+ import { __decorate, __metadata } from 'tslib';
2
+ import { singleton } from '../../core/injection/index.js';
3
+ import { Logger } from '../../core/logger/Logger.js';
4
+ import { ChatAdapterRegistry } from './ChatAdapterRegistry.js';
5
+ import { isRetryableError } from './isRetryableError.js';
6
+
7
+ let UnionChatAdapter = class UnionChatAdapter {
8
+ registry;
9
+ logger = new Logger('wabot:union-chat-adapter');
10
+ constructor(registry) {
11
+ this.registry = registry;
12
+ }
13
+ async nextItems(req) {
14
+ if (this.registry.size() === 0) {
15
+ throw new Error('No chat adapters registered. Call runChatAdapters([...]) before sending messages.');
16
+ }
17
+ if (req.models.length === 0) {
18
+ throw new Error('UnionChatAdapter received empty models list');
19
+ }
20
+ const groups = this.groupByProvider(req.models);
21
+ let lastError;
22
+ for (const group of groups) {
23
+ const adapter = this.registry.get(group.provider);
24
+ if (!adapter) {
25
+ this.logger.warn(`No adapter registered for provider '${group.provider}', skipping group`);
26
+ continue;
27
+ }
28
+ try {
29
+ return await adapter.nextItems({ ...req, models: group.entries });
30
+ }
31
+ catch (err) {
32
+ if (!isRetryableError(err)) {
33
+ throw err;
34
+ }
35
+ this.logger.warn(`Adapter for provider '${group.provider}' failed with retryable error, trying next group`, err instanceof Error ? { message: err.message } : { err });
36
+ lastError = err;
37
+ }
38
+ }
39
+ throw lastError ?? new Error('No adapter could handle the request');
40
+ }
41
+ groupByProvider(models) {
42
+ const fallbackProvider = this.registry.defaultProvider();
43
+ const groups = [];
44
+ for (const ref of models) {
45
+ const provider = ref.provider ?? fallbackProvider;
46
+ if (!provider)
47
+ continue;
48
+ const last = groups[groups.length - 1];
49
+ if (last && last.provider === provider) {
50
+ last.entries.push(ref);
51
+ }
52
+ else {
53
+ groups.push({ provider, entries: [ref] });
54
+ }
55
+ }
56
+ return groups;
57
+ }
58
+ };
59
+ UnionChatAdapter = __decorate([
60
+ singleton(),
61
+ __metadata("design:paramtypes", [ChatAdapterRegistry])
62
+ ], UnionChatAdapter);
63
+
64
+ export { UnionChatAdapter };
@@ -1,5 +1,5 @@
1
1
  function isChatMessageEmpty(message) {
2
- return (!message.text && !message.images?.length && !message.documents?.length && !message.object);
2
+ return !message.text && !message.images?.length && !message.documents?.length && !message.object;
3
3
  }
4
4
 
5
5
  export { isChatMessageEmpty };
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Returns true when an error from a chat adapter is worth retrying with another
3
+ * model or another adapter (transient/availability issues), false when retrying
4
+ * would just propagate the same configuration problem (auth, validation).
5
+ */
6
+ function isRetryableError(err) {
7
+ if (!err || typeof err !== 'object')
8
+ return true;
9
+ const status = readStatus(err);
10
+ if (status !== undefined) {
11
+ if (status === 408 || status === 425 || status === 429)
12
+ return true;
13
+ if (status >= 500)
14
+ return true;
15
+ if (status >= 400 && status < 500)
16
+ return false;
17
+ return false;
18
+ }
19
+ const code = readCode(err);
20
+ if (code) {
21
+ const transient = new Set([
22
+ 'ECONNRESET',
23
+ 'ECONNABORTED',
24
+ 'ECONNREFUSED',
25
+ 'ETIMEDOUT',
26
+ 'EAI_AGAIN',
27
+ 'ENETUNREACH',
28
+ 'ENOTFOUND',
29
+ 'EPIPE',
30
+ 'UND_ERR_CONNECT_TIMEOUT',
31
+ 'UND_ERR_HEADERS_TIMEOUT',
32
+ 'UND_ERR_BODY_TIMEOUT',
33
+ 'UND_ERR_SOCKET',
34
+ ]);
35
+ if (transient.has(code))
36
+ return true;
37
+ return false;
38
+ }
39
+ if (err instanceof Error) {
40
+ const name = err.name;
41
+ if (name === 'AbortError' || name === 'TimeoutError' || name === 'FetchError')
42
+ return true;
43
+ }
44
+ return true;
45
+ }
46
+ function readStatus(err) {
47
+ const candidates = [
48
+ err.status,
49
+ err.statusCode,
50
+ err.response?.status,
51
+ ];
52
+ for (const c of candidates) {
53
+ if (typeof c === 'number')
54
+ return c;
55
+ }
56
+ return undefined;
57
+ }
58
+ function readCode(err) {
59
+ const c = err.code;
60
+ return typeof c === 'string' ? c : undefined;
61
+ }
62
+
63
+ export { isRetryableError };
@@ -0,0 +1,11 @@
1
+ import { container } from '../../../core/injection/index.js';
2
+ import { ChatAdapterMetadataStore } from './ChatAdapterMetadataStore.js';
3
+
4
+ function chatAdapter(config) {
5
+ return function (target) {
6
+ const store = container.resolve(ChatAdapterMetadataStore);
7
+ store.save({ constructor: target, provider: config.provider });
8
+ };
9
+ }
10
+
11
+ export { chatAdapter };
@@ -0,0 +1,17 @@
1
+ import { __decorate } from 'tslib';
2
+ import { singleton } from '../../../core/injection/index.js';
3
+
4
+ let ChatAdapterMetadataStore = class ChatAdapterMetadataStore {
5
+ adapters = new Map();
6
+ save(metadata) {
7
+ this.adapters.set(metadata.constructor, metadata);
8
+ }
9
+ get(ctor) {
10
+ return this.adapters.get(ctor);
11
+ }
12
+ };
13
+ ChatAdapterMetadataStore = __decorate([
14
+ singleton()
15
+ ], ChatAdapterMetadataStore);
16
+
17
+ export { ChatAdapterMetadataStore };
@@ -0,0 +1,22 @@
1
+ import { container } from '../../core/injection/index.js';
2
+ import { ChatAdapter } from './ChatAdapter.js';
3
+ import { ChatAdapterRegistry } from './ChatAdapterRegistry.js';
4
+ import { UnionChatAdapter } from './UnionChatAdapter.js';
5
+ import { ChatAdapterMetadataStore } from './metadata/ChatAdapterMetadataStore.js';
6
+ import 'uuid';
7
+ import './metadata/ChatBotMetadataStore.js';
8
+
9
+ function runChatAdapters(adapters) {
10
+ const store = container.resolve(ChatAdapterMetadataStore);
11
+ const registry = container.resolve(ChatAdapterRegistry);
12
+ for (const ctor of adapters) {
13
+ const meta = store.get(ctor);
14
+ if (!meta) {
15
+ throw new Error(`${ctor.name} is missing the @chatAdapter({ provider }) decorator and cannot be registered`);
16
+ }
17
+ registry.register(meta.provider, container.resolve(ctor));
18
+ }
19
+ container.register(ChatAdapter, { useToken: UnionChatAdapter });
20
+ }
21
+
22
+ export { runChatAdapters };
@@ -1,9 +1,12 @@
1
1
  import { __decorate, __metadata } from 'tslib';
2
2
  import { Chat } from '../chat-bot/Chat.js';
3
+ import '../chat-bot/ChatAdapterRegistry.js';
3
4
  import '../chat-bot/ChatBot.js';
4
5
  import '../chat-bot/ChatOperator.js';
5
6
  import { ChatRepository } from '../chat-bot/ChatRepository.js';
7
+ import '../chat-bot/UnionChatAdapter.js';
6
8
  import { injectable } from '../../core/injection/index.js';
9
+ import '../chat-bot/metadata/ChatAdapterMetadataStore.js';
7
10
  import 'uuid';
8
11
  import '../chat-bot/metadata/ChatBotMetadataStore.js';
9
12
  import '../../core/error/setupErrorHandlers.js';
@@ -2,10 +2,13 @@ import { Auth } from '../../core/auth/Auth.js';
2
2
  import { container } from '../../core/injection/index.js';
3
3
  import { Logger } from '../../core/logger/Logger.js';
4
4
  import { Chat } from '../chat-bot/Chat.js';
5
+ import '../chat-bot/ChatAdapterRegistry.js';
5
6
  import { ChatBot } from '../chat-bot/ChatBot.js';
6
7
  import { ChatMemory } from '../chat-bot/ChatMemory.js';
7
8
  import '../chat-bot/ChatOperator.js';
8
9
  import { ChatRepository } from '../chat-bot/ChatRepository.js';
10
+ import '../chat-bot/UnionChatAdapter.js';
11
+ import '../chat-bot/metadata/ChatAdapterMetadataStore.js';
9
12
  import 'uuid';
10
13
  import { ChatBotMetadataStore } from '../chat-bot/metadata/ChatBotMetadataStore.js';
11
14
  import '../../core/error/setupErrorHandlers.js';
@@ -1,7 +1,7 @@
1
1
  import { __decorate } from 'tslib';
2
2
  import { singleton } from '../../core/injection/index.js';
3
3
  import { Logger } from '../../core/logger/Logger.js';
4
- import { Server } from 'http';
4
+ import { Server } from 'node:http';
5
5
 
6
6
  let HttpServerProvider = class HttpServerProvider {
7
7
  server = null;
@@ -14,6 +14,10 @@ class Mindset {
14
14
  workflow() {
15
15
  throw new Error('Method not implemented.');
16
16
  }
17
+ models() {
18
+ throw new Error('Method not implemented.');
19
+ }
20
+ /** @deprecated implement {@link Mindset.models} instead */
17
21
  llms() {
18
22
  throw new Error('Method not implemented.');
19
23
  }
@@ -9,6 +9,10 @@ import '../../core/error/setupErrorHandlers.js';
9
9
  import { Logger } from '../../core/logger/Logger.js';
10
10
  import { Container } from '../../core/injection/Container.js';
11
11
 
12
+ const MODEL_KIND_FALLBACK = {
13
+ visionLlm: 'llm',
14
+ audioLlm: 'llm',
15
+ };
12
16
  let MindsetOperator = class MindsetOperator {
13
17
  mindset;
14
18
  container;
@@ -34,8 +38,32 @@ let MindsetOperator = class MindsetOperator {
34
38
  workflow() {
35
39
  return this.mindset.workflow();
36
40
  }
37
- llms() {
38
- return this.mindset.llms();
41
+ /** @deprecated use {@link MindsetOperator.models} */
42
+ async llms() {
43
+ if (this.mindset.llms)
44
+ return this.mindset.llms();
45
+ const models = await this.models();
46
+ return models.llm ?? [];
47
+ }
48
+ async models() {
49
+ if (this.mindset.models)
50
+ return this.mindset.models();
51
+ if (this.mindset.llms)
52
+ return { llm: await this.mindset.llms() };
53
+ throw new Error(`Invalid ${this.mindset.constructor.name} - models() or llms() must be implemented`);
54
+ }
55
+ async resolveModels(kind) {
56
+ const models = await this.models();
57
+ const direct = models[kind];
58
+ if (direct && direct.length > 0)
59
+ return direct;
60
+ const fallbackKind = MODEL_KIND_FALLBACK[kind];
61
+ if (fallbackKind) {
62
+ const fallback = models[fallbackKind];
63
+ if (fallback && fallback.length > 0)
64
+ return fallback;
65
+ }
66
+ return [];
39
67
  }
40
68
  async systemPrompt() {
41
69
  let [context, identity, skills, limits, workflow] = await Promise.all([
@@ -1,4 +1,4 @@
1
- import { AsyncLocalStorage } from 'async_hooks';
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
2
 
3
3
  const pgStorage = new AsyncLocalStorage();
4
4
  /**
@@ -0,0 +1,73 @@
1
+ import { container, singleton } from '../../../core/injection/index.js';
2
+ import { withPgClient } from '../withPgClient.js';
3
+ import { buildQuerySql } from './buildQuerySql.js';
4
+ import { parseQueryMethodName } from './parseQueryMethodName.js';
5
+ import { PgJsonRepository } from './PgJsonRepository.js';
6
+ import { PgRepositoryMetadataStore } from './PgRepositoryMetadataStore.js';
7
+
8
+ const QUERY_CACHE_KEY = Symbol('wabot:pgQueryCache');
9
+ function makeQueryImpl(methodName, ast) {
10
+ return async function (...args) {
11
+ let cache = this[QUERY_CACHE_KEY];
12
+ if (!cache) {
13
+ cache = new Map();
14
+ Object.defineProperty(this, QUERY_CACHE_KEY, { value: cache, enumerable: false });
15
+ }
16
+ let built = cache.get(methodName);
17
+ if (!built) {
18
+ built = buildQuerySql(ast, this.table, this.columns);
19
+ cache.set(methodName, built);
20
+ }
21
+ const params = built.buildParams(args);
22
+ switch (ast.prefix) {
23
+ case 'find': {
24
+ return await this.query(built.sql, params);
25
+ }
26
+ case 'findOne': {
27
+ const items = await this.query(built.sql, params);
28
+ return items[0] ?? null;
29
+ }
30
+ case 'count': {
31
+ return await withPgClient(this.pool, async (client) => {
32
+ await this.ensureTable(client);
33
+ const result = await client.query(built.sql, params);
34
+ return result.rows[0]?.count ?? 0;
35
+ });
36
+ }
37
+ case 'exists': {
38
+ return await withPgClient(this.pool, async (client) => {
39
+ await this.ensureTable(client);
40
+ const result = await client.query(built.sql, params);
41
+ return Boolean(result.rows[0]?.exists);
42
+ });
43
+ }
44
+ case 'delete': {
45
+ await this.exec(built.sql, params);
46
+ return;
47
+ }
48
+ }
49
+ };
50
+ }
51
+ function pgJsonRepository(config) {
52
+ return function (target) {
53
+ if (target !== PgJsonRepository && !(target.prototype instanceof PgJsonRepository)) {
54
+ throw new Error(`@pgJsonRepository: ${target.name} must extend PgJsonRepository`);
55
+ }
56
+ const store = container.resolve(PgRepositoryMetadataStore);
57
+ store.saveRepositoryConfig(target, config);
58
+ const queryMethods = store.getQueryMethods(target);
59
+ for (const meta of queryMethods) {
60
+ if (Object.prototype.hasOwnProperty.call(target.prototype, meta.functionName)) {
61
+ const existing = target.prototype[meta.functionName];
62
+ if (typeof existing === 'function') {
63
+ continue;
64
+ }
65
+ }
66
+ const ast = parseQueryMethodName(meta.functionName);
67
+ target.prototype[meta.functionName] = makeQueryImpl(meta.functionName, ast);
68
+ }
69
+ singleton()(target);
70
+ };
71
+ }
72
+
73
+ export { pgJsonRepository };
@@ -0,0 +1,14 @@
1
+ import { container } from '../../../core/injection/index.js';
2
+ import { PgRepositoryMetadataStore } from './PgRepositoryMetadataStore.js';
3
+
4
+ function query() {
5
+ return function (target, propertyKey) {
6
+ const store = container.resolve(PgRepositoryMetadataStore);
7
+ store.saveQueryMethodMetadata({
8
+ repositoryConstructor: target.constructor,
9
+ functionName: propertyKey.toString(),
10
+ });
11
+ };
12
+ }
13
+
14
+ export { query };
@@ -0,0 +1,23 @@
1
+ import { __decorate, __metadata } from 'tslib';
2
+ import { Pool } from 'pg';
3
+ import { injectable, container } from '../../../core/injection/index.js';
4
+ import { PgCrudRepository } from '../PgCrudRepository.js';
5
+ import { PgRepositoryMetadataStore } from './PgRepositoryMetadataStore.js';
6
+
7
+ let PgJsonRepository = class PgJsonRepository extends PgCrudRepository {
8
+ constructor(pool) {
9
+ const ctor = new.target;
10
+ const store = container.resolve(PgRepositoryMetadataStore);
11
+ const config = store.getRepositoryConfig(ctor);
12
+ if (!config) {
13
+ throw new Error(`${ctor.name} must be decorated with @pgJsonRepository`);
14
+ }
15
+ super(pool, config);
16
+ }
17
+ };
18
+ PgJsonRepository = __decorate([
19
+ injectable(),
20
+ __metadata("design:paramtypes", [Pool])
21
+ ], PgJsonRepository);
22
+
23
+ export { PgJsonRepository };
@@ -0,0 +1,44 @@
1
+ import { __decorate } from 'tslib';
2
+ import { singleton } from '../../../core/injection/index.js';
3
+
4
+ let PgRepositoryMetadataStore = class PgRepositoryMetadataStore {
5
+ queryMethods = new Map();
6
+ repositoryConfigs = new Map();
7
+ saveQueryMethodMetadata(metadata) {
8
+ let perClass = this.queryMethods.get(metadata.repositoryConstructor);
9
+ if (!perClass) {
10
+ perClass = new Map();
11
+ this.queryMethods.set(metadata.repositoryConstructor, perClass);
12
+ }
13
+ perClass.set(metadata.functionName, metadata);
14
+ }
15
+ saveRepositoryConfig(ctor, config) {
16
+ this.repositoryConfigs.set(ctor, config);
17
+ }
18
+ getRepositoryConfig(ctor) {
19
+ return this.repositoryConfigs.get(ctor);
20
+ }
21
+ getQueryMethods(ctor) {
22
+ const collected = new Map();
23
+ const hierarchy = [];
24
+ let proto = ctor.prototype;
25
+ while (proto && proto.constructor !== Object) {
26
+ hierarchy.unshift(proto.constructor);
27
+ proto = Object.getPrototypeOf(proto);
28
+ }
29
+ for (const cls of hierarchy) {
30
+ const perClass = this.queryMethods.get(cls);
31
+ if (perClass) {
32
+ for (const [name, meta] of perClass) {
33
+ collected.set(name, meta);
34
+ }
35
+ }
36
+ }
37
+ return [...collected.values()];
38
+ }
39
+ };
40
+ PgRepositoryMetadataStore = __decorate([
41
+ singleton()
42
+ ], PgRepositoryMetadataStore);
43
+
44
+ export { PgRepositoryMetadataStore };