fireberry-api-client 1.0.0-beta.2.3.4 → 1.0.0

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
@@ -8,10 +8,17 @@ A standalone, framework-agnostic TypeScript/JavaScript client for the Fireberry
8
8
  - Zero runtime dependencies (uses native `fetch`)
9
9
  - Supports both ESM and CommonJS
10
10
  - Automatic retry on rate limits (429)
11
- - Optional metadata caching
11
+ - Optional metadata and query result caching
12
+ - Smart cache invalidation on mutations (auto-clears query cache when records are modified)
13
+ - Request deduplication (concurrent identical queries share a single API call)
14
+ - Parallel query execution with `queryAll()`
15
+ - Cursor-based pagination with async iterators
12
16
  - Lookup field relationship detection
13
- - Fluent QueryBuilder API
17
+ - Fluent QueryBuilder API with date helpers and debugging
18
+ - Query explain/dry-run for analyzing queries before execution
14
19
  - Batch operations with auto-chunking
20
+ - Schema generator for TypeScript types from live API metadata
21
+ - ERD generator for Mermaid diagrams
15
22
  - AbortController support for cancellation
16
23
 
17
24
  ## Installation
@@ -67,7 +74,10 @@ const client = new FireberryClient({
67
74
  maxRetries: 120, // Optional, max retry attempts
68
75
  retryDelay: 1000, // Optional, delay between retries in ms
69
76
  cacheMetadata: false, // Optional, enable metadata caching
70
- cacheTTL: 300000, // Optional, cache TTL in ms (5 min default)
77
+ cacheTTL: 300000, // Optional, metadata cache TTL in ms (5 min default)
78
+ cacheQueryResults: false, // Optional, enable query result caching
79
+ queryResultCacheTTL: 60000, // Optional, query cache TTL in ms (1 min default)
80
+ invalidateCacheOnMutation: true, // Optional, auto-clear query cache on create/update/delete (default: true)
71
81
  });
72
82
  ```
73
83
 
@@ -160,6 +170,192 @@ const contact = await client.queryBuilder()
160
170
  // This ensures records from Jan 15 are included (API quirk workaround)
161
171
  ```
162
172
 
173
+ ### QueryBuilder: Additional Methods
174
+
175
+ ```typescript
176
+ // whereIn - query with multiple values (OR'd together)
177
+ const accounts = await client.queryBuilder()
178
+ .objectType(1)
179
+ .whereIn('statuscode', [1, 2, 3]) // (statuscode = 1) or (statuscode = 2) or (statuscode = 3)
180
+ .execute();
181
+
182
+ // first() - return single record or null
183
+ const account = await client.queryBuilder()
184
+ .objectType(1)
185
+ .where('accountname').equals('Acme Corp')
186
+ .first(); // Returns Record<string, unknown> | null
187
+
188
+ // count() - get total count without fetching records
189
+ const total = await client.queryBuilder()
190
+ .objectType(1)
191
+ .where('statuscode').equals('1')
192
+ .count(); // Returns number
193
+ ```
194
+
195
+ ### QueryBuilder: Date Helpers
196
+
197
+ ```typescript
198
+ // Query records from today
199
+ const todaysRecords = await client.queryBuilder()
200
+ .objectType(1)
201
+ .whereDate('createdon').today()
202
+ .execute();
203
+
204
+ // Query records from this week
205
+ const thisWeeksRecords = await client.queryBuilder()
206
+ .objectType(1)
207
+ .whereDate('createdon').thisWeek()
208
+ .execute();
209
+
210
+ // Query records from this month
211
+ const thisMonthsRecords = await client.queryBuilder()
212
+ .objectType(1)
213
+ .whereDate('createdon').thisMonth()
214
+ .execute();
215
+
216
+ // Query records from N days ago
217
+ const last7Days = await client.queryBuilder()
218
+ .objectType(1)
219
+ .whereDate('createdon').daysAgo(7)
220
+ .execute();
221
+
222
+ // Query records between two dates
223
+ const dateRange = await client.queryBuilder()
224
+ .objectType(1)
225
+ .whereDate('createdon').between('2024-01-01', '2024-01-31')
226
+ .execute();
227
+
228
+ // Query records before/after a date
229
+ const beforeDate = await client.queryBuilder()
230
+ .objectType(1)
231
+ .whereDate('createdon').before('2024-06-01')
232
+ .execute();
233
+
234
+ const afterDate = await client.queryBuilder()
235
+ .objectType(1)
236
+ .whereDate('createdon').after('2024-01-01')
237
+ .execute();
238
+
239
+ // On or before/after (correctly handles API date quirks)
240
+ const onOrBefore = await client.queryBuilder()
241
+ .objectType(1)
242
+ .whereDate('createdon').onOrBefore('2024-06-30')
243
+ .execute();
244
+ ```
245
+
246
+ ### QueryBuilder: Debugging
247
+
248
+ ```typescript
249
+ // Get query result with metadata for debugging
250
+ const result = await client.queryBuilder()
251
+ .objectType(1)
252
+ .select('accountid', 'accountname')
253
+ .where('statuscode').equals('1')
254
+ .limit(50)
255
+ .executeWithDebug();
256
+
257
+ console.log(result.metadata);
258
+ // {
259
+ // objectType: '1',
260
+ // fields: ['accountid', 'accountname'],
261
+ // queryString: '(statuscode = 1)',
262
+ // pageNumber: 1,
263
+ // pageSize: 500,
264
+ // autoPage: true,
265
+ // sortBy: 'modifiedon',
266
+ // sortType: 'desc',
267
+ // limit: 50,
268
+ // executionTimeMs: 234
269
+ // }
270
+ ```
271
+
272
+ ### QueryBuilder: Explain (Dry Run)
273
+
274
+ Analyze a query without executing it to understand its behavior and get optimization suggestions:
275
+
276
+ ```typescript
277
+ const explanation = client.queryBuilder()
278
+ .objectType(1)
279
+ .select('*')
280
+ .where('statuscode').equals('1')
281
+ .limit(100)
282
+ .explain();
283
+
284
+ console.log(explanation);
285
+ // {
286
+ // objectType: '1',
287
+ // query: '(statuscode = 1)',
288
+ // fields: ['*'],
289
+ // usesWildcard: true,
290
+ // willAutoPage: true,
291
+ // limit: 100,
292
+ // pageSize: 500,
293
+ // sorting: { field: 'modifiedon', direction: 'desc' },
294
+ // estimatedApiCalls: 1,
295
+ // conditionCount: 1,
296
+ // showRealValue: false,
297
+ // warnings: ['Using wildcard (*) fields - consider specifying exact fields for better performance'],
298
+ // suggestions: ['Specify exact fields instead of * to reduce payload size']
299
+ // }
300
+
301
+ // Use explain to validate queries before execution
302
+ if (explanation.warnings.length > 0) {
303
+ console.warn('Query warnings:', explanation.warnings);
304
+ }
305
+ ```
306
+
307
+ ### Parallel Query Execution
308
+
309
+ Execute multiple queries in parallel with concurrency control:
310
+
311
+ ```typescript
312
+ const results = await client.queryAll([
313
+ { objectType: '1', fields: ['accountid', 'accountname'] },
314
+ { objectType: '2', fields: ['contactid', 'fullname'] },
315
+ { objectType: '4', fields: ['opportunityid', 'name'] },
316
+ ], {
317
+ concurrency: 5, // Optional, max parallel requests (default: 5)
318
+ });
319
+
320
+ // Results are returned in the same order as input queries
321
+ console.log(results[0].records); // Accounts
322
+ console.log(results[1].records); // Contacts
323
+ console.log(results[2].records); // Opportunities
324
+ ```
325
+
326
+ ### Streaming / Cursor-Based Pagination
327
+
328
+ Process large datasets without loading everything into memory:
329
+
330
+ ```typescript
331
+ // Process records in batches using async iterator
332
+ for await (const batch of client.queryStream({
333
+ objectType: '1',
334
+ fields: ['accountid', 'accountname'],
335
+ pageSize: 100,
336
+ })) {
337
+ console.log(`Processing ${batch.records.length} records (page ${batch.page})...`);
338
+ for (const record of batch.records) {
339
+ // Process each record
340
+ }
341
+ }
342
+
343
+ // Collect all records from stream
344
+ const allRecords: Record<string, unknown>[] = [];
345
+ for await (const batch of client.queryStream({ objectType: '1', fields: '*' })) {
346
+ allRecords.push(...batch.records);
347
+ }
348
+
349
+ // With limit
350
+ for await (const batch of client.queryStream({
351
+ objectType: '1',
352
+ fields: '*',
353
+ limit: 1000, // Stop after 1000 records
354
+ })) {
355
+ // Process batch
356
+ }
357
+ ```
358
+
163
359
  ### CRUD Operations
164
360
 
165
361
  ```typescript
@@ -227,23 +423,38 @@ const fieldsOnly = await client.metadata.getFields('1', { includeLookupRelations
227
423
  const values = await client.metadata.getFieldValues('1', 'statuscode');
228
424
  ```
229
425
 
230
- ### Metadata Caching
426
+ ### Caching
231
427
 
232
428
  ```typescript
233
429
  const client = new FireberryClient({
234
430
  apiKey: 'your-api-key',
235
431
  cacheMetadata: true,
236
- cacheTTL: 300000, // 5 minutes
432
+ cacheTTL: 300000, // Metadata cache: 5 minutes
433
+ cacheQueryResults: true,
434
+ queryResultCacheTTL: 60000, // Query cache: 1 minute
237
435
  });
238
436
 
239
437
  // Metadata calls are cached
240
438
  await client.metadata.getFields('1'); // Hits API
241
439
  await client.metadata.getFields('1'); // Uses cache
242
440
 
441
+ // Query results are cached (when cacheQueryResults is enabled)
442
+ await client.query({ objectType: '1', fields: '*' }); // Hits API
443
+ await client.query({ objectType: '1', fields: '*' }); // Uses cache
444
+
445
+ // Request deduplication (always active)
446
+ // Concurrent identical queries share a single API call
447
+ const [result1, result2] = await Promise.all([
448
+ client.query({ objectType: '1', fields: '*' }), // Makes API call
449
+ client.query({ objectType: '1', fields: '*' }), // Shares same promise
450
+ ]);
451
+
243
452
  // Manual cache control
244
- client.cache.clear(); // Clear all cache
245
- client.cache.clearFields('1'); // Clear fields for object 1
453
+ client.cache.clear(); // Clear all cache
454
+ client.cache.clearFields('1'); // Clear fields for object 1
246
455
  client.cache.clearFieldValues('1', 'statuscode'); // Clear specific field values
456
+ client.cache.clearQueryResults(); // Clear all query result cache
457
+ client.cache.clearQueryResultsForObject('1'); // Clear query cache for object 1
247
458
  ```
248
459
 
249
460
  ### Custom API Calls
@@ -384,6 +595,100 @@ isDropdownField('5'); // true
384
595
  isLookupField('6'); // true
385
596
  ```
386
597
 
598
+ ## Schema Generator
599
+
600
+ Generate TypeScript interfaces from your Fireberry metadata:
601
+
602
+ ```typescript
603
+ import { generateSchema, schemaBuilder } from 'fireberry-api-client/utils';
604
+
605
+ // Simple generation
606
+ const result = await generateSchema(client);
607
+ console.log(result.typescript); // TypeScript code
608
+ console.log(result.metadata); // { totalObjects: 15, totalFields: 234 }
609
+
610
+ // Write to file
611
+ import fs from 'fs';
612
+ fs.writeFileSync('./fireberry-types.ts', result.typescript);
613
+
614
+ // Fluent builder with options
615
+ const result = await schemaBuilder(client)
616
+ .include([1, 2, 4]) // Only Account, Contact, Opportunity
617
+ .exclude([1000]) // Exclude custom object 1000
618
+ .withComments() // Include JSDoc comments
619
+ .withFieldTypes() // Include field type info
620
+ .withLookupInfo() // Include related object type for lookups
621
+ .withPrefix('FB') // Prefix interfaces: FBAccount, FBContact
622
+ .asReadonly() // Generate readonly interfaces
623
+ .generate();
624
+
625
+ // Generated output example:
626
+ // /**
627
+ // * Account (Object Type: 1)
628
+ // * System Name: Account
629
+ // */
630
+ // export interface FBAccount {
631
+ // /** Account Name @type text */
632
+ // accountname?: string;
633
+ // /** Status @type dropdown */
634
+ // statuscode?: string | number;
635
+ // /** Primary Contact @type lookup @relatedObjectType 2 */
636
+ // primarycontactid?: string;
637
+ // }
638
+ ```
639
+
640
+ ## ERD Generator
641
+
642
+ Generate Mermaid ERD diagrams from your Fireberry schema:
643
+
644
+ ```typescript
645
+ import { erdBuilder, generateFireberryERD } from 'fireberry-api-client/utils';
646
+
647
+ // Fluent builder API
648
+ const result = await erdBuilder(client)
649
+ .include([1, 2, 4, 9]) // Account, Contact, Opportunity, custom object
650
+ .exclude([1000]) // Exclude specific objects
651
+ .settings({
652
+ includeFields: true, // Show fields in entities
653
+ showFieldTypes: true, // Show field types (text, lookup, etc.)
654
+ onlyRelationshipFields: false, // Show only lookup fields
655
+ maxFieldsPerEntity: 10, // Limit fields per entity (0 = unlimited)
656
+ includeFieldLabels: false, // Include field labels as comments
657
+ title: 'My CRM Schema', // Diagram title
658
+ useDisplayNames: false, // Use system names (recommended)
659
+ includeFrontmatter: false, // Exclude YAML frontmatter
660
+ })
661
+ .generate();
662
+
663
+ console.log(result.mermaid); // Mermaid ERD code
664
+ console.log(result.objects); // Processed objects
665
+ console.log(result.relationships); // Found relationships
666
+ console.log(result.warnings); // Any warnings
667
+
668
+ // Direct function alternative
669
+ const result = await generateFireberryERD(client, {
670
+ include: [1, 2, 4],
671
+ settings: { includeFields: true, maxFieldsPerEntity: 5 },
672
+ });
673
+
674
+ // Example Mermaid output:
675
+ // erDiagram
676
+ // Account {
677
+ // text accountname
678
+ // lookup primarycontactid FK
679
+ // dropdown statuscode
680
+ // }
681
+ // Contact {
682
+ // text fullname
683
+ // lookup accountid FK
684
+ // }
685
+ //
686
+ // Account }o--|| Contact : "primarycontactid"
687
+ // Contact }o--|| Account : "accountid"
688
+ ```
689
+
690
+ Render the Mermaid code using any Mermaid-compatible viewer (VS Code extension, GitHub, Notion, etc.).
691
+
387
692
  ## Object Type Reference
388
693
 
389
694
  | ID | Object | ID Field | Name Field |