agentlang 0.10.1 → 0.10.2
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/out/api/http.d.ts.map +1 -1
- package/out/api/http.js +136 -0
- package/out/api/http.js.map +1 -1
- package/out/language/generated/ast.d.ts +27 -2
- package/out/language/generated/ast.d.ts.map +1 -1
- package/out/language/generated/ast.js +20 -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 +246 -206
- package/out/language/generated/grammar.js.map +1 -1
- package/out/language/main.cjs +263 -206
- package/out/language/main.cjs.map +2 -2
- package/out/language/parser.d.ts.map +1 -1
- package/out/language/parser.js +28 -2
- package/out/language/parser.js.map +1 -1
- package/out/language/syntax.d.ts +14 -0
- package/out/language/syntax.d.ts.map +1 -1
- package/out/language/syntax.js +60 -27
- package/out/language/syntax.js.map +1 -1
- package/out/runtime/api.d.ts +2 -0
- package/out/runtime/api.d.ts.map +1 -1
- package/out/runtime/api.js +3 -0
- package/out/runtime/api.js.map +1 -1
- package/out/runtime/datefns.d.ts +34 -0
- package/out/runtime/datefns.d.ts.map +1 -0
- package/out/runtime/datefns.js +82 -0
- package/out/runtime/datefns.js.map +1 -0
- package/out/runtime/exec-graph.d.ts.map +1 -1
- package/out/runtime/exec-graph.js +22 -3
- package/out/runtime/exec-graph.js.map +1 -1
- package/out/runtime/interpreter.d.ts +9 -0
- package/out/runtime/interpreter.d.ts.map +1 -1
- package/out/runtime/interpreter.js +71 -7
- package/out/runtime/interpreter.js.map +1 -1
- package/out/runtime/module.d.ts +8 -0
- package/out/runtime/module.d.ts.map +1 -1
- package/out/runtime/module.js +23 -0
- package/out/runtime/module.js.map +1 -1
- package/out/runtime/modules/ai.d.ts +7 -3
- package/out/runtime/modules/ai.d.ts.map +1 -1
- package/out/runtime/modules/ai.js +67 -21
- package/out/runtime/modules/ai.js.map +1 -1
- package/out/runtime/modules/core.d.ts.map +1 -1
- package/out/runtime/modules/core.js +5 -1
- package/out/runtime/modules/core.js.map +1 -1
- package/out/runtime/monitor.d.ts +6 -0
- package/out/runtime/monitor.d.ts.map +1 -1
- package/out/runtime/monitor.js +21 -1
- package/out/runtime/monitor.js.map +1 -1
- package/out/runtime/relgraph.d.ts.map +1 -1
- package/out/runtime/relgraph.js +7 -3
- package/out/runtime/relgraph.js.map +1 -1
- package/out/runtime/resolvers/interface.d.ts +3 -2
- package/out/runtime/resolvers/interface.d.ts.map +1 -1
- package/out/runtime/resolvers/interface.js +3 -2
- package/out/runtime/resolvers/interface.js.map +1 -1
- package/out/runtime/resolvers/sqldb/dbutil.d.ts.map +1 -1
- package/out/runtime/resolvers/sqldb/dbutil.js +17 -4
- package/out/runtime/resolvers/sqldb/dbutil.js.map +1 -1
- package/out/runtime/resolvers/sqldb/impl.d.ts +1 -1
- package/out/runtime/resolvers/sqldb/impl.d.ts.map +1 -1
- package/out/runtime/resolvers/sqldb/impl.js +17 -7
- package/out/runtime/resolvers/sqldb/impl.js.map +1 -1
- package/out/runtime/util.d.ts +3 -2
- package/out/runtime/util.d.ts.map +1 -1
- package/out/runtime/util.js +13 -2
- package/out/runtime/util.js.map +1 -1
- package/out/syntaxes/agentlang.monarch.js +1 -1
- package/out/syntaxes/agentlang.monarch.js.map +1 -1
- package/out/test-harness.d.ts +36 -0
- package/out/test-harness.d.ts.map +1 -0
- package/out/test-harness.js +341 -0
- package/out/test-harness.js.map +1 -0
- package/package.json +4 -1
- package/src/api/http.ts +138 -0
- package/src/language/agentlang.langium +3 -1
- package/src/language/generated/ast.ts +32 -1
- package/src/language/generated/grammar.ts +246 -206
- package/src/language/parser.ts +33 -1
- package/src/language/syntax.ts +71 -24
- package/src/runtime/api.ts +5 -0
- package/src/runtime/datefns.ts +112 -0
- package/src/runtime/exec-graph.ts +23 -2
- package/src/runtime/interpreter.ts +82 -6
- package/src/runtime/module.ts +26 -0
- package/src/runtime/modules/ai.ts +78 -31
- package/src/runtime/modules/core.ts +5 -1
- package/src/runtime/monitor.ts +27 -1
- package/src/runtime/relgraph.ts +7 -3
- package/src/runtime/resolvers/interface.ts +4 -2
- package/src/runtime/resolvers/sqldb/dbutil.ts +20 -6
- package/src/runtime/resolvers/sqldb/impl.ts +17 -7
- package/src/runtime/util.ts +19 -2
- package/src/syntaxes/agentlang.monarch.ts +1 -1
- package/src/test-harness.ts +423 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
import { parseAndEvaluateStatement } from './runtime/interpreter.js';
|
|
2
|
+
import { fetchModule, Instance, Module, Record } from './runtime/module.js';
|
|
3
|
+
|
|
4
|
+
type AttributeMap = { [key: string]: any };
|
|
5
|
+
|
|
6
|
+
// A related entity within a relationship pattern
|
|
7
|
+
export interface RelEntity {
|
|
8
|
+
entity: string; // entity name, e.g. "Post"
|
|
9
|
+
attrs: AttributeMap; // entity attributes
|
|
10
|
+
query?: boolean; // true = query mode ({Entity? {}} or {Entity {k? v}})
|
|
11
|
+
rels?: RelSpec; // nested relationships
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Maps relationship names to related entity specs
|
|
15
|
+
export type RelSpec = {
|
|
16
|
+
[relName: string]: RelEntity | RelEntity[];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function formatValue(value: any): string {
|
|
20
|
+
if (typeof value === 'string') return `"${value}"`;
|
|
21
|
+
if (Array.isArray(value)) return '[' + value.map(formatValue).join(', ') + ']';
|
|
22
|
+
return String(value);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function buildEntityPattern(
|
|
26
|
+
fqName: string,
|
|
27
|
+
attrs: AttributeMap,
|
|
28
|
+
queryMode: boolean | string = false
|
|
29
|
+
): string {
|
|
30
|
+
const parts: string[] = [];
|
|
31
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
32
|
+
if (queryMode === true) {
|
|
33
|
+
parts.push(`${key}? ${formatValue(value)}`);
|
|
34
|
+
} else if (typeof queryMode === 'string' && key === queryMode) {
|
|
35
|
+
parts.push(`${key}? ${formatValue(value)}`);
|
|
36
|
+
} else {
|
|
37
|
+
parts.push(`${key} ${formatValue(value)}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return `{${fqName} {${parts.join(', ')}}}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function buildRelEntityPattern(moduleName: string, spec: RelEntity): string {
|
|
44
|
+
const fqName = `${moduleName}/${spec.entity}`;
|
|
45
|
+
let pattern: string;
|
|
46
|
+
if (spec.query && Object.keys(spec.attrs).length === 0) {
|
|
47
|
+
pattern = `{${fqName}? {}}`;
|
|
48
|
+
} else {
|
|
49
|
+
pattern = buildEntityPattern(fqName, spec.attrs, spec.query === true);
|
|
50
|
+
}
|
|
51
|
+
if (spec.rels) {
|
|
52
|
+
// Strip trailing } and append nested rels
|
|
53
|
+
const inner = pattern.slice(0, -1);
|
|
54
|
+
const relParts = buildRelParts(moduleName, spec.rels);
|
|
55
|
+
pattern = `${inner},\n${relParts}}`;
|
|
56
|
+
}
|
|
57
|
+
return pattern;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function buildRelParts(moduleName: string, rels: RelSpec): string {
|
|
61
|
+
const parts: string[] = [];
|
|
62
|
+
for (const [relName, relEntities] of Object.entries(rels)) {
|
|
63
|
+
if (Array.isArray(relEntities)) {
|
|
64
|
+
const entityPatterns = relEntities.map(e => buildRelEntityPattern(moduleName, e));
|
|
65
|
+
parts.push(`${relName} [${entityPatterns.join(', ')}]`);
|
|
66
|
+
} else {
|
|
67
|
+
parts.push(`${relName} ${buildRelEntityPattern(moduleName, relEntities)}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return parts.join(',\n');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function buildPattern(
|
|
74
|
+
fqName: string,
|
|
75
|
+
attrs: AttributeMap,
|
|
76
|
+
queryMode: boolean | string = false,
|
|
77
|
+
moduleName?: string,
|
|
78
|
+
rels?: RelSpec
|
|
79
|
+
): string {
|
|
80
|
+
const parts: string[] = [];
|
|
81
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
82
|
+
if (queryMode === true) {
|
|
83
|
+
parts.push(`${key}? ${formatValue(value)}`);
|
|
84
|
+
} else if (typeof queryMode === 'string' && key === queryMode) {
|
|
85
|
+
parts.push(`${key}? ${formatValue(value)}`);
|
|
86
|
+
} else {
|
|
87
|
+
parts.push(`${key} ${formatValue(value)}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
let pattern = `{${fqName} {${parts.join(', ')}}}`;
|
|
91
|
+
if (rels && moduleName) {
|
|
92
|
+
// Strip outer closing } and append relationship parts
|
|
93
|
+
pattern = pattern.slice(0, -1);
|
|
94
|
+
const relParts = buildRelParts(moduleName, rels);
|
|
95
|
+
pattern = `${pattern},\n${relParts}}`;
|
|
96
|
+
}
|
|
97
|
+
return pattern;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function toObject(result: any): any {
|
|
101
|
+
if (result instanceof Instance) {
|
|
102
|
+
const obj: any = result.userAttributesAsObject();
|
|
103
|
+
if (result.relatedInstances) {
|
|
104
|
+
result.relatedInstances.forEach((insts: Instance[], relName: string) => {
|
|
105
|
+
obj[relName] = insts.map(toObject);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return obj;
|
|
109
|
+
}
|
|
110
|
+
if (Array.isArray(result)) {
|
|
111
|
+
return result.map(toObject);
|
|
112
|
+
}
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function evalPattern(pattern: string): Promise<any> {
|
|
117
|
+
try {
|
|
118
|
+
const result = await parseAndEvaluateStatement(pattern);
|
|
119
|
+
return toObject(result);
|
|
120
|
+
} catch (err: any) {
|
|
121
|
+
const message = err?.message ?? String(err);
|
|
122
|
+
throw new Error(`Error evaluating pattern: ${pattern}\n ${message}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function is(condition: boolean, message?: string): void {
|
|
127
|
+
if (!condition) {
|
|
128
|
+
const err = new Error(message ?? 'Assertion failed');
|
|
129
|
+
if (err.stack) {
|
|
130
|
+
const lines = err.stack.split('\n');
|
|
131
|
+
const callerLine = lines.find(l => !l.includes('test-harness') && l.includes('.test.'));
|
|
132
|
+
if (callerLine) {
|
|
133
|
+
err.message += `\n at ${callerLine.trim()}`;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
throw err;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface AssertionResult {
|
|
141
|
+
passed: boolean;
|
|
142
|
+
message: string;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface TestResult {
|
|
146
|
+
passed: boolean;
|
|
147
|
+
total: number;
|
|
148
|
+
failures: number;
|
|
149
|
+
results: AssertionResult[];
|
|
150
|
+
error?: { message: string; pattern?: string };
|
|
151
|
+
duration: number;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
class TestAssertionError extends Error {
|
|
155
|
+
constructor(message: string) {
|
|
156
|
+
super(message);
|
|
157
|
+
this.name = 'TestAssertionError';
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
|
|
162
|
+
|
|
163
|
+
export async function runTests(
|
|
164
|
+
moduleDefinition: string,
|
|
165
|
+
testScript: string,
|
|
166
|
+
initModule: (moduleName: string, code: string) => Promise<void>
|
|
167
|
+
): Promise<TestResult> {
|
|
168
|
+
const start = Date.now();
|
|
169
|
+
const assertions: AssertionResult[] = [];
|
|
170
|
+
let testError: { message: string; pattern?: string } | undefined;
|
|
171
|
+
|
|
172
|
+
const match = moduleDefinition.match(/^\s*module\s+([\w.]+)/);
|
|
173
|
+
if (!match) {
|
|
174
|
+
return {
|
|
175
|
+
passed: false,
|
|
176
|
+
total: 0,
|
|
177
|
+
failures: 1,
|
|
178
|
+
results: [],
|
|
179
|
+
error: { message: 'Invalid module definition: missing "module <name>" declaration' },
|
|
180
|
+
duration: Date.now() - start,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
const moduleName = match[1];
|
|
184
|
+
const moduleBody = moduleDefinition.substring(match[0].length);
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
const proxy = await testModule(moduleName, moduleBody, initModule);
|
|
188
|
+
|
|
189
|
+
const trackingIs = (condition: boolean, message?: string) => {
|
|
190
|
+
if (condition) {
|
|
191
|
+
assertions.push({ passed: true, message: message ?? 'OK' });
|
|
192
|
+
} else {
|
|
193
|
+
const failMsg = message ?? 'Assertion failed';
|
|
194
|
+
assertions.push({ passed: false, message: failMsg });
|
|
195
|
+
throw new TestAssertionError(failMsg);
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const paramNames = Object.keys(proxy);
|
|
200
|
+
const paramValues = Object.values(proxy);
|
|
201
|
+
const testFn = new AsyncFunction(...paramNames, 'is', testScript);
|
|
202
|
+
await testFn(...paramValues, trackingIs);
|
|
203
|
+
} catch (err: any) {
|
|
204
|
+
if (!(err instanceof TestAssertionError)) {
|
|
205
|
+
const message = err?.message ?? String(err);
|
|
206
|
+
const patternMatch = message.match(/Error evaluating pattern: (.+)\n/);
|
|
207
|
+
testError = {
|
|
208
|
+
message,
|
|
209
|
+
pattern: patternMatch ? patternMatch[1] : undefined,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const failures = assertions.filter(a => !a.passed).length;
|
|
215
|
+
return {
|
|
216
|
+
passed: !testError && failures === 0,
|
|
217
|
+
total: assertions.length,
|
|
218
|
+
failures,
|
|
219
|
+
results: assertions,
|
|
220
|
+
error: testError,
|
|
221
|
+
duration: Date.now() - start,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// --- Pattern parsing for runPatternTests ---
|
|
226
|
+
|
|
227
|
+
interface ParsedAttr {
|
|
228
|
+
name: string;
|
|
229
|
+
query: boolean;
|
|
230
|
+
value: string;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
interface ParsedPattern {
|
|
234
|
+
isDelete: boolean;
|
|
235
|
+
fqName: string;
|
|
236
|
+
entityQuery: boolean; // {Entity? {}}
|
|
237
|
+
attrs: ParsedAttr[];
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function splitTopLevel(s: string): string[] {
|
|
241
|
+
const parts: string[] = [];
|
|
242
|
+
let current = '';
|
|
243
|
+
let depth = 0;
|
|
244
|
+
let inString = false;
|
|
245
|
+
for (let i = 0; i < s.length; i++) {
|
|
246
|
+
const ch = s[i];
|
|
247
|
+
if (ch === '"' && (i === 0 || s[i - 1] !== '\\')) {
|
|
248
|
+
inString = !inString;
|
|
249
|
+
}
|
|
250
|
+
if (!inString) {
|
|
251
|
+
if (ch === '[' || ch === '{') depth++;
|
|
252
|
+
else if (ch === ']' || ch === '}') depth--;
|
|
253
|
+
else if (ch === ',' && depth === 0) {
|
|
254
|
+
parts.push(current.trim());
|
|
255
|
+
current = '';
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
current += ch;
|
|
260
|
+
}
|
|
261
|
+
if (current.trim()) parts.push(current.trim());
|
|
262
|
+
return parts;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function parseAttr(s: string): ParsedAttr {
|
|
266
|
+
const m = s.match(/^(\w+)(\?)?\s+([\s\S]+)$/);
|
|
267
|
+
if (!m) throw new Error(`Cannot parse attribute: ${s}`);
|
|
268
|
+
return { name: m[1], query: !!m[2], value: m[3].trim() };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function parseCrudPattern(s: string): ParsedPattern | null {
|
|
272
|
+
let input = s.trim();
|
|
273
|
+
|
|
274
|
+
const isDelete = input.startsWith('delete');
|
|
275
|
+
if (isDelete) input = input.replace(/^delete\s+/, '');
|
|
276
|
+
|
|
277
|
+
if (input[0] !== '{') return null;
|
|
278
|
+
|
|
279
|
+
// Skip outer opening {
|
|
280
|
+
let i = 1;
|
|
281
|
+
while (i < input.length && input[i] === ' ') i++;
|
|
282
|
+
|
|
283
|
+
// Read fqName
|
|
284
|
+
let fqName = '';
|
|
285
|
+
while (i < input.length && !/[\s{?]/.test(input[i])) {
|
|
286
|
+
fqName += input[i];
|
|
287
|
+
i++;
|
|
288
|
+
}
|
|
289
|
+
if (!fqName.includes('/')) return null;
|
|
290
|
+
|
|
291
|
+
// Check for entity-level query ?
|
|
292
|
+
while (i < input.length && input[i] === ' ') i++;
|
|
293
|
+
let entityQuery = false;
|
|
294
|
+
if (input[i] === '?') {
|
|
295
|
+
entityQuery = true;
|
|
296
|
+
i++;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Find inner {attrs} - match braces
|
|
300
|
+
while (i < input.length && input[i] !== '{') i++;
|
|
301
|
+
if (i >= input.length) return null;
|
|
302
|
+
|
|
303
|
+
let depth = 0;
|
|
304
|
+
const start = i;
|
|
305
|
+
while (i < input.length) {
|
|
306
|
+
if (input[i] === '"') {
|
|
307
|
+
i++;
|
|
308
|
+
while (i < input.length && input[i] !== '"') i++;
|
|
309
|
+
} else if (input[i] === '{') {
|
|
310
|
+
depth++;
|
|
311
|
+
} else if (input[i] === '}') {
|
|
312
|
+
depth--;
|
|
313
|
+
if (depth === 0) break;
|
|
314
|
+
}
|
|
315
|
+
i++;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const attrsStr = input.substring(start + 1, i).trim();
|
|
319
|
+
const attrs = attrsStr ? splitTopLevel(attrsStr).map(parseAttr) : [];
|
|
320
|
+
|
|
321
|
+
return { isDelete, fqName, entityQuery, attrs };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function patternToScript(pattern: string, eventNames: Set<string>): string {
|
|
325
|
+
const trimmed = pattern.trim();
|
|
326
|
+
if (trimmed.startsWith('is(')) return trimmed;
|
|
327
|
+
|
|
328
|
+
const parsed = parseCrudPattern(trimmed);
|
|
329
|
+
if (!parsed) throw new Error(`Cannot parse pattern: ${trimmed}`);
|
|
330
|
+
|
|
331
|
+
const entryName = parsed.fqName.split('/')[1];
|
|
332
|
+
const jsObj = parsed.attrs.map(a => `${a.name}: ${a.value}`).join(', ');
|
|
333
|
+
|
|
334
|
+
if (parsed.isDelete) {
|
|
335
|
+
return `result = await delete_${entryName}({${jsObj}});`;
|
|
336
|
+
}
|
|
337
|
+
if (eventNames.has(entryName)) {
|
|
338
|
+
return `result = await ${entryName}({${jsObj}});`;
|
|
339
|
+
}
|
|
340
|
+
if (parsed.entityQuery || parsed.attrs.every(a => a.query)) {
|
|
341
|
+
return `result = (await get_${entryName}({${jsObj}}))[0];`;
|
|
342
|
+
}
|
|
343
|
+
if (parsed.attrs.some(a => a.query)) {
|
|
344
|
+
return `result = await update_${entryName}({${jsObj}});`;
|
|
345
|
+
}
|
|
346
|
+
return `result = await create_${entryName}({${jsObj}});`;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export async function runPatternTests(
|
|
350
|
+
moduleDefinition: string,
|
|
351
|
+
patterns: string[],
|
|
352
|
+
initModule: (moduleName: string, code: string) => Promise<void>
|
|
353
|
+
): Promise<TestResult> {
|
|
354
|
+
const eventNames = new Set<string>();
|
|
355
|
+
for (const m of moduleDefinition.matchAll(/\bevent\s+(\w+)\s*\{/g)) {
|
|
356
|
+
eventNames.add(m[1]);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const lines: string[] = ['let result;'];
|
|
360
|
+
for (const pat of patterns) {
|
|
361
|
+
lines.push(patternToScript(pat, eventNames));
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return runTests(moduleDefinition, lines.join('\n'), initModule);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export interface TestModuleProxy {
|
|
368
|
+
[key: string]: (attrs: AttributeMap, rels?: RelSpec) => Promise<any>;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export async function testModule(
|
|
372
|
+
moduleName: string,
|
|
373
|
+
code: string,
|
|
374
|
+
initModule: (moduleName: string, code: string) => Promise<void>
|
|
375
|
+
): Promise<TestModuleProxy> {
|
|
376
|
+
await initModule(moduleName, code);
|
|
377
|
+
const mod: Module = fetchModule(moduleName);
|
|
378
|
+
const proxy: TestModuleProxy = {};
|
|
379
|
+
|
|
380
|
+
for (const entityName of mod.getEntityNames()) {
|
|
381
|
+
const fqName = `${moduleName}/${entityName}`;
|
|
382
|
+
const record = mod.getEntry(entityName) as Record;
|
|
383
|
+
const idAttrName = record.getIdAttributeName();
|
|
384
|
+
|
|
385
|
+
proxy[`create_${entityName}`] = async (attrs: AttributeMap, rels?: RelSpec) => {
|
|
386
|
+
const pattern = buildPattern(fqName, attrs, false, moduleName, rels);
|
|
387
|
+
return await evalPattern(pattern);
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
proxy[`get_${entityName}`] = async (attrs: AttributeMap, rels?: RelSpec) => {
|
|
391
|
+
const pattern = buildPattern(fqName, attrs, true, moduleName, rels);
|
|
392
|
+
return await evalPattern(pattern);
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
proxy[`update_${entityName}`] = async (attrs: AttributeMap, rels?: RelSpec) => {
|
|
396
|
+
if (!idAttrName) {
|
|
397
|
+
throw new Error(`Cannot update ${fqName}: no @id attribute defined`);
|
|
398
|
+
}
|
|
399
|
+
if (!(idAttrName in attrs)) {
|
|
400
|
+
throw new Error(`Cannot update ${fqName}: @id attribute '${idAttrName}' not provided`);
|
|
401
|
+
}
|
|
402
|
+
const pattern = buildPattern(fqName, attrs, idAttrName, moduleName, rels);
|
|
403
|
+
return await evalPattern(pattern);
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
proxy[`delete_${entityName}`] = async (attrs: AttributeMap) => {
|
|
407
|
+
const pattern = `delete ${buildPattern(fqName, attrs, true)}`;
|
|
408
|
+
return await evalPattern(pattern);
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
for (const eventName of mod.getEventNames()) {
|
|
413
|
+
if (mod.isPrePostEvent(eventName)) continue;
|
|
414
|
+
const fqName = `${moduleName}/${eventName}`;
|
|
415
|
+
|
|
416
|
+
proxy[eventName] = async (attrs: AttributeMap) => {
|
|
417
|
+
const pattern = buildPattern(fqName, attrs);
|
|
418
|
+
return await evalPattern(pattern);
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return proxy;
|
|
423
|
+
}
|