agentlang 0.10.0 → 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 +87 -44
- package/out/language/generated/ast.d.ts.map +1 -1
- package/out/language/generated/ast.js +65 -31
- 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 +337 -330
- package/out/language/generated/grammar.js.map +1 -1
- package/out/language/main.cjs +396 -361
- package/out/language/main.cjs.map +2 -2
- package/out/language/parser.d.ts +11 -1
- package/out/language/parser.d.ts.map +1 -1
- package/out/language/parser.js +74 -8
- 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/agents/common.d.ts +2 -2
- package/out/runtime/agents/common.js +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 +85 -20
- 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 +9 -8
- package/out/runtime/modules/ai.d.ts.map +1 -1
- package/out/runtime/modules/ai.js +92 -37
- 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/services/documentFetcher.js +2 -2
- package/out/runtime/services/documentFetcher.js.map +1 -1
- package/out/runtime/util.d.ts +12 -1
- package/out/runtime/util.d.ts.map +1 -1
- package/out/runtime/util.js +38 -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/out/utils/fs/index.d.ts +12 -2
- package/out/utils/fs/index.d.ts.map +1 -1
- package/out/utils/fs/index.js +27 -6
- package/out/utils/fs/index.js.map +1 -1
- package/package.json +4 -1
- package/src/api/http.ts +138 -0
- package/src/language/agentlang.langium +9 -10
- package/src/language/generated/ast.ts +98 -44
- package/src/language/generated/grammar.ts +337 -330
- package/src/language/parser.ts +87 -6
- package/src/language/syntax.ts +71 -24
- package/src/runtime/agents/common.ts +1 -1
- 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 +107 -19
- package/src/runtime/module.ts +26 -0
- package/src/runtime/modules/ai.ts +110 -54
- 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/services/documentFetcher.ts +2 -2
- package/src/runtime/util.ts +62 -2
- package/src/syntaxes/agentlang.monarch.ts +1 -1
- package/src/test-harness.ts +423 -0
- package/src/utils/fs/index.ts +30 -6
|
@@ -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
|
+
}
|
package/src/utils/fs/index.ts
CHANGED
|
@@ -4,25 +4,49 @@
|
|
|
4
4
|
|
|
5
5
|
export * from './interfaces.js';
|
|
6
6
|
import { ExtendedFileSystem } from './interfaces.js';
|
|
7
|
-
import { createNodeFS } from './node-fs.js';
|
|
8
|
-
import { createLightningFS } from './lightning-fs.js';
|
|
9
7
|
|
|
10
8
|
/**
|
|
11
9
|
* Create the appropriate filesystem implementation based on environment
|
|
10
|
+
* Uses dynamic imports to avoid bundling Node.js-specific code in browser builds
|
|
12
11
|
* @returns Promise resolving to appropriate filesystem implementation
|
|
13
12
|
*/
|
|
14
13
|
export async function createFS(options?: any): Promise<ExtendedFileSystem> {
|
|
15
14
|
// Check if we're in a browser or Node environment
|
|
16
15
|
if (typeof window === 'undefined') {
|
|
17
|
-
// Node.js environment
|
|
16
|
+
// Node.js environment - use dynamic import to avoid bundling in browser
|
|
17
|
+
const { createNodeFS } = await import('./node-fs.js');
|
|
18
18
|
return createNodeFS();
|
|
19
19
|
} else {
|
|
20
20
|
// Browser environment - use Lightning FS
|
|
21
|
+
const { createLightningFS } = await import('./lightning-fs.js');
|
|
21
22
|
return createLightningFS(options);
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
//
|
|
26
|
-
export { createNodeFS } from './node-fs.js';
|
|
27
|
-
export { createLightningFS } from './lightning-fs.js';
|
|
26
|
+
// Re-export interface types (these are safe for browser)
|
|
28
27
|
export * from './interfaces.js';
|
|
28
|
+
|
|
29
|
+
// Export factory functions that use dynamic imports internally
|
|
30
|
+
// These are async to support dynamic loading based on environment
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Create Node.js filesystem - only works in Node.js environment
|
|
34
|
+
* @returns Promise resolving to NodeFileSystem instance
|
|
35
|
+
*/
|
|
36
|
+
export async function createNodeFS(): Promise<ExtendedFileSystem> {
|
|
37
|
+
if (typeof window !== 'undefined') {
|
|
38
|
+
throw new Error('createNodeFS is only available in Node.js environment');
|
|
39
|
+
}
|
|
40
|
+
const module = await import('./node-fs.js');
|
|
41
|
+
return module.createNodeFS();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Create Lightning FS - works in browser environment
|
|
46
|
+
* @param options Optional configuration for Lightning FS
|
|
47
|
+
* @returns Promise resolving to LightningFileSystem instance
|
|
48
|
+
*/
|
|
49
|
+
export async function createLightningFS(options?: any): Promise<ExtendedFileSystem> {
|
|
50
|
+
const module = await import('./lightning-fs.js');
|
|
51
|
+
return module.createLightningFS(options);
|
|
52
|
+
}
|