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.
- package/README.md +2 -19
- package/out/language/generated/ast.d.ts +2 -1
- package/out/language/generated/ast.d.ts.map +1 -1
- package/out/language/generated/ast.js +1 -0
- package/out/language/generated/ast.js.map +1 -1
- package/out/language/generated/grammar.d.ts.map +1 -1
- package/out/language/generated/grammar.js +20 -1
- package/out/language/generated/grammar.js.map +1 -1
- package/out/language/main.cjs +21 -1
- package/out/language/main.cjs.map +2 -2
- package/out/language/syntax.js +1 -1
- package/out/language/syntax.js.map +1 -1
- package/out/runtime/agents/impl/anthropic.d.ts +26 -0
- package/out/runtime/agents/impl/anthropic.d.ts.map +1 -0
- package/out/runtime/agents/impl/anthropic.js +106 -0
- package/out/runtime/agents/impl/anthropic.js.map +1 -0
- package/out/runtime/agents/impl/openai.d.ts +22 -0
- package/out/runtime/agents/impl/openai.d.ts.map +1 -1
- package/out/runtime/agents/impl/openai.js +83 -4
- package/out/runtime/agents/impl/openai.js.map +1 -1
- package/out/runtime/agents/registry.d.ts.map +1 -1
- package/out/runtime/agents/registry.js +49 -5
- package/out/runtime/agents/registry.js.map +1 -1
- package/out/runtime/interpreter.d.ts +1 -0
- package/out/runtime/interpreter.d.ts.map +1 -1
- package/out/runtime/interpreter.js +13 -7
- package/out/runtime/interpreter.js.map +1 -1
- package/out/runtime/jsmodules.js +1 -1
- package/out/runtime/jsmodules.js.map +1 -1
- package/out/runtime/resolvers/interface.d.ts +2 -2
- package/out/runtime/resolvers/interface.d.ts.map +1 -1
- package/out/runtime/resolvers/interface.js +4 -4
- package/out/runtime/resolvers/interface.js.map +1 -1
- package/out/runtime/resolvers/sqldb/database.d.ts +3 -3
- package/out/runtime/resolvers/sqldb/database.d.ts.map +1 -1
- package/out/runtime/resolvers/sqldb/database.js +19 -10
- package/out/runtime/resolvers/sqldb/database.js.map +1 -1
- package/out/runtime/resolvers/sqldb/dbutil.d.ts +1 -1
- package/out/runtime/resolvers/sqldb/dbutil.d.ts.map +1 -1
- package/out/runtime/resolvers/sqldb/dbutil.js +12 -4
- package/out/runtime/resolvers/sqldb/dbutil.js.map +1 -1
- package/out/runtime/resolvers/sqldb/impl.d.ts +2 -2
- package/out/runtime/resolvers/sqldb/impl.d.ts.map +1 -1
- package/out/runtime/resolvers/sqldb/impl.js +17 -17
- package/out/runtime/resolvers/sqldb/impl.js.map +1 -1
- package/out/syntaxes/agentlang.monarch.js +1 -1
- package/out/syntaxes/agentlang.monarch.js.map +1 -1
- package/package.json +8 -10
- package/src/language/agentlang.langium +2 -1
- package/src/language/generated/ast.ts +4 -1
- package/src/language/generated/grammar.ts +20 -1
- package/src/language/syntax.ts +1 -1
- package/src/runtime/agents/impl/anthropic.ts +166 -0
- package/src/runtime/agents/impl/openai.ts +124 -4
- package/src/runtime/agents/registry.ts +51 -4
- package/src/runtime/interpreter.ts +14 -6
- package/src/runtime/jsmodules.ts +1 -1
- package/src/runtime/resolvers/interface.ts +9 -4
- package/src/runtime/resolvers/sqldb/database.ts +21 -8
- package/src/runtime/resolvers/sqldb/dbutil.ts +11 -4
- package/src/runtime/resolvers/sqldb/impl.ts +19 -16
- package/src/syntaxes/agentlang.monarch.ts +1 -1
- package/out/cli/docs.d.ts +0 -2
- package/out/cli/docs.d.ts.map +0 -1
- package/out/cli/docs.js +0 -236
- package/out/cli/docs.js.map +0 -1
- package/out/cli/openapi-docs.yml +0 -695
- package/out/index.d.ts +0 -19
- package/out/index.d.ts.map +0 -1
- package/out/index.js +0 -25
- 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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
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
|
|
7
|
-
|
|
8
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
package/src/runtime/jsmodules.ts
CHANGED
|
@@ -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('.'))
|
|
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(
|
|
104
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
?
|
|
606
|
-
:
|
|
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
|
-
?
|
|
623
|
-
:
|
|
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 ?
|
|
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
|
|
34
|
-
|
|
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:
|
|
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 =
|
|
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>()
|