s4kit 0.1.9 → 0.1.11

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 CHANGED
@@ -64,28 +64,78 @@ The platform handles CSRF tokens, authentication, and connection pooling — you
64
64
 
65
65
  ---
66
66
 
67
- ## Type Generation
67
+ ## Type Generation (Recommended)
68
68
 
69
- Generate TypeScript types from your SAP system for full autocomplete and type safety:
69
+ > **This is the key feature of S4Kit.** Generate TypeScript types directly from your SAP system's OData metadata for full autocomplete and compile-time type safety.
70
+
71
+ ### Why Generate Types?
72
+
73
+ Without types, the SDK works but you lose the main benefit - type safety:
74
+ ```typescript
75
+ // Without types - works but no autocomplete, no type checking
76
+ const partners = await client.A_BusinessPartner.list(); // partners is any[]
77
+ ```
78
+
79
+ With generated types:
80
+ ```typescript
81
+ // With types - full IDE support and compile-time validation
82
+ const partners = await client.A_BusinessPartner.list({
83
+ select: ['BusinessPartner', 'BusinessPartnerName'], // ← Autocomplete!
84
+ });
85
+ partners.forEach(p => console.log(p.BusinessPartnerName)); // ← Type-safe!
86
+ ```
87
+
88
+ ### Generating Types
70
89
 
71
90
  ```bash
72
- npx s4kit generate-types --api-key sk_live_... --base-url https://staging.proxy.s4kit.com/api/proxy --output ./types
91
+ # Basic usage
92
+ npx s4kit generate-types --api-key sk_live_... --output ./types
93
+
94
+ # With all options
95
+ npx s4kit generate-types \
96
+ --api-key sk_live_... # Required
97
+ --output ./types # Output directory (default: ./s4kit-types)
98
+ --base-url https://staging.proxy.s4kit.com/api/proxy # Custom proxy URL
99
+ --connection my-sap-system # Specific connection only
73
100
  ```
74
101
 
102
+ ### Using Generated Types
103
+
75
104
  ```typescript
76
105
  import { S4Kit } from 's4kit';
77
- import './types'; // Enable type inference
106
+ import './types'; // This enables type inference
78
107
 
79
108
  const client = S4Kit({ apiKey: 'sk_live_...' });
80
109
 
81
110
  // Full autocomplete on entity names and fields
82
111
  const partners = await client.A_BusinessPartner.list({
83
- select: ['BusinessPartner', 'BusinessPartnerName'], // ← Type-safe!
112
+ select: ['BusinessPartner', 'BusinessPartnerName'],
84
113
  filter: { BusinessPartnerCategory: '1' }
85
114
  });
86
115
 
87
116
  // partners is A_BusinessPartner[], not any[]
88
- partners.forEach(p => console.log(p.BusinessPartnerName)); // ← Autocomplete works!
117
+ partners.forEach(p => console.log(p.BusinessPartnerName));
118
+ ```
119
+
120
+ ### What You Get
121
+
122
+ - **Entity autocomplete** - `client.` shows all available entities
123
+ - **Field autocomplete** - `select`, `filter`, `orderBy` show valid fields
124
+ - **Type-safe filters** - operators match field types (string fields get `contains`, number fields get `gt`/`lt`)
125
+ - **Proper return types** - query results are typed, not `any[]`
126
+ - **Navigation properties** - `expand` options show available relations
127
+ - **Compile-time errors** - typos in field names caught before runtime
128
+
129
+ ### Regenerating Types
130
+
131
+ Regenerate types when:
132
+ - You connect a new SAP service
133
+ - The SAP system's schema changes
134
+ - You add new services to your API key
135
+
136
+ ```bash
137
+ # Regenerate all types
138
+ npx s4kit generate-types --api-key sk_live_... --output ./types
89
139
  ```
90
140
 
91
141
  ---
package/dist/index.d.cts CHANGED
@@ -286,7 +286,7 @@ interface EntityHandler<T = any> {
286
286
  * List entities with optional query options
287
287
  * @example
288
288
  * ```ts
289
- * const items = await client.sap.Products.list({ top: 10 });
289
+ * const items = await client.Products.list({ top: 10 });
290
290
  * ```
291
291
  */
292
292
  list(options?: QueryOptions<T>): Promise<T[]>;
@@ -294,7 +294,7 @@ interface EntityHandler<T = any> {
294
294
  * List entities with count
295
295
  * @example
296
296
  * ```ts
297
- * const { value, count } = await client.sap.Products.listWithCount({ top: 10 });
297
+ * const { value, count } = await client.Products.listWithCount({ top: 10 });
298
298
  * console.log(`Showing ${value.length} of ${count} total`);
299
299
  * ```
300
300
  */
@@ -303,8 +303,8 @@ interface EntityHandler<T = any> {
303
303
  * Get single entity by key
304
304
  * @example
305
305
  * ```ts
306
- * const product = await client.sap.Products.get(1);
307
- * const partner = await client.sap.A_BusinessPartner.get('BP001');
306
+ * const product = await client.Products.get(1);
307
+ * const partner = await client.A_BusinessPartner.get('BP001');
308
308
  * ```
309
309
  */
310
310
  get(id: EntityKey, options?: QueryOptions<T>): Promise<T>;
@@ -312,7 +312,7 @@ interface EntityHandler<T = any> {
312
312
  * Count matching entities
313
313
  * @example
314
314
  * ```ts
315
- * const total = await client.sap.Products.count({ filter: "Price gt 100" });
315
+ * const total = await client.Products.count({ filter: "Price gt 100" });
316
316
  * ```
317
317
  */
318
318
  count(options?: Omit<QueryOptions<T>, 'top' | 'skip' | 'select'>): Promise<number>;
@@ -320,7 +320,7 @@ interface EntityHandler<T = any> {
320
320
  * Create a new entity (POST)
321
321
  * @example
322
322
  * ```ts
323
- * const created = await client.sap.Products.create({ Name: 'Widget', Price: 9.99 });
323
+ * const created = await client.Products.create({ Name: 'Widget', Price: 9.99 });
324
324
  * ```
325
325
  */
326
326
  create(data: Partial<T> | T, options?: QueryOptions<T>): Promise<T>;
@@ -340,7 +340,7 @@ interface EntityHandler<T = any> {
340
340
  *
341
341
  * @example Composition relationship (works)
342
342
  * ```ts
343
- * const order = await client.sap.Orders.createDeep({
343
+ * const order = await client.Orders.createDeep({
344
344
  * OrderID: '001',
345
345
  * Items: [{ Product: 'A', Quantity: 10 }] // Created with order
346
346
  * });
@@ -349,11 +349,11 @@ interface EntityHandler<T = any> {
349
349
  * @example Association relationship (use separate calls instead)
350
350
  * ```ts
351
351
  * // DON'T: This won't create the books - they'll be ignored
352
- * await client.sap.Authors.createDeep({ name: 'Author', books: [{...}] });
352
+ * await client.Authors.createDeep({ name: 'Author', books: [{...}] });
353
353
  *
354
354
  * // DO: Create separately with FK reference
355
- * const author = await client.sap.Authors.create({ name: 'Author' });
356
- * await client.sap.Books.createMany([
355
+ * const author = await client.Authors.create({ name: 'Author' });
356
+ * await client.Books.createMany([
357
357
  * { title: 'Book 1', author_ID: author.ID }
358
358
  * ]);
359
359
  * ```
@@ -363,7 +363,7 @@ interface EntityHandler<T = any> {
363
363
  * Partial update (PATCH) - only updates specified fields
364
364
  * @example
365
365
  * ```ts
366
- * const updated = await client.sap.Products.update(1, { Price: 12.99 });
366
+ * const updated = await client.Products.update(1, { Price: 12.99 });
367
367
  * ```
368
368
  */
369
369
  update(id: EntityKey, data: Partial<T>, options?: QueryOptions<T>): Promise<T>;
@@ -371,7 +371,7 @@ interface EntityHandler<T = any> {
371
371
  * Full replacement (PUT) - replaces entire entity
372
372
  * @example
373
373
  * ```ts
374
- * const replaced = await client.sap.Products.replace(1, fullProductData);
374
+ * const replaced = await client.Products.replace(1, fullProductData);
375
375
  * ```
376
376
  */
377
377
  replace(id: EntityKey, data: T, options?: QueryOptions<T>): Promise<T>;
@@ -379,7 +379,7 @@ interface EntityHandler<T = any> {
379
379
  * Upsert - create or update based on key
380
380
  * @example
381
381
  * ```ts
382
- * const result = await client.sap.Products.upsert({ ID: 1, Name: 'Widget', Price: 9.99 });
382
+ * const result = await client.Products.upsert({ ID: 1, Name: 'Widget', Price: 9.99 });
383
383
  * ```
384
384
  */
385
385
  upsert(data: T, options?: QueryOptions<T>): Promise<T>;
@@ -387,7 +387,7 @@ interface EntityHandler<T = any> {
387
387
  * Delete entity by key
388
388
  * @example
389
389
  * ```ts
390
- * await client.sap.Products.delete(1);
390
+ * await client.Products.delete(1);
391
391
  * ```
392
392
  */
393
393
  delete(id: EntityKey, options?: QueryOptions<T>): Promise<void>;
@@ -428,7 +428,7 @@ interface EntityHandler<T = any> {
428
428
  * Access navigation property (related entities)
429
429
  * @example
430
430
  * ```ts
431
- * const items = await client.sap.Orders.nav(1, 'Items').list();
431
+ * const items = await client.Orders.nav(1, 'Items').list();
432
432
  * ```
433
433
  */
434
434
  nav<R = any>(id: EntityKey, property: string): EntityHandler<R>;
@@ -436,7 +436,7 @@ interface EntityHandler<T = any> {
436
436
  * Call an OData function (GET operation)
437
437
  * @example
438
438
  * ```ts
439
- * const result = await client.sap.Products.func('GetTopSelling', { count: 10 });
439
+ * const result = await client.Products.func('GetTopSelling', { count: 10 });
440
440
  * ```
441
441
  */
442
442
  func<R = any>(name: string, params?: Record<string, any>): Promise<R>;
@@ -444,7 +444,7 @@ interface EntityHandler<T = any> {
444
444
  * Call an OData action (POST operation)
445
445
  * @example
446
446
  * ```ts
447
- * await client.sap.Orders.action('Approve', { orderId: '001' });
447
+ * await client.Orders.action('Approve', { orderId: '001' });
448
448
  * ```
449
449
  */
450
450
  action<R = any>(name: string, params?: Record<string, any>): Promise<R>;
@@ -452,7 +452,7 @@ interface EntityHandler<T = any> {
452
452
  * Call bound function on specific entity
453
453
  * @example
454
454
  * ```ts
455
- * const total = await client.sap.Orders.boundFunc(1, 'CalculateTotal');
455
+ * const total = await client.Orders.boundFunc(1, 'CalculateTotal');
456
456
  * ```
457
457
  */
458
458
  boundFunc<R = any>(id: EntityKey, name: string, params?: Record<string, any>): Promise<R>;
@@ -460,7 +460,7 @@ interface EntityHandler<T = any> {
460
460
  * Call bound action on specific entity
461
461
  * @example
462
462
  * ```ts
463
- * await client.sap.Orders.boundAction('001', 'Submit');
463
+ * await client.Orders.boundAction('001', 'Submit');
464
464
  * ```
465
465
  */
466
466
  boundAction<R = any>(id: EntityKey, name: string, params?: Record<string, any>): Promise<R>;
@@ -469,7 +469,7 @@ interface EntityHandler<T = any> {
469
469
  * @example
470
470
  * ```ts
471
471
  * // Iterate through all pages
472
- * for await (const page of client.sap.Products.paginate({ top: 100 })) {
472
+ * for await (const page of client.Products.paginate({ top: 100 })) {
473
473
  * console.log(`Processing ${page.value.length} items`);
474
474
  * for (const product of page.value) {
475
475
  * // process product
@@ -477,7 +477,7 @@ interface EntityHandler<T = any> {
477
477
  * }
478
478
  *
479
479
  * // Or collect all items
480
- * const all = await client.sap.Products.all({ filter: "Active eq true" });
480
+ * const all = await client.Products.all({ filter: "Active eq true" });
481
481
  * ```
482
482
  */
483
483
  paginate(options?: PaginateOptions<T>): AsyncIterable<ListResponse<T>>;
@@ -485,7 +485,7 @@ interface EntityHandler<T = any> {
485
485
  * Get all entities (automatically handles pagination)
486
486
  * @example
487
487
  * ```ts
488
- * const allProducts = await client.sap.Products.all({ filter: "Active eq true" });
488
+ * const allProducts = await client.Products.all({ filter: "Active eq true" });
489
489
  * ```
490
490
  */
491
491
  all(options?: PaginateOptions<T>): Promise<T[]>;
@@ -507,7 +507,7 @@ type EntityKey = string | number | CompositeKey;
507
507
  * Composite key for entities with multiple key fields
508
508
  * @example
509
509
  * ```ts
510
- * const item = await client.sap.OrderItems.get({ OrderID: '001', ItemNo: 10 });
510
+ * const item = await client.OrderItems.get({ OrderID: '001', ItemNo: 10 });
511
511
  * ```
512
512
  */
513
513
  type CompositeKey = Record<string, string | number>;
@@ -868,7 +868,7 @@ declare class S4KitBase {
868
868
  }
869
869
  /**
870
870
  * Create S4Kit client with dynamic entity access
871
- * Allows: client.Products.list() instead of client.sap.Products.list()
871
+ * Allows: client.Products.list() instead of client.Products.list()
872
872
  *
873
873
  * Returns S4KitClientWithDynamicAccess which:
874
874
  * - Without generated types: allows any entity access (returns EntityHandler<any>)
@@ -1126,7 +1126,7 @@ declare class FilterBuilder<T = any> {
1126
1126
  *
1127
1127
  * @example
1128
1128
  * ```ts
1129
- * const products = await query(client.sap.Products)
1129
+ * const products = await query(client.Products)
1130
1130
  * .select('Name', 'Price')
1131
1131
  * .where('Price', 'gt', 100)
1132
1132
  * .orderBy('Name', 'asc')
package/dist/index.d.ts CHANGED
@@ -286,7 +286,7 @@ interface EntityHandler<T = any> {
286
286
  * List entities with optional query options
287
287
  * @example
288
288
  * ```ts
289
- * const items = await client.sap.Products.list({ top: 10 });
289
+ * const items = await client.Products.list({ top: 10 });
290
290
  * ```
291
291
  */
292
292
  list(options?: QueryOptions<T>): Promise<T[]>;
@@ -294,7 +294,7 @@ interface EntityHandler<T = any> {
294
294
  * List entities with count
295
295
  * @example
296
296
  * ```ts
297
- * const { value, count } = await client.sap.Products.listWithCount({ top: 10 });
297
+ * const { value, count } = await client.Products.listWithCount({ top: 10 });
298
298
  * console.log(`Showing ${value.length} of ${count} total`);
299
299
  * ```
300
300
  */
@@ -303,8 +303,8 @@ interface EntityHandler<T = any> {
303
303
  * Get single entity by key
304
304
  * @example
305
305
  * ```ts
306
- * const product = await client.sap.Products.get(1);
307
- * const partner = await client.sap.A_BusinessPartner.get('BP001');
306
+ * const product = await client.Products.get(1);
307
+ * const partner = await client.A_BusinessPartner.get('BP001');
308
308
  * ```
309
309
  */
310
310
  get(id: EntityKey, options?: QueryOptions<T>): Promise<T>;
@@ -312,7 +312,7 @@ interface EntityHandler<T = any> {
312
312
  * Count matching entities
313
313
  * @example
314
314
  * ```ts
315
- * const total = await client.sap.Products.count({ filter: "Price gt 100" });
315
+ * const total = await client.Products.count({ filter: "Price gt 100" });
316
316
  * ```
317
317
  */
318
318
  count(options?: Omit<QueryOptions<T>, 'top' | 'skip' | 'select'>): Promise<number>;
@@ -320,7 +320,7 @@ interface EntityHandler<T = any> {
320
320
  * Create a new entity (POST)
321
321
  * @example
322
322
  * ```ts
323
- * const created = await client.sap.Products.create({ Name: 'Widget', Price: 9.99 });
323
+ * const created = await client.Products.create({ Name: 'Widget', Price: 9.99 });
324
324
  * ```
325
325
  */
326
326
  create(data: Partial<T> | T, options?: QueryOptions<T>): Promise<T>;
@@ -340,7 +340,7 @@ interface EntityHandler<T = any> {
340
340
  *
341
341
  * @example Composition relationship (works)
342
342
  * ```ts
343
- * const order = await client.sap.Orders.createDeep({
343
+ * const order = await client.Orders.createDeep({
344
344
  * OrderID: '001',
345
345
  * Items: [{ Product: 'A', Quantity: 10 }] // Created with order
346
346
  * });
@@ -349,11 +349,11 @@ interface EntityHandler<T = any> {
349
349
  * @example Association relationship (use separate calls instead)
350
350
  * ```ts
351
351
  * // DON'T: This won't create the books - they'll be ignored
352
- * await client.sap.Authors.createDeep({ name: 'Author', books: [{...}] });
352
+ * await client.Authors.createDeep({ name: 'Author', books: [{...}] });
353
353
  *
354
354
  * // DO: Create separately with FK reference
355
- * const author = await client.sap.Authors.create({ name: 'Author' });
356
- * await client.sap.Books.createMany([
355
+ * const author = await client.Authors.create({ name: 'Author' });
356
+ * await client.Books.createMany([
357
357
  * { title: 'Book 1', author_ID: author.ID }
358
358
  * ]);
359
359
  * ```
@@ -363,7 +363,7 @@ interface EntityHandler<T = any> {
363
363
  * Partial update (PATCH) - only updates specified fields
364
364
  * @example
365
365
  * ```ts
366
- * const updated = await client.sap.Products.update(1, { Price: 12.99 });
366
+ * const updated = await client.Products.update(1, { Price: 12.99 });
367
367
  * ```
368
368
  */
369
369
  update(id: EntityKey, data: Partial<T>, options?: QueryOptions<T>): Promise<T>;
@@ -371,7 +371,7 @@ interface EntityHandler<T = any> {
371
371
  * Full replacement (PUT) - replaces entire entity
372
372
  * @example
373
373
  * ```ts
374
- * const replaced = await client.sap.Products.replace(1, fullProductData);
374
+ * const replaced = await client.Products.replace(1, fullProductData);
375
375
  * ```
376
376
  */
377
377
  replace(id: EntityKey, data: T, options?: QueryOptions<T>): Promise<T>;
@@ -379,7 +379,7 @@ interface EntityHandler<T = any> {
379
379
  * Upsert - create or update based on key
380
380
  * @example
381
381
  * ```ts
382
- * const result = await client.sap.Products.upsert({ ID: 1, Name: 'Widget', Price: 9.99 });
382
+ * const result = await client.Products.upsert({ ID: 1, Name: 'Widget', Price: 9.99 });
383
383
  * ```
384
384
  */
385
385
  upsert(data: T, options?: QueryOptions<T>): Promise<T>;
@@ -387,7 +387,7 @@ interface EntityHandler<T = any> {
387
387
  * Delete entity by key
388
388
  * @example
389
389
  * ```ts
390
- * await client.sap.Products.delete(1);
390
+ * await client.Products.delete(1);
391
391
  * ```
392
392
  */
393
393
  delete(id: EntityKey, options?: QueryOptions<T>): Promise<void>;
@@ -428,7 +428,7 @@ interface EntityHandler<T = any> {
428
428
  * Access navigation property (related entities)
429
429
  * @example
430
430
  * ```ts
431
- * const items = await client.sap.Orders.nav(1, 'Items').list();
431
+ * const items = await client.Orders.nav(1, 'Items').list();
432
432
  * ```
433
433
  */
434
434
  nav<R = any>(id: EntityKey, property: string): EntityHandler<R>;
@@ -436,7 +436,7 @@ interface EntityHandler<T = any> {
436
436
  * Call an OData function (GET operation)
437
437
  * @example
438
438
  * ```ts
439
- * const result = await client.sap.Products.func('GetTopSelling', { count: 10 });
439
+ * const result = await client.Products.func('GetTopSelling', { count: 10 });
440
440
  * ```
441
441
  */
442
442
  func<R = any>(name: string, params?: Record<string, any>): Promise<R>;
@@ -444,7 +444,7 @@ interface EntityHandler<T = any> {
444
444
  * Call an OData action (POST operation)
445
445
  * @example
446
446
  * ```ts
447
- * await client.sap.Orders.action('Approve', { orderId: '001' });
447
+ * await client.Orders.action('Approve', { orderId: '001' });
448
448
  * ```
449
449
  */
450
450
  action<R = any>(name: string, params?: Record<string, any>): Promise<R>;
@@ -452,7 +452,7 @@ interface EntityHandler<T = any> {
452
452
  * Call bound function on specific entity
453
453
  * @example
454
454
  * ```ts
455
- * const total = await client.sap.Orders.boundFunc(1, 'CalculateTotal');
455
+ * const total = await client.Orders.boundFunc(1, 'CalculateTotal');
456
456
  * ```
457
457
  */
458
458
  boundFunc<R = any>(id: EntityKey, name: string, params?: Record<string, any>): Promise<R>;
@@ -460,7 +460,7 @@ interface EntityHandler<T = any> {
460
460
  * Call bound action on specific entity
461
461
  * @example
462
462
  * ```ts
463
- * await client.sap.Orders.boundAction('001', 'Submit');
463
+ * await client.Orders.boundAction('001', 'Submit');
464
464
  * ```
465
465
  */
466
466
  boundAction<R = any>(id: EntityKey, name: string, params?: Record<string, any>): Promise<R>;
@@ -469,7 +469,7 @@ interface EntityHandler<T = any> {
469
469
  * @example
470
470
  * ```ts
471
471
  * // Iterate through all pages
472
- * for await (const page of client.sap.Products.paginate({ top: 100 })) {
472
+ * for await (const page of client.Products.paginate({ top: 100 })) {
473
473
  * console.log(`Processing ${page.value.length} items`);
474
474
  * for (const product of page.value) {
475
475
  * // process product
@@ -477,7 +477,7 @@ interface EntityHandler<T = any> {
477
477
  * }
478
478
  *
479
479
  * // Or collect all items
480
- * const all = await client.sap.Products.all({ filter: "Active eq true" });
480
+ * const all = await client.Products.all({ filter: "Active eq true" });
481
481
  * ```
482
482
  */
483
483
  paginate(options?: PaginateOptions<T>): AsyncIterable<ListResponse<T>>;
@@ -485,7 +485,7 @@ interface EntityHandler<T = any> {
485
485
  * Get all entities (automatically handles pagination)
486
486
  * @example
487
487
  * ```ts
488
- * const allProducts = await client.sap.Products.all({ filter: "Active eq true" });
488
+ * const allProducts = await client.Products.all({ filter: "Active eq true" });
489
489
  * ```
490
490
  */
491
491
  all(options?: PaginateOptions<T>): Promise<T[]>;
@@ -507,7 +507,7 @@ type EntityKey = string | number | CompositeKey;
507
507
  * Composite key for entities with multiple key fields
508
508
  * @example
509
509
  * ```ts
510
- * const item = await client.sap.OrderItems.get({ OrderID: '001', ItemNo: 10 });
510
+ * const item = await client.OrderItems.get({ OrderID: '001', ItemNo: 10 });
511
511
  * ```
512
512
  */
513
513
  type CompositeKey = Record<string, string | number>;
@@ -868,7 +868,7 @@ declare class S4KitBase {
868
868
  }
869
869
  /**
870
870
  * Create S4Kit client with dynamic entity access
871
- * Allows: client.Products.list() instead of client.sap.Products.list()
871
+ * Allows: client.Products.list() instead of client.Products.list()
872
872
  *
873
873
  * Returns S4KitClientWithDynamicAccess which:
874
874
  * - Without generated types: allows any entity access (returns EntityHandler<any>)
@@ -1126,7 +1126,7 @@ declare class FilterBuilder<T = any> {
1126
1126
  *
1127
1127
  * @example
1128
1128
  * ```ts
1129
- * const products = await query(client.sap.Products)
1129
+ * const products = await query(client.Products)
1130
1130
  * .select('Name', 'Price')
1131
1131
  * .where('Price', 'gt', 100)
1132
1132
  * .orderBy('Name', 'asc')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "s4kit",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "The lightweight, type-safe SDK for SAP S/4HANA",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",