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.
@@ -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 };