@mkja/o-data 0.0.1 → 0.0.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.
@@ -0,0 +1,78 @@
1
+ import type { Schema } from './schema';
2
+ import type { QueryableEntity, EntitySetToQueryableEntity, EntitySetToQueryableEntity as ResolveEntitySet, ImportedActionKeys, ImportedFunctionKeys, ResolveActionFromImport, ResolveFunctionFromImport, BoundActionKeysForEntitySet, BoundFunctionKeysForEntitySet } from './types';
3
+ import type { CollectionQueryObject, SingleQueryObject, QueryOperationOptions } from './query';
4
+ import type { CreateObject, UpdateObject, CreateOperationOptions, UpdateOperationOptions, OperationParameters } from './operations';
5
+ type Fetch = (input: Request, init?: RequestInit) => Promise<Response>;
6
+ export type OdataBatchClientOptions = {
7
+ baseUrl: string;
8
+ transport: Fetch;
9
+ };
10
+ type EntitySetNames<S extends Schema<S>> = keyof S['entitysets'];
11
+ type EntitySetToQE<S extends Schema<S>, ES extends EntitySetNames<S>> = EntitySetToQueryableEntity<S, ES>;
12
+ export declare class OdataBatch<S extends Schema<S>> {
13
+ #private;
14
+ constructor(schema: S, options: OdataBatchClientOptions);
15
+ /**
16
+ * Access an entityset within this batch.
17
+ */
18
+ entitysets<E extends EntitySetNames<S>>(entityset: E): BatchCollectionOperation<S, EntitySetToQE<S, E>, E>;
19
+ /**
20
+ * Execute an unbound global action in the batch (always in a changeset).
21
+ */
22
+ action<A extends ImportedActionKeys<S>>(name: A, payload: {
23
+ parameters: OperationParameters<S, NonNullable<S['actions']>[ResolveActionFromImport<S, A>]['parameters']>;
24
+ }): number;
25
+ /**
26
+ * Execute an unbound global function in the batch (never in a changeset).
27
+ */
28
+ function<F extends ImportedFunctionKeys<S>>(name: F, payload: {
29
+ parameters: OperationParameters<S, NonNullable<S['functions']>[ResolveFunctionFromImport<S, F>]['parameters']>;
30
+ }): number;
31
+ /**
32
+ * Add a prepared request to the batch.
33
+ */
34
+ private addRequest;
35
+ /**
36
+ * Build the HTTP Request representing this $batch.
37
+ *
38
+ * This does not execute the request itself.
39
+ */
40
+ buildRequest(): Promise<Request>;
41
+ /**
42
+ * Build the batch request and send it via the configured transport.
43
+ */
44
+ execute(): Promise<Response>;
45
+ }
46
+ /**
47
+ * Public API type for OdataBatch. Use this when typing batch variables.
48
+ * Excludes internal request-registry methods used by operation builders.
49
+ */
50
+ export type OdataBatchPublic<S extends Schema<S>> = Pick<OdataBatch<S>, 'entitysets' | 'action' | 'function' | 'buildRequest' | 'execute'>;
51
+ declare class BatchCollectionOperation<S extends Schema<S>, QE extends QueryableEntity, E extends EntitySetNames<S> = EntitySetNames<S>> {
52
+ #private;
53
+ constructor(batch: OdataBatch<S>, schema: S, entityset: QE, entitysetName: E, path: string, baseUrl: string);
54
+ query<Q extends CollectionQueryObject<QE, S>, O extends QueryOperationOptions>(q: Q, _o?: O): number;
55
+ create<O extends CreateOperationOptions<QE>>(c: CreateObject<QE>, o?: O): number;
56
+ key(key: string): BatchSingleOperation<S, QE, E>;
57
+ action<K extends BoundActionKeysForEntitySet<S, E, 'collection'>>(name: K, payload: {
58
+ parameters: OperationParameters<S, NonNullable<S['actions']>[K]['parameters']>;
59
+ }): number;
60
+ function<K extends BoundFunctionKeysForEntitySet<S, E, 'collection'>>(name: K, payload: {
61
+ parameters: OperationParameters<S, NonNullable<S['functions']>[K]['parameters']>;
62
+ }): number;
63
+ }
64
+ declare class BatchSingleOperation<S extends Schema<S>, QE extends QueryableEntity, E extends EntitySetNames<S> = EntitySetNames<S>> {
65
+ #private;
66
+ constructor(batch: OdataBatch<S>, schema: S, entityset: QE, entitysetName: E, path: string, baseUrl: string);
67
+ query<Q extends SingleQueryObject<QE, S>, O extends QueryOperationOptions>(q: Q, _o?: O): number;
68
+ update<O extends UpdateOperationOptions<QE>>(u: UpdateObject<QE>, o?: O): number;
69
+ delete(): number;
70
+ navigate<N extends keyof QE['navigations']>(navigation_property: N): QE['navigations'][N]['targetEntitysetKey'] extends string ? QE['navigations'][N]['collection'] extends true ? BatchCollectionOperation<S, ResolveEntitySet<S, QE['navigations'][N]['targetEntitysetKey']>> : BatchSingleOperation<S, ResolveEntitySet<S, QE['navigations'][N]['targetEntitysetKey']>> : QE['navigations'][N]['collection'] extends true ? BatchCollectionOperation<S, QueryableEntity> : BatchSingleOperation<S, QueryableEntity>;
71
+ action<K extends BoundActionKeysForEntitySet<S, E, 'entity'>>(name: K, payload: {
72
+ parameters: OperationParameters<S, NonNullable<S['actions']>[K]['parameters']>;
73
+ }): number;
74
+ function<K extends BoundFunctionKeysForEntitySet<S, E, 'entity'>>(name: K, payload: {
75
+ parameters: OperationParameters<S, NonNullable<S['functions']>[K]['parameters']>;
76
+ }): number;
77
+ }
78
+ export {};
package/dist/batch.js ADDED
@@ -0,0 +1,313 @@
1
+ // ============================================================================
2
+ // OData $batch support
3
+ // ============================================================================
4
+ import { buildQueryString, buildCreateRequest, buildUpdateRequest, buildActionRequest, buildFunctionRequest, normalizePath, } from './serialization.js';
5
+ import { buildQueryableEntity } from './runtime.js';
6
+ // ============================================================================
7
+ // Batch Builder
8
+ // ============================================================================
9
+ export class OdataBatch {
10
+ #schema;
11
+ #options;
12
+ #requests = [];
13
+ #nextId = 1;
14
+ constructor(schema, options) {
15
+ this.#schema = schema;
16
+ this.#options = options;
17
+ }
18
+ /**
19
+ * Access an entityset within this batch.
20
+ */
21
+ entitysets(entityset) {
22
+ const entity = buildQueryableEntity(this.#schema, String(entityset));
23
+ return new BatchCollectionOperation(this, this.#schema, entity, entityset, String(entityset), this.#options.baseUrl);
24
+ }
25
+ /**
26
+ * Execute an unbound global action in the batch (always in a changeset).
27
+ */
28
+ action(name, payload) {
29
+ const actionName = this.#schema.actionImports?.[name]?.action;
30
+ if (!actionName || !this.#schema.actions || !(actionName in this.#schema.actions)) {
31
+ throw new Error(`Action '${String(name)}' not found`);
32
+ }
33
+ const actionDef = this.#schema.actions[actionName];
34
+ const parameterDefs = actionDef.parameters;
35
+ const namespace = this.#schema.namespace || '';
36
+ const request = buildActionRequest('', namespace, String(name), payload.parameters, parameterDefs, this.#schema, this.#options.baseUrl, false);
37
+ return this.addRequest('action-unbound', request, true);
38
+ }
39
+ /**
40
+ * Execute an unbound global function in the batch (never in a changeset).
41
+ */
42
+ function(name, payload) {
43
+ const functionName = this.#schema.functionImports?.[name]?.function;
44
+ if (!functionName || !this.#schema.functions || !(functionName in this.#schema.functions)) {
45
+ throw new Error(`Function '${String(name)}' not found`);
46
+ }
47
+ const namespace = this.#schema.namespace || '';
48
+ const request = buildFunctionRequest('', namespace, String(name), payload.parameters, this.#options.baseUrl, false);
49
+ return this.addRequest('function-unbound', request, false);
50
+ }
51
+ /**
52
+ * Add a prepared request to the batch.
53
+ */
54
+ addRequest(kind, request, inChangeset) {
55
+ const id = this.#nextId++;
56
+ this.#requests.push({ id, kind, request, inChangeset });
57
+ return id;
58
+ }
59
+ /** @internal Used by operation builders to register requests. */
60
+ addCollectionQuery(request) {
61
+ return this.addRequest('query-collection', request, false);
62
+ }
63
+ /** @internal */
64
+ addSingleQuery(request) {
65
+ return this.addRequest('query-single', request, false);
66
+ }
67
+ /** @internal */
68
+ addCreate(request) {
69
+ return this.addRequest('create', request, true);
70
+ }
71
+ /** @internal */
72
+ addUpdate(request) {
73
+ return this.addRequest('update', request, true);
74
+ }
75
+ /** @internal */
76
+ addDelete(request) {
77
+ return this.addRequest('delete', request, true);
78
+ }
79
+ /** @internal */
80
+ addBoundCollectionAction(request) {
81
+ return this.addRequest('action-bound-collection', request, true);
82
+ }
83
+ /** @internal */
84
+ addBoundEntityAction(request) {
85
+ return this.addRequest('action-bound-entity', request, true);
86
+ }
87
+ /** @internal */
88
+ addBoundCollectionFunction(request) {
89
+ return this.addRequest('function-bound-collection', request, false);
90
+ }
91
+ /** @internal */
92
+ addBoundEntityFunction(request) {
93
+ return this.addRequest('function-bound-entity', request, false);
94
+ }
95
+ /**
96
+ * Build the HTTP Request representing this $batch.
97
+ *
98
+ * This does not execute the request itself.
99
+ */
100
+ async buildRequest() {
101
+ const batchBoundary = `batch_${Math.random().toString(36).slice(2)}`;
102
+ const lines = [];
103
+ // Use full pathname so the batch request line is e.g. "POST /api/data/v9.0/emails HTTP/1.1".
104
+ // Dynamics (and some other OData services) resolve relative URLs in batch from the host root,
105
+ // so a path relative to the service root (e.g. "/emails") would become https://host/emails and 404.
106
+ const toRelativePath = (url) => {
107
+ const fullUrl = new URL(url);
108
+ const path = fullUrl.pathname.replace(/\/+$/, '') || '/';
109
+ return path + fullUrl.search;
110
+ };
111
+ const pushPartForRequest = (req, bodyText, contentId) => {
112
+ lines.push(`Content-Type: application/http`);
113
+ lines.push(`Content-Transfer-Encoding: binary`);
114
+ if (contentId != null) {
115
+ lines.push(`Content-ID: ${contentId}`);
116
+ }
117
+ lines.push('');
118
+ const method = req.request.method || 'GET';
119
+ const relativeUrl = toRelativePath(req.request.url);
120
+ lines.push(`${method} ${relativeUrl} HTTP/1.1`);
121
+ req.request.headers.forEach((value, key) => {
122
+ if (key.toLowerCase() === 'host')
123
+ return;
124
+ lines.push(`${key}: ${value}`);
125
+ });
126
+ lines.push('');
127
+ if (bodyText) {
128
+ lines.push(bodyText);
129
+ }
130
+ };
131
+ lines.push(`--${batchBoundary}`);
132
+ let currentChangesetBoundary = null;
133
+ let currentChangesetHasOperations = false;
134
+ let currentContentId = 1;
135
+ const flushChangeset = () => {
136
+ if (currentChangesetBoundary && currentChangesetHasOperations) {
137
+ lines.push(`--${currentChangesetBoundary}--`);
138
+ lines.push(`--${batchBoundary}`);
139
+ }
140
+ currentChangesetBoundary = null;
141
+ currentChangesetHasOperations = false;
142
+ currentContentId = 1;
143
+ };
144
+ for (const req of this.#requests) {
145
+ const bodyText = req.request.body ? await req.request.clone().text() : '';
146
+ if (req.inChangeset) {
147
+ if (!currentChangesetBoundary) {
148
+ currentChangesetBoundary = `changeset_${Math.random().toString(36).slice(2)}`;
149
+ lines.push(`Content-Type: multipart/mixed; boundary=${currentChangesetBoundary}`);
150
+ lines.push('');
151
+ }
152
+ lines.push(`--${currentChangesetBoundary}`);
153
+ pushPartForRequest(req, bodyText, currentContentId++);
154
+ currentChangesetHasOperations = true;
155
+ }
156
+ else {
157
+ flushChangeset();
158
+ pushPartForRequest(req, bodyText);
159
+ lines.push(`--${batchBoundary}`);
160
+ }
161
+ }
162
+ flushChangeset();
163
+ lines[lines.length - 1] = `--${batchBoundary}--`;
164
+ const body = lines.join('\r\n');
165
+ const url = normalizePath(this.#options.baseUrl, '$batch');
166
+ const headers = new Headers({
167
+ 'Content-Type': `multipart/mixed; boundary=${batchBoundary}`,
168
+ });
169
+ return new Request(url, {
170
+ method: 'POST',
171
+ headers,
172
+ body,
173
+ });
174
+ }
175
+ /**
176
+ * Build the batch request and send it via the configured transport.
177
+ */
178
+ async execute() {
179
+ const request = await this.buildRequest();
180
+ return this.#options.transport(request);
181
+ }
182
+ }
183
+ // ============================================================================
184
+ // Batch Collection & Single Operations
185
+ // ============================================================================
186
+ class BatchCollectionOperation {
187
+ #batch;
188
+ #schema;
189
+ #entityset;
190
+ #entitysetName;
191
+ #path;
192
+ #baseUrl;
193
+ constructor(batch, schema, entityset, entitysetName, path, baseUrl) {
194
+ this.#batch = batch;
195
+ this.#schema = schema;
196
+ this.#entityset = entityset;
197
+ this.#entitysetName = entitysetName;
198
+ this.#path = path;
199
+ this.#baseUrl = baseUrl;
200
+ }
201
+ query(q, _o) {
202
+ const queryString = buildQueryString(q, this.#entityset, this.#schema);
203
+ const url = normalizePath(this.#baseUrl, this.#path + queryString);
204
+ const request = new Request(url, { method: 'GET' });
205
+ return this.#batch.addCollectionQuery(request);
206
+ }
207
+ create(c, o) {
208
+ const request = buildCreateRequest(this.#path, c, o, this.#baseUrl, this.#entityset, this.#schema);
209
+ return this.#batch.addCreate(request);
210
+ }
211
+ key(key) {
212
+ const newPath = `${this.#path}(${key})`;
213
+ return new BatchSingleOperation(this.#batch, this.#schema, this.#entityset, this.#entitysetName, newPath, this.#baseUrl);
214
+ }
215
+ action(name, payload) {
216
+ if (!this.#schema.actions || !(name in this.#schema.actions)) {
217
+ throw new Error(`Action '${String(name)}' not found`);
218
+ }
219
+ const actions = this.#schema.actions;
220
+ const actionDef = actions[name];
221
+ const parameterDefs = actionDef.parameters;
222
+ const namespace = this.#schema.namespace || '';
223
+ const request = buildActionRequest(this.#path, namespace, String(name), payload.parameters, parameterDefs, this.#schema, this.#baseUrl, true);
224
+ return this.#batch.addBoundCollectionAction(request);
225
+ }
226
+ function(name, payload) {
227
+ if (!this.#schema.functions || !(name in this.#schema.functions)) {
228
+ throw new Error(`Function '${String(name)}' not found`);
229
+ }
230
+ const namespace = this.#schema.namespace || '';
231
+ const request = buildFunctionRequest(this.#path, namespace, String(name), payload.parameters, this.#baseUrl, true);
232
+ return this.#batch.addBoundCollectionFunction(request);
233
+ }
234
+ }
235
+ class BatchSingleOperation {
236
+ #batch;
237
+ #schema;
238
+ #entityset;
239
+ #entitysetName;
240
+ #path;
241
+ #baseUrl;
242
+ constructor(batch, schema, entityset, entitysetName, path, baseUrl) {
243
+ this.#batch = batch;
244
+ this.#schema = schema;
245
+ this.#entityset = entityset;
246
+ this.#entitysetName = entitysetName;
247
+ this.#path = path;
248
+ this.#baseUrl = baseUrl;
249
+ }
250
+ query(q, _o) {
251
+ const queryString = buildQueryString(q, this.#entityset, this.#schema);
252
+ const url = normalizePath(this.#baseUrl, this.#path + queryString);
253
+ const request = new Request(url, { method: 'GET' });
254
+ return this.#batch.addSingleQuery(request);
255
+ }
256
+ update(u, o) {
257
+ const request = buildUpdateRequest(this.#path, u, o, this.#baseUrl, this.#entityset, this.#schema);
258
+ return this.#batch.addUpdate(request);
259
+ }
260
+ delete() {
261
+ const url = normalizePath(this.#baseUrl, this.#path);
262
+ const request = new Request(url, { method: 'DELETE' });
263
+ return this.#batch.addDelete(request);
264
+ }
265
+ navigate(navigation_property) {
266
+ const navigation = this.#entityset.navigations[navigation_property];
267
+ if (!navigation) {
268
+ throw new Error(`Navigation property '${String(navigation_property)}' not found`);
269
+ }
270
+ const targetEntitysetKey = navigation.targetEntitysetKey;
271
+ const newPath = `${this.#path}/${String(navigation_property)}`;
272
+ const actualTargetKey = typeof targetEntitysetKey === 'string'
273
+ ? targetEntitysetKey
274
+ : Array.isArray(targetEntitysetKey) && targetEntitysetKey.length > 0
275
+ ? targetEntitysetKey[0]
276
+ : '';
277
+ if (actualTargetKey && actualTargetKey in this.#schema.entitysets) {
278
+ const targetEntity = buildQueryableEntity(this.#schema, actualTargetKey);
279
+ if (navigation.collection) {
280
+ return new BatchCollectionOperation(this.#batch, this.#schema, targetEntity, actualTargetKey, newPath, this.#baseUrl);
281
+ }
282
+ else {
283
+ return new BatchSingleOperation(this.#batch, this.#schema, targetEntity, actualTargetKey, newPath, this.#baseUrl);
284
+ }
285
+ }
286
+ const fallbackEntity = buildQueryableEntity(this.#schema, actualTargetKey || '');
287
+ if (navigation.collection) {
288
+ return new BatchCollectionOperation(this.#batch, this.#schema, fallbackEntity, actualTargetKey, newPath, this.#baseUrl);
289
+ }
290
+ else {
291
+ return new BatchSingleOperation(this.#batch, this.#schema, fallbackEntity, actualTargetKey, newPath, this.#baseUrl);
292
+ }
293
+ }
294
+ action(name, payload) {
295
+ if (!this.#schema.actions || !(name in this.#schema.actions)) {
296
+ throw new Error(`Action '${String(name)}' not found`);
297
+ }
298
+ const actions = this.#schema.actions;
299
+ const actionDef = actions[name];
300
+ const parameterDefs = actionDef.parameters;
301
+ const namespace = this.#schema.namespace || '';
302
+ const request = buildActionRequest(this.#path, namespace, String(name), payload.parameters, parameterDefs, this.#schema, this.#baseUrl, true);
303
+ return this.#batch.addBoundEntityAction(request);
304
+ }
305
+ function(name, payload) {
306
+ if (!this.#schema.functions || !(name in this.#schema.functions)) {
307
+ throw new Error(`Function '${String(name)}' not found`);
308
+ }
309
+ const namespace = this.#schema.namespace || '';
310
+ const request = buildFunctionRequest(this.#path, namespace, String(name), payload.parameters, this.#baseUrl, true);
311
+ return this.#batch.addBoundEntityFunction(request);
312
+ }
313
+ }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  import type { Schema } from './schema';
2
2
  import type { QueryableEntity, EntitySetToQueryableEntity, EntitySetToQueryableEntity as ResolveEntitySet, ImportedActionKeys, ImportedFunctionKeys, ResolveActionFromImport, ResolveFunctionFromImport, BoundActionKeysForEntitySet, BoundFunctionKeysForEntitySet } from './types';
3
+ import { OdataBatch } from './batch.js';
4
+ import type { OdataBatchPublic } from './batch.js';
5
+ export { OdataBatch };
6
+ export type { OdataBatchPublic };
3
7
  import type { CollectionQueryResponse, SingleQueryResponse, CreateResponse, UpdateResponse, DeleteResponse, ActionResponse, FunctionResponse } from './response';
4
8
  import type { CollectionQueryObject, SingleQueryObject, QueryOperationOptions } from './query';
5
9
  import type { CreateObject, UpdateObject, CreateOperationOptions, UpdateOperationOptions, OperationParameters } from './operations';
@@ -29,6 +33,14 @@ export declare class OdataClient<S extends Schema<S>> {
29
33
  function<F extends ImportedFunctionKeys<S>>(name: F, payload: {
30
34
  parameters: OperationParameters<S, NonNullable<S['functions']>[ResolveFunctionFromImport<S, F>]['parameters']>;
31
35
  }): Promise<FunctionResponse<S, NonNullable<S['functions']>[ResolveFunctionFromImport<S, F>]['returnType']>>;
36
+ /**
37
+ * Create a new batch builder.
38
+ *
39
+ * The returned batch can be used with the same fluent API surface as the
40
+ * regular client, but operations are queued into a $batch request instead
41
+ * of being executed immediately.
42
+ */
43
+ batch(): OdataBatchPublic<S>;
32
44
  }
33
45
  declare class CollectionOperation<S extends Schema<S>, QE extends QueryableEntity, E extends EntitySetNames<S> = EntitySetNames<S>> {
34
46
  #private;
@@ -68,7 +80,7 @@ declare class SingleOperation<S extends Schema<S>, QE extends QueryableEntity, E
68
80
  /**
69
81
  * Query a single entity.
70
82
  */
71
- query<Q extends SingleQueryObject<QE, S>, O extends QueryOperationOptions>(q: Q, o?: O): Promise<SingleQueryResponse<QE, Q, O>>;
83
+ query<Q extends SingleQueryObject<QE, S>, O extends QueryOperationOptions>(q: Q, o?: O): Promise<SingleQueryResponse<QE, Q, O, S>>;
72
84
  /**
73
85
  * Build the full URL for this operation.
74
86
  */
@@ -98,4 +110,3 @@ declare class SingleOperation<S extends Schema<S>, QE extends QueryableEntity, E
98
110
  parameters: OperationParameters<S, NonNullable<S['functions']>[K]['parameters']>;
99
111
  }): Promise<FunctionResponse<S, NonNullable<S['functions']>[K]['returnType']>>;
100
112
  }
101
- export {};
package/dist/index.js CHANGED
@@ -2,6 +2,8 @@
2
2
  // OData Client Implementation
3
3
  // ============================================================================
4
4
  import { buildQueryableEntity } from './runtime.js';
5
+ import { OdataBatch } from './batch.js';
6
+ export { OdataBatch };
5
7
  import { buildQueryString, buildCreateRequest, buildUpdateRequest, buildActionRequest, buildFunctionRequest } from './serialization.js';
6
8
  // ============================================================================
7
9
  // OdataClient
@@ -51,28 +53,13 @@ export class OdataClient {
51
53
  result: { error },
52
54
  };
53
55
  }
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;
56
+ const result = response.status === 204 ? {} : await response.json();
67
57
  return {
68
58
  ok: true,
69
59
  status: response.status,
70
60
  statusText: response.statusText,
71
61
  headers: response.headers,
72
- result: {
73
- data: value !== undefined ? value : json,
74
- ...odataProps, // @odata.context, etc.
75
- },
62
+ result,
76
63
  };
77
64
  }
78
65
  /**
@@ -104,31 +91,34 @@ export class OdataClient {
104
91
  result: { error },
105
92
  };
106
93
  }
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
- };
94
+ let result;
95
+ try {
96
+ result = await response.json();
97
+ }
98
+ catch {
99
+ result = {};
120
100
  }
121
101
  return {
122
102
  ok: true,
123
103
  status: response.status,
124
104
  statusText: response.statusText,
125
105
  headers: response.headers,
126
- result: {
127
- data: json,
128
- ...odataProps, // @odata.context, etc.
129
- },
106
+ result,
130
107
  };
131
108
  }
109
+ /**
110
+ * Create a new batch builder.
111
+ *
112
+ * The returned batch can be used with the same fluent API surface as the
113
+ * regular client, but operations are queued into a $batch request instead
114
+ * of being executed immediately.
115
+ */
116
+ batch() {
117
+ return new OdataBatch(this.#schema, {
118
+ baseUrl: this.#options.baseUrl,
119
+ transport: this.#options.transport,
120
+ });
121
+ }
132
122
  }
133
123
  // ============================================================================
134
124
  // CollectionOperation
@@ -339,28 +329,13 @@ class SingleOperation {
339
329
  result: { error },
340
330
  };
341
331
  }
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;
332
+ const result = response.status === 204 ? {} : await response.json();
355
333
  return {
356
334
  ok: true,
357
335
  status: response.status,
358
336
  statusText: response.statusText,
359
337
  headers: response.headers,
360
- result: {
361
- data: value !== undefined ? value : json,
362
- ...odataProps, // @odata.context, etc.
363
- },
338
+ result,
364
339
  };
365
340
  }
366
341
  /**
@@ -391,29 +366,19 @@ class SingleOperation {
391
366
  result: { error },
392
367
  };
393
368
  }
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
- };
369
+ let result;
370
+ try {
371
+ result = await response.json();
372
+ }
373
+ catch {
374
+ result = {};
407
375
  }
408
376
  return {
409
377
  ok: true,
410
378
  status: response.status,
411
379
  statusText: response.statusText,
412
380
  headers: response.headers,
413
- result: {
414
- data: json,
415
- ...odataProps, // @odata.context, etc.
416
- },
381
+ result,
417
382
  };
418
383
  }
419
384
  }
@@ -953,7 +953,7 @@ export async function generateSchema(configPath) {
953
953
  return out;
954
954
  }
955
955
  // Generate schema output
956
- let out = `import { schema } from "o-data/schema";\n\n`;
956
+ let out = `import { schema } from "@mkja/o-data/schema";\n\n`;
957
957
  out += `export const ${namespace.replace(/\./g, '_').toLowerCase()}_schema = schema({\n`;
958
958
  out += ` namespace: "${namespace}",\n`;
959
959
  if (alias) {