@mkja/o-data 0.0.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.
- package/README.md +416 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +13 -0
- package/dist/filter.d.ts +40 -0
- package/dist/filter.js +278 -0
- package/dist/index.d.ts +101 -0
- package/dist/index.js +419 -0
- package/dist/operations.d.ts +75 -0
- package/dist/operations.js +3 -0
- package/dist/parser/config.d.ts +152 -0
- package/dist/parser/config.js +3 -0
- package/dist/parser/index.d.ts +1 -0
- package/dist/parser/index.js +1157 -0
- package/dist/query.d.ts +43 -0
- package/dist/query.js +3 -0
- package/dist/response.d.ts +132 -0
- package/dist/response.js +3 -0
- package/dist/runtime.d.ts +4 -0
- package/dist/runtime.js +113 -0
- package/dist/schema.d.ts +128 -0
- package/dist/schema.js +11 -0
- package/dist/serialization.d.ts +42 -0
- package/dist/serialization.js +533 -0
- package/dist/types.d.ts +155 -0
- package/dist/types.js +3 -0
- package/package.json +34 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// OData Client Implementation
|
|
3
|
+
// ============================================================================
|
|
4
|
+
import { buildQueryableEntity } from './runtime.js';
|
|
5
|
+
import { buildQueryString, buildCreateRequest, buildUpdateRequest, buildActionRequest, buildFunctionRequest } from './serialization.js';
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// OdataClient
|
|
8
|
+
// ============================================================================
|
|
9
|
+
export class OdataClient {
|
|
10
|
+
#schema;
|
|
11
|
+
#options;
|
|
12
|
+
constructor(schema, options) {
|
|
13
|
+
this.#schema = schema;
|
|
14
|
+
this.#options = options;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Access an entityset collection.
|
|
18
|
+
*/
|
|
19
|
+
entitysets(entityset) {
|
|
20
|
+
const entity = buildQueryableEntity(this.#schema, String(entityset));
|
|
21
|
+
return new CollectionOperation(this.#schema, entity, entityset, String(entityset), this.#options);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Execute an unbound global action.
|
|
25
|
+
*/
|
|
26
|
+
async action(name, payload) {
|
|
27
|
+
const actionName = this.#schema.actionImports?.[name]?.action;
|
|
28
|
+
if (!actionName || !this.#schema.actions || !(actionName in this.#schema.actions)) {
|
|
29
|
+
throw new Error(`Action '${String(name)}' not found`);
|
|
30
|
+
}
|
|
31
|
+
const actionDef = this.#schema.actions[actionName];
|
|
32
|
+
const parameterDefs = actionDef.parameters;
|
|
33
|
+
const namespace = this.#schema.namespace || '';
|
|
34
|
+
const request = buildActionRequest('', namespace, String(name), // Use import name, not resolved action name
|
|
35
|
+
payload.parameters, parameterDefs, this.#schema, this.#options.baseUrl, false // Unbound actions use import name, not FQN
|
|
36
|
+
);
|
|
37
|
+
const response = await this.#options.transport(request);
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
let error;
|
|
40
|
+
try {
|
|
41
|
+
error = await response.json();
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
error = await response.text();
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
ok: false,
|
|
48
|
+
status: response.status,
|
|
49
|
+
statusText: response.statusText,
|
|
50
|
+
headers: response.headers,
|
|
51
|
+
result: { error },
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
if (response.status === 204) {
|
|
55
|
+
return {
|
|
56
|
+
ok: true,
|
|
57
|
+
status: 204,
|
|
58
|
+
statusText: response.statusText,
|
|
59
|
+
headers: response.headers,
|
|
60
|
+
result: {
|
|
61
|
+
data: undefined,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const json = await response.json();
|
|
66
|
+
const { value, ...odataProps } = json;
|
|
67
|
+
return {
|
|
68
|
+
ok: true,
|
|
69
|
+
status: response.status,
|
|
70
|
+
statusText: response.statusText,
|
|
71
|
+
headers: response.headers,
|
|
72
|
+
result: {
|
|
73
|
+
data: value !== undefined ? value : json,
|
|
74
|
+
...odataProps, // @odata.context, etc.
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Execute an unbound global function.
|
|
80
|
+
*/
|
|
81
|
+
async function(name, payload) {
|
|
82
|
+
const functionName = this.#schema.functionImports?.[name]?.function;
|
|
83
|
+
if (!functionName || !this.#schema.functions || !(functionName in this.#schema.functions)) {
|
|
84
|
+
throw new Error(`Function '${String(name)}' not found`);
|
|
85
|
+
}
|
|
86
|
+
const namespace = this.#schema.namespace || '';
|
|
87
|
+
const request = buildFunctionRequest('', namespace, String(name), // Use import name, not resolved function name
|
|
88
|
+
payload.parameters, this.#options.baseUrl, false // Unbound functions use import name, not FQN
|
|
89
|
+
);
|
|
90
|
+
const response = await this.#options.transport(request);
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
let error;
|
|
93
|
+
try {
|
|
94
|
+
error = await response.json();
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
error = await response.text();
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
ok: false,
|
|
101
|
+
status: response.status,
|
|
102
|
+
statusText: response.statusText,
|
|
103
|
+
headers: response.headers,
|
|
104
|
+
result: { error },
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
const json = await response.json();
|
|
108
|
+
const { value, ...odataProps } = json;
|
|
109
|
+
if (value !== undefined) {
|
|
110
|
+
return {
|
|
111
|
+
ok: true,
|
|
112
|
+
status: response.status,
|
|
113
|
+
statusText: response.statusText,
|
|
114
|
+
headers: response.headers,
|
|
115
|
+
result: {
|
|
116
|
+
data: value,
|
|
117
|
+
...odataProps, // @odata.context, etc.
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
ok: true,
|
|
123
|
+
status: response.status,
|
|
124
|
+
statusText: response.statusText,
|
|
125
|
+
headers: response.headers,
|
|
126
|
+
result: {
|
|
127
|
+
data: json,
|
|
128
|
+
...odataProps, // @odata.context, etc.
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// ============================================================================
|
|
134
|
+
// CollectionOperation
|
|
135
|
+
// ============================================================================
|
|
136
|
+
class CollectionOperation {
|
|
137
|
+
#schema;
|
|
138
|
+
#entityset;
|
|
139
|
+
#entitysetName;
|
|
140
|
+
#path;
|
|
141
|
+
#options;
|
|
142
|
+
constructor(schema, entityset, entitysetName, path, options) {
|
|
143
|
+
this.#schema = schema;
|
|
144
|
+
this.#entityset = entityset;
|
|
145
|
+
this.#entitysetName = entitysetName;
|
|
146
|
+
this.#path = path;
|
|
147
|
+
this.#options = options;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Query a collection of entities.
|
|
151
|
+
*/
|
|
152
|
+
async query(q, o) {
|
|
153
|
+
const queryString = buildQueryString(q, this.#entityset, this.#schema);
|
|
154
|
+
const url = this.buildUrl(queryString);
|
|
155
|
+
const request = new Request(url);
|
|
156
|
+
const response = await this.#options.transport(request);
|
|
157
|
+
const data = await response.json();
|
|
158
|
+
return {
|
|
159
|
+
ok: response.ok,
|
|
160
|
+
status: response.status,
|
|
161
|
+
statusText: response.statusText,
|
|
162
|
+
headers: response.headers,
|
|
163
|
+
result: data,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Build the full URL for this operation.
|
|
168
|
+
*/
|
|
169
|
+
buildUrl(queryString = '') {
|
|
170
|
+
const baseUrl = this.#options.baseUrl.endsWith('/')
|
|
171
|
+
? this.#options.baseUrl.slice(0, -1)
|
|
172
|
+
: this.#options.baseUrl;
|
|
173
|
+
return `${baseUrl}/${this.#path}${queryString}`;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Create a new entity.
|
|
177
|
+
*/
|
|
178
|
+
async create(c, o) {
|
|
179
|
+
const request = buildCreateRequest(this.#path, c, o, this.#options.baseUrl, this.#entityset, this.#schema);
|
|
180
|
+
const response = await this.#options.transport(request);
|
|
181
|
+
const data = await response.json();
|
|
182
|
+
return {
|
|
183
|
+
ok: response.ok,
|
|
184
|
+
status: response.status,
|
|
185
|
+
statusText: response.statusText,
|
|
186
|
+
headers: response.headers,
|
|
187
|
+
result: data,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Access a single entity by key.
|
|
192
|
+
*/
|
|
193
|
+
key(key) {
|
|
194
|
+
const newPath = `${this.#path}(${key})`;
|
|
195
|
+
return new SingleOperation(this.#schema, this.#entityset, this.#entitysetName, newPath, this.#options);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Execute a bound action on the collection.
|
|
199
|
+
*/
|
|
200
|
+
async action(name, payload) {
|
|
201
|
+
// TODO: Implement bound action execution - filter by target and scope at runtime
|
|
202
|
+
throw new Error('Not implemented');
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Execute a bound function on the collection.
|
|
206
|
+
*/
|
|
207
|
+
async function(name, payload) {
|
|
208
|
+
// TODO: Implement bound function execution - filter by target and scope at runtime
|
|
209
|
+
throw new Error('Not implemented');
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// ============================================================================
|
|
213
|
+
// SingleOperation
|
|
214
|
+
// ============================================================================
|
|
215
|
+
class SingleOperation {
|
|
216
|
+
#schema;
|
|
217
|
+
#entityset;
|
|
218
|
+
#entitysetName;
|
|
219
|
+
#path;
|
|
220
|
+
#options;
|
|
221
|
+
constructor(schema, entityset, entitysetName, path, options) {
|
|
222
|
+
this.#schema = schema;
|
|
223
|
+
this.#entityset = entityset;
|
|
224
|
+
this.#entitysetName = entitysetName;
|
|
225
|
+
this.#path = path;
|
|
226
|
+
this.#options = options;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Query a single entity.
|
|
230
|
+
*/
|
|
231
|
+
async query(q, o) {
|
|
232
|
+
const queryString = buildQueryString(q, this.#entityset, this.#schema);
|
|
233
|
+
const url = this.buildUrl(queryString);
|
|
234
|
+
const request = new Request(url);
|
|
235
|
+
const response = await this.#options.transport(request);
|
|
236
|
+
const data = await response.json();
|
|
237
|
+
return {
|
|
238
|
+
ok: response.ok,
|
|
239
|
+
status: response.status,
|
|
240
|
+
statusText: response.statusText,
|
|
241
|
+
headers: response.headers,
|
|
242
|
+
result: data,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Build the full URL for this operation.
|
|
247
|
+
*/
|
|
248
|
+
buildUrl(queryString = '') {
|
|
249
|
+
const baseUrl = this.#options.baseUrl.endsWith('/')
|
|
250
|
+
? this.#options.baseUrl.slice(0, -1)
|
|
251
|
+
: this.#options.baseUrl;
|
|
252
|
+
return `${baseUrl}/${this.#path}${queryString}`;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Update an entity.
|
|
256
|
+
*/
|
|
257
|
+
async update(u, o) {
|
|
258
|
+
const request = buildUpdateRequest(this.#path, u, o, this.#options.baseUrl, this.#entityset, this.#schema);
|
|
259
|
+
const response = await this.#options.transport(request);
|
|
260
|
+
const data = await response.json();
|
|
261
|
+
return {
|
|
262
|
+
ok: response.ok,
|
|
263
|
+
status: response.status,
|
|
264
|
+
statusText: response.statusText,
|
|
265
|
+
headers: response.headers,
|
|
266
|
+
result: data,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Delete an entity.
|
|
271
|
+
*/
|
|
272
|
+
async delete() {
|
|
273
|
+
// TODO: Implement delete execution
|
|
274
|
+
throw new Error('Not implemented');
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Navigate to a related entity or collection.
|
|
278
|
+
*/
|
|
279
|
+
navigate(navigation_property) {
|
|
280
|
+
// TODO: Implement navigation
|
|
281
|
+
const navigation = this.#entityset.navigations[navigation_property];
|
|
282
|
+
if (!navigation) {
|
|
283
|
+
throw new Error(`Navigation property '${String(navigation_property)}' not found`);
|
|
284
|
+
}
|
|
285
|
+
const targetEntitysetKey = navigation.targetEntitysetKey;
|
|
286
|
+
const newPath = `${this.#path}/${String(navigation_property)}`;
|
|
287
|
+
// Build QueryableEntity shape from schema at runtime
|
|
288
|
+
const actualTargetKey = typeof targetEntitysetKey === 'string'
|
|
289
|
+
? targetEntitysetKey
|
|
290
|
+
: Array.isArray(targetEntitysetKey) && targetEntitysetKey.length > 0
|
|
291
|
+
? targetEntitysetKey[0]
|
|
292
|
+
: '';
|
|
293
|
+
if (actualTargetKey && actualTargetKey in this.#schema.entitysets) {
|
|
294
|
+
const targetEntity = buildQueryableEntity(this.#schema, actualTargetKey);
|
|
295
|
+
if (navigation.collection) {
|
|
296
|
+
return new CollectionOperation(this.#schema, targetEntity, actualTargetKey, newPath, this.#options);
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
return new SingleOperation(this.#schema, targetEntity, actualTargetKey, newPath, this.#options);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// Fallback for union types or invalid targets
|
|
303
|
+
const fallbackEntity = buildQueryableEntity(this.#schema, actualTargetKey || '');
|
|
304
|
+
if (navigation.collection) {
|
|
305
|
+
return new CollectionOperation(this.#schema, fallbackEntity, actualTargetKey, newPath, this.#options);
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
return new SingleOperation(this.#schema, fallbackEntity, actualTargetKey, newPath, this.#options);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Execute a bound action on the entity.
|
|
313
|
+
*/
|
|
314
|
+
async action(name, payload) {
|
|
315
|
+
if (!this.#schema.actions || !(name in this.#schema.actions)) {
|
|
316
|
+
throw new Error(`Action '${String(name)}' not found`);
|
|
317
|
+
}
|
|
318
|
+
const actions = this.#schema.actions;
|
|
319
|
+
const actionDef = actions[name];
|
|
320
|
+
const parameterDefs = actionDef.parameters;
|
|
321
|
+
const namespace = this.#schema.namespace || '';
|
|
322
|
+
const request = buildActionRequest(this.#path, namespace, String(name), // Use action name, not import name
|
|
323
|
+
payload.parameters, parameterDefs, this.#schema, this.#options.baseUrl, true // Bound actions always use FQN
|
|
324
|
+
);
|
|
325
|
+
const response = await this.#options.transport(request);
|
|
326
|
+
if (!response.ok) {
|
|
327
|
+
let error;
|
|
328
|
+
try {
|
|
329
|
+
error = await response.json();
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
error = await response.text();
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
ok: false,
|
|
336
|
+
status: response.status,
|
|
337
|
+
statusText: response.statusText,
|
|
338
|
+
headers: response.headers,
|
|
339
|
+
result: { error },
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
if (response.status === 204) {
|
|
343
|
+
return {
|
|
344
|
+
ok: true,
|
|
345
|
+
status: 204,
|
|
346
|
+
statusText: response.statusText,
|
|
347
|
+
headers: response.headers,
|
|
348
|
+
result: {
|
|
349
|
+
data: undefined,
|
|
350
|
+
},
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
const json = await response.json();
|
|
354
|
+
const { value, ...odataProps } = json;
|
|
355
|
+
return {
|
|
356
|
+
ok: true,
|
|
357
|
+
status: response.status,
|
|
358
|
+
statusText: response.statusText,
|
|
359
|
+
headers: response.headers,
|
|
360
|
+
result: {
|
|
361
|
+
data: value !== undefined ? value : json,
|
|
362
|
+
...odataProps, // @odata.context, etc.
|
|
363
|
+
},
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Execute a bound function on the entity.
|
|
368
|
+
*/
|
|
369
|
+
async function(name, payload) {
|
|
370
|
+
if (!this.#schema.functions || !(name in this.#schema.functions)) {
|
|
371
|
+
throw new Error(`Function '${String(name)}' not found`);
|
|
372
|
+
}
|
|
373
|
+
const namespace = this.#schema.namespace || '';
|
|
374
|
+
const request = buildFunctionRequest(this.#path, namespace, String(name), // Use function name, not import name
|
|
375
|
+
payload.parameters, this.#options.baseUrl, true // Bound functions always use FQN
|
|
376
|
+
);
|
|
377
|
+
const response = await this.#options.transport(request);
|
|
378
|
+
if (!response.ok) {
|
|
379
|
+
let error;
|
|
380
|
+
try {
|
|
381
|
+
error = await response.json();
|
|
382
|
+
}
|
|
383
|
+
catch {
|
|
384
|
+
error = await response.text();
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
ok: false,
|
|
388
|
+
status: response.status,
|
|
389
|
+
statusText: response.statusText,
|
|
390
|
+
headers: response.headers,
|
|
391
|
+
result: { error },
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
const json = await response.json();
|
|
395
|
+
const { value, ...odataProps } = json;
|
|
396
|
+
if (value !== undefined) {
|
|
397
|
+
return {
|
|
398
|
+
ok: true,
|
|
399
|
+
status: response.status,
|
|
400
|
+
statusText: response.statusText,
|
|
401
|
+
headers: response.headers,
|
|
402
|
+
result: {
|
|
403
|
+
data: value,
|
|
404
|
+
...odataProps, // @odata.context, etc.
|
|
405
|
+
},
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
return {
|
|
409
|
+
ok: true,
|
|
410
|
+
status: response.status,
|
|
411
|
+
statusText: response.statusText,
|
|
412
|
+
headers: response.headers,
|
|
413
|
+
result: {
|
|
414
|
+
data: json,
|
|
415
|
+
...odataProps, // @odata.context, etc.
|
|
416
|
+
},
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { QueryableEntity, ODataTypeToTS, EntitySetsForEntityType, EntitySetToQueryableEntity } from './types';
|
|
2
|
+
import type { Schema, ODataType, NavigationType } from './schema';
|
|
3
|
+
type IsNullable<T> = null extends T ? true : false;
|
|
4
|
+
type Writable<T> = {
|
|
5
|
+
-readonly [K in keyof T]: T[K];
|
|
6
|
+
};
|
|
7
|
+
type WritablePropertyTypes<QE extends QueryableEntity> = Writable<QE['properties']>;
|
|
8
|
+
type RequiredCreateProperties<QE extends QueryableEntity> = {
|
|
9
|
+
[K in keyof QE['properties'] as IsNullable<QE['properties'][K]> extends true ? never : K]: WritablePropertyTypes<QE>[K];
|
|
10
|
+
};
|
|
11
|
+
type OptionalCreateProperties<QE extends QueryableEntity> = {
|
|
12
|
+
[K in keyof QE['properties'] as IsNullable<QE['properties'][K]> extends true ? K : never]?: WritablePropertyTypes<QE>[K];
|
|
13
|
+
};
|
|
14
|
+
type CreatePropertyTypes<QE extends QueryableEntity> = RequiredCreateProperties<QE> & OptionalCreateProperties<QE>;
|
|
15
|
+
type UpdatePropertyTypes<QE extends QueryableEntity> = {
|
|
16
|
+
[K in keyof QE['properties']]?: WritablePropertyTypes<QE>[K];
|
|
17
|
+
};
|
|
18
|
+
type NavTargetEntity<N extends {
|
|
19
|
+
target: any;
|
|
20
|
+
}> = N['target'];
|
|
21
|
+
type NavEntitysetKey<N extends {
|
|
22
|
+
targetEntitysetKey: string | string[];
|
|
23
|
+
}> = N['targetEntitysetKey'] extends string ? N['targetEntitysetKey'] : N['targetEntitysetKey'] extends (infer T)[] ? T : never;
|
|
24
|
+
type _CreateObject<QE extends QueryableEntity> = CreatePropertyTypes<QE> & CreateNavigationProperties<QE>;
|
|
25
|
+
type CreateNavigationValue<N extends {
|
|
26
|
+
collection: boolean;
|
|
27
|
+
target: any;
|
|
28
|
+
targetEntitysetKey: string | string[];
|
|
29
|
+
}> = N['collection'] extends true ? string[] | number[] | [NavEntitysetKey<N>, string | number][] | _CreateObject<NavTargetEntity<N>>[] : string | number | [NavEntitysetKey<N>, string | number] | _CreateObject<NavTargetEntity<N>>;
|
|
30
|
+
type CreateNavigationProperties<QE extends QueryableEntity> = {
|
|
31
|
+
[K in keyof QE['navigations']]?: CreateNavigationValue<QE['navigations'][K]>;
|
|
32
|
+
};
|
|
33
|
+
type SingleNavUpdateValue<N extends {
|
|
34
|
+
collection: boolean;
|
|
35
|
+
targetEntitysetKey: string | string[];
|
|
36
|
+
}> = N['collection'] extends true ? never : string | number | [NavEntitysetKey<N>, string | number] | null;
|
|
37
|
+
type CollectionNavUpdateSpec = {
|
|
38
|
+
replace?: (string | number | [string, string | number])[];
|
|
39
|
+
add?: (string | number | [string, string | number])[];
|
|
40
|
+
remove?: (string | number | [string, string | number])[];
|
|
41
|
+
};
|
|
42
|
+
type SingleNavUpdates<QE extends QueryableEntity> = {
|
|
43
|
+
[K in keyof QE['navigations'] as QE['navigations'][K]['collection'] extends true ? never : K]?: SingleNavUpdateValue<QE['navigations'][K]>;
|
|
44
|
+
};
|
|
45
|
+
type CollectionNavUpdates<QE extends QueryableEntity> = {
|
|
46
|
+
[K in keyof QE['navigations'] as QE['navigations'][K]['collection'] extends true ? K : never]?: CollectionNavUpdateSpec;
|
|
47
|
+
};
|
|
48
|
+
export type CreateObject<QE extends QueryableEntity> = _CreateObject<QE>;
|
|
49
|
+
export type UpdateObject<QE extends QueryableEntity> = UpdatePropertyTypes<QE> & SingleNavUpdates<QE> & CollectionNavUpdates<QE>;
|
|
50
|
+
export type CreateOperationOptions<QE extends QueryableEntity> = {
|
|
51
|
+
prefer?: {
|
|
52
|
+
return_representation?: boolean;
|
|
53
|
+
};
|
|
54
|
+
select?: readonly (keyof QE['properties'])[];
|
|
55
|
+
headers?: Record<string, string>;
|
|
56
|
+
};
|
|
57
|
+
export type UpdateOperationOptions<QE extends QueryableEntity> = {
|
|
58
|
+
prefer?: {
|
|
59
|
+
return_representation?: boolean;
|
|
60
|
+
};
|
|
61
|
+
select?: readonly (keyof QE['properties'])[];
|
|
62
|
+
headers?: Record<string, string>;
|
|
63
|
+
};
|
|
64
|
+
type NavigationParameterValue<S extends Schema<S>, N extends NavigationType<any>> = N extends NavigationType<infer Target> ? Target extends keyof S['entitytypes'] ? EntitySetsForEntityType<S, Target> extends infer EntitySetKey ? EntitySetKey extends string ? N['collection'] extends true ? string[] | number[] | [EntitySetKey, string | number][] | CreateObject<EntitySetToQueryableEntity<S, EntitySetKey>>[] : string | number | [EntitySetKey, string | number] | CreateObject<EntitySetToQueryableEntity<S, EntitySetKey>> : N['collection'] extends true ? string[] | number[] | [string, string | number][] | any[] : string | number | [string, string | number] | any : N['collection'] extends true ? string[] | number[] | [string, string | number][] | any[] : string | number | [string, string | number] | any : never : never;
|
|
65
|
+
type MappedParameters<S extends Schema<S>, P extends Record<string, ODataType<any>>> = {
|
|
66
|
+
[K in keyof P]: P[K] extends NavigationType<any> ? NavigationParameterValue<S, P[K]> : ODataTypeToTS<P[K], S>;
|
|
67
|
+
};
|
|
68
|
+
type RequiredOperationParameters<S extends Schema<S>, P extends Record<string, ODataType<any>>> = {
|
|
69
|
+
[K in keyof MappedParameters<S, P> as IsNullable<MappedParameters<S, P>[K]> extends true ? never : K]: MappedParameters<S, P>[K];
|
|
70
|
+
};
|
|
71
|
+
type OptionalOperationParameters<S extends Schema<S>, P extends Record<string, ODataType<any>>> = {
|
|
72
|
+
[K in keyof MappedParameters<S, P> as IsNullable<MappedParameters<S, P>[K]> extends true ? K : never]?: MappedParameters<S, P>[K];
|
|
73
|
+
};
|
|
74
|
+
export type OperationParameters<S extends Schema<S>, P extends Record<string, ODataType<any>>> = RequiredOperationParameters<S, P> & OptionalOperationParameters<S, P>;
|
|
75
|
+
export {};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* High-level negative filters applied by name.
|
|
3
|
+
* Each array is interpreted as a list of strings or RegExp patterns.
|
|
4
|
+
* For strings, the parser internally does `new RegExp(pattern)`.
|
|
5
|
+
*/
|
|
6
|
+
export interface ExcludeFilters {
|
|
7
|
+
/**
|
|
8
|
+
* Entity set names to exclude, e.g. ['^msdyn', /^tmp_/].
|
|
9
|
+
* Applied to entity set names, not type names.
|
|
10
|
+
*/
|
|
11
|
+
entities?: (string | RegExp)[];
|
|
12
|
+
/**
|
|
13
|
+
* Complex type names to exclude (FQN or short name patterns).
|
|
14
|
+
*/
|
|
15
|
+
complexTypes?: (string | RegExp)[];
|
|
16
|
+
/**
|
|
17
|
+
* Action names to exclude (bound and unbound).
|
|
18
|
+
*/
|
|
19
|
+
actions?: (string | RegExp)[];
|
|
20
|
+
/**
|
|
21
|
+
* Function names to exclude (bound and unbound).
|
|
22
|
+
*/
|
|
23
|
+
functions?: (string | RegExp)[];
|
|
24
|
+
/**
|
|
25
|
+
* Structural property names to exclude on all entity and complex types.
|
|
26
|
+
* Example: ['^adx', '^coop_(?!customerid$|personalnumber$|kimcustomerid$)'].
|
|
27
|
+
*/
|
|
28
|
+
properties?: (string | RegExp)[];
|
|
29
|
+
/**
|
|
30
|
+
* Navigation property names to exclude on all entity types.
|
|
31
|
+
*/
|
|
32
|
+
navigations?: (string | RegExp)[];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Hard masks that run after discovery and selection mode.
|
|
36
|
+
* Masks have higher precedence than `wanted*` and `only*` lists.
|
|
37
|
+
*/
|
|
38
|
+
export interface MaskRules {
|
|
39
|
+
/**
|
|
40
|
+
* Entity set or entity type names to completely hide.
|
|
41
|
+
* Matches against entity set name, short type name, or FQN.
|
|
42
|
+
*/
|
|
43
|
+
entities?: (string | RegExp)[];
|
|
44
|
+
/**
|
|
45
|
+
* Per-entity masks for bound actions.
|
|
46
|
+
* Key: entity identifier (set name, short type name, or FQN).
|
|
47
|
+
* Value:
|
|
48
|
+
* - 'ALL' → mask all bound actions for that entity.
|
|
49
|
+
* - string[] / RegExp[] → mask only matching bound actions.
|
|
50
|
+
*/
|
|
51
|
+
boundActionsByEntity?: Record<string, (string | RegExp)[] | 'ALL'>;
|
|
52
|
+
/**
|
|
53
|
+
* Per-entity masks for bound functions.
|
|
54
|
+
* Shape is identical to `boundActionsByEntity` but for functions.
|
|
55
|
+
*/
|
|
56
|
+
boundFunctionsByEntity?: Record<string, (string | RegExp)[] | 'ALL'>;
|
|
57
|
+
/**
|
|
58
|
+
* Global masks for unbound actions (by operation name).
|
|
59
|
+
*/
|
|
60
|
+
unboundActions?: (string | RegExp)[];
|
|
61
|
+
/**
|
|
62
|
+
* Global masks for unbound functions (by operation name).
|
|
63
|
+
*/
|
|
64
|
+
unboundFunctions?: (string | RegExp)[];
|
|
65
|
+
/**
|
|
66
|
+
* Per-entity whitelist for bound actions.
|
|
67
|
+
* Key: entity identifier (set name, short type name, or FQN).
|
|
68
|
+
* Value: names / patterns of bound actions to KEEP for that entity.
|
|
69
|
+
* When a rule exists for an entity, any non-matching bound action
|
|
70
|
+
* on that entity is implicitly removed.
|
|
71
|
+
*/
|
|
72
|
+
onlyBoundActionsByEntity?: Record<string, (string | RegExp)[]>;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* How `only*` lists are interpreted.
|
|
76
|
+
* - 'additive' (default): start from `wanted*` + dependencies, then filter.
|
|
77
|
+
* - 'only': treat `only*` lists as hard whitelists.
|
|
78
|
+
*/
|
|
79
|
+
export type SelectionMode = 'additive' | 'only';
|
|
80
|
+
/**
|
|
81
|
+
* Top-level configuration for the schema generator.
|
|
82
|
+
* This is what you pass to `defineConfig` in `odata-parser.config.ts`.
|
|
83
|
+
*/
|
|
84
|
+
export interface ParserConfig {
|
|
85
|
+
/**
|
|
86
|
+
* Path to the CSDL XML file, relative to the config file.
|
|
87
|
+
*/
|
|
88
|
+
inputPath: string;
|
|
89
|
+
/**
|
|
90
|
+
* Directory where the generated TypeScript schema file will be written.
|
|
91
|
+
* The generator will write `<outputPath>/generated-o-data-schema.ts`.
|
|
92
|
+
*/
|
|
93
|
+
outputPath: string;
|
|
94
|
+
/**
|
|
95
|
+
* List of entity set names you explicitly care about, or 'ALL'.
|
|
96
|
+
* - When array: acts as a whitelist for entity sets discovered via
|
|
97
|
+
* the entity container and navigation; navs never introduce sets
|
|
98
|
+
* that are not in this list.
|
|
99
|
+
* - When 'ALL': include all entity sets (still subject to excludes/masks).
|
|
100
|
+
*/
|
|
101
|
+
wantedEntities?: string[] | 'ALL';
|
|
102
|
+
/**
|
|
103
|
+
* Unbound actions to include, or 'ALL' for all unbound actions.
|
|
104
|
+
* Exclude filters and masks still apply on top.
|
|
105
|
+
*/
|
|
106
|
+
wantedUnboundActions?: string[] | 'ALL';
|
|
107
|
+
/**
|
|
108
|
+
* Unbound functions to include, or 'ALL' for all unbound functions.
|
|
109
|
+
* Exclude filters and masks still apply on top.
|
|
110
|
+
*/
|
|
111
|
+
wantedUnboundFunctions?: string[] | 'ALL';
|
|
112
|
+
/**
|
|
113
|
+
* Name-based negative filters applied early in discovery.
|
|
114
|
+
* Use these for broad "never include X" rules.
|
|
115
|
+
*/
|
|
116
|
+
excludeFilters?: ExcludeFilters;
|
|
117
|
+
/**
|
|
118
|
+
* Controls whether `only*` lists act as hard whitelists.
|
|
119
|
+
* - 'additive' (default): `wanted*` + dependencies → filter → masks.
|
|
120
|
+
* - 'only': final entities/operations must also be present in the
|
|
121
|
+
* corresponding `only*` list, if provided.
|
|
122
|
+
*/
|
|
123
|
+
selectionMode?: SelectionMode;
|
|
124
|
+
/**
|
|
125
|
+
* Additional whitelist for entity sets / types when `selectionMode === 'only'`.
|
|
126
|
+
* Entries can be entity set names or short entity type names.
|
|
127
|
+
*/
|
|
128
|
+
onlyEntities?: string[];
|
|
129
|
+
/**
|
|
130
|
+
* Global whitelist for bound actions when `selectionMode === 'only'`.
|
|
131
|
+
* Keeps only actions whose name appears here (after excludes/masks).
|
|
132
|
+
*/
|
|
133
|
+
onlyBoundActions?: string[];
|
|
134
|
+
/**
|
|
135
|
+
* Global whitelist for bound functions when `selectionMode === 'only'`.
|
|
136
|
+
*/
|
|
137
|
+
onlyBoundFunctions?: string[];
|
|
138
|
+
/**
|
|
139
|
+
* Global whitelist for unbound actions when `selectionMode === 'only'`.
|
|
140
|
+
*/
|
|
141
|
+
onlyUnboundActions?: string[];
|
|
142
|
+
/**
|
|
143
|
+
* Global whitelist for unbound functions when `selectionMode === 'only'`.
|
|
144
|
+
*/
|
|
145
|
+
onlyUnboundFunctions?: string[];
|
|
146
|
+
/**
|
|
147
|
+
* Hard masks and per-entity operation rules that run after discovery
|
|
148
|
+
* and selection mode. Masks always win over `wanted*` and `only*`.
|
|
149
|
+
*/
|
|
150
|
+
mask?: MaskRules;
|
|
151
|
+
}
|
|
152
|
+
export declare function defineConfig(config: ParserConfig): ParserConfig;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function generateSchema(configPath?: string): Promise<void>;
|