@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.
- package/README.md +443 -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/dist/batch.d.ts
ADDED
|
@@ -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
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
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
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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
|
}
|
package/dist/parser/index.js
CHANGED
|
@@ -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) {
|