agentlang 0.10.2 → 0.10.3

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 (145) hide show
  1. package/README.md +7 -14
  2. package/out/api/http.d.ts +4 -0
  3. package/out/api/http.d.ts.map +1 -1
  4. package/out/api/http.js +171 -26
  5. package/out/api/http.js.map +1 -1
  6. package/out/cli/main.d.ts.map +1 -1
  7. package/out/cli/main.js +3 -0
  8. package/out/cli/main.js.map +1 -1
  9. package/out/extension/main.cjs +250 -250
  10. package/out/extension/main.cjs.map +2 -2
  11. package/out/language/agentlang-validator.d.ts.map +1 -1
  12. package/out/language/agentlang-validator.js +4 -0
  13. package/out/language/agentlang-validator.js.map +1 -1
  14. package/out/language/error-reporter.d.ts +53 -0
  15. package/out/language/error-reporter.d.ts.map +1 -0
  16. package/out/language/error-reporter.js +879 -0
  17. package/out/language/error-reporter.js.map +1 -0
  18. package/out/language/generated/ast.d.ts +51 -1
  19. package/out/language/generated/ast.d.ts.map +1 -1
  20. package/out/language/generated/ast.js +40 -0
  21. package/out/language/generated/ast.js.map +1 -1
  22. package/out/language/generated/grammar.d.ts.map +1 -1
  23. package/out/language/generated/grammar.js +286 -190
  24. package/out/language/generated/grammar.js.map +1 -1
  25. package/out/language/main.cjs +828 -694
  26. package/out/language/main.cjs.map +3 -3
  27. package/out/language/parser.d.ts +4 -2
  28. package/out/language/parser.d.ts.map +1 -1
  29. package/out/language/parser.js +30 -97
  30. package/out/language/parser.js.map +1 -1
  31. package/out/language/syntax.d.ts +2 -0
  32. package/out/language/syntax.d.ts.map +1 -1
  33. package/out/language/syntax.js +6 -0
  34. package/out/language/syntax.js.map +1 -1
  35. package/out/runtime/api.d.ts.map +1 -1
  36. package/out/runtime/api.js +22 -0
  37. package/out/runtime/api.js.map +1 -1
  38. package/out/runtime/defs.d.ts +1 -0
  39. package/out/runtime/defs.d.ts.map +1 -1
  40. package/out/runtime/defs.js +2 -1
  41. package/out/runtime/defs.js.map +1 -1
  42. package/out/runtime/document-retriever.d.ts +24 -0
  43. package/out/runtime/document-retriever.d.ts.map +1 -0
  44. package/out/runtime/document-retriever.js +258 -0
  45. package/out/runtime/document-retriever.js.map +1 -0
  46. package/out/runtime/embeddings/chunker.d.ts +18 -0
  47. package/out/runtime/embeddings/chunker.d.ts.map +1 -1
  48. package/out/runtime/embeddings/chunker.js +47 -15
  49. package/out/runtime/embeddings/chunker.js.map +1 -1
  50. package/out/runtime/embeddings/openai.d.ts.map +1 -1
  51. package/out/runtime/embeddings/openai.js +22 -9
  52. package/out/runtime/embeddings/openai.js.map +1 -1
  53. package/out/runtime/embeddings/provider.d.ts +1 -0
  54. package/out/runtime/embeddings/provider.d.ts.map +1 -1
  55. package/out/runtime/embeddings/provider.js +20 -1
  56. package/out/runtime/embeddings/provider.js.map +1 -1
  57. package/out/runtime/integration-client.d.ts +21 -0
  58. package/out/runtime/integration-client.d.ts.map +1 -0
  59. package/out/runtime/integration-client.js +112 -0
  60. package/out/runtime/integration-client.js.map +1 -0
  61. package/out/runtime/integrations.d.ts.map +1 -1
  62. package/out/runtime/integrations.js +20 -9
  63. package/out/runtime/integrations.js.map +1 -1
  64. package/out/runtime/interpreter.d.ts +1 -0
  65. package/out/runtime/interpreter.d.ts.map +1 -1
  66. package/out/runtime/interpreter.js +152 -17
  67. package/out/runtime/interpreter.js.map +1 -1
  68. package/out/runtime/loader.d.ts.map +1 -1
  69. package/out/runtime/loader.js +70 -7
  70. package/out/runtime/loader.js.map +1 -1
  71. package/out/runtime/logger.d.ts.map +1 -1
  72. package/out/runtime/logger.js +8 -1
  73. package/out/runtime/logger.js.map +1 -1
  74. package/out/runtime/module.d.ts +10 -0
  75. package/out/runtime/module.d.ts.map +1 -1
  76. package/out/runtime/module.js +68 -3
  77. package/out/runtime/module.js.map +1 -1
  78. package/out/runtime/modules/ai.d.ts +9 -2
  79. package/out/runtime/modules/ai.d.ts.map +1 -1
  80. package/out/runtime/modules/ai.js +219 -67
  81. package/out/runtime/modules/ai.js.map +1 -1
  82. package/out/runtime/resolvers/interface.d.ts +4 -0
  83. package/out/runtime/resolvers/interface.d.ts.map +1 -1
  84. package/out/runtime/resolvers/interface.js +14 -1
  85. package/out/runtime/resolvers/interface.js.map +1 -1
  86. package/out/runtime/resolvers/sqldb/database.d.ts +2 -0
  87. package/out/runtime/resolvers/sqldb/database.d.ts.map +1 -1
  88. package/out/runtime/resolvers/sqldb/database.js +142 -126
  89. package/out/runtime/resolvers/sqldb/database.js.map +1 -1
  90. package/out/runtime/resolvers/sqldb/dbutil.d.ts.map +1 -1
  91. package/out/runtime/resolvers/sqldb/dbutil.js +8 -0
  92. package/out/runtime/resolvers/sqldb/dbutil.js.map +1 -1
  93. package/out/runtime/resolvers/sqldb/impl.d.ts +1 -0
  94. package/out/runtime/resolvers/sqldb/impl.d.ts.map +1 -1
  95. package/out/runtime/resolvers/sqldb/impl.js +7 -0
  96. package/out/runtime/resolvers/sqldb/impl.js.map +1 -1
  97. package/out/runtime/resolvers/vector/lancedb-store.d.ts +16 -0
  98. package/out/runtime/resolvers/vector/lancedb-store.d.ts.map +1 -0
  99. package/out/runtime/resolvers/vector/lancedb-store.js +159 -0
  100. package/out/runtime/resolvers/vector/lancedb-store.js.map +1 -0
  101. package/out/runtime/resolvers/vector/types.d.ts +32 -0
  102. package/out/runtime/resolvers/vector/types.d.ts.map +1 -0
  103. package/out/runtime/resolvers/vector/types.js +2 -0
  104. package/out/runtime/resolvers/vector/types.js.map +1 -0
  105. package/out/runtime/services/documentFetcher.d.ts.map +1 -1
  106. package/out/runtime/services/documentFetcher.js +21 -6
  107. package/out/runtime/services/documentFetcher.js.map +1 -1
  108. package/out/runtime/state.d.ts +19 -1
  109. package/out/runtime/state.d.ts.map +1 -1
  110. package/out/runtime/state.js +36 -1
  111. package/out/runtime/state.js.map +1 -1
  112. package/out/syntaxes/agentlang.monarch.js +1 -1
  113. package/out/syntaxes/agentlang.monarch.js.map +1 -1
  114. package/package.json +19 -19
  115. package/src/api/http.ts +197 -37
  116. package/src/cli/main.ts +3 -0
  117. package/src/language/agentlang-validator.ts +3 -0
  118. package/src/language/agentlang.langium +3 -1
  119. package/src/language/error-reporter.ts +1028 -0
  120. package/src/language/generated/ast.ts +62 -0
  121. package/src/language/generated/grammar.ts +286 -190
  122. package/src/language/parser.ts +31 -100
  123. package/src/language/syntax.ts +8 -0
  124. package/src/runtime/api.ts +31 -0
  125. package/src/runtime/defs.ts +2 -1
  126. package/src/runtime/document-retriever.ts +311 -0
  127. package/src/runtime/embeddings/chunker.ts +52 -14
  128. package/src/runtime/embeddings/openai.ts +27 -9
  129. package/src/runtime/embeddings/provider.ts +22 -1
  130. package/src/runtime/integration-client.ts +158 -0
  131. package/src/runtime/integrations.ts +20 -11
  132. package/src/runtime/interpreter.ts +142 -12
  133. package/src/runtime/loader.ts +83 -5
  134. package/src/runtime/logger.ts +12 -1
  135. package/src/runtime/module.ts +78 -3
  136. package/src/runtime/modules/ai.ts +263 -76
  137. package/src/runtime/resolvers/interface.ts +19 -1
  138. package/src/runtime/resolvers/sqldb/database.ts +158 -130
  139. package/src/runtime/resolvers/sqldb/dbutil.ts +8 -0
  140. package/src/runtime/resolvers/sqldb/impl.ts +8 -0
  141. package/src/runtime/resolvers/vector/lancedb-store.ts +187 -0
  142. package/src/runtime/resolvers/vector/types.ts +39 -0
  143. package/src/runtime/services/documentFetcher.ts +21 -6
  144. package/src/runtime/state.ts +40 -1
  145. package/src/syntaxes/agentlang.monarch.ts +1 -1
package/src/api/http.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import chalk from 'chalk';
2
- import express, { Request, Response } from 'express';
2
+ import express, { Express, Request, Response } from 'express';
3
3
  import * as path from 'path';
4
4
  import {
5
5
  getAllChildRelationships,
@@ -8,6 +8,7 @@ import {
8
8
  Instance,
9
9
  InstanceAttributes,
10
10
  isBetweenRelationship,
11
+ getRelationship,
11
12
  makeInstance,
12
13
  objectAsInstanceAttributes,
13
14
  Relationship,
@@ -58,15 +59,15 @@ import {
58
59
  createFileRecord,
59
60
  deleteFileRecord,
60
61
  } from '../runtime/modules/files.js';
62
+ import {
63
+ getOAuthAuthorizeUrl,
64
+ exchangeOAuthCode,
65
+ getIntegrationAccessToken,
66
+ } from '../runtime/integration-client.js';
61
67
  import * as XLSX from 'xlsx';
62
68
  import { objectToQueryPattern } from '../language/parser.js';
63
69
 
64
- export async function startServer(
65
- appSpec: ApplicationSpec,
66
- port: number,
67
- host?: string,
68
- config?: Config
69
- ) {
70
+ export async function createApp(appSpec: ApplicationSpec, config?: Config): Promise<Express> {
70
71
  const app = express();
71
72
  app.use(express.json());
72
73
 
@@ -207,6 +208,79 @@ export async function startServer(
207
208
  handleQueryAsXlsxPost(req, res);
208
209
  });
209
210
 
211
+ // --- Built-in OAuth proxy ---
212
+ if (config?.integrations?.oauth) {
213
+ const connections = config.integrations.connections;
214
+
215
+ // Resolve provider key (e.g. 'google_drive') to integration entity name
216
+ // (e.g. 'google-drive') by extracting from the config path.
217
+ function resolveIntegrationName(provider: string): string {
218
+ const entry = connections[provider];
219
+ if (!entry) return provider;
220
+ const configPath = typeof entry === 'string' ? entry : entry.config;
221
+ if (!configPath) return provider;
222
+ return configPath.split('/')[0];
223
+ }
224
+
225
+ app.get('/agentlang/oauth/authorize-url', async (req: Request, res: Response) => {
226
+ try {
227
+ const provider = req.query.provider as string;
228
+ const redirectUri = req.query.redirectUri as string;
229
+ if (!provider || !redirectUri) {
230
+ res.status(400).json({ error: 'provider and redirectUri query params are required' });
231
+ return;
232
+ }
233
+ if (!connections[provider]) {
234
+ res.status(400).json({ error: `Unknown provider: ${provider}` });
235
+ return;
236
+ }
237
+ const result = await getOAuthAuthorizeUrl(resolveIntegrationName(provider), redirectUri);
238
+ res.json(result);
239
+ } catch (err: any) {
240
+ logger.error(`OAuth authorize-url error: ${err}`);
241
+ res.status(500).json({ error: err.message || 'Failed to get authorization URL' });
242
+ }
243
+ });
244
+
245
+ app.post('/agentlang/oauth/exchange', async (req: Request, res: Response) => {
246
+ try {
247
+ const { provider, code, state } = req.body;
248
+ if (!provider || !code || !state) {
249
+ res.status(400).json({ error: 'provider, code, and state are required in request body' });
250
+ return;
251
+ }
252
+ if (!connections[provider]) {
253
+ res.status(400).json({ error: `Unknown provider: ${provider}` });
254
+ return;
255
+ }
256
+ const result = await exchangeOAuthCode(resolveIntegrationName(provider), code, state);
257
+ res.json(result);
258
+ } catch (err: any) {
259
+ logger.error(`OAuth exchange error: ${err}`);
260
+ res.status(500).json({ error: err.message || 'Failed to exchange authorization code' });
261
+ }
262
+ });
263
+
264
+ app.get('/agentlang/oauth/access-token', async (req: Request, res: Response) => {
265
+ try {
266
+ const provider = req.query.provider as string;
267
+ if (!provider) {
268
+ res.status(400).json({ error: 'provider query param is required' });
269
+ return;
270
+ }
271
+ if (!connections[provider]) {
272
+ res.status(400).json({ error: `Unknown provider: ${provider}` });
273
+ return;
274
+ }
275
+ const result = await getIntegrationAccessToken(resolveIntegrationName(provider));
276
+ res.json(result);
277
+ } catch (err: any) {
278
+ logger.error(`OAuth access-token error: ${err}`);
279
+ res.status(500).json({ error: err.message || 'Failed to get access token' });
280
+ }
281
+ });
282
+ }
283
+
210
284
  if (isNodeEnv && upload && uploadDir) {
211
285
  app.post('/uploadFile', upload.single('file'), (req: Request, res: Response) => {
212
286
  handleFileUpload(req, res, config);
@@ -295,19 +369,6 @@ export async function startServer(
295
369
  });
296
370
  });
297
371
 
298
- const cb = () => {
299
- console.log(
300
- chalk.green(
301
- `Application ${chalk.bold(appName + ' version ' + appVersion)} started on port ${chalk.bold(port)}`
302
- )
303
- );
304
- };
305
- if (host) {
306
- app.listen(port, host, cb);
307
- } else {
308
- app.listen(port, cb);
309
- }
310
-
311
372
  setEventEndpointsUpdater((moduleName: string) => {
312
373
  const m = fetchModule(moduleName);
313
374
  const eventNames = m.getEventNames();
@@ -329,6 +390,39 @@ export async function startServer(
329
390
  addBetweenHandlers(moduleName, n);
330
391
  });
331
392
  });
393
+
394
+ return app;
395
+ }
396
+
397
+ export async function startServer(
398
+ appSpec: ApplicationSpec,
399
+ port: number,
400
+ host?: string,
401
+ config?: Config
402
+ ) {
403
+ const app = await createApp(appSpec, config);
404
+ const appName: string = appSpec.name;
405
+ const appVersion: string = appSpec.version;
406
+
407
+ // Expose port and host on globalThis so resolver code (e.g. integration-manager's
408
+ // auth-resolver) can make self-referencing HTTP calls to the correct address.
409
+ (globalThis as any).__agentlang_port = port;
410
+ if (host) {
411
+ (globalThis as any).__agentlang_host = host;
412
+ }
413
+
414
+ const cb = () => {
415
+ console.log(
416
+ chalk.green(
417
+ `Application ${chalk.bold(appName + ' version ' + appVersion)} started on port ${chalk.bold(port)}`
418
+ )
419
+ );
420
+ };
421
+ if (host) {
422
+ app.listen(port, host, cb);
423
+ } else {
424
+ app.listen(port, cb);
425
+ }
332
426
  }
333
427
 
334
428
  function ok(res: Response) {
@@ -356,23 +450,69 @@ function internalError(res: Response) {
356
450
  };
357
451
  }
358
452
 
359
- function patternFromAttributes(
453
+ function formatAttrValue(v: any, n: string): string {
454
+ let av = isString(v) ? `"${v}"` : v;
455
+ if (av instanceof Object) {
456
+ av = JSON.stringify(av);
457
+ }
458
+ if (isPathAttribute(n)) {
459
+ av = escapeSepInPath(av);
460
+ }
461
+ return `${n} ${av}`;
462
+ }
463
+
464
+ function buildChildPattern(otherFqName: string, childObj: { [key: string]: any }): string {
465
+ const childAttrs = Object.entries(childObj)
466
+ .map(([k, v]: [string, any]) => {
467
+ let av: any;
468
+ if (isString(v)) {
469
+ av = `"${v}"`;
470
+ } else if (v instanceof Object) {
471
+ av = JSON.stringify(v);
472
+ } else {
473
+ av = v;
474
+ }
475
+ return `${k} ${av}`;
476
+ })
477
+ .join(', ');
478
+ return `{${otherFqName} {${childAttrs}}, @upsert}`;
479
+ }
480
+
481
+ export function patternFromAttributes(
360
482
  moduleName: string,
361
483
  recName: string,
362
- attrs: InstanceAttributes
484
+ attrs: InstanceAttributes,
485
+ entityFqName?: string
363
486
  ): string {
364
487
  const attrsStrs = new Array<string>();
488
+ const relPatterns = new Array<string>();
489
+
365
490
  attrs.forEach((v: any, n: string) => {
366
- let av = isString(v) ? `"${v}"` : v;
367
- if (av instanceof Object) {
368
- av = JSON.stringify(av);
369
- }
370
- if (isPathAttribute(n)) {
371
- av = escapeSepInPath(av);
491
+ if (entityFqName) {
492
+ try {
493
+ if (isBetweenRelationship(n, moduleName)) {
494
+ const rel = getRelationship(n, moduleName);
495
+ const otherFqName =
496
+ rel.getParentFqName() === entityFqName ? rel.getChildFqName() : rel.getParentFqName();
497
+ const children = Array.isArray(v) ? v : [v];
498
+ const childPatterns = children
499
+ .map((child: any) => buildChildPattern(otherFqName, child))
500
+ .join(', ');
501
+ relPatterns.push(`${n} [${childPatterns}]`);
502
+ return;
503
+ }
504
+ } catch {
505
+ // Not a relationship — fall through to normal attribute handling
506
+ }
372
507
  }
373
- attrsStrs.push(`${n} ${av}`);
508
+ attrsStrs.push(formatAttrValue(v, n));
374
509
  });
375
- return `{${moduleName}/${recName} { ${attrsStrs.join(',\n')} }}`;
510
+
511
+ const entityPat = `{${moduleName}/${recName} {${attrsStrs.join(',\n')}}`;
512
+ if (relPatterns.length > 0) {
513
+ return `${entityPat}, ${relPatterns.join(', ')}}`;
514
+ }
515
+ return `${entityPat}}`;
376
516
  }
377
517
 
378
518
  function normalizeRequestPath(path: string[], moduleName: string): string[] {
@@ -433,7 +573,7 @@ async function handleEventPost(
433
573
  eventName,
434
574
  objectAsInstanceAttributes(req.body)
435
575
  ).setAuthContext(sessionInfo);
436
- evaluate(inst, ok(res)).catch(internalError(res));
576
+ evaluate(inst).then(ok(res)).catch(internalError(res));
437
577
  } catch (err: any) {
438
578
  logger.error(err);
439
579
  res.status(500).send(err.toString());
@@ -452,9 +592,15 @@ async function handleEntityPost(
452
592
  res.status(401).send('Authorization required');
453
593
  return;
454
594
  }
595
+ const entityFqName = makeFqName(moduleName, entityName);
455
596
  const pattern = req.params.path
456
597
  ? createChildPattern(moduleName, entityName, req)
457
- : patternFromAttributes(moduleName, entityName, objectAsInstanceAttributes(req.body));
598
+ : patternFromAttributes(
599
+ moduleName,
600
+ entityName,
601
+ objectAsInstanceAttributes(req.body),
602
+ entityFqName
603
+ );
458
604
  parseAndEvaluateStatement(pattern, sessionInfo.userId).then(ok(res)).catch(internalError(res));
459
605
  } catch (err: any) {
460
606
  logger.error(err);
@@ -493,15 +639,23 @@ const joinTags = new Map()
493
639
  .set('@leftJoinOn', '@left_join')
494
640
  .set('@rightJoinOn', '@right_join');
495
641
 
496
- function objectAsAttributesPattern(entityFqName: string, obj: object): [string, boolean] {
642
+ const paginationTags = new Set(['@limit', '@offset']);
643
+
644
+ function objectAsAttributesPattern(entityFqName: string, obj: object): [string, boolean, string] {
497
645
  const attrs = new Array<string>();
498
646
  let joinType: string | undefined;
499
647
  let joinOnAttr: string | undefined;
648
+ const paginationParts = new Array<string>();
500
649
  Object.keys(obj).forEach(key => {
501
650
  const s: string = obj[key as keyof object];
502
651
  if (joinTags.has(key)) {
503
652
  joinType = joinTags.get(key);
504
653
  joinOnAttr = s;
654
+ } else if (paginationTags.has(key)) {
655
+ const n = parseInt(s, 10);
656
+ if (!Number.isNaN(n) && Number.isInteger(n) && n >= 0) {
657
+ paginationParts.push(`${key}(${n})`);
658
+ }
505
659
  } else {
506
660
  let v = s;
507
661
  if (!s.startsWith('"')) {
@@ -529,12 +683,18 @@ function objectAsAttributesPattern(entityFqName: string, obj: object): [string,
529
683
  });
530
684
  const intoPat = `@into {${intoSpec.join(', ')}}`;
531
685
  joinOnAttr = reverseJoin ? splitRefs(joinOnAttr)[1] : joinOnAttr;
686
+ const paginationStr = paginationParts.length > 0 ? `,\n${paginationParts.join(',\n')}` : '';
532
687
  return [
533
- `${pat},\n${joinType} ${targetEntity} {${targetAttr}? ${entityFqName}.${joinOnAttr}}, \n${intoPat}`,
688
+ `${pat},\n${joinType} ${targetEntity} {${targetAttr}? ${entityFqName}.${joinOnAttr}}, \n${intoPat}${paginationStr}`,
534
689
  hasQueryAttrs,
690
+ '',
535
691
  ];
536
692
  } else {
537
- return [pat, hasQueryAttrs];
693
+ return [
694
+ pat,
695
+ hasQueryAttrs,
696
+ paginationParts.length > 0 ? `,\n${paginationParts.join(',\n')}` : '',
697
+ ];
538
698
  }
539
699
  }
540
700
 
@@ -547,9 +707,9 @@ function queryPatternFromPath(path: string, req: Request): string {
547
707
  const fqName = `${moduleName}/${entityName}`;
548
708
  if (parts.length == 2 && id === undefined) {
549
709
  if (req.query && Object.keys(req.query).length > 0) {
550
- const [pat, hasQueryAttrs] = objectAsAttributesPattern(fqName, req.query);
710
+ const [pat, hasQueryAttrs, paginationPat] = objectAsAttributesPattern(fqName, req.query);
551
711
  const n = hasQueryAttrs ? fqName : `${fqName}?`;
552
- return `{${n} ${pat}}`;
712
+ return `{${n} ${pat}${paginationPat}}`;
553
713
  } else {
554
714
  return `{${fqName}? {}}`;
555
715
  }
package/src/cli/main.ts CHANGED
@@ -21,6 +21,7 @@ import { Instance, Module } from '../runtime/module.js';
21
21
  import { ModuleDefinition } from '../language/generated/ast.js';
22
22
  import { Config } from '../runtime/state.js';
23
23
  import { prepareIntegrations } from '../runtime/integrations.js';
24
+ import { configureIntegrationClient } from '../runtime/integration-client.js';
24
25
  import { isExecGraphEnabled, isNodeEnv } from '../utils/runtime.js';
25
26
  import { OpenAPIClientAxios } from 'openapi-client-axios';
26
27
  import { registerOpenApiModule } from '../runtime/openapi.js';
@@ -239,6 +240,8 @@ export const runModule = async (fileName: string, releaseDb: boolean = false): P
239
240
  config.integrations.password,
240
241
  config.integrations.connections
241
242
  );
243
+ // Configure the thin HTTP client to talk to integration-manager for auth
244
+ configureIntegrationClient(config.integrations.host);
242
245
  }
243
246
  if (config.openapi) {
244
247
  await loadOpenApiSpec(config.openapi);
@@ -25,6 +25,9 @@ export class AgentlangValidator {
25
25
  if (def.$type === 'PublicEventDefinition') {
26
26
  def = def.def;
27
27
  }
28
+ if (!def?.schema?.attributes) {
29
+ return;
30
+ }
28
31
  def.schema.attributes.forEach((a: AttributeDefinition) => {
29
32
  if (reported.has(a.name)) {
30
33
  accept('error', `'${def.name} " - attribute has non-unique name '${a.name}'.`, {
@@ -131,7 +131,7 @@ CrudMap: '{' (name=QueryId (':')? '{''}'
131
131
  '}';
132
132
 
133
133
  QueryOption: join=JoinSpec | into=SelectIntoSpec | where=WhereSpec | groupByClause=GroupByClause | orderByClause=OrderByClause
134
- | upsert='@upsert' | distinct='@distinct';
134
+ | limitClause=LimitClause | offsetClause=OffsetClause | upsert='@upsert' | distinct='@distinct';
135
135
 
136
136
  CrudMapBody: '{' (attributes+=SetAttribute (',' attributes+=SetAttribute)*)+ properties+=PropertyDefinition* '}';
137
137
 
@@ -148,6 +148,8 @@ WhereSpecClause: lhs=QueryId op=SqlComparisonOpr? rhs=Expr;
148
148
 
149
149
  GroupByClause: '@groupBy' ('(' (colNames+=QualifiedName (',' colNames+=QualifiedName)*)+ ')');
150
150
  OrderByClause: '@orderBy' ('(' (colNames+=QualifiedName (',' colNames+=QualifiedName)*)+ ')') (order=('@asc' | '@desc'))?;
151
+ LimitClause: '@limit' '(' value=INT ')';
152
+ OffsetClause: '@offset' '(' value=INT ')';
151
153
 
152
154
  FullTextSearch: '{' name=QueryId query=Literal options=MapLiteral? '}';
153
155