agentlang 0.0.11 → 0.0.13

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 (71) hide show
  1. package/README.md +2 -19
  2. package/out/language/generated/ast.d.ts +2 -1
  3. package/out/language/generated/ast.d.ts.map +1 -1
  4. package/out/language/generated/ast.js +1 -0
  5. package/out/language/generated/ast.js.map +1 -1
  6. package/out/language/generated/grammar.d.ts.map +1 -1
  7. package/out/language/generated/grammar.js +20 -1
  8. package/out/language/generated/grammar.js.map +1 -1
  9. package/out/language/main.cjs +21 -1
  10. package/out/language/main.cjs.map +2 -2
  11. package/out/language/syntax.js +1 -1
  12. package/out/language/syntax.js.map +1 -1
  13. package/out/runtime/agents/impl/anthropic.d.ts +26 -0
  14. package/out/runtime/agents/impl/anthropic.d.ts.map +1 -0
  15. package/out/runtime/agents/impl/anthropic.js +106 -0
  16. package/out/runtime/agents/impl/anthropic.js.map +1 -0
  17. package/out/runtime/agents/impl/openai.d.ts +22 -0
  18. package/out/runtime/agents/impl/openai.d.ts.map +1 -1
  19. package/out/runtime/agents/impl/openai.js +83 -4
  20. package/out/runtime/agents/impl/openai.js.map +1 -1
  21. package/out/runtime/agents/registry.d.ts.map +1 -1
  22. package/out/runtime/agents/registry.js +49 -5
  23. package/out/runtime/agents/registry.js.map +1 -1
  24. package/out/runtime/interpreter.d.ts +1 -0
  25. package/out/runtime/interpreter.d.ts.map +1 -1
  26. package/out/runtime/interpreter.js +13 -7
  27. package/out/runtime/interpreter.js.map +1 -1
  28. package/out/runtime/jsmodules.js +1 -1
  29. package/out/runtime/jsmodules.js.map +1 -1
  30. package/out/runtime/resolvers/interface.d.ts +2 -2
  31. package/out/runtime/resolvers/interface.d.ts.map +1 -1
  32. package/out/runtime/resolvers/interface.js +4 -4
  33. package/out/runtime/resolvers/interface.js.map +1 -1
  34. package/out/runtime/resolvers/sqldb/database.d.ts +3 -3
  35. package/out/runtime/resolvers/sqldb/database.d.ts.map +1 -1
  36. package/out/runtime/resolvers/sqldb/database.js +19 -10
  37. package/out/runtime/resolvers/sqldb/database.js.map +1 -1
  38. package/out/runtime/resolvers/sqldb/dbutil.d.ts +1 -1
  39. package/out/runtime/resolvers/sqldb/dbutil.d.ts.map +1 -1
  40. package/out/runtime/resolvers/sqldb/dbutil.js +12 -4
  41. package/out/runtime/resolvers/sqldb/dbutil.js.map +1 -1
  42. package/out/runtime/resolvers/sqldb/impl.d.ts +2 -2
  43. package/out/runtime/resolvers/sqldb/impl.d.ts.map +1 -1
  44. package/out/runtime/resolvers/sqldb/impl.js +17 -17
  45. package/out/runtime/resolvers/sqldb/impl.js.map +1 -1
  46. package/out/syntaxes/agentlang.monarch.js +1 -1
  47. package/out/syntaxes/agentlang.monarch.js.map +1 -1
  48. package/package.json +8 -10
  49. package/src/language/agentlang.langium +2 -1
  50. package/src/language/generated/ast.ts +4 -1
  51. package/src/language/generated/grammar.ts +20 -1
  52. package/src/language/syntax.ts +1 -1
  53. package/src/runtime/agents/impl/anthropic.ts +166 -0
  54. package/src/runtime/agents/impl/openai.ts +124 -4
  55. package/src/runtime/agents/registry.ts +51 -4
  56. package/src/runtime/interpreter.ts +14 -6
  57. package/src/runtime/jsmodules.ts +1 -1
  58. package/src/runtime/resolvers/interface.ts +9 -4
  59. package/src/runtime/resolvers/sqldb/database.ts +21 -8
  60. package/src/runtime/resolvers/sqldb/dbutil.ts +11 -4
  61. package/src/runtime/resolvers/sqldb/impl.ts +19 -16
  62. package/src/syntaxes/agentlang.monarch.ts +1 -1
  63. package/out/cli/docs.d.ts +0 -2
  64. package/out/cli/docs.d.ts.map +0 -1
  65. package/out/cli/docs.js +0 -236
  66. package/out/cli/docs.js.map +0 -1
  67. package/out/cli/openapi-docs.yml +0 -695
  68. package/out/index.d.ts +0 -19
  69. package/out/index.d.ts.map +0 -1
  70. package/out/index.js +0 -25
  71. package/out/index.js.map +0 -1
@@ -0,0 +1,166 @@
1
+ import { ChatAnthropic } from '@langchain/anthropic';
2
+ import { AgentServiceProvider, AIResponse, asAIResponse } from '../provider.js';
3
+ import { BaseMessage } from '@langchain/core/messages';
4
+
5
+ export interface AnthropicConfig {
6
+ model?: string;
7
+ temperature?: number;
8
+ maxTokens?: number;
9
+ maxRetries?: number;
10
+ apiKey?: string;
11
+ clientOptions?: {
12
+ defaultHeaders?: Record<string, string>;
13
+ [key: string]: any;
14
+ };
15
+ enablePromptCaching?: boolean;
16
+ cacheControl?: 'ephemeral';
17
+ }
18
+
19
+ export class AnthropicProvider implements AgentServiceProvider {
20
+ private model: ChatAnthropic;
21
+ private config: AnthropicConfig;
22
+
23
+ constructor(config?: Map<string, any>) {
24
+ this.config = this.parseConfig(config);
25
+
26
+ const chatConfig: any = {
27
+ model: this.config.model,
28
+ temperature: this.config.temperature,
29
+ maxTokens: this.config.maxTokens,
30
+ maxRetries: this.config.maxRetries,
31
+ };
32
+
33
+ if (this.config.apiKey) {
34
+ chatConfig.apiKey = this.config.apiKey;
35
+ }
36
+
37
+ if (this.config.clientOptions) {
38
+ chatConfig.clientOptions = this.config.clientOptions;
39
+ }
40
+
41
+ if (this.config.enablePromptCaching) {
42
+ chatConfig.clientOptions = {
43
+ ...chatConfig.clientOptions,
44
+ defaultHeaders: {
45
+ ...chatConfig.clientOptions?.defaultHeaders,
46
+ 'anthropic-beta': 'prompt-caching-2024-07-31',
47
+ },
48
+ };
49
+ }
50
+
51
+ this.model = new ChatAnthropic(chatConfig);
52
+ }
53
+
54
+ private parseConfig(config?: Map<string, any>): AnthropicConfig {
55
+ const defaultConfig: AnthropicConfig = {
56
+ model: 'claude-sonnet-4-20250514',
57
+ temperature: 0.7,
58
+ maxTokens: 8192,
59
+ maxRetries: 2,
60
+ enablePromptCaching: false,
61
+ cacheControl: 'ephemeral',
62
+ };
63
+
64
+ if (!config) {
65
+ return {
66
+ ...defaultConfig,
67
+ apiKey: process.env.ANTHROPIC_API_KEY,
68
+ };
69
+ }
70
+
71
+ const apiKey = config.get('apiKey') || config.get('api_key') || process.env.ANTHROPIC_API_KEY;
72
+
73
+ return {
74
+ model: config.get('model') || defaultConfig.model,
75
+ temperature: config.get('temperature') ?? defaultConfig.temperature,
76
+ maxTokens: config.get('maxTokens') || config.get('max_tokens') || defaultConfig.maxTokens,
77
+ maxRetries: config.get('maxRetries') || config.get('max_retries') || defaultConfig.maxRetries,
78
+ enablePromptCaching:
79
+ config.get('enablePromptCaching') ||
80
+ config.get('enable_prompt_caching') ||
81
+ defaultConfig.enablePromptCaching,
82
+ cacheControl:
83
+ config.get('cacheControl') || config.get('cache_control') || defaultConfig.cacheControl,
84
+ apiKey,
85
+ clientOptions: config.get('clientOptions') || config.get('client_options'),
86
+ };
87
+ }
88
+
89
+ async invoke(messages: BaseMessage[]): Promise<AIResponse> {
90
+ if (!this.config.apiKey) {
91
+ throw new Error(
92
+ 'Anthropic API key is required. Set ANTHROPIC_API_KEY environment variable or provide apiKey in config.'
93
+ );
94
+ }
95
+
96
+ let processedMessages = messages;
97
+
98
+ if (this.config.enablePromptCaching && messages.length > 0) {
99
+ processedMessages = this.applyCacheControl(messages);
100
+ }
101
+
102
+ return asAIResponse(await this.model.invoke(processedMessages));
103
+ }
104
+
105
+ private applyCacheControl(messages: BaseMessage[]): BaseMessage[] {
106
+ // Apply cache control to the last system message if present
107
+ // This follows Anthropic's recommendation to cache long context at the end of system messages
108
+ if (messages.length === 0) return messages;
109
+
110
+ const processedMessages = [...messages];
111
+
112
+ // Find the last system message and apply cache control
113
+ for (let i = processedMessages.length - 1; i >= 0; i--) {
114
+ const message = processedMessages[i];
115
+ if (message._getType() === 'system') {
116
+ // Apply cache control to system message content
117
+ const content = message.content;
118
+ if (typeof content === 'string' && content.length > 1000) {
119
+ // Only cache if content is substantial (>1000 chars as a heuristic)
120
+ (message as any).additional_kwargs = {
121
+ ...((message as any).additional_kwargs || {}),
122
+ cache_control: { type: 'ephemeral' },
123
+ };
124
+ }
125
+ break;
126
+ }
127
+ }
128
+
129
+ return processedMessages;
130
+ }
131
+
132
+ getConfig(): AnthropicConfig {
133
+ return { ...this.config };
134
+ }
135
+
136
+ updateConfig(newConfig: Partial<AnthropicConfig>): void {
137
+ this.config = { ...this.config, ...newConfig };
138
+
139
+ const chatConfig: any = {
140
+ model: this.config.model,
141
+ temperature: this.config.temperature,
142
+ maxTokens: this.config.maxTokens,
143
+ maxRetries: this.config.maxRetries,
144
+ };
145
+
146
+ if (this.config.apiKey) {
147
+ chatConfig.apiKey = this.config.apiKey;
148
+ }
149
+
150
+ if (this.config.clientOptions) {
151
+ chatConfig.clientOptions = this.config.clientOptions;
152
+ }
153
+
154
+ if (this.config.enablePromptCaching) {
155
+ chatConfig.clientOptions = {
156
+ ...chatConfig.clientOptions,
157
+ defaultHeaders: {
158
+ ...chatConfig.clientOptions?.defaultHeaders,
159
+ 'anthropic-beta': 'prompt-caching-2024-07-31',
160
+ },
161
+ };
162
+ }
163
+
164
+ this.model = new ChatAnthropic(chatConfig);
165
+ }
166
+ }
@@ -2,18 +2,138 @@ import { ChatOpenAI } from '@langchain/openai';
2
2
  import { AgentServiceProvider, AIResponse, asAIResponse } from '../provider.js';
3
3
  import { BaseMessage } from '@langchain/core/messages';
4
4
 
5
+ export interface OpenAIConfig {
6
+ model?: string;
7
+ temperature?: number;
8
+ maxTokens?: number;
9
+ topP?: number;
10
+ frequencyPenalty?: number;
11
+ presencePenalty?: number;
12
+ maxRetries?: number;
13
+ apiKey?: string;
14
+ configuration?: {
15
+ baseURL?: string;
16
+ defaultHeaders?: Record<string, string>;
17
+ [key: string]: any;
18
+ };
19
+ streamUsage?: boolean;
20
+ logprobs?: boolean;
21
+ topLogprobs?: number;
22
+ }
23
+
5
24
  export class OpenAIProvider implements AgentServiceProvider {
6
25
  private model: ChatOpenAI;
26
+ private config: OpenAIConfig;
7
27
 
8
28
  constructor(config?: Map<string, any>) {
9
- let modelName = 'gpt-4.1-2025-04-14';
10
- if (config) {
11
- modelName = config.get('model') || modelName;
29
+ this.config = this.parseConfig(config);
30
+
31
+ const chatConfig: any = {
32
+ model: this.config.model,
33
+ temperature: this.config.temperature,
34
+ maxTokens: this.config.maxTokens,
35
+ topP: this.config.topP,
36
+ frequencyPenalty: this.config.frequencyPenalty,
37
+ presencePenalty: this.config.presencePenalty,
38
+ maxRetries: this.config.maxRetries,
39
+ streamUsage: this.config.streamUsage,
40
+ logprobs: this.config.logprobs,
41
+ topLogprobs: this.config.topLogprobs,
42
+ };
43
+
44
+ if (this.config.apiKey) {
45
+ chatConfig.apiKey = this.config.apiKey;
46
+ }
47
+
48
+ if (this.config.configuration) {
49
+ chatConfig.configuration = this.config.configuration;
12
50
  }
13
- this.model = new ChatOpenAI({ model: modelName });
51
+
52
+ this.model = new ChatOpenAI(chatConfig);
53
+ }
54
+
55
+ private parseConfig(config?: Map<string, any>): OpenAIConfig {
56
+ const defaultConfig: OpenAIConfig = {
57
+ model: 'gpt-4o',
58
+ temperature: 0.7,
59
+ maxTokens: 4096,
60
+ topP: 1.0,
61
+ frequencyPenalty: 0,
62
+ presencePenalty: 0,
63
+ maxRetries: 2,
64
+ streamUsage: true,
65
+ logprobs: false,
66
+ };
67
+
68
+ if (!config) {
69
+ return {
70
+ ...defaultConfig,
71
+ apiKey: process.env.OPENAI_API_KEY,
72
+ };
73
+ }
74
+
75
+ const apiKey = config.get('apiKey') || config.get('api_key') || process.env.OPENAI_API_KEY;
76
+
77
+ return {
78
+ model: config.get('model') || defaultConfig.model,
79
+ temperature: config.get('temperature') ?? defaultConfig.temperature,
80
+ maxTokens: config.get('maxTokens') || config.get('max_tokens') || defaultConfig.maxTokens,
81
+ topP: config.get('topP') || config.get('top_p') || defaultConfig.topP,
82
+ frequencyPenalty:
83
+ config.get('frequencyPenalty') ||
84
+ config.get('frequency_penalty') ||
85
+ defaultConfig.frequencyPenalty,
86
+ presencePenalty:
87
+ config.get('presencePenalty') ||
88
+ config.get('presence_penalty') ||
89
+ defaultConfig.presencePenalty,
90
+ maxRetries: config.get('maxRetries') || config.get('max_retries') || defaultConfig.maxRetries,
91
+ streamUsage:
92
+ config.get('streamUsage') || config.get('stream_usage') || defaultConfig.streamUsage,
93
+ logprobs: config.get('logprobs') ?? defaultConfig.logprobs,
94
+ topLogprobs: config.get('topLogprobs') || config.get('top_logprobs'),
95
+ apiKey,
96
+ configuration: config.get('configuration'),
97
+ };
14
98
  }
15
99
 
16
100
  async invoke(messages: BaseMessage[]): Promise<AIResponse> {
101
+ if (!this.config.apiKey) {
102
+ throw new Error(
103
+ 'OpenAI API key is required. Set OPENAI_API_KEY environment variable or provide apiKey in config.'
104
+ );
105
+ }
17
106
  return asAIResponse(await this.model.invoke(messages));
18
107
  }
108
+
109
+ getConfig(): OpenAIConfig {
110
+ return { ...this.config };
111
+ }
112
+
113
+ updateConfig(newConfig: Partial<OpenAIConfig>): void {
114
+ this.config = { ...this.config, ...newConfig };
115
+
116
+ const chatConfig: any = {
117
+ model: this.config.model,
118
+ temperature: this.config.temperature,
119
+ maxTokens: this.config.maxTokens,
120
+ topP: this.config.topP,
121
+ frequencyPenalty: this.config.frequencyPenalty,
122
+ presencePenalty: this.config.presencePenalty,
123
+ maxRetries: this.config.maxRetries,
124
+ streamUsage: this.config.streamUsage,
125
+ logprobs: this.config.logprobs,
126
+ topLogprobs: this.config.topLogprobs,
127
+ };
128
+
129
+ if (this.config.apiKey) {
130
+ chatConfig.apiKey = this.config.apiKey;
131
+ }
132
+
133
+ if (this.config.configuration) {
134
+ chatConfig.configuration = this.config.configuration;
135
+ }
136
+
137
+ this.model = new ChatOpenAI(chatConfig);
138
+ }
19
139
  }
@@ -1,9 +1,56 @@
1
1
  import { OpenAIProvider } from './impl/openai.js';
2
+ import { AnthropicProvider } from './impl/anthropic.js';
2
3
 
3
- const Providers = new Map().set('openai', OpenAIProvider);
4
+ const Providers = new Map().set('openai', OpenAIProvider).set('anthropic', AnthropicProvider);
4
5
 
5
6
  export function provider(service: string) {
6
- const p = Providers.get(service.toLowerCase());
7
- if (p) return p;
8
- else throw new Error(`No provider found for ${service}`);
7
+ const requestedService = service.toLowerCase();
8
+ let p = Providers.get(requestedService);
9
+
10
+ if (p) {
11
+ // Check if the requested provider has its API key available
12
+ if (isProviderAvailable(requestedService)) {
13
+ return p;
14
+ } else {
15
+ // Try to find an alternative available provider
16
+ const availableService = getAvailableProvider();
17
+ if (availableService && availableService !== requestedService) {
18
+ p = Providers.get(availableService);
19
+ if (p) return p;
20
+ }
21
+ throw new Error(
22
+ `${service} provider requested but ${service.toUpperCase()}_API_KEY not found. Available providers: ${getAvailableProviders().join(', ') || 'none'}`
23
+ );
24
+ }
25
+ } else {
26
+ throw new Error(`No provider found for ${service}`);
27
+ }
28
+ }
29
+
30
+ function isProviderAvailable(service: string): boolean {
31
+ switch (service) {
32
+ case 'openai':
33
+ return !!process.env.OPENAI_API_KEY;
34
+ case 'anthropic':
35
+ return !!process.env.ANTHROPIC_API_KEY;
36
+ default:
37
+ return false;
38
+ }
39
+ }
40
+
41
+ function getAvailableProvider(): string | null {
42
+ if (process.env.ANTHROPIC_API_KEY) {
43
+ return 'anthropic';
44
+ }
45
+ if (process.env.OPENAI_API_KEY) {
46
+ return 'openai';
47
+ }
48
+ return null;
49
+ }
50
+
51
+ function getAvailableProviders(): string[] {
52
+ const available: string[] = [];
53
+ if (process.env.ANTHROPIC_API_KEY) available.push('anthropic');
54
+ if (process.env.OPENAI_API_KEY) available.push('openai');
55
+ return available;
9
56
  }
@@ -408,15 +408,21 @@ export class Environment extends Instance {
408
408
  return this.activeTransactions;
409
409
  }
410
410
 
411
+ async resetActiveTransactions(commit: boolean): Promise<Environment> {
412
+ await this.endAllTransactions(commit);
413
+ this.activeTransactions = new Map<string, string>();
414
+ return this;
415
+ }
416
+
411
417
  async getTransactionForResolver(resolver: Resolver): Promise<string> {
412
418
  const n: string = resolver.getName();
413
- let txnId: string | undefined = this.getActiveTransactions().get(n);
419
+ let txnId: string | undefined = this.activeTransactions.get(n);
414
420
  if (txnId) {
415
421
  return txnId;
416
422
  } else {
417
423
  txnId = await resolver.startTransaction();
418
424
  if (txnId) {
419
- this.getActiveTransactions().set(n, txnId);
425
+ this.activeTransactions.set(n, txnId);
420
426
  return txnId;
421
427
  } else {
422
428
  throw new Error(`Failed to start transaction for ${n}`);
@@ -430,7 +436,7 @@ export class Environment extends Instance {
430
436
  }
431
437
 
432
438
  private async endAllTransactions(commit: boolean): Promise<void> {
433
- const txns: Map<string, string> = this.getActiveTransactions();
439
+ const txns: Map<string, string> = this.activeTransactions;
434
440
  for (const n of txns.keys()) {
435
441
  const txnId: string | undefined = txns.get(n);
436
442
  if (txnId) {
@@ -952,6 +958,7 @@ async function evaluateCrudMap(crud: CrudMap, env: Environment): Promise<void> {
952
958
  const attrs = inst.attributes;
953
959
  const qattrs = inst.queryAttributes;
954
960
  const isQueryAll = crud.name.endsWith(QuerySuffix);
961
+ const distinct: boolean = crud.distinct.length > 0;
955
962
  if (attrs.size > 0) {
956
963
  await maybeValidateOneOfRefs(inst, env);
957
964
  }
@@ -964,7 +971,7 @@ async function evaluateCrudMap(crud: CrudMap, env: Environment): Promise<void> {
964
971
  if (qattrs == undefined && !isQueryAll) {
965
972
  throw new Error(`Pattern for ${entryName} with 'into' clause must be a query`);
966
973
  }
967
- await evaluateJoinQuery(crud.into, inst, crud.relationships, env);
974
+ await evaluateJoinQuery(crud.into, inst, crud.relationships, distinct, env);
968
975
  return;
969
976
  }
970
977
  if (isEntityInstance(inst) || isBetweenRelationship(inst.name, inst.moduleName)) {
@@ -1051,7 +1058,7 @@ async function evaluateCrudMap(crud: CrudMap, env: Environment): Promise<void> {
1051
1058
  isReadForUpdate,
1052
1059
  env.isInDeleteMode()
1053
1060
  );
1054
- const insts: Instance[] = await res.queryInstances(inst, isQueryAll);
1061
+ const insts: Instance[] = await res.queryInstances(inst, isQueryAll, distinct);
1055
1062
  env.setLastResult(insts);
1056
1063
  }
1057
1064
  if (crud.relationships != undefined) {
@@ -1184,6 +1191,7 @@ async function evaluateJoinQuery(
1184
1191
  intoSpec: SelectIntoSpec,
1185
1192
  inst: Instance,
1186
1193
  relationships: RelationshipPattern[],
1194
+ distinct: boolean,
1187
1195
  env: Environment
1188
1196
  ): Promise<void> {
1189
1197
  const normIntoSpec = new Map<string, string>();
@@ -1196,7 +1204,7 @@ async function evaluateJoinQuery(
1196
1204
  joinsSpec = await walkJoinQueryPattern(relationships[i], joinsSpec, env);
1197
1205
  }
1198
1206
  const resolver = await getResolverForPath(inst.name, moduleName, env);
1199
- const result: Result = await resolver.queryByJoin(inst, joinsSpec, normIntoSpec);
1207
+ const result: Result = await resolver.queryByJoin(inst, joinsSpec, normIntoSpec, distinct);
1200
1208
  env.setLastResult(result);
1201
1209
  }
1202
1210
 
@@ -17,7 +17,7 @@ export async function importModule(path: string, name: string, moduleFileName?:
17
17
  }
18
18
  path = `${s}${sep}${path}`;
19
19
  }
20
- if ((path.startsWith(sep) || path.startsWith('.')) && moduleFileName) {
20
+ if (!(path.startsWith(sep) || path.startsWith('.'))) {
21
21
  path = process.cwd() + sep + path;
22
22
  }
23
23
  const m = await import(/* @vite-ignore */ path);
@@ -100,8 +100,12 @@ export class Resolver {
100
100
  * @param {Instance} inst - an Instance with query attributes
101
101
  * @param {boolean} queryAll - if this flag is set, fetch all instances
102
102
  */
103
- public async queryInstances(inst: Instance, queryAll: boolean): Promise<any> {
104
- return this.notImpl(`queryInstances(${inst}, ${queryAll})`);
103
+ public async queryInstances(
104
+ inst: Instance,
105
+ queryAll: boolean,
106
+ distinct: boolean = false
107
+ ): Promise<any> {
108
+ return this.notImpl(`queryInstances(${inst}, ${queryAll}, ${distinct})`);
105
109
  }
106
110
 
107
111
  /**
@@ -130,9 +134,10 @@ export class Resolver {
130
134
  public async queryByJoin(
131
135
  inst: Instance,
132
136
  joinsSpec: JoinInfo[],
133
- intoSpec: Map<string, string>
137
+ intoSpec: Map<string, string>,
138
+ distinct: boolean = false
134
139
  ): Promise<any> {
135
- return this.notImpl(`queryByJoin(${inst}, ${joinsSpec}, ${intoSpec})`);
140
+ return this.notImpl(`queryByJoin(${inst}, ${joinsSpec}, ${intoSpec}, ${distinct})`);
136
141
  }
137
142
 
138
143
  /**
@@ -185,7 +185,11 @@ function makeSqliteDataSource(
185
185
  });
186
186
  }
187
187
 
188
- export const DbType = 'sqlite';
188
+ const DbType = 'sqlite';
189
+
190
+ function getDbType(config?: DatabaseConfig): string {
191
+ return process.env.AL_DB_TYPE || config?.type || DbType;
192
+ }
189
193
 
190
194
  function getDsFunction(
191
195
  config: DatabaseConfig | undefined
@@ -194,7 +198,7 @@ function getDsFunction(
194
198
  config: DatabaseConfig | undefined,
195
199
  synchronize?: boolean | undefined
196
200
  ) => DataSource {
197
- switch (process.env.AL_DB_TYPE || config?.type || DbType) {
201
+ switch (getDbType(config)) {
198
202
  case 'sqlite':
199
203
  return makeSqliteDataSource;
200
204
  case 'postgres':
@@ -204,6 +208,10 @@ function getDsFunction(
204
208
  }
205
209
  }
206
210
 
211
+ export function isUsingSqlite(): boolean {
212
+ return getDbType() == 'sqlite';
213
+ }
214
+
207
215
  export async function initDatabase(config: DatabaseConfig | undefined) {
208
216
  if (defaultDataSource == undefined) {
209
217
  const mkds = getDsFunction(config);
@@ -602,8 +610,8 @@ function mkBetweenClause(tableName: string | undefined, k: string, queryVals: an
602
610
  const v1 = isstr ? `'${ov[0]}'` : ov[0];
603
611
  const v2 = isstr ? `'${ov[1]}'` : ov[1];
604
612
  const s = tableName
605
- ? `${tableName}.${k} BETWEEN ${v1} AND ${v2}`
606
- : `${k} BETWEEN ${v1} AND ${v2}`;
613
+ ? `"${tableName}"."${k}" BETWEEN ${v1} AND ${v2}`
614
+ : `"${k}" BETWEEN ${v1} AND ${v2}`;
607
615
  delete queryVals[k];
608
616
  return s;
609
617
  } else {
@@ -619,8 +627,8 @@ function objectToWhereClause(queryObj: object, queryVals: any, tableName?: strin
619
627
  op == 'between'
620
628
  ? mkBetweenClause(tableName, value[0], queryVals)
621
629
  : tableName
622
- ? `${tableName}.${value[0]} ${op} :${value[0]}`
623
- : `${value[0]} ${op} :${value[0]}`;
630
+ ? `"${tableName}"."${value[0]}" ${op} :${value[0]}`
631
+ : `"${value[0]}" ${op} :${value[0]}`;
624
632
  clauses.push(clause);
625
633
  });
626
634
  return clauses.join(' AND ');
@@ -637,7 +645,7 @@ function objectToRawWhereClause(queryObj: object, queryVals: any, tableName?: st
637
645
  } else {
638
646
  const ov: any = queryVals[k];
639
647
  const v = isString(ov) ? `'${ov}'` : ov;
640
- clause = tableName ? `${tableName}.${k} ${op} ${v}` : `${k} ${op} ${v}`;
648
+ clause = tableName ? `"${tableName}"."${k}" ${op} ${v}` : `"${k}" ${op} ${v}`;
641
649
  }
642
650
  clauses.push(clause);
643
651
  });
@@ -652,6 +660,7 @@ export async function getMany(
652
660
  tableName: string,
653
661
  queryObj: object | undefined,
654
662
  queryVals: object | undefined,
663
+ distinct: boolean,
655
664
  ctx: DbContext
656
665
  ): Promise<any> {
657
666
  const alias: string = tableName.toLowerCase();
@@ -697,6 +706,9 @@ export async function getMany(
697
706
  if (ownersJoinCond) {
698
707
  qb.innerJoin(ot, otAlias, ownersJoinCond.join(' AND '));
699
708
  }
709
+ if (distinct) {
710
+ qb.distinct(true);
711
+ }
700
712
  qb.where(queryStr, queryVals);
701
713
  return await qb.getMany();
702
714
  }
@@ -707,6 +719,7 @@ export async function getManyByJoin(
707
719
  queryVals: object | undefined,
708
720
  joinClauses: JoinClause[],
709
721
  intoSpec: Map<string, string>,
722
+ distinct: boolean,
710
723
  ctx: DbContext
711
724
  ): Promise<any> {
712
725
  const alias: string = tableName.toLowerCase();
@@ -746,7 +759,7 @@ export async function getManyByJoin(
746
759
  }
747
760
  }
748
761
  });
749
- const sql = `SELECT ${intoSpecToSql(intoSpec)} FROM ${tableName} ${joinSql.join('\n')} WHERE ${queryStr}`;
762
+ const sql = `SELECT ${distinct ? 'DISTINCT' : ''} ${intoSpecToSql(intoSpec)} FROM ${tableName} ${joinSql.join('\n')} WHERE ${queryStr}`;
750
763
  logger.debug(`Join Query: ${sql}`);
751
764
  const qb = getDatasourceForTransaction(ctx.txnId).getRepository(tableName).manager;
752
765
  return await qb.query(sql);
@@ -30,8 +30,15 @@ export type TableSchema = {
30
30
  columns: TableSpec;
31
31
  };
32
32
 
33
- export function asTableName(moduleName: string, entityName: string): string {
34
- return `${moduleName}_${entityName}`.toLowerCase();
33
+ export function asTableReference(moduleName: string, ref: string): string {
34
+ if (ref.indexOf('.') > 0) {
35
+ const parts = ref.split('.')
36
+ const r = `${moduleName}_${parts[0]}`.toLowerCase()
37
+ const colref = parts.slice(1).join('.')
38
+ return `"${r}"."${colref}"`;
39
+ } else {
40
+ return `${moduleName}_${ref}`.toLowerCase()
41
+ }
35
42
  }
36
43
 
37
44
  export function modulesAsDbSchema(): TableSchema[] {
@@ -46,7 +53,7 @@ export function modulesAsDbSchema(): TableSchema[] {
46
53
  const allEntries: Record[] = entities.concat(betRels) as Record[];
47
54
  allEntries.forEach((ent: Record) => {
48
55
  const tspec: TableSchema = {
49
- name: asTableName(n, ent.name),
56
+ name: asTableReference(n, ent.name),
50
57
  columns: entitySchemaToTable(ent.schema),
51
58
  };
52
59
  result.push(tspec);
@@ -85,7 +92,7 @@ function ormSchemaFromRecordSchema(moduleName: string, entry: Record, hasOwnPk?:
85
92
  const entityName = entry.name
86
93
  const scm: RecordSchema = entry.schema
87
94
  const result = new EntitySchemaOptions<any>()
88
- result.tableName = asTableName(moduleName, entityName)
95
+ result.tableName = asTableReference(moduleName, entityName)
89
96
  result.name = result.tableName
90
97
  const cols = new Map<string, any>()
91
98
  const indices = new Array<any>()