@mkja/o-data 0.0.1 → 0.0.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/README.md +444 -416
- package/dist/batch.d.ts +78 -0
- package/dist/batch.js +313 -0
- package/dist/index.d.ts +13 -2
- package/dist/index.js +33 -68
- package/dist/parser/index.js +1 -1
- package/dist/response.d.ts +9 -31
- package/dist/schema.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,416 +1,444 @@
|
|
|
1
|
-
## o-data
|
|
2
|
-
|
|
3
|
-
**o-data** is a TypeScript‑first OData 4.01 client and schema generator.
|
|
4
|
-
|
|
5
|
-
It has two parts:
|
|
6
|
-
|
|
7
|
-
- **Runtime library** – a strongly‑typed client for querying and mutating OData services.
|
|
8
|
-
- **CLI parser / schema generator** – reads an OData CSDL XML document and generates a typed schema module the runtime can consume.
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## Features
|
|
13
|
-
|
|
14
|
-
- **Schema‑driven, fully typed client**
|
|
15
|
-
- Describe your service once in a TypeScript schema; get strong types for queries, payloads, and responses.
|
|
16
|
-
- **Fluent query builder**
|
|
17
|
-
- `$select`, `$expand` (with nested options), `$filter`, `$orderby`, `$top`, `$skip`, `$count`.
|
|
18
|
-
- `$filter` DSL with navigation, `any` / `all`, enums, dates, and string functions.
|
|
19
|
-
- **Navigation‑aware create/update**
|
|
20
|
-
- Supports `@odata.bind` for single and collection navigations, deep inserts, and batch references.
|
|
21
|
-
- **Actions & functions**
|
|
22
|
-
- Bound and unbound operations, with correct URL shapes and parameter serialization.
|
|
23
|
-
- **
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
},
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
.
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
},
|
|
313
|
-
});
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
1
|
+
## o-data
|
|
2
|
+
|
|
3
|
+
**o-data** is a TypeScript‑first OData 4.01 client and schema generator.
|
|
4
|
+
|
|
5
|
+
It has two parts:
|
|
6
|
+
|
|
7
|
+
- **Runtime library** – a strongly‑typed client for querying and mutating OData services.
|
|
8
|
+
- **CLI parser / schema generator** – reads an OData CSDL XML document and generates a typed schema module the runtime can consume.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- **Schema‑driven, fully typed client**
|
|
15
|
+
- Describe your service once in a TypeScript schema; get strong types for queries, payloads, and responses.
|
|
16
|
+
- **Fluent query builder**
|
|
17
|
+
- `$select`, `$expand` (with nested options), `$filter`, `$orderby`, `$top`, `$skip`, `$count`.
|
|
18
|
+
- `$filter` DSL with navigation, `any` / `all`, enums, dates, and string functions.
|
|
19
|
+
- **Navigation‑aware create/update**
|
|
20
|
+
- Supports `@odata.bind` for single and collection navigations, deep inserts, and batch references.
|
|
21
|
+
- **Actions & functions**
|
|
22
|
+
- Bound and unbound operations, with correct URL shapes and parameter serialization.
|
|
23
|
+
- **Batch requests ($batch)**
|
|
24
|
+
- Same fluent API as the client; queue operations and send them in a single multipart request. GET/query/function outside changesets; create/update/delete/action inside changesets.
|
|
25
|
+
- **Schema generator from CSDL**
|
|
26
|
+
- CLI reads your OData metadata XML and emits a typed `schema({...})` module.
|
|
27
|
+
- Powerful include/exclude/masking rules for keeping the generated surface small and relevant.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# with bun
|
|
35
|
+
bun add o-data
|
|
36
|
+
|
|
37
|
+
# or with npm
|
|
38
|
+
npm install o-data
|
|
39
|
+
|
|
40
|
+
# or with pnpm
|
|
41
|
+
pnpm add o-data
|
|
42
|
+
|
|
43
|
+
# or with yarn
|
|
44
|
+
yarn add o-data
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The runtime expects a `fetch`‑compatible environment (`Request`, `Response`, `Headers`); it works in modern Node (with `fetch`) and browsers.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Runtime: Getting started
|
|
52
|
+
|
|
53
|
+
### 1. Define or generate a schema
|
|
54
|
+
|
|
55
|
+
You can either write a schema by hand:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
// schema.ts
|
|
59
|
+
import { schema } from "o-data/schema";
|
|
60
|
+
|
|
61
|
+
export const crmSchema = schema({
|
|
62
|
+
namespace: "Microsoft.Dynamics.CRM",
|
|
63
|
+
alias: "mscrm",
|
|
64
|
+
enumtypes: {
|
|
65
|
+
IncidentStatus: {
|
|
66
|
+
isFlags: false,
|
|
67
|
+
members: {
|
|
68
|
+
Active: 0,
|
|
69
|
+
Resolved: 1,
|
|
70
|
+
Cancelled: 2,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
entitytypes: {
|
|
75
|
+
Incident: {
|
|
76
|
+
properties: {
|
|
77
|
+
id: { type: "Edm.Guid" },
|
|
78
|
+
title: { type: "Edm.String" },
|
|
79
|
+
status: { type: "enum", target: "IncidentStatus" },
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
entitysets: {
|
|
84
|
+
incidents: { entitytype: "Incident" },
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
…or generate one from a CSDL XML using the CLI (see **Schema generator (CLI)** below).
|
|
90
|
+
|
|
91
|
+
### 2. Create an `OdataClient`
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
// client.ts
|
|
95
|
+
import { OdataClient } from "o-data";
|
|
96
|
+
import { crmSchema } from "./schema"; // or generated-o-data-schema
|
|
97
|
+
|
|
98
|
+
const client = new OdataClient(crmSchema, {
|
|
99
|
+
baseUrl: "https://example.com/api/data/v9.0/",
|
|
100
|
+
transport: fetch, // any (req: Request) => Promise<Response>
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Querying data
|
|
107
|
+
|
|
108
|
+
OData response shapes: **collections** use `result.value`; **single-entity, CRUD, actions, and functions** use a flat `result` (data at top level alongside `@odata.*` metadata).
|
|
109
|
+
|
|
110
|
+
### Collection queries
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
// GET /incidents?$select=title,status&$top=10&$orderby=title asc
|
|
114
|
+
const response = await client.entitysets("incidents").query({
|
|
115
|
+
select: ["title", "status"],
|
|
116
|
+
top: 10,
|
|
117
|
+
orderby: ["title", "asc"],
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (response.ok) {
|
|
121
|
+
const incidents = response.result.value; // typed by schema + query
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Expands and nested options
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
// GET /incidents?$expand=incident_contact($select=name,email)
|
|
129
|
+
const res = await client.entitysets("incidents").query({
|
|
130
|
+
expand: {
|
|
131
|
+
incident_contact: {
|
|
132
|
+
select: ["name", "email"],
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Filter builder
|
|
139
|
+
|
|
140
|
+
Filters use a small builder DSL that respects your schema:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
// GET /incidents?$filter=status eq Namespace.IncidentStatus'Active'
|
|
144
|
+
const res = await client.entitysets("incidents").query({
|
|
145
|
+
filter: (h) => h.clause("status", "eq", "Active"),
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Navigation + logical operators
|
|
149
|
+
const res2 = await client.entitysets("incidents").query({
|
|
150
|
+
filter: (h) =>
|
|
151
|
+
h
|
|
152
|
+
.clause("title", "contains", "Support")
|
|
153
|
+
.and(
|
|
154
|
+
h.nav("incident_contact", (nh) =>
|
|
155
|
+
nh.clause("email", "eq", "user@example.com"),
|
|
156
|
+
),
|
|
157
|
+
),
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Supported operators include `eq`, `ne`, `gt`, `ge`, `lt`, `le`, `in`, `contains`, `startswith`, `endswith`.
|
|
162
|
+
For enums, you can pass either the member name (`"Active"`) or the underlying numeric value (`1`); they are serialized as FQN enum literals.
|
|
163
|
+
|
|
164
|
+
### Single‑entity queries and navigation
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
// GET /incidents(guid-123)?$select=title
|
|
168
|
+
const incident = await client
|
|
169
|
+
.entitysets("incidents")
|
|
170
|
+
.key("guid-123")
|
|
171
|
+
.query({ select: ["title"] });
|
|
172
|
+
|
|
173
|
+
// GET /incidents(guid-123)/incident_contact
|
|
174
|
+
const contact = await client
|
|
175
|
+
.entitysets("incidents")
|
|
176
|
+
.key("guid-123")
|
|
177
|
+
.navigate("incident_contact")
|
|
178
|
+
.query({});
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Creating and updating entities
|
|
184
|
+
|
|
185
|
+
The library infers create/update shapes from your schema and takes care of `@odata.bind` and deep inserts.
|
|
186
|
+
|
|
187
|
+
### Create
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
// Basic create
|
|
191
|
+
const created = await client.entitysets("incidents").create({
|
|
192
|
+
title: "New incident",
|
|
193
|
+
description: "Description",
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Create with navigation bind (single‑valued)
|
|
197
|
+
await client.entitysets("incidents").create({
|
|
198
|
+
title: "Linked to contact",
|
|
199
|
+
incident_contact: "guid-contact-id", // → "incident_contact@odata.bind": "/contacts(guid-contact-id)"
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Create with collection navigation bind
|
|
203
|
+
await client.entitysets("contacts").create({
|
|
204
|
+
name: "John",
|
|
205
|
+
contact_incidents: ["incident-id-1", "incident-id-2"],
|
|
206
|
+
// → "contact_incidents@odata.bind": ["/incidents(incident-id-1)", "/incidents(incident-id-2)"]
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// Deep insert
|
|
210
|
+
await client.entitysets("incidents").create({
|
|
211
|
+
title: "Deep insert example",
|
|
212
|
+
incident_contact: {
|
|
213
|
+
name: "Nested contact",
|
|
214
|
+
email: "nested@example.com",
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
You can control response shape via options:
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
await client.entitysets("incidents").create(
|
|
223
|
+
{ title: "Return representation" },
|
|
224
|
+
{
|
|
225
|
+
select: ["title", "description"],
|
|
226
|
+
prefer: { return_representation: true },
|
|
227
|
+
},
|
|
228
|
+
);
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Update
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
// Simple PATCH
|
|
235
|
+
await client.entitysets("incidents").key("guid-123").update({
|
|
236
|
+
title: "Updated title",
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Repoint single navigation
|
|
240
|
+
await client.entitysets("incidents").key("guid-123").update({
|
|
241
|
+
incident_contact: "guid-new-contact",
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Collection navigation operations
|
|
245
|
+
await client
|
|
246
|
+
.entitysets("contacts")
|
|
247
|
+
.key("guid-contact")
|
|
248
|
+
.update({
|
|
249
|
+
contact_incidents: {
|
|
250
|
+
add: ["incident-id-3"],
|
|
251
|
+
remove: ["incident-id-1"],
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Options for update mirror create: `select`, `prefer.return_representation`, custom headers.
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Batch requests
|
|
261
|
+
|
|
262
|
+
Use `client.batch()` to build a `$batch` request with the same fluent API. Operations are queued and sent in a single multipart request:
|
|
263
|
+
|
|
264
|
+
- **GET, query, function** – outside changesets (read-only)
|
|
265
|
+
- **Create, update, delete, action** – inside changesets (atomic)
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
const batch = client.batch();
|
|
269
|
+
|
|
270
|
+
batch.entitysets("incidents").query({ select: ["title"], top: 10 });
|
|
271
|
+
batch.entitysets("incidents").create({ title: "New" });
|
|
272
|
+
batch.entitysets("incidents").key("guid-123").update({ title: "Updated" });
|
|
273
|
+
batch.entitysets("incidents").key("guid-456").delete();
|
|
274
|
+
|
|
275
|
+
const response = await batch.execute();
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
`batch.execute()` returns the raw multipart `Response`; parsing individual operation responses is the application's responsibility. Use `batch.buildRequest()` to obtain the `Request` without sending it.
|
|
279
|
+
|
|
280
|
+
You can also use `.navigate(...)`, bound and unbound actions, and functions within a batch with the same API as the client.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Actions and functions
|
|
285
|
+
|
|
286
|
+
### Bound actions
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
// POST /incidents(guid-123)/Namespace.assignIncident
|
|
290
|
+
const res = await client
|
|
291
|
+
.entitysets("incidents")
|
|
292
|
+
.key("guid-123")
|
|
293
|
+
.action("assignIncident", {
|
|
294
|
+
parameters: {
|
|
295
|
+
assigneeId: "guid-user",
|
|
296
|
+
priority: 1,
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
if (res.ok) {
|
|
301
|
+
const ok: boolean = res.result.value; // mapped from Edm.Boolean (flat at top level)
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Unbound actions (via imports)
|
|
306
|
+
|
|
307
|
+
```ts
|
|
308
|
+
// POST /BulkCreate
|
|
309
|
+
const res = await client.action("BulkCreate", {
|
|
310
|
+
parameters: {
|
|
311
|
+
entities: ["1", "2", "3"],
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Bound functions
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
// GET /incidents(guid-123)/Namespace.getRelatedCount(relationType=@relationType)?@relationType='contact'
|
|
320
|
+
const res = await client
|
|
321
|
+
.entitysets("incidents")
|
|
322
|
+
.key("guid-123")
|
|
323
|
+
.function("getRelatedCount", {
|
|
324
|
+
parameters: { relationType: "contact" },
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
if (res.ok) {
|
|
328
|
+
const count: number = res.result.value; // flat at top level
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Unbound functions (via imports)
|
|
333
|
+
|
|
334
|
+
```ts
|
|
335
|
+
// GET /Search(query=@query,entityTypes=@entityTypes)?@query='test'&@entityTypes=...
|
|
336
|
+
const res = await client.function("Search", {
|
|
337
|
+
parameters: {
|
|
338
|
+
query: "test",
|
|
339
|
+
entityTypes: ["Incident", "Contact"],
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
For navigation‑typed parameters (actions/functions), you can use the same patterns as for create/update: IDs, `[entityset, id]`, deep insert objects, or arrays thereof; the library converts them to `@odata.bind` or nested objects as needed.
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## Schema generator (CLI)
|
|
349
|
+
|
|
350
|
+
The CLI reads an OData CSDL XML document and generates a strongly‑typed schema module that plugs into the runtime.
|
|
351
|
+
|
|
352
|
+
### 1. Create a config file
|
|
353
|
+
|
|
354
|
+
Create `odata-parser.config.js` (or `.ts`) in your project root:
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
// odata-parser.config.ts
|
|
358
|
+
import { defineConfig } from "o-data/parser";
|
|
359
|
+
|
|
360
|
+
export default defineConfig({
|
|
361
|
+
inputPath: "./metadata.xml",
|
|
362
|
+
outputPath: "./src/schema",
|
|
363
|
+
wantedEntities: "ALL", // or ['incidents', 'contacts']
|
|
364
|
+
wantedUnboundActions: "ALL",
|
|
365
|
+
wantedUnboundFunctions: "ALL",
|
|
366
|
+
excludeFilters: {
|
|
367
|
+
entities: [/^msdyn_/], // drop system sets
|
|
368
|
+
properties: [/^adx_/], // drop noisy props
|
|
369
|
+
},
|
|
370
|
+
selectionMode: "additive", // or 'only' for strict whitelists
|
|
371
|
+
// onlyEntities, onlyBoundActions, onlyUnboundActions, mask, ... are available
|
|
372
|
+
});
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Running the generator will produce e.g. `src/schema/generated-o-data-schema.ts` that looks like:
|
|
376
|
+
|
|
377
|
+
```ts
|
|
378
|
+
import { schema } from "o-data/schema";
|
|
379
|
+
|
|
380
|
+
export const myservice_schema = schema({
|
|
381
|
+
namespace: "My.Service",
|
|
382
|
+
alias: "ms",
|
|
383
|
+
enumtypes: { /* ... */ },
|
|
384
|
+
complextypes: { /* ... */ },
|
|
385
|
+
entitytypes: { /* ... */ },
|
|
386
|
+
entitysets: { /* ... */ },
|
|
387
|
+
actions: { /* ... */ },
|
|
388
|
+
functions: { /* ... */ },
|
|
389
|
+
actionImports: { /* ... */ },
|
|
390
|
+
functionImports: { /* ... */ },
|
|
391
|
+
});
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### 2. Run the CLI
|
|
395
|
+
|
|
396
|
+
From your project root:
|
|
397
|
+
|
|
398
|
+
```bash
|
|
399
|
+
# using the global CLI name exposed by this package
|
|
400
|
+
npx o-data path/to/odata-parser.config.js
|
|
401
|
+
|
|
402
|
+
# or (when installed locally in a Node/Bun project)
|
|
403
|
+
bun x o-data path/to/odata-parser.config.js
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
If you omit the path, the CLI looks for `odata-parser.config.js` (and then `.ts`) in the current working directory.
|
|
407
|
+
|
|
408
|
+
Then in your code:
|
|
409
|
+
|
|
410
|
+
```ts
|
|
411
|
+
import { myservice_schema } from "./schema/generated-o-data-schema";
|
|
412
|
+
import { OdataClient } from "o-data";
|
|
413
|
+
|
|
414
|
+
const client = new OdataClient(myservice_schema, {
|
|
415
|
+
baseUrl: "https://example.com/odata/",
|
|
416
|
+
transport: fetch,
|
|
417
|
+
});
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
422
|
+
## Status and limitations
|
|
423
|
+
|
|
424
|
+
- The library is still **early (0.0.x)**; APIs may change.
|
|
425
|
+
- Some operations are marked `TODO` in the runtime (e.g. delete support outside batch, collection‑bound actions/functions implementation, richer pagination).
|
|
426
|
+
- The generator doesn’t yet handle OData operation overloading beyond keeping the first operation per name.
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
## Development
|
|
431
|
+
|
|
432
|
+
- **Build**
|
|
433
|
+
|
|
434
|
+
```bash
|
|
435
|
+
bun x tsc -p tsconfig.build.json
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
- **Tests**
|
|
439
|
+
|
|
440
|
+
```bash
|
|
441
|
+
bun test
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
---
|