@open-rap/runtime 0.0.0-stage → 0.1.1

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 (144) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +14 -2
  3. package/dist/abap/background.d.ts +14 -0
  4. package/dist/abap/background.js +130 -0
  5. package/dist/abap/background.js.map +1 -0
  6. package/dist/abap/bridge.d.ts +62 -0
  7. package/dist/abap/bridge.js +402 -0
  8. package/dist/abap/bridge.js.map +1 -0
  9. package/dist/abap/convert.d.ts +26 -0
  10. package/dist/abap/convert.js +197 -0
  11. package/dist/abap/convert.js.map +1 -0
  12. package/dist/abap/http.d.ts +34 -0
  13. package/dist/abap/http.js +133 -0
  14. package/dist/abap/http.js.map +1 -0
  15. package/dist/abap/mail.d.ts +10 -0
  16. package/dist/abap/mail.js +83 -0
  17. package/dist/abap/mail.js.map +1 -0
  18. package/dist/abap/odata-client.d.ts +33 -0
  19. package/dist/abap/odata-client.js +576 -0
  20. package/dist/abap/odata-client.js.map +1 -0
  21. package/dist/abap/sql.d.ts +92 -0
  22. package/dist/abap/sql.js +261 -0
  23. package/dist/abap/sql.js.map +1 -0
  24. package/dist/abap/utils.d.ts +41 -0
  25. package/dist/abap/utils.js +976 -0
  26. package/dist/abap/utils.js.map +1 -0
  27. package/dist/app.d.ts +53 -0
  28. package/dist/app.js +169 -0
  29. package/dist/app.js.map +1 -0
  30. package/dist/auth/auth.d.ts +68 -0
  31. package/dist/auth/auth.js +140 -0
  32. package/dist/auth/auth.js.map +1 -0
  33. package/dist/auth/authorizations.d.ts +39 -0
  34. package/dist/auth/authorizations.js +103 -0
  35. package/dist/auth/authorizations.js.map +1 -0
  36. package/dist/auth/business-users.d.ts +15 -0
  37. package/dist/auth/business-users.js +36 -0
  38. package/dist/auth/business-users.js.map +1 -0
  39. package/dist/auth/dev-oauth.d.ts +38 -0
  40. package/dist/auth/dev-oauth.js +156 -0
  41. package/dist/auth/dev-oauth.js.map +1 -0
  42. package/dist/auth/environment.d.ts +7 -0
  43. package/dist/auth/environment.js +57 -0
  44. package/dist/auth/environment.js.map +1 -0
  45. package/dist/auth/session.d.ts +45 -0
  46. package/dist/auth/session.js +257 -0
  47. package/dist/auth/session.js.map +1 -0
  48. package/dist/auth/user.d.ts +21 -0
  49. package/dist/auth/user.js +12 -0
  50. package/dist/auth/user.js.map +1 -0
  51. package/dist/db/database.d.ts +30 -0
  52. package/dist/db/database.js +131 -0
  53. package/dist/db/database.js.map +1 -0
  54. package/dist/db/seed.d.ts +9 -0
  55. package/dist/db/seed.js +63 -0
  56. package/dist/db/seed.js.map +1 -0
  57. package/dist/index.d.ts +20 -0
  58. package/dist/index.js +21 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/launchpad/dev.d.ts +14 -0
  61. package/dist/launchpad/dev.js +48 -0
  62. package/dist/launchpad/dev.js.map +1 -0
  63. package/dist/launchpad/proxy.d.ts +30 -0
  64. package/dist/launchpad/proxy.js +98 -0
  65. package/dist/launchpad/proxy.js.map +1 -0
  66. package/dist/launchpad/server.d.ts +77 -0
  67. package/dist/launchpad/server.js +177 -0
  68. package/dist/launchpad/server.js.map +1 -0
  69. package/dist/mcp/server.d.ts +22 -0
  70. package/dist/mcp/server.js +222 -0
  71. package/dist/mcp/server.js.map +1 -0
  72. package/dist/odata/access.d.ts +31 -0
  73. package/dist/odata/access.js +142 -0
  74. package/dist/odata/access.js.map +1 -0
  75. package/dist/odata/batch.d.ts +36 -0
  76. package/dist/odata/batch.js +147 -0
  77. package/dist/odata/batch.js.map +1 -0
  78. package/dist/odata/csrf.d.ts +13 -0
  79. package/dist/odata/csrf.js +27 -0
  80. package/dist/odata/csrf.js.map +1 -0
  81. package/dist/odata/custom.d.ts +90 -0
  82. package/dist/odata/custom.js +373 -0
  83. package/dist/odata/custom.js.map +1 -0
  84. package/dist/odata/draft.d.ts +78 -0
  85. package/dist/odata/draft.js +590 -0
  86. package/dist/odata/draft.js.map +1 -0
  87. package/dist/odata/errors.d.ts +27 -0
  88. package/dist/odata/errors.js +41 -0
  89. package/dist/odata/errors.js.map +1 -0
  90. package/dist/odata/handler.d.ts +90 -0
  91. package/dist/odata/handler.js +312 -0
  92. package/dist/odata/handler.js.map +1 -0
  93. package/dist/odata/json.d.ts +23 -0
  94. package/dist/odata/json.js +85 -0
  95. package/dist/odata/json.js.map +1 -0
  96. package/dist/odata/read.d.ts +90 -0
  97. package/dist/odata/read.js +686 -0
  98. package/dist/odata/read.js.map +1 -0
  99. package/dist/odata/service.d.ts +45 -0
  100. package/dist/odata/service.js +145 -0
  101. package/dist/odata/service.js.map +1 -0
  102. package/dist/odata/uri/ast.d.ts +103 -0
  103. package/dist/odata/uri/ast.js +3 -0
  104. package/dist/odata/uri/ast.js.map +1 -0
  105. package/dist/odata/uri/parser.d.ts +26 -0
  106. package/dist/odata/uri/parser.js +697 -0
  107. package/dist/odata/uri/parser.js.map +1 -0
  108. package/dist/odata/write.d.ts +127 -0
  109. package/dist/odata/write.js +799 -0
  110. package/dist/odata/write.js.map +1 -0
  111. package/dist/platform.d.ts +14 -0
  112. package/dist/platform.js +66 -0
  113. package/dist/platform.js.map +1 -0
  114. package/dist/rap/bo.d.ts +76 -0
  115. package/dist/rap/bo.js +184 -0
  116. package/dist/rap/bo.js.map +1 -0
  117. package/dist/rap/kernel.d.ts +7 -0
  118. package/dist/rap/kernel.js +287 -0
  119. package/dist/rap/kernel.js.map +1 -0
  120. package/dist/rap/runtime.d.ts +84 -0
  121. package/dist/rap/runtime.js +128 -0
  122. package/dist/rap/runtime.js.map +1 -0
  123. package/dist/rap/transaction.d.ts +227 -0
  124. package/dist/rap/transaction.js +1385 -0
  125. package/dist/rap/transaction.js.map +1 -0
  126. package/dist/rap/types.d.ts +74 -0
  127. package/dist/rap/types.js +25 -0
  128. package/dist/rap/types.js.map +1 -0
  129. package/dist/server.d.ts +34 -0
  130. package/dist/server.js +114 -0
  131. package/dist/server.js.map +1 -0
  132. package/dist/ui/plugin.d.ts +14 -0
  133. package/dist/ui/plugin.js +47 -0
  134. package/dist/ui/plugin.js.map +1 -0
  135. package/dist/ui/resources.d.ts +22 -0
  136. package/dist/ui/resources.js +88 -0
  137. package/dist/ui/resources.js.map +1 -0
  138. package/dist/ui/variants.d.ts +25 -0
  139. package/dist/ui/variants.js +68 -0
  140. package/dist/ui/variants.js.map +1 -0
  141. package/dist/values.d.ts +43 -0
  142. package/dist/values.js +367 -0
  143. package/dist/values.js.map +1 -0
  144. package/package.json +60 -3
@@ -0,0 +1,697 @@
1
+ /**
2
+ * Chevrotain grammar of OData URL expressions: $filter, $orderby, $select, $expand (with nested
3
+ * options), key predicates and function parameters. Resource paths are split by hand.
4
+ */
5
+ import { createToken, EmbeddedActionsParser, Lexer } from "chevrotain";
6
+ import { badRequest, ODataError } from "../errors.js";
7
+ const WhiteSpace = createToken({ name: "WhiteSpace", pattern: /\s+/, group: Lexer.SKIPPED });
8
+ const StringLiteral = createToken({ name: "StringLiteral", pattern: /'(?:[^']|'')*'/ });
9
+ /** `Namespace.EnumType'MEMBER'` (D55). */
10
+ const EnumLiteral = createToken({ name: "EnumLiteral", pattern: /[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)+'[^']*'/ });
11
+ const DateTimeOffset = createToken({ name: "DateTimeOffset", pattern: /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:\d{2})/i });
12
+ const GuidLiteral = createToken({ name: "GuidLiteral", pattern: /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/ });
13
+ const DateLiteral = createToken({ name: "DateLiteral", pattern: /\d{4}-\d{2}-\d{2}/ });
14
+ const TimeLiteral = createToken({ name: "TimeLiteral", pattern: /\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?/ });
15
+ const DecimalLiteral = createToken({ name: "DecimalLiteral", pattern: /-?\d+\.\d+(?:[eE][+-]?\d+)?|-?\d+[eE][+-]?\d+/ });
16
+ const IntegerLiteral = createToken({ name: "IntegerLiteral", pattern: /-?\d+/ });
17
+ const Identifier = createToken({ name: "Identifier", pattern: /[A-Za-z_][A-Za-z0-9_]*/ });
18
+ const DollarName = createToken({ name: "DollarName", pattern: /\$[A-Za-z]+/ });
19
+ const LParen = createToken({ name: "LParen", pattern: /\(/ });
20
+ const RParen = createToken({ name: "RParen", pattern: /\)/ });
21
+ const Comma = createToken({ name: "Comma", pattern: /,/ });
22
+ const Slash = createToken({ name: "Slash", pattern: /\// });
23
+ const Semicolon = createToken({ name: "Semicolon", pattern: /;/ });
24
+ const Equals = createToken({ name: "Equals", pattern: /=/ });
25
+ const Star = createToken({ name: "Star", pattern: /\*/ });
26
+ const Dot = createToken({ name: "Dot", pattern: /\./ });
27
+ const Colon = createToken({ name: "Colon", pattern: /:/ });
28
+ const keywords = new Map();
29
+ function keyword(word) {
30
+ const token = createToken({ name: `K_${word}`, pattern: new RegExp(`${word}(?![A-Za-z0-9_])`), longer_alt: Identifier, categories: [Identifier] });
31
+ keywords.set(word, token);
32
+ return token;
33
+ }
34
+ const K = {
35
+ eq: keyword("eq"),
36
+ ne: keyword("ne"),
37
+ gt: keyword("gt"),
38
+ ge: keyword("ge"),
39
+ lt: keyword("lt"),
40
+ le: keyword("le"),
41
+ and: keyword("and"),
42
+ or: keyword("or"),
43
+ not: keyword("not"),
44
+ in: keyword("in"),
45
+ true: keyword("true"),
46
+ false: keyword("false"),
47
+ null: keyword("null"),
48
+ asc: keyword("asc"),
49
+ desc: keyword("desc"),
50
+ };
51
+ const tokens = [
52
+ WhiteSpace,
53
+ EnumLiteral,
54
+ StringLiteral,
55
+ DateTimeOffset,
56
+ GuidLiteral,
57
+ DateLiteral,
58
+ TimeLiteral,
59
+ DecimalLiteral,
60
+ IntegerLiteral,
61
+ ...[...keywords.values()].sort((a, b) => String(b.name).length - String(a.name).length),
62
+ Identifier,
63
+ DollarName,
64
+ LParen,
65
+ RParen,
66
+ Comma,
67
+ Slash,
68
+ Semicolon,
69
+ Equals,
70
+ Star,
71
+ Dot,
72
+ Colon,
73
+ ];
74
+ const lexer = new Lexer(tokens, { ensureOptimizations: false, positionTracking: "onlyOffset" });
75
+ const img = (t) => (typeof t?.image === "string" ? t.image : "");
76
+ const COMPARISON = { eq: "eq", ne: "ne", gt: "gt", ge: "ge", lt: "lt", le: "le" };
77
+ class ExpressionParser extends EmbeddedActionsParser {
78
+ constructor() {
79
+ super(tokens, { recoveryEnabled: false, maxLookahead: 3 });
80
+ this.performSelfAnalysis();
81
+ }
82
+ filter = this.RULE("filter", () => this.SUBRULE(this.orExpr));
83
+ orExpr = this.RULE("orExpr", () => {
84
+ let left = this.SUBRULE(this.andExpr);
85
+ this.MANY(() => {
86
+ this.CONSUME(K.or);
87
+ const right = this.SUBRULE1(this.andExpr);
88
+ left = this.ACTION(() => ({ kind: "binary", op: "or", left, right }));
89
+ });
90
+ return left;
91
+ });
92
+ andExpr = this.RULE("andExpr", () => {
93
+ let left = this.SUBRULE(this.notExpr);
94
+ this.MANY(() => {
95
+ this.CONSUME(K.and);
96
+ const right = this.SUBRULE1(this.notExpr);
97
+ left = this.ACTION(() => ({ kind: "binary", op: "and", left, right }));
98
+ });
99
+ return left;
100
+ });
101
+ // keywords are also identifiers: `not (…)` is the operator, not a function call
102
+ notExpr = this.RULE("notExpr", () => this.OR({
103
+ IGNORE_AMBIGUITIES: true,
104
+ DEF: [
105
+ {
106
+ ALT: () => {
107
+ this.CONSUME(K.not);
108
+ const operand = this.SUBRULE(this.notExpr);
109
+ return this.ACTION(() => ({ kind: "not", operand }));
110
+ },
111
+ },
112
+ { ALT: () => this.SUBRULE(this.comparison) },
113
+ ]
114
+ }));
115
+ comparison = this.RULE("comparison", () => {
116
+ const left = this.SUBRULE(this.operand);
117
+ let result = left;
118
+ this.OPTION(() => {
119
+ this.OR([
120
+ {
121
+ ALT: () => {
122
+ const op = this.OR1([
123
+ { ALT: () => this.CONSUME(K.eq) },
124
+ { ALT: () => this.CONSUME(K.ne) },
125
+ { ALT: () => this.CONSUME(K.gt) },
126
+ { ALT: () => this.CONSUME(K.ge) },
127
+ { ALT: () => this.CONSUME(K.lt) },
128
+ { ALT: () => this.CONSUME(K.le) },
129
+ ]);
130
+ const right = this.SUBRULE1(this.operand);
131
+ result = this.ACTION(() => ({ kind: "binary", op: COMPARISON[img(op)] ?? "eq", left, right }));
132
+ },
133
+ },
134
+ {
135
+ ALT: () => {
136
+ this.CONSUME(K.in);
137
+ this.CONSUME(LParen);
138
+ const list = [];
139
+ this.AT_LEAST_ONE_SEP({
140
+ SEP: Comma,
141
+ DEF: () => {
142
+ const value = this.SUBRULE2(this.operand);
143
+ this.ACTION(() => list.push(value));
144
+ },
145
+ });
146
+ this.CONSUME(RParen);
147
+ result = this.ACTION(() => ({ kind: "in", operand: left, list }));
148
+ },
149
+ },
150
+ ]);
151
+ });
152
+ return result;
153
+ });
154
+ // literals first: true, false and null are also identifiers
155
+ operand = this.RULE("operand", () => this.OR({
156
+ IGNORE_AMBIGUITIES: true,
157
+ DEF: [
158
+ {
159
+ ALT: () => {
160
+ this.CONSUME(LParen);
161
+ const inner = this.SUBRULE(this.orExpr);
162
+ this.CONSUME(RParen);
163
+ return inner;
164
+ },
165
+ },
166
+ { ALT: () => this.SUBRULE(this.literal) },
167
+ {
168
+ // function call: an identifier directly followed by "("
169
+ GATE: () => this.LA(2).tokenType === LParen,
170
+ ALT: () => {
171
+ const name = this.CONSUME(Identifier);
172
+ this.CONSUME1(LParen);
173
+ const args = [];
174
+ this.MANY_SEP({
175
+ SEP: Comma,
176
+ DEF: () => {
177
+ const arg = this.SUBRULE1(this.orExpr);
178
+ this.ACTION(() => args.push(arg));
179
+ },
180
+ });
181
+ this.CONSUME1(RParen);
182
+ return this.ACTION(() => ({ kind: "call", name: img(name).toLowerCase(), args }));
183
+ },
184
+ },
185
+ {
186
+ ALT: () => {
187
+ const path = this.SUBRULE(this.memberPath);
188
+ return this.ACTION(() => ({ kind: "member", path }));
189
+ },
190
+ },
191
+ ]
192
+ }));
193
+ memberPath = this.RULE("memberPath", () => {
194
+ const path = [];
195
+ this.OR([{ ALT: () => path.push(img(this.CONSUME(Identifier))) }, { ALT: () => path.push(img(this.CONSUME(DollarName))) }]);
196
+ this.MANY(() => {
197
+ this.CONSUME(Slash);
198
+ this.OR1([{ ALT: () => path.push(img(this.CONSUME1(Identifier))) }, { ALT: () => path.push(img(this.CONSUME1(DollarName))) }]);
199
+ });
200
+ return path;
201
+ });
202
+ literal = this.RULE("literal", () => {
203
+ const literal = this.OR([
204
+ { ALT: () => ({ type: "string", value: img(this.CONSUME(StringLiteral)).slice(1, -1).replace(/''/g, "'") }) },
205
+ { ALT: () => ({ type: "enum", value: (/'([^']*)'$/.exec(img(this.CONSUME(EnumLiteral)))?.[1] ?? "").split(",")[0] ?? "" }) },
206
+ { ALT: () => ({ type: "datetime", value: img(this.CONSUME(DateTimeOffset)) }) },
207
+ { ALT: () => ({ type: "guid", value: img(this.CONSUME(GuidLiteral)).toLowerCase() }) },
208
+ { ALT: () => ({ type: "date", value: img(this.CONSUME(DateLiteral)) }) },
209
+ { ALT: () => ({ type: "time", value: img(this.CONSUME(TimeLiteral)) }) },
210
+ { ALT: () => ({ type: "decimal", value: img(this.CONSUME(DecimalLiteral)) }) },
211
+ { ALT: () => ({ type: "int", value: img(this.CONSUME(IntegerLiteral)) }) },
212
+ { ALT: () => (this.CONSUME(K.true), { type: "bool", value: true }) },
213
+ { ALT: () => (this.CONSUME(K.false), { type: "bool", value: false }) },
214
+ { ALT: () => (this.CONSUME(K.null), { type: "null", value: null }) },
215
+ ]);
216
+ return { kind: "literal", literal };
217
+ });
218
+ orderby = this.RULE("orderby", () => {
219
+ const items = [];
220
+ this.AT_LEAST_ONE_SEP({
221
+ SEP: Comma,
222
+ DEF: () => {
223
+ const path = this.SUBRULE(this.memberPath);
224
+ let descending = false;
225
+ this.OPTION(() => {
226
+ this.OR([{ ALT: () => this.CONSUME(K.asc) }, { ALT: () => { this.CONSUME(K.desc); descending = true; } }]);
227
+ });
228
+ this.ACTION(() => items.push({ path, descending }));
229
+ },
230
+ });
231
+ return items;
232
+ });
233
+ select = this.RULE("select", () => {
234
+ const items = [];
235
+ this.AT_LEAST_ONE_SEP({
236
+ SEP: Comma,
237
+ DEF: () => {
238
+ this.OR([
239
+ { ALT: () => { this.CONSUME(Star); items.push(["*"]); } },
240
+ {
241
+ ALT: () => {
242
+ const path = this.SUBRULE(this.qualifiedPath);
243
+ this.ACTION(() => items.push(path));
244
+ },
245
+ },
246
+ ]);
247
+ },
248
+ });
249
+ return items;
250
+ });
251
+ /** A member path whose segments may be namespace-qualified (actions in $select). */
252
+ qualifiedPath = this.RULE("qualifiedPath", () => {
253
+ const path = [];
254
+ const segment = () => {
255
+ let text = img(this.CONSUME(Identifier));
256
+ this.MANY(() => {
257
+ this.CONSUME(Dot);
258
+ this.OR([{ ALT: () => (text += `.${img(this.CONSUME1(Identifier))}`) }, { ALT: () => { this.CONSUME(Star); text += ".*"; } }]);
259
+ });
260
+ path.push(text);
261
+ };
262
+ segment();
263
+ this.MANY1(() => {
264
+ this.CONSUME(Slash);
265
+ let text = img(this.CONSUME2(Identifier));
266
+ this.MANY2(() => {
267
+ this.CONSUME1(Dot);
268
+ text += `.${img(this.CONSUME3(Identifier))}`;
269
+ });
270
+ path.push(text);
271
+ });
272
+ return path;
273
+ });
274
+ expand = this.RULE("expand", () => {
275
+ const items = [];
276
+ this.AT_LEAST_ONE_SEP({
277
+ SEP: Comma,
278
+ DEF: () => {
279
+ const path = this.OR([{ ALT: () => this.SUBRULE(this.memberPath) }, { ALT: () => { this.CONSUME(Star); return ["*"]; } }]);
280
+ const options = {};
281
+ this.OPTION(() => {
282
+ this.CONSUME(LParen);
283
+ this.MANY_SEP({
284
+ SEP: Semicolon,
285
+ DEF: () => this.SUBRULE(this.expandOption, { ARGS: [options] }),
286
+ });
287
+ this.CONSUME(RParen);
288
+ });
289
+ this.ACTION(() => items.push({ path, options }));
290
+ },
291
+ });
292
+ return items;
293
+ });
294
+ expandOption = this.RULE("expandOption", (options) => {
295
+ const name = this.CONSUME(DollarName);
296
+ this.CONSUME(Equals);
297
+ const option = img(name).toLowerCase();
298
+ this.OR([
299
+ {
300
+ GATE: () => option === "$filter",
301
+ ALT: () => {
302
+ const filter = this.SUBRULE(this.orExpr);
303
+ this.ACTION(() => (options.filter = filter));
304
+ },
305
+ },
306
+ {
307
+ GATE: () => option === "$orderby",
308
+ ALT: () => {
309
+ const orderby = this.SUBRULE(this.orderby);
310
+ this.ACTION(() => (options.orderby = orderby));
311
+ },
312
+ },
313
+ {
314
+ GATE: () => option === "$select",
315
+ ALT: () => {
316
+ const select = this.SUBRULE(this.select);
317
+ this.ACTION(() => (options.select = select));
318
+ },
319
+ },
320
+ {
321
+ GATE: () => option === "$expand",
322
+ ALT: () => {
323
+ const expand = this.SUBRULE(this.expand);
324
+ this.ACTION(() => (options.expand = expand));
325
+ },
326
+ },
327
+ {
328
+ GATE: () => option === "$top" || option === "$skip",
329
+ ALT: () => {
330
+ const value = Number.parseInt(img(this.CONSUME(IntegerLiteral)), 10);
331
+ this.ACTION(() => {
332
+ if (option === "$top")
333
+ options.top = value;
334
+ else
335
+ options.skip = value;
336
+ });
337
+ },
338
+ },
339
+ {
340
+ GATE: () => option === "$count",
341
+ ALT: () => {
342
+ const value = this.OR1([{ ALT: () => (this.CONSUME(K.true), true) }, { ALT: () => (this.CONSUME(K.false), false) }]);
343
+ this.ACTION(() => (options.count = value));
344
+ },
345
+ },
346
+ {
347
+ GATE: () => option === "$search",
348
+ ALT: () => {
349
+ const value = this.OR2([{ ALT: () => img(this.CONSUME1(Identifier)) }, { ALT: () => img(this.CONSUME(StringLiteral)).slice(1, -1) }]);
350
+ this.ACTION(() => (options.search = value));
351
+ },
352
+ },
353
+ ]);
354
+ });
355
+ /** `(value)` or `(Name=value,…)`. */
356
+ keyPredicate = this.RULE("keyPredicate", () => {
357
+ this.CONSUME(LParen);
358
+ const result = this.OR([
359
+ {
360
+ GATE: () => this.LA(2).tokenType === Equals,
361
+ ALT: () => {
362
+ const values = [];
363
+ this.AT_LEAST_ONE_SEP({
364
+ SEP: Comma,
365
+ DEF: () => {
366
+ const name = img(this.CONSUME(Identifier));
367
+ this.CONSUME(Equals);
368
+ const value = this.SUBRULE(this.literal);
369
+ this.ACTION(() => values.push({ name, value }));
370
+ },
371
+ });
372
+ return { kind: "named", values };
373
+ },
374
+ },
375
+ {
376
+ ALT: () => {
377
+ const value = this.SUBRULE1(this.literal);
378
+ return { kind: "single", value };
379
+ },
380
+ },
381
+ ]);
382
+ this.CONSUME(RParen);
383
+ return result;
384
+ });
385
+ /** Function parameters: `(Name=value,…)`, possibly empty. */
386
+ parameters = this.RULE("parameters", () => {
387
+ const values = [];
388
+ this.CONSUME(LParen);
389
+ this.MANY_SEP({
390
+ SEP: Comma,
391
+ DEF: () => {
392
+ const name = img(this.CONSUME(Identifier));
393
+ this.CONSUME(Equals);
394
+ const value = this.SUBRULE(this.literal);
395
+ this.ACTION(() => values.push({ name, value }));
396
+ },
397
+ });
398
+ this.CONSUME(RParen);
399
+ return values;
400
+ });
401
+ }
402
+ let parser;
403
+ function run(text, what, rule) {
404
+ const lexed = lexer.tokenize(text);
405
+ if (lexed.errors.length > 0) {
406
+ const e = lexed.errors[0];
407
+ throw badRequest(`Invalid ${what}: unexpected character at position ${(e?.offset ?? 0) + 1} in "${text}"`);
408
+ }
409
+ parser ??= new ExpressionParser();
410
+ parser.input = lexed.tokens;
411
+ const result = rule(parser);
412
+ if (parser.errors.length > 0) {
413
+ const e = parser.errors[0];
414
+ const at = e?.token.tokenType.name === "EOF" ? "end" : `"${e?.token.image ?? ""}" (position ${(e?.token.startOffset ?? 0) + 1})`;
415
+ throw badRequest(`Invalid ${what} "${text}": syntax error at ${at}`);
416
+ }
417
+ return result;
418
+ }
419
+ export const parseFilter = (text) => run(text, "$filter", (p) => p.filter());
420
+ export const parseOrderby = (text) => run(text, "$orderby", (p) => p.orderby());
421
+ export const parseSelect = (text) => run(text, "$select", (p) => p.select());
422
+ export const parseExpand = (text) => run(text, "$expand", (p) => p.expand());
423
+ export const parseKeyPredicate = (text) => run(text, "key predicate", (p) => p.keyPredicate());
424
+ export const parseParameters = (text) => run(text, "function parameters", (p) => p.parameters());
425
+ // ---------------------------------------------------------------------------------------------
426
+ // $apply (D52): filter(…), groupby((…),aggregate(…)), aggregate(…), separated by /
427
+ /** Splits `text` at `separator` outside parentheses and string literals. */
428
+ function splitTopLevel(text, separator) {
429
+ const parts = [];
430
+ let depth = 0;
431
+ let quoted = false;
432
+ let start = 0;
433
+ for (let i = 0; i < text.length; i++) {
434
+ const c = text[i];
435
+ if (c === "'") {
436
+ if (quoted && text[i + 1] === "'") {
437
+ i++;
438
+ continue;
439
+ }
440
+ quoted = !quoted;
441
+ }
442
+ else if (!quoted && c === "(") {
443
+ depth++;
444
+ }
445
+ else if (!quoted && c === ")") {
446
+ depth--;
447
+ }
448
+ else if (!quoted && depth === 0 && c === separator) {
449
+ parts.push(text.slice(start, i).trim());
450
+ start = i + 1;
451
+ }
452
+ }
453
+ parts.push(text.slice(start).trim());
454
+ return parts.filter((p) => p !== "");
455
+ }
456
+ /** `name(arguments)`: the name and the text between the outer parentheses. */
457
+ function callOf(text) {
458
+ const match = /^([A-Za-z]+)\s*\(([\s\S]*)\)$/.exec(text.trim());
459
+ if (match === null) {
460
+ throw badRequest(`$apply: cannot read ${text}`);
461
+ }
462
+ return { name: (match[1] ?? "").toLowerCase(), inner: match[2] ?? "" };
463
+ }
464
+ const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
465
+ function aggregates(text) {
466
+ return splitTopLevel(text, ",").map((item) => {
467
+ const count = /^\$count\s+as\s+([A-Za-z_][A-Za-z0-9_]*)$/i.exec(item);
468
+ if (count !== null) {
469
+ return { method: "count", alias: count[1] ?? "" };
470
+ }
471
+ const match = /^([A-Za-z_][A-Za-z0-9_]*)\s+with\s+(sum|min|max|average|countdistinct)\s+as\s+([A-Za-z_][A-Za-z0-9_]*)$/i.exec(item);
472
+ if (match === null) {
473
+ throw badRequest(`$apply: the aggregate ${item} is not "Property with sum|min|max|average|countdistinct as Alias" or "$count as Alias"`);
474
+ }
475
+ return { property: match[1] ?? "", method: (match[2] ?? "sum").toLowerCase(), alias: match[3] ?? "" };
476
+ });
477
+ }
478
+ export function parseApply(text) {
479
+ const steps = [];
480
+ for (const part of splitTopLevel(text, "/")) {
481
+ const { name, inner } = callOf(part);
482
+ switch (name) {
483
+ case "filter":
484
+ steps.push({ kind: "filter", filter: parseFilter(inner) });
485
+ break;
486
+ case "aggregate":
487
+ steps.push({ kind: "aggregate", aggregate: aggregates(inner) });
488
+ break;
489
+ case "groupby": {
490
+ const [group, rest, extra] = splitTopLevel(inner, ",");
491
+ if (group === undefined || !group.startsWith("(") || !group.endsWith(")") || extra !== undefined) {
492
+ throw badRequest("$apply: groupby((Property,…)) or groupby((Property,…),aggregate(…))");
493
+ }
494
+ const properties = splitTopLevel(group.slice(1, -1), ",");
495
+ if (properties.length === 0 || properties.some((p) => !IDENTIFIER.test(p))) {
496
+ throw badRequest(`$apply: groupby needs properties, not ${group}`);
497
+ }
498
+ const aggregate = rest === undefined ? [] : (() => {
499
+ const call = callOf(rest);
500
+ if (call.name !== "aggregate") {
501
+ throw badRequest(`$apply: only aggregate(…) may follow the properties of groupby, not ${rest}`);
502
+ }
503
+ return aggregates(call.inner);
504
+ })();
505
+ steps.push({ kind: "groupby", properties, aggregate });
506
+ break;
507
+ }
508
+ default:
509
+ throw notImplementedApply(name);
510
+ }
511
+ }
512
+ const grouping = steps.filter((s) => s.kind !== "filter");
513
+ if (grouping.length > 1 || steps.findIndex((s) => s.kind !== "filter") < steps.length - grouping.length) {
514
+ throw notImplementedApply("transformations after groupby or aggregate");
515
+ }
516
+ return steps;
517
+ }
518
+ function notImplementedApply(what) {
519
+ return new ODataError(501, "NOT_IMPLEMENTED", `$apply: ${what} is not supported (filter, groupby, aggregate are)`);
520
+ }
521
+ // ---------------------------------------------------------------------------------------------
522
+ // query string and resource path
523
+ /** Splits a query string without turning `+` into a space (OData URLs percent-encode it). */
524
+ export function parseQueryString(query) {
525
+ const result = new Map();
526
+ for (const part of query.split("&")) {
527
+ if (part === "") {
528
+ continue;
529
+ }
530
+ const index = part.indexOf("=");
531
+ const rawName = index < 0 ? part : part.slice(0, index);
532
+ const rawValue = index < 0 ? "" : part.slice(index + 1);
533
+ let name;
534
+ let value;
535
+ try {
536
+ name = decodeURIComponent(rawName);
537
+ value = decodeURIComponent(rawValue);
538
+ }
539
+ catch {
540
+ throw badRequest(`Invalid percent-encoding in the query option ${rawName}`);
541
+ }
542
+ if (result.has(name)) {
543
+ throw badRequest(`Query option ${name} is given twice`);
544
+ }
545
+ result.set(name, value);
546
+ }
547
+ return result;
548
+ }
549
+ const SYSTEM_OPTIONS = new Set(["$filter", "$orderby", "$select", "$expand", "$top", "$skip", "$count", "$search", "$format", "$skiptoken", "$apply", "$levels", "$schemaversion", "$deltatoken", "$id"]);
550
+ /** System query options of the request; custom options (sap-client, …) are ignored. */
551
+ export function parseQueryOptions(query) {
552
+ const options = {};
553
+ for (const [name, value] of query) {
554
+ const lower = name.toLowerCase();
555
+ if (!lower.startsWith("$")) {
556
+ continue;
557
+ }
558
+ if (!SYSTEM_OPTIONS.has(lower)) {
559
+ throw badRequest(`Unknown system query option ${name}`);
560
+ }
561
+ switch (lower) {
562
+ case "$filter":
563
+ options.filter = parseFilter(value);
564
+ break;
565
+ case "$orderby":
566
+ options.orderby = parseOrderby(value);
567
+ break;
568
+ case "$select":
569
+ options.select = parseSelect(value);
570
+ break;
571
+ case "$expand":
572
+ options.expand = parseExpand(value);
573
+ break;
574
+ case "$top":
575
+ case "$skip": {
576
+ if (!/^\d+$/.test(value)) {
577
+ throw badRequest(`${name} must be a non-negative integer`);
578
+ }
579
+ const n = Number.parseInt(value, 10);
580
+ if (lower === "$top")
581
+ options.top = n;
582
+ else
583
+ options.skip = n;
584
+ break;
585
+ }
586
+ case "$count":
587
+ if (value !== "true" && value !== "false") {
588
+ throw badRequest("$count must be true or false");
589
+ }
590
+ options.count = value === "true";
591
+ break;
592
+ case "$search":
593
+ options.search = value;
594
+ break;
595
+ case "$format":
596
+ if (!/^json$|^application\/json/i.test(value)) {
597
+ throw new ODataError(406, "NOT_ACCEPTABLE", `$format=${value} is not supported (JSON only)`);
598
+ }
599
+ break;
600
+ case "$apply":
601
+ options.apply = parseApply(value);
602
+ break;
603
+ default:
604
+ throw badRequest(`${name} is not supported`);
605
+ }
606
+ }
607
+ return options;
608
+ }
609
+ /** Splits the resource path into segments (outside parentheses and string literals). */
610
+ export function splitPath(path) {
611
+ const segments = [];
612
+ let depth = 0;
613
+ let inString = false;
614
+ let current = "";
615
+ for (let i = 0; i < path.length; i++) {
616
+ const c = path[i];
617
+ if (c === "'" && depth > 0) {
618
+ inString = !inString;
619
+ }
620
+ else if (!inString && c === "(") {
621
+ depth++;
622
+ }
623
+ else if (!inString && c === ")") {
624
+ depth--;
625
+ }
626
+ if (c === "/" && depth === 0 && !inString) {
627
+ segments.push(current);
628
+ current = "";
629
+ }
630
+ else {
631
+ current += c;
632
+ }
633
+ }
634
+ if (current !== "") {
635
+ segments.push(current);
636
+ }
637
+ return segments;
638
+ }
639
+ /**
640
+ * Parses the resource path (already relative to the service root, percent-decoded per segment):
641
+ * `Travel(…)/_Booking`, `Travel/$count`, `Travel(…)/ns.action`, `$1/_Booking`.
642
+ */
643
+ export function parseResourcePath(path, isEntitySet, namespace) {
644
+ const segments = [];
645
+ const raw = splitPath(path.replace(/^\/+/, ""));
646
+ raw.forEach((text, index) => {
647
+ let decoded;
648
+ try {
649
+ decoded = decodeURIComponent(text);
650
+ }
651
+ catch {
652
+ throw badRequest(`Invalid percent-encoding in the resource path segment ${text}`);
653
+ }
654
+ if (decoded === "$count") {
655
+ segments.push({ kind: "count" });
656
+ return;
657
+ }
658
+ if (decoded === "$value") {
659
+ segments.push({ kind: "value" });
660
+ return;
661
+ }
662
+ if (index === 0 && /^\$\d+$/.test(decoded)) {
663
+ segments.push({ kind: "contentId", id: decoded.slice(1) });
664
+ return;
665
+ }
666
+ const match = /^([A-Za-z_][A-Za-z0-9_.]*)(\(.*\))?$/.exec(decoded);
667
+ if (match === null) {
668
+ throw badRequest(`Invalid resource path segment ${decoded}`);
669
+ }
670
+ const name = match[1];
671
+ const parens = match[2];
672
+ if (name.includes(".")) {
673
+ const dot = name.lastIndexOf(".");
674
+ const operation = { kind: "operation", namespace: name.slice(0, dot), name: name.slice(dot + 1) };
675
+ if (operation.namespace !== namespace && operation.namespace !== "SAP__self") {
676
+ throw badRequest(`Unknown namespace ${operation.namespace}`);
677
+ }
678
+ if (parens !== undefined) {
679
+ operation.parameters = parseParameters(parens);
680
+ }
681
+ segments.push(operation);
682
+ return;
683
+ }
684
+ const key = parens === undefined ? undefined : parseKeyPredicate(parens);
685
+ if (index === 0) {
686
+ if (!isEntitySet(name)) {
687
+ throw badRequest(`Unknown entity set ${name}`);
688
+ }
689
+ segments.push(key === undefined ? { kind: "entitySet", name } : { kind: "entitySet", name, key });
690
+ }
691
+ else {
692
+ segments.push(key === undefined ? { kind: "navigation", name } : { kind: "navigation", name, key });
693
+ }
694
+ });
695
+ return segments;
696
+ }
697
+ //# sourceMappingURL=parser.js.map