s4kit 0.0.1 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +188 -0
- package/dist/cli.cjs +211 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +188 -0
- package/dist/index.cjs +1661 -0
- package/dist/index.d.cts +1175 -0
- package/dist/index.d.ts +1175 -0
- package/dist/index.js +1600 -0
- package/package.json +60 -6
- package/index.js +0 -1
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,1175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK configuration options
|
|
3
|
+
*/
|
|
4
|
+
interface S4KitConfig {
|
|
5
|
+
apiKey: string;
|
|
6
|
+
baseUrl?: string;
|
|
7
|
+
connection?: string;
|
|
8
|
+
service?: string;
|
|
9
|
+
timeout?: number;
|
|
10
|
+
retries?: number;
|
|
11
|
+
debug?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Complete OData query options with full type safety
|
|
15
|
+
* @template T - Entity type for type-safe field selection
|
|
16
|
+
*/
|
|
17
|
+
interface QueryOptions<T = any> {
|
|
18
|
+
/** Select specific fields - type-safe when T is provided */
|
|
19
|
+
select?: Array<keyof T & string>;
|
|
20
|
+
/**
|
|
21
|
+
* Filter expression - supports multiple formats:
|
|
22
|
+
* - String: `"Name eq 'John'"` (raw OData)
|
|
23
|
+
* - Object: `{ Name: 'John' }` (eq implied) or `{ Price: { gt: 100 } }`
|
|
24
|
+
* - Array: `[{ Category: 'A' }, { Price: { gt: 100 } }]` (AND)
|
|
25
|
+
*/
|
|
26
|
+
filter?: Filter<T>;
|
|
27
|
+
/** Maximum number of results to return */
|
|
28
|
+
top?: number;
|
|
29
|
+
/** Number of results to skip (for pagination) */
|
|
30
|
+
skip?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Sort expression - supports multiple formats:
|
|
33
|
+
* - String: `"Name desc"` or `"Price asc, Name desc"`
|
|
34
|
+
* - Object: `{ Name: 'desc' }` (type-safe)
|
|
35
|
+
* - Array: `[{ Price: 'desc' }, { Name: 'asc' }]`
|
|
36
|
+
*/
|
|
37
|
+
orderBy?: OrderBy<T>;
|
|
38
|
+
/**
|
|
39
|
+
* Expand navigation properties - supports multiple formats:
|
|
40
|
+
* - Array: `['Products', 'Category']`
|
|
41
|
+
* - Object: `{ Products: true }` or `{ Products: { select: ['Name'], top: 5 } }`
|
|
42
|
+
*/
|
|
43
|
+
expand?: Expand<T>;
|
|
44
|
+
/** Request inline count of total matching entities */
|
|
45
|
+
count?: boolean;
|
|
46
|
+
/** Full-text search query */
|
|
47
|
+
search?: string;
|
|
48
|
+
/** Override connection for this request */
|
|
49
|
+
connection?: string;
|
|
50
|
+
/** Override service for this request */
|
|
51
|
+
service?: string;
|
|
52
|
+
/** Get raw OData response with metadata (default: false) */
|
|
53
|
+
raw?: boolean;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Sort direction
|
|
57
|
+
*/
|
|
58
|
+
type OrderDirection = 'asc' | 'desc';
|
|
59
|
+
/**
|
|
60
|
+
* Type-safe orderBy object
|
|
61
|
+
* @example { Name: 'desc' } or { Price: 'asc', Name: 'desc' }
|
|
62
|
+
*/
|
|
63
|
+
type OrderByObject<T = any> = {
|
|
64
|
+
[K in keyof T & string]?: OrderDirection;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* All supported orderBy formats
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* // String (simple)
|
|
71
|
+
* orderBy: 'Name desc'
|
|
72
|
+
* orderBy: 'Price asc, Name desc'
|
|
73
|
+
*
|
|
74
|
+
* // Object (type-safe, recommended)
|
|
75
|
+
* orderBy: { Name: 'desc' }
|
|
76
|
+
* orderBy: { Price: 'asc' }
|
|
77
|
+
*
|
|
78
|
+
* // Array of objects (multiple sort criteria)
|
|
79
|
+
* orderBy: [{ Price: 'desc' }, { Name: 'asc' }]
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
type OrderBy<T = any> = string | OrderByObject<T> | Array<OrderByObject<T>>;
|
|
83
|
+
/**
|
|
84
|
+
* Filter comparison operators
|
|
85
|
+
*/
|
|
86
|
+
type FilterComparisonOperator = 'eq' | 'ne' | 'gt' | 'ge' | 'lt' | 'le';
|
|
87
|
+
/**
|
|
88
|
+
* Filter string operators
|
|
89
|
+
*/
|
|
90
|
+
type FilterStringOperator = 'contains' | 'startswith' | 'endswith';
|
|
91
|
+
/**
|
|
92
|
+
* Filter condition with operator
|
|
93
|
+
* @example { gt: 100 } or { contains: 'Pro' }
|
|
94
|
+
*/
|
|
95
|
+
type FilterCondition = {
|
|
96
|
+
[K in FilterComparisonOperator]?: string | number | boolean | null | Date;
|
|
97
|
+
} | {
|
|
98
|
+
[K in FilterStringOperator]?: string;
|
|
99
|
+
} | {
|
|
100
|
+
in?: (string | number)[];
|
|
101
|
+
} | {
|
|
102
|
+
between?: [number, number] | [Date, Date];
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Filter value - either direct value (eq implied) or condition object
|
|
106
|
+
* @example 'John' (equals 'John') or { gt: 100 } (greater than 100)
|
|
107
|
+
*/
|
|
108
|
+
type FilterValue = string | number | boolean | null | Date | FilterCondition;
|
|
109
|
+
/**
|
|
110
|
+
* Type-safe filter object
|
|
111
|
+
* @example
|
|
112
|
+
* ```ts
|
|
113
|
+
* // Simple equality (eq implied)
|
|
114
|
+
* filter: { Name: 'John' }
|
|
115
|
+
* filter: { Active: true, Category: 'Electronics' }
|
|
116
|
+
*
|
|
117
|
+
* // With operators
|
|
118
|
+
* filter: { Price: { gt: 100 } }
|
|
119
|
+
* filter: { Name: { contains: 'Pro' } }
|
|
120
|
+
* filter: { Status: { in: ['active', 'pending'] } }
|
|
121
|
+
*
|
|
122
|
+
* // Combined
|
|
123
|
+
* filter: { Category: 'Electronics', Price: { gt: 100, lt: 500 } }
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
type FilterObject<T = any> = {
|
|
127
|
+
[K in keyof T & string]?: FilterValue;
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* Logical operators for complex filters
|
|
131
|
+
* @example
|
|
132
|
+
* ```ts
|
|
133
|
+
* // OR conditions
|
|
134
|
+
* filter: { $or: [{ Status: 'active' }, { Role: 'admin' }] }
|
|
135
|
+
*
|
|
136
|
+
* // NOT condition
|
|
137
|
+
* filter: { $not: { Status: 'deleted' } }
|
|
138
|
+
*
|
|
139
|
+
* // Combined
|
|
140
|
+
* filter: {
|
|
141
|
+
* Category: 'Electronics',
|
|
142
|
+
* $or: [{ Price: { lt: 100 } }, { OnSale: true }]
|
|
143
|
+
* }
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
interface FilterLogical<T = any> {
|
|
147
|
+
/** OR - any condition matches */
|
|
148
|
+
$or?: FilterExpression<T>[];
|
|
149
|
+
/** AND - all conditions match (explicit, fields are implicitly ANDed) */
|
|
150
|
+
$and?: FilterExpression<T>[];
|
|
151
|
+
/** NOT - negate the condition */
|
|
152
|
+
$not?: FilterExpression<T>;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Complete filter expression (fields + logical operators)
|
|
156
|
+
*/
|
|
157
|
+
type FilterExpression<T = any> = FilterObject<T> & FilterLogical<T>;
|
|
158
|
+
/**
|
|
159
|
+
* All supported filter formats
|
|
160
|
+
* @example
|
|
161
|
+
* ```ts
|
|
162
|
+
* // String (OData syntax)
|
|
163
|
+
* filter: "Name eq 'John' and Price gt 100"
|
|
164
|
+
*
|
|
165
|
+
* // Object (type-safe, recommended)
|
|
166
|
+
* filter: { Name: 'John' } // eq implied
|
|
167
|
+
* filter: { Price: { gt: 100 } } // with operator
|
|
168
|
+
* filter: { Category: 'A', Price: { lt: 50 }} // multiple (AND)
|
|
169
|
+
*
|
|
170
|
+
* // Array (multiple conditions, AND implied)
|
|
171
|
+
* filter: [
|
|
172
|
+
* { Category: 'Electronics' },
|
|
173
|
+
* { Price: { gt: 100 } }
|
|
174
|
+
* ]
|
|
175
|
+
*
|
|
176
|
+
* // OR conditions
|
|
177
|
+
* filter: { $or: [{ Status: 'active' }, { Role: 'admin' }] }
|
|
178
|
+
*
|
|
179
|
+
* // NOT condition
|
|
180
|
+
* filter: { $not: { Status: 'deleted' } }
|
|
181
|
+
*
|
|
182
|
+
* // Complex nested
|
|
183
|
+
* filter: {
|
|
184
|
+
* Category: 'Electronics',
|
|
185
|
+
* $or: [
|
|
186
|
+
* { Price: { lt: 100 } },
|
|
187
|
+
* { $and: [{ OnSale: true }, { Stock: { gt: 0 } }] }
|
|
188
|
+
* ]
|
|
189
|
+
* }
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
type Filter<T = any> = string | FilterExpression<T> | FilterExpression<T>[];
|
|
193
|
+
/**
|
|
194
|
+
* Expand nested options (for object syntax)
|
|
195
|
+
* @example { select: ['Name'], filter: { Active: true }, top: 10 }
|
|
196
|
+
*/
|
|
197
|
+
interface ExpandNestedOptions {
|
|
198
|
+
/** Select specific fields from expanded entity */
|
|
199
|
+
select?: string[];
|
|
200
|
+
/** Filter expanded entities */
|
|
201
|
+
filter?: Filter<any>;
|
|
202
|
+
/** Limit expanded results */
|
|
203
|
+
top?: number;
|
|
204
|
+
/** Skip expanded results */
|
|
205
|
+
skip?: number;
|
|
206
|
+
/** Order expanded results */
|
|
207
|
+
orderBy?: OrderBy<any>;
|
|
208
|
+
/** Nested expand */
|
|
209
|
+
expand?: Expand<any>;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Expand value - true for simple include, or options object
|
|
213
|
+
*/
|
|
214
|
+
type ExpandValue = true | ExpandNestedOptions;
|
|
215
|
+
/**
|
|
216
|
+
* Object-style expand (Prisma-like)
|
|
217
|
+
* @example { Products: true } or { Products: { select: ['Name'], top: 5 } }
|
|
218
|
+
*/
|
|
219
|
+
type ExpandObject = Record<string, ExpandValue>;
|
|
220
|
+
/**
|
|
221
|
+
* All supported expand formats
|
|
222
|
+
* @example
|
|
223
|
+
* ```ts
|
|
224
|
+
* // Simple array
|
|
225
|
+
* expand: ['Products', 'Category']
|
|
226
|
+
*
|
|
227
|
+
* // Object with boolean (simple include)
|
|
228
|
+
* expand: { Products: true, Category: true }
|
|
229
|
+
*
|
|
230
|
+
* // Object with options
|
|
231
|
+
* expand: {
|
|
232
|
+
* Products: {
|
|
233
|
+
* select: ['Name', 'Price'],
|
|
234
|
+
* filter: { Active: true },
|
|
235
|
+
* top: 10,
|
|
236
|
+
* orderBy: { Name: 'asc' }
|
|
237
|
+
* }
|
|
238
|
+
* }
|
|
239
|
+
*
|
|
240
|
+
* // Nested expand
|
|
241
|
+
* expand: {
|
|
242
|
+
* Products: {
|
|
243
|
+
* select: ['Name'],
|
|
244
|
+
* expand: { Supplier: true }
|
|
245
|
+
* }
|
|
246
|
+
* }
|
|
247
|
+
* ```
|
|
248
|
+
*/
|
|
249
|
+
type Expand<T = any> = string[] | ExpandObject;
|
|
250
|
+
/**
|
|
251
|
+
* List response with optional count
|
|
252
|
+
*/
|
|
253
|
+
interface ListResponse<T> {
|
|
254
|
+
/** The data items */
|
|
255
|
+
value: T[];
|
|
256
|
+
/** Total count (when count: true is requested) */
|
|
257
|
+
count?: number;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Response with both data and metadata
|
|
261
|
+
*/
|
|
262
|
+
interface ODataResponse<T> {
|
|
263
|
+
value: T;
|
|
264
|
+
'@odata.context'?: string;
|
|
265
|
+
'@odata.count'?: number;
|
|
266
|
+
'@odata.nextLink'?: string;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Complete entity handler with all OData operations
|
|
270
|
+
*/
|
|
271
|
+
interface EntityHandler<T = any> {
|
|
272
|
+
/**
|
|
273
|
+
* List entities with optional query options
|
|
274
|
+
* @example
|
|
275
|
+
* ```ts
|
|
276
|
+
* const items = await client.sap.Products.list({ top: 10 });
|
|
277
|
+
* ```
|
|
278
|
+
*/
|
|
279
|
+
list(options?: QueryOptions<T>): Promise<T[]>;
|
|
280
|
+
/**
|
|
281
|
+
* List entities with count
|
|
282
|
+
* @example
|
|
283
|
+
* ```ts
|
|
284
|
+
* const { value, count } = await client.sap.Products.listWithCount({ top: 10 });
|
|
285
|
+
* console.log(`Showing ${value.length} of ${count} total`);
|
|
286
|
+
* ```
|
|
287
|
+
*/
|
|
288
|
+
listWithCount(options?: QueryOptions<T>): Promise<ListResponse<T>>;
|
|
289
|
+
/**
|
|
290
|
+
* Get single entity by key
|
|
291
|
+
* @example
|
|
292
|
+
* ```ts
|
|
293
|
+
* const product = await client.sap.Products.get(1);
|
|
294
|
+
* const partner = await client.sap.A_BusinessPartner.get('BP001');
|
|
295
|
+
* ```
|
|
296
|
+
*/
|
|
297
|
+
get(id: EntityKey, options?: QueryOptions<T>): Promise<T>;
|
|
298
|
+
/**
|
|
299
|
+
* Count matching entities
|
|
300
|
+
* @example
|
|
301
|
+
* ```ts
|
|
302
|
+
* const total = await client.sap.Products.count({ filter: "Price gt 100" });
|
|
303
|
+
* ```
|
|
304
|
+
*/
|
|
305
|
+
count(options?: Omit<QueryOptions<T>, 'top' | 'skip' | 'select'>): Promise<number>;
|
|
306
|
+
/**
|
|
307
|
+
* Create a new entity (POST)
|
|
308
|
+
* @example
|
|
309
|
+
* ```ts
|
|
310
|
+
* const created = await client.sap.Products.create({ Name: 'Widget', Price: 9.99 });
|
|
311
|
+
* ```
|
|
312
|
+
*/
|
|
313
|
+
create(data: Partial<T> | T, options?: QueryOptions<T>): Promise<T>;
|
|
314
|
+
/**
|
|
315
|
+
* Create with related entities (deep insert)
|
|
316
|
+
* @example
|
|
317
|
+
* ```ts
|
|
318
|
+
* const order = await client.sap.Orders.createDeep({
|
|
319
|
+
* OrderID: '001',
|
|
320
|
+
* Items: [{ Product: 'A', Quantity: 10 }]
|
|
321
|
+
* });
|
|
322
|
+
* ```
|
|
323
|
+
*/
|
|
324
|
+
createDeep(data: DeepInsertData<T>, options?: QueryOptions<T>): Promise<T>;
|
|
325
|
+
/**
|
|
326
|
+
* Partial update (PATCH) - only updates specified fields
|
|
327
|
+
* @example
|
|
328
|
+
* ```ts
|
|
329
|
+
* const updated = await client.sap.Products.update(1, { Price: 12.99 });
|
|
330
|
+
* ```
|
|
331
|
+
*/
|
|
332
|
+
update(id: EntityKey, data: Partial<T>, options?: QueryOptions<T>): Promise<T>;
|
|
333
|
+
/**
|
|
334
|
+
* Full replacement (PUT) - replaces entire entity
|
|
335
|
+
* @example
|
|
336
|
+
* ```ts
|
|
337
|
+
* const replaced = await client.sap.Products.replace(1, fullProductData);
|
|
338
|
+
* ```
|
|
339
|
+
*/
|
|
340
|
+
replace(id: EntityKey, data: T, options?: QueryOptions<T>): Promise<T>;
|
|
341
|
+
/**
|
|
342
|
+
* Upsert - create or update based on key
|
|
343
|
+
* @example
|
|
344
|
+
* ```ts
|
|
345
|
+
* const result = await client.sap.Products.upsert({ ID: 1, Name: 'Widget', Price: 9.99 });
|
|
346
|
+
* ```
|
|
347
|
+
*/
|
|
348
|
+
upsert(data: T, options?: QueryOptions<T>): Promise<T>;
|
|
349
|
+
/**
|
|
350
|
+
* Delete entity by key
|
|
351
|
+
* @example
|
|
352
|
+
* ```ts
|
|
353
|
+
* await client.sap.Products.delete(1);
|
|
354
|
+
* ```
|
|
355
|
+
*/
|
|
356
|
+
delete(id: EntityKey, options?: QueryOptions<T>): Promise<void>;
|
|
357
|
+
/**
|
|
358
|
+
* Access navigation property (related entities)
|
|
359
|
+
* @example
|
|
360
|
+
* ```ts
|
|
361
|
+
* const items = await client.sap.Orders.nav(1, 'Items').list();
|
|
362
|
+
* ```
|
|
363
|
+
*/
|
|
364
|
+
nav<R = any>(id: EntityKey, property: string): EntityHandler<R>;
|
|
365
|
+
/**
|
|
366
|
+
* Call an OData function (GET operation)
|
|
367
|
+
* @example
|
|
368
|
+
* ```ts
|
|
369
|
+
* const result = await client.sap.Products.func('GetTopSelling', { count: 10 });
|
|
370
|
+
* ```
|
|
371
|
+
*/
|
|
372
|
+
func<R = any>(name: string, params?: Record<string, any>): Promise<R>;
|
|
373
|
+
/**
|
|
374
|
+
* Call an OData action (POST operation)
|
|
375
|
+
* @example
|
|
376
|
+
* ```ts
|
|
377
|
+
* await client.sap.Orders.action('Approve', { orderId: '001' });
|
|
378
|
+
* ```
|
|
379
|
+
*/
|
|
380
|
+
action<R = any>(name: string, params?: Record<string, any>): Promise<R>;
|
|
381
|
+
/**
|
|
382
|
+
* Call bound function on specific entity
|
|
383
|
+
* @example
|
|
384
|
+
* ```ts
|
|
385
|
+
* const total = await client.sap.Orders.boundFunc(1, 'CalculateTotal');
|
|
386
|
+
* ```
|
|
387
|
+
*/
|
|
388
|
+
boundFunc<R = any>(id: EntityKey, name: string, params?: Record<string, any>): Promise<R>;
|
|
389
|
+
/**
|
|
390
|
+
* Call bound action on specific entity
|
|
391
|
+
* @example
|
|
392
|
+
* ```ts
|
|
393
|
+
* await client.sap.Orders.boundAction('001', 'Submit');
|
|
394
|
+
* ```
|
|
395
|
+
*/
|
|
396
|
+
boundAction<R = any>(id: EntityKey, name: string, params?: Record<string, any>): Promise<R>;
|
|
397
|
+
/**
|
|
398
|
+
* Paginate through all entities with automatic page handling
|
|
399
|
+
* @example
|
|
400
|
+
* ```ts
|
|
401
|
+
* // Iterate through all pages
|
|
402
|
+
* for await (const page of client.sap.Products.paginate({ top: 100 })) {
|
|
403
|
+
* console.log(`Processing ${page.value.length} items`);
|
|
404
|
+
* for (const product of page.value) {
|
|
405
|
+
* // process product
|
|
406
|
+
* }
|
|
407
|
+
* }
|
|
408
|
+
*
|
|
409
|
+
* // Or collect all items
|
|
410
|
+
* const all = await client.sap.Products.all({ filter: "Active eq true" });
|
|
411
|
+
* ```
|
|
412
|
+
*/
|
|
413
|
+
paginate(options?: PaginateOptions<T>): AsyncIterable<ListResponse<T>>;
|
|
414
|
+
/**
|
|
415
|
+
* Get all entities (automatically handles pagination)
|
|
416
|
+
* @example
|
|
417
|
+
* ```ts
|
|
418
|
+
* const allProducts = await client.sap.Products.all({ filter: "Active eq true" });
|
|
419
|
+
* ```
|
|
420
|
+
*/
|
|
421
|
+
all(options?: PaginateOptions<T>): Promise<T[]>;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Pagination options (extends QueryOptions)
|
|
425
|
+
*/
|
|
426
|
+
interface PaginateOptions<T = any> extends Omit<QueryOptions<T>, 'skip'> {
|
|
427
|
+
/** Page size (default: 100) */
|
|
428
|
+
pageSize?: number;
|
|
429
|
+
/** Maximum number of items to retrieve (default: unlimited) */
|
|
430
|
+
maxItems?: number;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Entity key type - supports string, number, or composite keys
|
|
434
|
+
*/
|
|
435
|
+
type EntityKey = string | number | CompositeKey;
|
|
436
|
+
/**
|
|
437
|
+
* Composite key for entities with multiple key fields
|
|
438
|
+
* @example
|
|
439
|
+
* ```ts
|
|
440
|
+
* const item = await client.sap.OrderItems.get({ OrderID: '001', ItemNo: 10 });
|
|
441
|
+
* ```
|
|
442
|
+
*/
|
|
443
|
+
type CompositeKey = Record<string, string | number>;
|
|
444
|
+
/**
|
|
445
|
+
* Deep insert data type
|
|
446
|
+
*/
|
|
447
|
+
type DeepInsertData<T> = T & {
|
|
448
|
+
[K in string]?: any[] | any;
|
|
449
|
+
};
|
|
450
|
+
/**
|
|
451
|
+
* Batch operation types
|
|
452
|
+
*/
|
|
453
|
+
type BatchOperation<T = any> = BatchGet<T> | BatchCreate<T> | BatchUpdate<T> | BatchDelete;
|
|
454
|
+
interface BatchGet<T = any> {
|
|
455
|
+
method: 'GET';
|
|
456
|
+
entity: string;
|
|
457
|
+
id?: EntityKey;
|
|
458
|
+
options?: QueryOptions<T>;
|
|
459
|
+
}
|
|
460
|
+
interface BatchCreate<T = any> {
|
|
461
|
+
method: 'POST';
|
|
462
|
+
entity: string;
|
|
463
|
+
data: Partial<T>;
|
|
464
|
+
}
|
|
465
|
+
interface BatchUpdate<T = any> {
|
|
466
|
+
method: 'PATCH' | 'PUT';
|
|
467
|
+
entity: string;
|
|
468
|
+
id: EntityKey;
|
|
469
|
+
data: Partial<T>;
|
|
470
|
+
}
|
|
471
|
+
interface BatchDelete {
|
|
472
|
+
method: 'DELETE';
|
|
473
|
+
entity: string;
|
|
474
|
+
id: EntityKey;
|
|
475
|
+
}
|
|
476
|
+
interface BatchResult<T = any> {
|
|
477
|
+
success: boolean;
|
|
478
|
+
status: number;
|
|
479
|
+
data?: T;
|
|
480
|
+
error?: ODataError;
|
|
481
|
+
}
|
|
482
|
+
interface Changeset {
|
|
483
|
+
operations: Array<BatchCreate<any> | BatchUpdate<any> | BatchDelete>;
|
|
484
|
+
}
|
|
485
|
+
interface ODataError {
|
|
486
|
+
code: string;
|
|
487
|
+
message: string;
|
|
488
|
+
target?: string;
|
|
489
|
+
details?: ODataErrorDetail[];
|
|
490
|
+
innererror?: {
|
|
491
|
+
message?: string;
|
|
492
|
+
type?: string;
|
|
493
|
+
stacktrace?: string;
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
interface ODataErrorDetail {
|
|
497
|
+
code: string;
|
|
498
|
+
message: string;
|
|
499
|
+
target?: string;
|
|
500
|
+
}
|
|
501
|
+
interface RequestInterceptor {
|
|
502
|
+
(request: InterceptedRequest): InterceptedRequest | Promise<InterceptedRequest>;
|
|
503
|
+
}
|
|
504
|
+
interface ResponseInterceptor {
|
|
505
|
+
(response: InterceptedResponse): InterceptedResponse | Promise<InterceptedResponse>;
|
|
506
|
+
}
|
|
507
|
+
interface ErrorInterceptor {
|
|
508
|
+
(error: IS4KitError): IS4KitError | Promise<IS4KitError>;
|
|
509
|
+
}
|
|
510
|
+
interface InterceptedRequest {
|
|
511
|
+
url: string;
|
|
512
|
+
method: string;
|
|
513
|
+
headers: Record<string, string>;
|
|
514
|
+
body?: any;
|
|
515
|
+
searchParams?: Record<string, string>;
|
|
516
|
+
}
|
|
517
|
+
interface InterceptedResponse {
|
|
518
|
+
status: number;
|
|
519
|
+
headers: Record<string, string>;
|
|
520
|
+
data: any;
|
|
521
|
+
}
|
|
522
|
+
interface IS4KitError extends Error {
|
|
523
|
+
readonly status?: number;
|
|
524
|
+
readonly code?: string;
|
|
525
|
+
readonly odataError?: ODataError;
|
|
526
|
+
readonly request?: InterceptedRequest;
|
|
527
|
+
readonly suggestion?: string;
|
|
528
|
+
readonly friendlyMessage: string;
|
|
529
|
+
readonly help: string;
|
|
530
|
+
readonly details: ODataErrorDetail[];
|
|
531
|
+
hasCode(code: string): boolean;
|
|
532
|
+
toJSON(): Record<string, any>;
|
|
533
|
+
}
|
|
534
|
+
type FilterOperator = 'eq' | 'ne' | 'gt' | 'ge' | 'lt' | 'le' | 'contains' | 'startswith' | 'endswith' | 'in' | 'has';
|
|
535
|
+
type LogicalOperator = 'and' | 'or' | 'not';
|
|
536
|
+
interface FluentQuery<T> {
|
|
537
|
+
select<K extends keyof T>(...fields: K[]): FluentQuery<Pick<T, K>>;
|
|
538
|
+
filter(expression: string): FluentQuery<T>;
|
|
539
|
+
where<K extends keyof T>(field: K, operator: FilterOperator, value: any): FluentQuery<T>;
|
|
540
|
+
and<K extends keyof T>(field: K, operator: FilterOperator, value: any): FluentQuery<T>;
|
|
541
|
+
or<K extends keyof T>(field: K, operator: FilterOperator, value: any): FluentQuery<T>;
|
|
542
|
+
orderBy<K extends keyof T>(field: K, direction?: 'asc' | 'desc'): FluentQuery<T>;
|
|
543
|
+
expand(property: string, nested?: (q: FluentQuery<any>) => FluentQuery<any>): FluentQuery<T>;
|
|
544
|
+
top(count: number): FluentQuery<T>;
|
|
545
|
+
skip(count: number): FluentQuery<T>;
|
|
546
|
+
search(term: string): FluentQuery<T>;
|
|
547
|
+
count(): FluentQuery<T>;
|
|
548
|
+
execute(): Promise<T[]>;
|
|
549
|
+
executeWithCount(): Promise<ListResponse<T>>;
|
|
550
|
+
first(): Promise<T | undefined>;
|
|
551
|
+
single(): Promise<T>;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
interface RequestOptions {
|
|
555
|
+
connection?: string;
|
|
556
|
+
service?: string;
|
|
557
|
+
raw?: boolean;
|
|
558
|
+
headers?: Record<string, string>;
|
|
559
|
+
}
|
|
560
|
+
interface HttpClientConfig extends S4KitConfig {
|
|
561
|
+
onRequest?: RequestInterceptor[];
|
|
562
|
+
onResponse?: ResponseInterceptor[];
|
|
563
|
+
onError?: ErrorInterceptor[];
|
|
564
|
+
}
|
|
565
|
+
declare class HttpClient {
|
|
566
|
+
private client;
|
|
567
|
+
private config;
|
|
568
|
+
private requestInterceptors;
|
|
569
|
+
private responseInterceptors;
|
|
570
|
+
private errorInterceptors;
|
|
571
|
+
private debug;
|
|
572
|
+
constructor(config: HttpClientConfig);
|
|
573
|
+
/**
|
|
574
|
+
* Add a request interceptor
|
|
575
|
+
* @example
|
|
576
|
+
* ```ts
|
|
577
|
+
* client.onRequest((req) => {
|
|
578
|
+
* console.log(`${req.method} ${req.url}`);
|
|
579
|
+
* return req;
|
|
580
|
+
* });
|
|
581
|
+
* ```
|
|
582
|
+
*/
|
|
583
|
+
onRequest(interceptor: RequestInterceptor): this;
|
|
584
|
+
/**
|
|
585
|
+
* Add a response interceptor
|
|
586
|
+
* @example
|
|
587
|
+
* ```ts
|
|
588
|
+
* client.onResponse((res) => {
|
|
589
|
+
* console.log(`Response: ${res.status}`);
|
|
590
|
+
* return res;
|
|
591
|
+
* });
|
|
592
|
+
* ```
|
|
593
|
+
*/
|
|
594
|
+
onResponse(interceptor: ResponseInterceptor): this;
|
|
595
|
+
/**
|
|
596
|
+
* Add an error interceptor
|
|
597
|
+
* @example
|
|
598
|
+
* ```ts
|
|
599
|
+
* client.onError((err) => {
|
|
600
|
+
* if (err.status === 401) {
|
|
601
|
+
* // Refresh token logic
|
|
602
|
+
* }
|
|
603
|
+
* return err;
|
|
604
|
+
* });
|
|
605
|
+
* ```
|
|
606
|
+
*/
|
|
607
|
+
onError(interceptor: ErrorInterceptor): this;
|
|
608
|
+
private log;
|
|
609
|
+
private buildHeaders;
|
|
610
|
+
private executeRequest;
|
|
611
|
+
get<T>(path: string, searchParams?: Record<string, any>, options?: RequestOptions): Promise<T>;
|
|
612
|
+
post<T>(path: string, json: any, options?: RequestOptions): Promise<T>;
|
|
613
|
+
put<T>(path: string, json: any, options?: RequestOptions): Promise<T>;
|
|
614
|
+
patch<T>(path: string, json: any, options?: RequestOptions): Promise<T>;
|
|
615
|
+
delete(path: string, options?: RequestOptions): Promise<void>;
|
|
616
|
+
/**
|
|
617
|
+
* Execute multiple operations in a single batch request
|
|
618
|
+
* @example
|
|
619
|
+
* ```ts
|
|
620
|
+
* const results = await client.batch([
|
|
621
|
+
* { method: 'GET', entity: 'Products', id: 1 },
|
|
622
|
+
* { method: 'POST', entity: 'Products', data: { Name: 'New' } },
|
|
623
|
+
* { method: 'DELETE', entity: 'Products', id: 2 },
|
|
624
|
+
* ]);
|
|
625
|
+
* ```
|
|
626
|
+
*/
|
|
627
|
+
batch<T = any>(operations: BatchOperation<T>[], options?: RequestOptions): Promise<BatchResult<T>[]>;
|
|
628
|
+
/**
|
|
629
|
+
* Execute operations in an atomic changeset (all succeed or all fail)
|
|
630
|
+
* @example
|
|
631
|
+
* ```ts
|
|
632
|
+
* const results = await client.changeset({
|
|
633
|
+
* operations: [
|
|
634
|
+
* { method: 'POST', entity: 'Orders', data: orderData },
|
|
635
|
+
* { method: 'POST', entity: 'OrderItems', data: itemData },
|
|
636
|
+
* ]
|
|
637
|
+
* });
|
|
638
|
+
* ```
|
|
639
|
+
*/
|
|
640
|
+
changeset<T = any>(changeset: Changeset, options?: RequestOptions): Promise<BatchResult<T>[]>;
|
|
641
|
+
private buildBatchBody;
|
|
642
|
+
private buildChangesetBody;
|
|
643
|
+
private buildBatchPart;
|
|
644
|
+
private formatKey;
|
|
645
|
+
private parseBatchResponse;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* S4Kit client methods (interceptors, batch operations)
|
|
650
|
+
*/
|
|
651
|
+
interface S4KitMethods {
|
|
652
|
+
onRequest(interceptor: RequestInterceptor): S4KitClient;
|
|
653
|
+
onResponse(interceptor: ResponseInterceptor): S4KitClient;
|
|
654
|
+
onError(interceptor: ErrorInterceptor): S4KitClient;
|
|
655
|
+
batch<T = any>(operations: BatchOperation<T>[]): Promise<BatchResult<T>[]>;
|
|
656
|
+
changeset<T = any>(changeset: Changeset): Promise<BatchResult<T>[]>;
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Augmentable interface for typed entity access.
|
|
660
|
+
* Generated type files augment this interface to provide type-safe entity properties.
|
|
661
|
+
*
|
|
662
|
+
* @example
|
|
663
|
+
* ```ts
|
|
664
|
+
* // In generated types file:
|
|
665
|
+
* declare module 's4kit' {
|
|
666
|
+
* interface S4KitClient {
|
|
667
|
+
* Customers: EntityHandler<Customer>;
|
|
668
|
+
* }
|
|
669
|
+
* }
|
|
670
|
+
* // Now client.Customers.list() returns Customer[]
|
|
671
|
+
* ```
|
|
672
|
+
*/
|
|
673
|
+
interface S4KitClient extends S4KitMethods {
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* S4Kit client type with dynamic entity access.
|
|
677
|
+
* Combines the augmentable interface with an index signature for runtime flexibility.
|
|
678
|
+
*
|
|
679
|
+
* @example client.Products.list() or client.A_BusinessPartner.get('123')
|
|
680
|
+
*/
|
|
681
|
+
type S4KitClientWithDynamicAccess = S4KitClient & {
|
|
682
|
+
/** Access any entity by name (fallback for non-augmented entities) */
|
|
683
|
+
[entityName: string]: EntityHandler<any>;
|
|
684
|
+
};
|
|
685
|
+
/**
|
|
686
|
+
* S4Kit - The simplest way to integrate with SAP S/4HANA
|
|
687
|
+
*
|
|
688
|
+
* @example
|
|
689
|
+
* ```ts
|
|
690
|
+
* import { S4Kit } from 's4kit';
|
|
691
|
+
*
|
|
692
|
+
* const client = new S4Kit({
|
|
693
|
+
* apiKey: 'sk_live_xxx',
|
|
694
|
+
* });
|
|
695
|
+
*
|
|
696
|
+
* // List entities - clean, simple API
|
|
697
|
+
* const partners = await client.A_BusinessPartner.list({
|
|
698
|
+
* top: 10,
|
|
699
|
+
* select: ['BusinessPartner', 'BusinessPartnerFullName'],
|
|
700
|
+
* });
|
|
701
|
+
*
|
|
702
|
+
* // Get single entity
|
|
703
|
+
* const partner = await client.A_BusinessPartner.get('BP001');
|
|
704
|
+
*
|
|
705
|
+
* // Create entity
|
|
706
|
+
* const created = await client.A_BusinessPartner.create({
|
|
707
|
+
* BusinessPartnerFullName: 'Acme Corp',
|
|
708
|
+
* });
|
|
709
|
+
*
|
|
710
|
+
* // Update entity
|
|
711
|
+
* const updated = await client.A_BusinessPartner.update('BP001', {
|
|
712
|
+
* BusinessPartnerFullName: 'Acme Corporation',
|
|
713
|
+
* });
|
|
714
|
+
*
|
|
715
|
+
* // Delete entity
|
|
716
|
+
* await client.A_BusinessPartner.delete('BP001');
|
|
717
|
+
* ```
|
|
718
|
+
*/
|
|
719
|
+
declare class S4KitBase {
|
|
720
|
+
protected httpClient: HttpClient;
|
|
721
|
+
constructor(config: S4KitConfig | HttpClientConfig);
|
|
722
|
+
/**
|
|
723
|
+
* Add a request interceptor
|
|
724
|
+
* @example
|
|
725
|
+
* ```ts
|
|
726
|
+
* client.onRequest((req) => {
|
|
727
|
+
* console.log(`${req.method} ${req.url}`);
|
|
728
|
+
* return req;
|
|
729
|
+
* });
|
|
730
|
+
* ```
|
|
731
|
+
*/
|
|
732
|
+
onRequest(interceptor: RequestInterceptor): this;
|
|
733
|
+
/**
|
|
734
|
+
* Add a response interceptor
|
|
735
|
+
* @example
|
|
736
|
+
* ```ts
|
|
737
|
+
* client.onResponse((res) => {
|
|
738
|
+
* console.log(`Response: ${res.status}`);
|
|
739
|
+
* return res;
|
|
740
|
+
* });
|
|
741
|
+
* ```
|
|
742
|
+
*/
|
|
743
|
+
onResponse(interceptor: ResponseInterceptor): this;
|
|
744
|
+
/**
|
|
745
|
+
* Add an error interceptor
|
|
746
|
+
* @example
|
|
747
|
+
* ```ts
|
|
748
|
+
* client.onError((err) => {
|
|
749
|
+
* console.error(`Error: ${err.message}`);
|
|
750
|
+
* return err;
|
|
751
|
+
* });
|
|
752
|
+
* ```
|
|
753
|
+
*/
|
|
754
|
+
onError(interceptor: ErrorInterceptor): this;
|
|
755
|
+
/**
|
|
756
|
+
* Execute multiple operations in a single batch request
|
|
757
|
+
*
|
|
758
|
+
* @example
|
|
759
|
+
* ```ts
|
|
760
|
+
* const results = await client.batch([
|
|
761
|
+
* { method: 'GET', entity: 'Products', id: 1 },
|
|
762
|
+
* { method: 'POST', entity: 'Products', data: { Name: 'New Product' } },
|
|
763
|
+
* { method: 'PATCH', entity: 'Products', id: 2, data: { Price: 29.99 } },
|
|
764
|
+
* { method: 'DELETE', entity: 'Products', id: 3 },
|
|
765
|
+
* ]);
|
|
766
|
+
*
|
|
767
|
+
* // Check results
|
|
768
|
+
* for (const result of results) {
|
|
769
|
+
* if (result.success) {
|
|
770
|
+
* console.log('Success:', result.data);
|
|
771
|
+
* } else {
|
|
772
|
+
* console.error('Failed:', result.error);
|
|
773
|
+
* }
|
|
774
|
+
* }
|
|
775
|
+
* ```
|
|
776
|
+
*/
|
|
777
|
+
batch<T = any>(operations: BatchOperation<T>[]): Promise<BatchResult<T>[]>;
|
|
778
|
+
/**
|
|
779
|
+
* Execute operations in an atomic changeset (all succeed or all fail)
|
|
780
|
+
*
|
|
781
|
+
* @example
|
|
782
|
+
* ```ts
|
|
783
|
+
* // All operations succeed or all fail together
|
|
784
|
+
* const results = await client.changeset({
|
|
785
|
+
* operations: [
|
|
786
|
+
* { method: 'POST', entity: 'Orders', data: orderData },
|
|
787
|
+
* { method: 'POST', entity: 'OrderItems', data: item1Data },
|
|
788
|
+
* { method: 'POST', entity: 'OrderItems', data: item2Data },
|
|
789
|
+
* ]
|
|
790
|
+
* });
|
|
791
|
+
* ```
|
|
792
|
+
*/
|
|
793
|
+
changeset<T = any>(changeset: Changeset): Promise<BatchResult<T>[]>;
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Create S4Kit client with dynamic entity access
|
|
797
|
+
* Allows: client.Products.list() instead of client.sap.Products.list()
|
|
798
|
+
*
|
|
799
|
+
* Returns S4KitClientWithDynamicAccess which:
|
|
800
|
+
* - Without generated types: allows any entity access (returns EntityHandler<any>)
|
|
801
|
+
* - With generated types: provides full type inference for known entities
|
|
802
|
+
*/
|
|
803
|
+
declare function S4Kit(config: S4KitConfig | HttpClientConfig): S4KitClientWithDynamicAccess;
|
|
804
|
+
declare namespace S4Kit {
|
|
805
|
+
var prototype: S4KitBase;
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Create an S4Kit client instance
|
|
809
|
+
*
|
|
810
|
+
* @example
|
|
811
|
+
* ```ts
|
|
812
|
+
* import { createClient } from 's4kit';
|
|
813
|
+
*
|
|
814
|
+
* const client = createClient({
|
|
815
|
+
* apiKey: 'sk_live_xxx',
|
|
816
|
+
* });
|
|
817
|
+
*
|
|
818
|
+
* const products = await client.Products.list({ top: 10 });
|
|
819
|
+
* ```
|
|
820
|
+
*/
|
|
821
|
+
declare function createClient(config: S4KitConfig): S4KitClientWithDynamicAccess;
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* Base S4Kit error class with enhanced OData support
|
|
825
|
+
*/
|
|
826
|
+
declare class S4KitError extends Error implements IS4KitError {
|
|
827
|
+
readonly status?: number;
|
|
828
|
+
readonly code?: string;
|
|
829
|
+
readonly odataError?: ODataError;
|
|
830
|
+
readonly request?: InterceptedRequest;
|
|
831
|
+
readonly suggestion?: string;
|
|
832
|
+
constructor(message: string, options?: {
|
|
833
|
+
status?: number;
|
|
834
|
+
code?: string;
|
|
835
|
+
odataError?: ODataError;
|
|
836
|
+
request?: InterceptedRequest;
|
|
837
|
+
cause?: Error;
|
|
838
|
+
suggestion?: string;
|
|
839
|
+
});
|
|
840
|
+
/**
|
|
841
|
+
* Get a user-friendly error message with context
|
|
842
|
+
*/
|
|
843
|
+
get friendlyMessage(): string;
|
|
844
|
+
/**
|
|
845
|
+
* Get actionable help text
|
|
846
|
+
*/
|
|
847
|
+
get help(): string;
|
|
848
|
+
private extractEntityFromUrl;
|
|
849
|
+
/**
|
|
850
|
+
* Get all error details (for validation errors with multiple issues)
|
|
851
|
+
*/
|
|
852
|
+
get details(): ODataErrorDetail[];
|
|
853
|
+
/**
|
|
854
|
+
* Check if this is a specific OData error code
|
|
855
|
+
*/
|
|
856
|
+
hasCode(code: string): boolean;
|
|
857
|
+
/**
|
|
858
|
+
* Convert to a plain object for logging
|
|
859
|
+
*/
|
|
860
|
+
toJSON(): {
|
|
861
|
+
name: string;
|
|
862
|
+
message: string;
|
|
863
|
+
status: number | undefined;
|
|
864
|
+
code: string | undefined;
|
|
865
|
+
odataError: ODataError | undefined;
|
|
866
|
+
request: {
|
|
867
|
+
method: string;
|
|
868
|
+
url: string;
|
|
869
|
+
} | undefined;
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* Network/connection error
|
|
874
|
+
*/
|
|
875
|
+
declare class NetworkError extends S4KitError {
|
|
876
|
+
constructor(message: string, options?: {
|
|
877
|
+
cause?: Error;
|
|
878
|
+
request?: InterceptedRequest;
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* Request timeout error
|
|
883
|
+
*/
|
|
884
|
+
declare class TimeoutError extends S4KitError {
|
|
885
|
+
constructor(timeout: number, options?: {
|
|
886
|
+
request?: InterceptedRequest;
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Authentication error (401)
|
|
891
|
+
*/
|
|
892
|
+
declare class AuthenticationError extends S4KitError {
|
|
893
|
+
constructor(message?: string, options?: {
|
|
894
|
+
odataError?: ODataError;
|
|
895
|
+
request?: InterceptedRequest;
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Authorization error (403)
|
|
900
|
+
*/
|
|
901
|
+
declare class AuthorizationError extends S4KitError {
|
|
902
|
+
constructor(message?: string, options?: {
|
|
903
|
+
odataError?: ODataError;
|
|
904
|
+
request?: InterceptedRequest;
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* Resource not found error (404)
|
|
909
|
+
*/
|
|
910
|
+
declare class NotFoundError extends S4KitError {
|
|
911
|
+
constructor(entity?: string, id?: string | number, options?: {
|
|
912
|
+
odataError?: ODataError;
|
|
913
|
+
request?: InterceptedRequest;
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Validation error (400) with field-level details
|
|
918
|
+
*/
|
|
919
|
+
declare class ValidationError extends S4KitError {
|
|
920
|
+
readonly fieldErrors: Map<string, string>;
|
|
921
|
+
constructor(message: string, options?: {
|
|
922
|
+
odataError?: ODataError;
|
|
923
|
+
request?: InterceptedRequest;
|
|
924
|
+
});
|
|
925
|
+
/**
|
|
926
|
+
* Get error for a specific field
|
|
927
|
+
*/
|
|
928
|
+
getFieldError(field: string): string | undefined;
|
|
929
|
+
/**
|
|
930
|
+
* Check if a specific field has an error
|
|
931
|
+
*/
|
|
932
|
+
hasFieldError(field: string): boolean;
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* Conflict error (409) - e.g., optimistic locking failure
|
|
936
|
+
*/
|
|
937
|
+
declare class ConflictError extends S4KitError {
|
|
938
|
+
constructor(message?: string, options?: {
|
|
939
|
+
odataError?: ODataError;
|
|
940
|
+
request?: InterceptedRequest;
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* Rate limit exceeded error (429)
|
|
945
|
+
*/
|
|
946
|
+
declare class RateLimitError extends S4KitError {
|
|
947
|
+
readonly retryAfter?: number;
|
|
948
|
+
constructor(retryAfter?: number, options?: {
|
|
949
|
+
odataError?: ODataError;
|
|
950
|
+
request?: InterceptedRequest;
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* Server error (5xx)
|
|
955
|
+
*/
|
|
956
|
+
declare class ServerError extends S4KitError {
|
|
957
|
+
constructor(message?: string, status?: number, options?: {
|
|
958
|
+
odataError?: ODataError;
|
|
959
|
+
request?: InterceptedRequest;
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
963
|
+
* Batch operation error with per-request results
|
|
964
|
+
*/
|
|
965
|
+
declare class BatchError extends S4KitError {
|
|
966
|
+
readonly results: Array<{
|
|
967
|
+
success: boolean;
|
|
968
|
+
error?: S4KitError;
|
|
969
|
+
index: number;
|
|
970
|
+
}>;
|
|
971
|
+
constructor(message: string, results: Array<{
|
|
972
|
+
success: boolean;
|
|
973
|
+
error?: S4KitError;
|
|
974
|
+
index: number;
|
|
975
|
+
}>);
|
|
976
|
+
/**
|
|
977
|
+
* Get all failed operations
|
|
978
|
+
*/
|
|
979
|
+
get failures(): {
|
|
980
|
+
success: boolean;
|
|
981
|
+
error?: S4KitError;
|
|
982
|
+
index: number;
|
|
983
|
+
}[];
|
|
984
|
+
/**
|
|
985
|
+
* Get all successful operations
|
|
986
|
+
*/
|
|
987
|
+
get successes(): {
|
|
988
|
+
success: boolean;
|
|
989
|
+
error?: S4KitError;
|
|
990
|
+
index: number;
|
|
991
|
+
}[];
|
|
992
|
+
}
|
|
993
|
+
/**
|
|
994
|
+
* Parse an HTTP response into the appropriate S4Kit error
|
|
995
|
+
*/
|
|
996
|
+
declare function parseHttpError(status: number, body: any, request?: InterceptedRequest): S4KitError;
|
|
997
|
+
/**
|
|
998
|
+
* Parse OData error format from response body
|
|
999
|
+
*/
|
|
1000
|
+
declare function parseODataError(body: any): ODataError | undefined;
|
|
1001
|
+
/**
|
|
1002
|
+
* Check if an error is retryable
|
|
1003
|
+
*/
|
|
1004
|
+
declare function isRetryable(error: Error): boolean;
|
|
1005
|
+
/**
|
|
1006
|
+
* Type guard: Check if error is an S4KitError
|
|
1007
|
+
*/
|
|
1008
|
+
declare function isS4KitError(error: unknown): error is S4KitError;
|
|
1009
|
+
|
|
1010
|
+
/**
|
|
1011
|
+
* Build OData query parameters from QueryOptions
|
|
1012
|
+
*/
|
|
1013
|
+
declare function buildQuery(options?: QueryOptions<any>): Record<string, string>;
|
|
1014
|
+
/**
|
|
1015
|
+
* Build $filter string from various formats
|
|
1016
|
+
* @example
|
|
1017
|
+
* - String: "Name eq 'John'" → "Name eq 'John'"
|
|
1018
|
+
* - Object: { Name: 'John' } → "Name eq 'John'"
|
|
1019
|
+
* - Object: { Price: { gt: 100 } } → "Price gt 100"
|
|
1020
|
+
* - Object: { Name: 'John', Active: true } → "Name eq 'John' and Active eq true"
|
|
1021
|
+
* - Array: [{ Name: 'John' }, { Age: { gt: 18 } }] → "Name eq 'John' and Age gt 18"
|
|
1022
|
+
* - OR: { $or: [{ A: 1 }, { B: 2 }] } → "(A eq 1 or B eq 2)"
|
|
1023
|
+
* - NOT: { $not: { Status: 'deleted' } } → "not (Status eq 'deleted')"
|
|
1024
|
+
* - Complex: { A: 1, $or: [{ B: 2 }, { C: 3 }] } → "A eq 1 and (B eq 2 or C eq 3)"
|
|
1025
|
+
*/
|
|
1026
|
+
declare function buildFilter(filter: Filter<any>): string;
|
|
1027
|
+
/**
|
|
1028
|
+
* Create a fluent filter builder for type-safe filter construction
|
|
1029
|
+
*
|
|
1030
|
+
* @example
|
|
1031
|
+
* ```ts
|
|
1032
|
+
* const filter = createFilter<Product>()
|
|
1033
|
+
* .where('Price', 'gt', 100)
|
|
1034
|
+
* .and('Category', 'eq', 'Electronics')
|
|
1035
|
+
* .or('Name', 'contains', 'Pro')
|
|
1036
|
+
* .build();
|
|
1037
|
+
* // Returns: "Price gt 100 and Category eq 'Electronics' or contains(Name,'Pro')"
|
|
1038
|
+
* ```
|
|
1039
|
+
*/
|
|
1040
|
+
declare function createFilter<T = any>(): FilterBuilder<T>;
|
|
1041
|
+
declare class FilterBuilder<T = any> {
|
|
1042
|
+
private parts;
|
|
1043
|
+
/**
|
|
1044
|
+
* Add a filter condition
|
|
1045
|
+
*/
|
|
1046
|
+
where<K extends keyof T>(field: K, operator: FilterOperator, value: any): this;
|
|
1047
|
+
/**
|
|
1048
|
+
* Add AND condition
|
|
1049
|
+
*/
|
|
1050
|
+
and<K extends keyof T>(field: K, operator: FilterOperator, value: any): this;
|
|
1051
|
+
/**
|
|
1052
|
+
* Add OR condition
|
|
1053
|
+
*/
|
|
1054
|
+
or<K extends keyof T>(field: K, operator: FilterOperator, value: any): this;
|
|
1055
|
+
/**
|
|
1056
|
+
* Add NOT condition
|
|
1057
|
+
*/
|
|
1058
|
+
not<K extends keyof T>(field: K, operator: FilterOperator, value: any): this;
|
|
1059
|
+
/**
|
|
1060
|
+
* Group conditions with parentheses
|
|
1061
|
+
*/
|
|
1062
|
+
group(fn: (builder: FilterBuilder<T>) => FilterBuilder<T>): this;
|
|
1063
|
+
/**
|
|
1064
|
+
* Add raw filter expression
|
|
1065
|
+
*/
|
|
1066
|
+
raw(expression: string): this;
|
|
1067
|
+
/**
|
|
1068
|
+
* Build the final filter string
|
|
1069
|
+
*/
|
|
1070
|
+
build(): string;
|
|
1071
|
+
/**
|
|
1072
|
+
* Build a single condition
|
|
1073
|
+
*/
|
|
1074
|
+
private buildCondition;
|
|
1075
|
+
/**
|
|
1076
|
+
* Format a value for OData filter
|
|
1077
|
+
*/
|
|
1078
|
+
private formatValue;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
/**
|
|
1082
|
+
* Create a fluent query builder for an entity
|
|
1083
|
+
*
|
|
1084
|
+
* @example
|
|
1085
|
+
* ```ts
|
|
1086
|
+
* const products = await query(client.sap.Products)
|
|
1087
|
+
* .select('Name', 'Price')
|
|
1088
|
+
* .where('Price', 'gt', 100)
|
|
1089
|
+
* .orderBy('Name', 'asc')
|
|
1090
|
+
* .top(10)
|
|
1091
|
+
* .execute();
|
|
1092
|
+
* ```
|
|
1093
|
+
*/
|
|
1094
|
+
declare function query<T>(handler: EntityHandler<T>): QueryBuilder<T>;
|
|
1095
|
+
declare class QueryBuilder<T> implements FluentQuery<T> {
|
|
1096
|
+
private handler;
|
|
1097
|
+
private options;
|
|
1098
|
+
private filterParts;
|
|
1099
|
+
constructor(handler: EntityHandler<T>);
|
|
1100
|
+
/**
|
|
1101
|
+
* Select specific fields
|
|
1102
|
+
*/
|
|
1103
|
+
select<K extends keyof T>(...fields: K[]): QueryBuilder<Pick<T, K>>;
|
|
1104
|
+
/**
|
|
1105
|
+
* Add raw filter expression
|
|
1106
|
+
*/
|
|
1107
|
+
filter(expression: string): this;
|
|
1108
|
+
/**
|
|
1109
|
+
* Add type-safe filter condition
|
|
1110
|
+
*/
|
|
1111
|
+
where<K extends keyof T>(field: K, operator: FilterOperator, value: any): this;
|
|
1112
|
+
/**
|
|
1113
|
+
* Add AND condition
|
|
1114
|
+
*/
|
|
1115
|
+
and<K extends keyof T>(field: K, operator: FilterOperator, value: any): this;
|
|
1116
|
+
/**
|
|
1117
|
+
* Add OR condition
|
|
1118
|
+
*/
|
|
1119
|
+
or<K extends keyof T>(field: K, operator: FilterOperator, value: any): this;
|
|
1120
|
+
/**
|
|
1121
|
+
* Order by field
|
|
1122
|
+
*/
|
|
1123
|
+
orderBy<K extends keyof T>(field: K, direction?: 'asc' | 'desc'): this;
|
|
1124
|
+
/**
|
|
1125
|
+
* Expand navigation property with optional nested query
|
|
1126
|
+
*/
|
|
1127
|
+
expand(property: string, nested?: (q: FluentQuery<any>) => FluentQuery<any>): this;
|
|
1128
|
+
/**
|
|
1129
|
+
* Limit results
|
|
1130
|
+
*/
|
|
1131
|
+
top(count: number): this;
|
|
1132
|
+
/**
|
|
1133
|
+
* Skip results (pagination)
|
|
1134
|
+
*/
|
|
1135
|
+
skip(count: number): this;
|
|
1136
|
+
/**
|
|
1137
|
+
* Full-text search
|
|
1138
|
+
*/
|
|
1139
|
+
search(term: string): this;
|
|
1140
|
+
/**
|
|
1141
|
+
* Request inline count
|
|
1142
|
+
*/
|
|
1143
|
+
count(): this;
|
|
1144
|
+
/**
|
|
1145
|
+
* Execute query and return results
|
|
1146
|
+
*/
|
|
1147
|
+
execute(): Promise<T[]>;
|
|
1148
|
+
/**
|
|
1149
|
+
* Execute query with inline count
|
|
1150
|
+
*/
|
|
1151
|
+
executeWithCount(): Promise<ListResponse<T>>;
|
|
1152
|
+
/**
|
|
1153
|
+
* Execute and return first result (or undefined)
|
|
1154
|
+
*/
|
|
1155
|
+
first(): Promise<T | undefined>;
|
|
1156
|
+
/**
|
|
1157
|
+
* Execute and return single result (throws if not exactly one)
|
|
1158
|
+
*/
|
|
1159
|
+
single(): Promise<T>;
|
|
1160
|
+
/**
|
|
1161
|
+
* Build final query options
|
|
1162
|
+
*/
|
|
1163
|
+
buildOptions(): QueryOptions<T>;
|
|
1164
|
+
}
|
|
1165
|
+
/**
|
|
1166
|
+
* Format entity key for URL path
|
|
1167
|
+
* Supports simple keys (string/number) and composite keys
|
|
1168
|
+
*/
|
|
1169
|
+
declare function formatKey(key: string | number | Record<string, string | number>): string;
|
|
1170
|
+
/**
|
|
1171
|
+
* Build function/action parameters for URL
|
|
1172
|
+
*/
|
|
1173
|
+
declare function buildFunctionParams(params?: Record<string, any>): string;
|
|
1174
|
+
|
|
1175
|
+
export { AuthenticationError, AuthorizationError, type BatchCreate, type BatchDelete, BatchError, type BatchGet, type BatchOperation, type BatchResult, type BatchUpdate, type Changeset, type CompositeKey, ConflictError, type DeepInsertData, type EntityHandler, type EntityKey, type ErrorInterceptor, type Expand, type ExpandNestedOptions, type ExpandObject, type ExpandValue, type Filter, FilterBuilder, type FilterComparisonOperator, type FilterCondition, type FilterExpression, type FilterLogical, type FilterObject, type FilterOperator, type FilterStringOperator, type FilterValue, type FluentQuery, type IS4KitError, type InterceptedRequest, type InterceptedResponse, type ListResponse, type LogicalOperator, NetworkError, NotFoundError, type ODataError, type ODataErrorDetail, type ODataResponse, type OrderBy, type OrderByObject, type OrderDirection, type PaginateOptions, QueryBuilder, type QueryOptions, RateLimitError, type RequestInterceptor, type ResponseInterceptor, S4Kit, type S4KitClient, type S4KitClientWithDynamicAccess, type S4KitConfig, S4KitError, ServerError, TimeoutError, ValidationError, buildFilter, buildFunctionParams, buildQuery, createClient, createFilter, formatKey, isRetryable, isS4KitError, parseHttpError, parseODataError, query };
|