simplesvelte 2.2.11 → 2.2.13

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.
@@ -48,29 +48,42 @@
48
48
  let detailsOpen = $state(false)
49
49
 
50
50
  // Initialize value as array for multiple mode, ensure it's always an array when multiple
51
- $effect(() => {
51
+ // Use derived to avoid timing issues with effects
52
+ let normalizedValue = $derived.by(() => {
52
53
  if (multiple && !Array.isArray(value)) {
53
- value = value ? [value] : []
54
+ return value ? [value] : []
54
55
  } else if (!multiple && Array.isArray(value)) {
55
- value = value.length > 0 ? value[0] : undefined
56
+ return value.length > 0 ? value[0] : undefined
57
+ }
58
+ return value
59
+ })
60
+
61
+ // Sync normalized value back to value prop when it differs
62
+ $effect(() => {
63
+ if (normalizedValue !== value) {
64
+ value = normalizedValue
56
65
  }
57
66
  })
58
67
 
59
68
  // For single select mode
60
69
  let selectedItem = $derived.by(() => {
61
70
  if (multiple) return null
62
- return items.find((item) => item.value === value)
71
+ const currentValue = normalizedValue
72
+ return items.find((item) => item.value === currentValue)
63
73
  })
64
74
  // For multi select mode
65
75
  let selectedItems = $derived.by(() => {
66
- if (!multiple || !Array.isArray(value)) return []
67
- return items.filter((item) => (value as (string | number)[]).includes(item.value))
76
+ if (!multiple) return []
77
+ const currentValue = normalizedValue
78
+ if (!Array.isArray(currentValue)) return []
79
+ return items.filter((item) => currentValue.includes(item.value))
68
80
  })
69
81
 
70
82
  // Check if an item is selected in multi-select mode
71
83
  function isItemSelected(itemValue: any): boolean {
72
- if (!multiple) return itemValue === value
73
- return Array.isArray(value) && value.includes(itemValue)
84
+ if (!multiple) return itemValue === normalizedValue
85
+ const currentValue = normalizedValue
86
+ return Array.isArray(currentValue) && currentValue.includes(itemValue)
74
87
  }
75
88
 
76
89
  // Toggle item selection in multi-select mode
@@ -79,6 +92,7 @@
79
92
  // Close dropdown and update filter immediately
80
93
  filter = items.find((item) => item.value === itemValue)?.label || ''
81
94
  detailsOpen = false
95
+ filterMode = 'auto'
82
96
 
83
97
  // Wait for DOM update so details closes properly
84
98
  await tick()
@@ -90,20 +104,22 @@
90
104
  return
91
105
  }
92
106
 
93
- if (!Array.isArray(value)) {
94
- value = [itemValue]
95
- } else if (value.includes(itemValue)) {
96
- value = value.filter((v) => v !== itemValue)
107
+ // For multiple selection, work with current array state
108
+ const currentValue = Array.isArray(normalizedValue) ? normalizedValue : []
109
+
110
+ if (currentValue.includes(itemValue)) {
111
+ value = currentValue.filter((v) => v !== itemValue)
97
112
  } else {
98
- value = [...value, itemValue]
113
+ value = [...currentValue, itemValue]
99
114
  }
100
115
  if (onchange) onchange(value)
101
116
  }
102
117
 
103
118
  // Remove specific item from multi-select
104
119
  function removeSelectedItem(itemValue: any) {
105
- if (Array.isArray(value)) {
106
- value = value.filter((v) => v !== itemValue)
120
+ const currentValue = normalizedValue
121
+ if (Array.isArray(currentValue)) {
122
+ value = currentValue.filter((v) => v !== itemValue)
107
123
  if (onchange) onchange(value)
108
124
  }
109
125
  }
@@ -117,16 +133,31 @@
117
133
  }
118
134
 
119
135
  let filter = $state('')
136
+ let filterMode = $state<'user' | 'auto'>('user') // Track if filter is user-controlled or auto-synced
120
137
 
121
- // Sync filter with selected item for single select mode
138
+ // Auto-sync filter for single select when not user-controlled
122
139
  $effect(() => {
123
- if (!multiple && selectedItem && !detailsOpen) {
124
- filter = selectedItem.label
125
- } else if (!multiple && !selectedItem && !detailsOpen) {
126
- filter = ''
140
+ if (!multiple && !detailsOpen && filterMode === 'auto') {
141
+ if (selectedItem) {
142
+ filter = selectedItem.label
143
+ } else {
144
+ filter = ''
145
+ }
127
146
  }
128
147
  })
129
148
 
149
+ // Reset filter mode when user starts typing
150
+ function handleFilterInput() {
151
+ filterMode = 'user'
152
+ }
153
+
154
+ // Set filter mode to auto when closing dropdown
155
+ function handleDropdownClose() {
156
+ if (!multiple) {
157
+ filterMode = 'auto'
158
+ }
159
+ }
160
+
130
161
  let filteredItems = $derived.by(() => {
131
162
  if (filter.length === 0) return items
132
163
  return items.filter((item) => item.label.toLowerCase().includes(filter.toLowerCase()))
@@ -247,12 +278,12 @@
247
278
  </script>
248
279
 
249
280
  <!-- Data inputs for form submission -->
250
- {#if multiple && Array.isArray(value)}
251
- {#each value as val, i (i)}
281
+ {#if multiple && Array.isArray(normalizedValue)}
282
+ {#each normalizedValue as val, i (val + '-' + i)}
252
283
  <input type="hidden" {name} value={val} />
253
284
  {/each}
254
- {:else if !multiple && value !== undefined && value !== null && value !== ''}
255
- <input type="hidden" {name} {value} />
285
+ {:else if !multiple && normalizedValue !== undefined && normalizedValue !== null && normalizedValue !== ''}
286
+ <input type="hidden" {name} value={normalizedValue} />
256
287
  {/if}
257
288
 
258
289
  <Label {label} {name} optional={!required} class={className} error={errorText}>
@@ -269,12 +300,14 @@
269
300
  }
270
301
  console.log('clickOutside')
271
302
  detailsOpen = false
303
+ handleDropdownClose()
272
304
  }}>
273
305
  <summary
274
306
  class="select h-max min-h-10 w-full min-w-12 cursor-pointer !bg-none pr-1"
275
307
  onclick={() => {
276
308
  searchEL?.focus()
277
309
  filter = ''
310
+ filterMode = 'user'
278
311
  }}>
279
312
  {#if multiple}
280
313
  <!-- Multi-select display with chips -->
@@ -299,11 +332,12 @@
299
332
  class="h-full outline-0 {detailsOpen ? 'cursor-text' : 'cursor-pointer'}"
300
333
  bind:this={searchEL}
301
334
  bind:value={filter}
335
+ oninput={handleFilterInput}
302
336
  onclick={() => {
303
337
  detailsOpen = true
304
338
  }}
305
339
  placeholder="Search..."
306
- required={required && (!Array.isArray(value) || value.length === 0)} />
340
+ required={required && (!Array.isArray(normalizedValue) || normalizedValue.length === 0)} />
307
341
  </div>
308
342
  {:else}
309
343
  <!-- Single-select display -->
@@ -312,14 +346,15 @@
312
346
  class="h-full w-full outline-0 {detailsOpen ? 'cursor-text' : 'cursor-pointer'}"
313
347
  bind:this={searchEL}
314
348
  bind:value={filter}
349
+ oninput={handleFilterInput}
315
350
  onclick={() => {
316
351
  detailsOpen = true
317
352
  }}
318
353
  placeholder={displayText}
319
- required={required && !value} />
354
+ required={required && !normalizedValue} />
320
355
  {/if}
321
356
 
322
- {#if !required && ((multiple && Array.isArray(value) && value.length > 0) || (!multiple && value))}
357
+ {#if !required && ((multiple && Array.isArray(normalizedValue) && normalizedValue.length > 0) || (!multiple && normalizedValue))}
323
358
  <button
324
359
  type="button"
325
360
  class="btn btn-sm btn-circle btn-ghost absolute top-1 right-1"
@@ -370,7 +405,7 @@
370
405
  {/if}
371
406
 
372
407
  <!-- Render only visible items (headers and options) -->
373
- {#each visibleItems.items as entry (entry.type === 'header' ? 'header-' + entry.group : entry.item.value)}
408
+ {#each visibleItems.items as entry, idx (entry.type === 'header' ? 'header-' + entry.group + '-' + idx : 'option-' + entry.item.value + '-' + idx)}
374
409
  {#if entry.type === 'header'}
375
410
  <li
376
411
  class="bg-base-200 top-0 z-10 flex items-center justify-center px-2 text-lg font-bold text-gray-700"
@@ -268,10 +268,22 @@ function normalizeRequest(request, skipPatterns) {
268
268
  ...request,
269
269
  // Normalize filter model
270
270
  filterModel: request.filterModel,
271
- // Normalize group keys
271
+ // Normalize group keys - but respect skipNormalization patterns
272
272
  groupKeys: request.groupKeys.map((key, index) => {
273
273
  const col = request.rowGroupCols[index];
274
274
  const fieldName = col?.field || col?.id;
275
+ // Check if this field should skip normalization
276
+ if (fieldName && skipPatterns) {
277
+ const shouldSkip = skipPatterns.some((pattern) => {
278
+ if (typeof pattern === 'string') {
279
+ return fieldName === pattern;
280
+ }
281
+ return pattern.test(fieldName);
282
+ });
283
+ if (shouldSkip) {
284
+ return key; // Return original key without normalization
285
+ }
286
+ }
275
287
  return normalizeValue(key, fieldName, skipPatterns);
276
288
  }),
277
289
  };
@@ -433,12 +445,28 @@ function applyDateFilter(where, field, filter) {
433
445
  if (/^\d{4}-\d{2}-\d{2}$/.test(dateFrom)) {
434
446
  dateFrom = new Date(dateFrom + 'T00:00:00.000Z').toISOString();
435
447
  }
448
+ // If it's a date with space-separated time (YYYY-MM-DD HH:MM:SS), convert to ISO format
449
+ else if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(dateFrom)) {
450
+ dateFrom = new Date(dateFrom.replace(' ', 'T') + '.000Z').toISOString();
451
+ }
452
+ // If it's already a valid date string, ensure it's in ISO format
453
+ else if (isDateString(dateFrom)) {
454
+ dateFrom = new Date(dateFrom).toISOString();
455
+ }
436
456
  }
437
457
  if (dateTo && typeof dateTo === 'string') {
438
458
  // If it's just a date (YYYY-MM-DD), convert to end of day in ISO format
439
459
  if (/^\d{4}-\d{2}-\d{2}$/.test(dateTo)) {
440
460
  dateTo = new Date(dateTo + 'T23:59:59.999Z').toISOString();
441
461
  }
462
+ // If it's a date with space-separated time (YYYY-MM-DD HH:MM:SS), convert to ISO format
463
+ else if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(dateTo)) {
464
+ dateTo = new Date(dateTo.replace(' ', 'T') + '.000Z').toISOString();
465
+ }
466
+ // If it's already a valid date string, ensure it's in ISO format
467
+ else if (isDateString(dateTo)) {
468
+ dateTo = new Date(dateTo).toISOString();
469
+ }
442
470
  }
443
471
  switch (type) {
444
472
  case 'equals':
@@ -1032,6 +1060,7 @@ export const defaultSSRMColDef = {
1032
1060
  enableRowGroup: true,
1033
1061
  flex: 1,
1034
1062
  menuTabs: ['filterMenuTab', 'generalMenuTab'],
1063
+ enableCellChangeFlash: true,
1035
1064
  };
1036
1065
  /**
1037
1066
  * Default grid options for SSRM
@@ -0,0 +1,398 @@
1
+ import type { AGGridRequest, AGGridResponse } from './ag-grid-refactored.js';
2
+ /**
3
+ * AG Grid sort model (from AG Grid API)
4
+ */
5
+ type AGGridSort = {
6
+ colId: string;
7
+ sort: 'asc' | 'desc';
8
+ };
9
+ /**
10
+ * Power Apps Web API OData response format
11
+ */
12
+ export type PowerAppsODataResponse<TEntity = any> = {
13
+ '@odata.context': string;
14
+ '@odata.count'?: number;
15
+ '@odata.nextLink'?: string;
16
+ value: TEntity[];
17
+ };
18
+ /**
19
+ * Power Apps Web API error response format
20
+ */
21
+ export type PowerAppsError = {
22
+ error: {
23
+ code: string;
24
+ message: string;
25
+ innererror?: {
26
+ message: string;
27
+ type: string;
28
+ stacktrace: string;
29
+ };
30
+ };
31
+ };
32
+ /**
33
+ * Configuration for Power Apps query builder
34
+ */
35
+ export type PowerAppsQueryConfig<TEntity = any> = {
36
+ /** Base URL for the Web API (e.g., "[Organization URI]/api/data/v9.2") */
37
+ baseUrl: string;
38
+ /** Entity set name (e.g., "accounts", "contacts") */
39
+ entitySet: string;
40
+ /** Optional: HTTP headers (auth, preferences, etc.) */
41
+ headers?: Record<string, string>;
42
+ /** Optional: Default columns to select */
43
+ defaultSelect?: string[];
44
+ /** Optional: Default sort order */
45
+ defaultOrderBy?: string;
46
+ /** Optional: Related entities to expand (simple strings or full ExpandConfig objects) */
47
+ expand?: (string | ExpandConfig)[];
48
+ /** Optional: Transform OData response to custom format */
49
+ transformResponse?: (data: TEntity[]) => TEntity[];
50
+ /** Optional: Maximum URL length before warning (default: 32768) */
51
+ maxUrlLength?: number;
52
+ };
53
+ /**
54
+ * OData query parameters
55
+ */
56
+ export type ODataQueryParams = {
57
+ $select?: string;
58
+ $filter?: string;
59
+ $orderby?: string;
60
+ $top?: number;
61
+ $skip?: number;
62
+ $count?: boolean;
63
+ $expand?: string;
64
+ };
65
+ /**
66
+ * Builds the OData $select query parameter from column selections
67
+ *
68
+ * @param columns - Array of column names to select
69
+ * @returns OData $select string (e.g., "name,revenue,statecode")
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * buildODataSelect(['accountid', 'name', 'revenue'])
74
+ * // Returns: "accountid,name,revenue"
75
+ *
76
+ * buildODataSelect(['contactid', 'fullname', 'parentcustomerid_account/name'])
77
+ * // Returns: "contactid,fullname,parentcustomerid_account/name"
78
+ * ```
79
+ */
80
+ export declare function buildODataSelect(columns?: string[]): string | undefined;
81
+ /**
82
+ * Builds the OData $orderby query parameter from AG Grid sort model
83
+ *
84
+ * OData orderby syntax: "field1 asc,field2 desc"
85
+ *
86
+ * @param sortModel - AG Grid sort model
87
+ * @param defaultOrderBy - Fallback sort if no sort model provided
88
+ * @returns OData $orderby string (e.g., "name asc,revenue desc")
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * buildODataOrderBy([
93
+ * { colId: 'name', sort: 'asc' },
94
+ * { colId: 'revenue', sort: 'desc' }
95
+ * ])
96
+ * // Returns: "name asc,revenue desc"
97
+ * ```
98
+ */
99
+ export declare function buildODataOrderBy(sortModel?: AGGridSort[], defaultOrderBy?: string): string | undefined;
100
+ /**
101
+ * Builds the OData $filter query parameter from AG Grid filter model
102
+ *
103
+ * Converts AG Grid's filter model to OData filter syntax, supporting:
104
+ * - Text filters: eq, ne, contains, startswith, endswith
105
+ * - Number filters: eq, ne, gt, ge, lt, le, inRange
106
+ * - Date filters: eq, ne, gt, ge, lt, le, inRange
107
+ * - Set filters: multi-value OR conditions
108
+ * - Null checks: blank/notBlank
109
+ *
110
+ * @param filterModel - AG Grid filter model
111
+ * @returns OData $filter string
112
+ *
113
+ * @example
114
+ * ```typescript
115
+ * // Text filter
116
+ * buildODataFilter({ name: { filterType: 'text', type: 'contains', filter: 'John' } })
117
+ * // Returns: "contains(name,'John')"
118
+ *
119
+ * // Number range
120
+ * buildODataFilter({
121
+ * revenue: { filterType: 'number', type: 'inRange', filter: 1000, filterTo: 5000 }
122
+ * })
123
+ * // Returns: "revenue ge 1000 and revenue le 5000"
124
+ *
125
+ * // Multiple filters (AND logic)
126
+ * buildODataFilter({
127
+ * name: { filterType: 'text', type: 'contains', filter: 'Corp' },
128
+ * revenue: { filterType: 'number', type: 'greaterThan', filter: 10000 }
129
+ * })
130
+ * // Returns: "contains(name,'Corp') and revenue gt 10000"
131
+ *
132
+ * // Set filter (OR logic for multiple values)
133
+ * buildODataFilter({
134
+ * status: { filterType: 'set', values: ['Active', 'Pending'] }
135
+ * })
136
+ * // Returns: "(status eq 'Active' or status eq 'Pending')"
137
+ * ```
138
+ */
139
+ export declare function buildODataFilter(filterModel?: Record<string, unknown>): string | undefined;
140
+ /**
141
+ * Configuration for expanding a related entity
142
+ */
143
+ export type ExpandConfig = {
144
+ /** Navigation property name to expand */
145
+ navigationProperty: string;
146
+ /** Optional: Columns to select from the expanded entity */
147
+ select?: string[];
148
+ /** Optional: Filter for collection-valued navigation properties */
149
+ filter?: string;
150
+ /** Optional: Order by for collection-valued navigation properties (not supported with nested expand) */
151
+ orderBy?: string;
152
+ /** Optional: Top N records for collection-valued navigation properties (not supported with nested expand) */
153
+ top?: number;
154
+ /** Optional: Nested expands */
155
+ expand?: ExpandConfig[];
156
+ };
157
+ /**
158
+ * Builds the OData $expand query parameter for joining related tables
159
+ *
160
+ * Supports:
161
+ * - Single-valued navigation properties (many-to-one lookups)
162
+ * - Collection-valued navigation properties (one-to-many relationships)
163
+ * - Nested expands (up to recommended limit)
164
+ * - Expand with $select, $filter, $orderby, $top
165
+ *
166
+ * Limitations:
167
+ * - Max 15 $expand options recommended per query
168
+ * - $orderby and $top not supported with nested $expand on collections
169
+ * - Nested $expand not supported with N:N relationships
170
+ *
171
+ * @param expands - Array of expand configurations
172
+ * @returns OData $expand string
173
+ *
174
+ * @example Single expand with select
175
+ * ```typescript
176
+ * buildODataExpand([
177
+ * { navigationProperty: 'primarycontactid', select: ['fullname', 'emailaddress1'] }
178
+ * ])
179
+ * // Returns: "primarycontactid($select=fullname,emailaddress1)"
180
+ * ```
181
+ *
182
+ * @example Multiple expands
183
+ * ```typescript
184
+ * buildODataExpand([
185
+ * { navigationProperty: 'primarycontactid', select: ['fullname'] },
186
+ * { navigationProperty: 'createdby', select: ['fullname'] }
187
+ * ])
188
+ * // Returns: "primarycontactid($select=fullname),createdby($select=fullname)"
189
+ * ```
190
+ *
191
+ * @example Collection with filter and order
192
+ * ```typescript
193
+ * buildODataExpand([
194
+ * {
195
+ * navigationProperty: 'Account_Tasks',
196
+ * select: ['subject', 'createdon'],
197
+ * filter: "contains(subject,'Task')",
198
+ * orderBy: 'createdon desc',
199
+ * top: 10
200
+ * }
201
+ * ])
202
+ * // Returns: "Account_Tasks($select=subject,createdon;$filter=contains(subject,'Task');$orderby=createdon desc;$top=10)"
203
+ * ```
204
+ *
205
+ * @example Nested expand
206
+ * ```typescript
207
+ * buildODataExpand([
208
+ * {
209
+ * navigationProperty: 'primarycontactid',
210
+ * select: ['fullname'],
211
+ * expand: [
212
+ * { navigationProperty: 'createdby', select: ['fullname'] }
213
+ * ]
214
+ * }
215
+ * ])
216
+ * // Returns: "primarycontactid($select=fullname;$expand=createdby($select=fullname))"
217
+ * ```
218
+ */
219
+ export declare function buildODataExpand(expands?: ExpandConfig[]): string | undefined;
220
+ /**
221
+ * Simplified expand builder for basic use cases
222
+ * Use this when you just need to expand navigation properties with optional column selection
223
+ *
224
+ * @param navigationProperties - Array of navigation property names or objects with select
225
+ * @returns OData $expand string
226
+ *
227
+ * @example
228
+ * ```typescript
229
+ * buildSimpleExpand(['primarycontactid', 'createdby'])
230
+ * // Returns: "primarycontactid,createdby"
231
+ *
232
+ * buildSimpleExpand([
233
+ * 'primarycontactid',
234
+ * { navigationProperty: 'createdby', select: ['fullname'] }
235
+ * ])
236
+ * // Returns: "primarycontactid,createdby($select=fullname)"
237
+ * ```
238
+ */
239
+ export declare function buildSimpleExpand(navigationProperties?: (string | {
240
+ navigationProperty: string;
241
+ select?: string[];
242
+ })[]): string | undefined;
243
+ /**
244
+ * Creates a Power Apps query handler for server-side data fetching
245
+ *
246
+ * This is the main API for querying Power Apps Web API. It accepts AG Grid requests
247
+ * and returns data in AG Grid response format, handling all OData translation automatically.
248
+ *
249
+ * @param config - Power Apps query configuration
250
+ * @returns Function that processes AG Grid requests and returns responses
251
+ *
252
+ * @example Basic usage
253
+ * ```typescript
254
+ * const accountsQuery = createPowerAppsQuery({
255
+ * baseUrl: 'https://org.crm.dynamics.com/api/data/v9.2',
256
+ * entitySet: 'accounts',
257
+ * headers: {
258
+ * 'Authorization': 'Bearer YOUR_TOKEN',
259
+ * 'Prefer': 'odata.include-annotations="*"'
260
+ * },
261
+ * defaultSelect: ['accountid', 'name', 'revenue']
262
+ * })
263
+ *
264
+ * const response = await accountsQuery(agGridRequest)
265
+ * // Returns: { rows: [...], lastRow: 1000 }
266
+ * ```
267
+ *
268
+ * @example With related data
269
+ * ```typescript
270
+ * const contactsQuery = createPowerAppsQuery({
271
+ * baseUrl: 'https://org.crm.dynamics.com/api/data/v9.2',
272
+ * entitySet: 'contacts',
273
+ * expand: [
274
+ * { navigationProperty: 'parentcustomerid_account', select: ['name'] }
275
+ * ]
276
+ * })
277
+ * ```
278
+ *
279
+ * @example With transformation
280
+ * ```typescript
281
+ * const query = createPowerAppsQuery({
282
+ * baseUrl: 'https://org.crm.dynamics.com/api/data/v9.2',
283
+ * entitySet: 'accounts',
284
+ * transformResponse: (data) => data.map(item => ({
285
+ * id: item.accountid,
286
+ * name: item.name
287
+ * }))
288
+ * })
289
+ * ```
290
+ */
291
+ export declare function createPowerAppsQuery<TEntity = any>(config: PowerAppsQueryConfig<TEntity>): (request: AGGridRequest) => Promise<AGGridResponse<TEntity>>;
292
+ /**
293
+ * Fetches the next page using the @odata.nextLink URL
294
+ *
295
+ * Use this to implement manual pagination when you need to fetch additional pages
296
+ * beyond what AG Grid requests automatically.
297
+ *
298
+ * @param nextLink - The @odata.nextLink URL from a previous response
299
+ * @param headers - Headers to include in the request (auth, etc.)
300
+ * @returns The next page of data
301
+ *
302
+ * @example
303
+ * ```typescript
304
+ * const firstPage = await accountsQuery(agGridRequest)
305
+ *
306
+ * // If there's more data, fetch the next page
307
+ * if (firstPage.nextLink) {
308
+ * const secondPage = await fetchNextPage(firstPage.nextLink, {
309
+ * 'Authorization': 'Bearer YOUR_TOKEN'
310
+ * })
311
+ * }
312
+ * ```
313
+ */
314
+ export declare function fetchNextPage<TEntity = any>(nextLink: string, headers?: Record<string, string>): Promise<PowerAppsODataResponse<TEntity>>;
315
+ /**
316
+ * Extended response type that includes pagination information
317
+ */
318
+ export type PowerAppsQueryResult<TEntity = any> = AGGridResponse<TEntity> & {
319
+ /** Next page link if more data available */
320
+ nextLink?: string;
321
+ /** Total count of records matching the filter */
322
+ totalCount?: number;
323
+ /** Raw OData context information */
324
+ odataContext?: string;
325
+ };
326
+ /**
327
+ * Enhanced query function that returns additional metadata
328
+ *
329
+ * Use this when you need access to pagination links and other OData metadata
330
+ *
331
+ * @param config - Power Apps query configuration
332
+ * @returns Function that processes requests and returns enhanced responses
333
+ *
334
+ * @example
335
+ * ```typescript
336
+ * const query = createPowerAppsQueryWithMetadata({
337
+ * baseUrl: 'https://org.crm.dynamics.com/api/data/v9.2',
338
+ * entitySet: 'accounts'
339
+ * })
340
+ *
341
+ * const result = await query(agGridRequest)
342
+ * console.log(`Got ${result.rows.length} rows, total: ${result.totalCount}`)
343
+ *
344
+ * if (result.nextLink) {
345
+ * console.log('More data available at:', result.nextLink)
346
+ * }
347
+ * ```
348
+ */
349
+ export declare function createPowerAppsQueryWithMetadata<TEntity = any>(config: PowerAppsQueryConfig<TEntity>): (request: AGGridRequest) => Promise<PowerAppsQueryResult<TEntity>>;
350
+ /**
351
+ * Utility to fetch all pages of data (use with caution for large datasets)
352
+ *
353
+ * This will continue fetching pages until no more data is available.
354
+ * Be careful with large datasets as this can consume significant memory and time.
355
+ *
356
+ * @param queryFn - The query function created by createPowerAppsQueryWithMetadata
357
+ * @param request - Initial AG Grid request
358
+ * @param maxPages - Optional limit on number of pages to fetch (default: unlimited)
359
+ * @returns All rows across all pages
360
+ *
361
+ * @example
362
+ * ```typescript
363
+ * const query = createPowerAppsQueryWithMetadata({
364
+ * baseUrl: 'https://org.crm.dynamics.com/api/data/v9.2',
365
+ * entitySet: 'accounts'
366
+ * })
367
+ *
368
+ * // Fetch up to 10 pages (1000 records if pageSize is 100)
369
+ * const allData = await fetchAllPages(query, agGridRequest, 10)
370
+ * console.log(`Fetched ${allData.length} total records`)
371
+ * ```
372
+ */
373
+ export declare function fetchAllPages<TEntity = any>(queryFn: (request: AGGridRequest) => Promise<PowerAppsQueryResult<TEntity>>, request: AGGridRequest, maxPages?: number): Promise<TEntity[]>;
374
+ export {};
375
+ /**
376
+ * Main exports:
377
+ *
378
+ * Query Builders:
379
+ * - createPowerAppsQuery() - Main query function (returns AG Grid format)
380
+ * - createPowerAppsQueryWithMetadata() - Enhanced query with pagination metadata
381
+ *
382
+ * Helper Functions:
383
+ * - buildODataSelect() - Build $select clause
384
+ * - buildODataOrderBy() - Build $orderby clause
385
+ * - buildODataFilter() - Build $filter clause (from AG Grid filters)
386
+ * - buildODataExpand() - Build $expand clause
387
+ * - buildSimpleExpand() - Simplified expand builder
388
+ * - fetchNextPage() - Fetch next page using @odata.nextLink
389
+ * - fetchAllPages() - Fetch all pages (use with caution)
390
+ *
391
+ * Types:
392
+ * - PowerAppsQueryConfig - Configuration for query builder
393
+ * - PowerAppsODataResponse - OData response format
394
+ * - PowerAppsError - Error response format
395
+ * - PowerAppsQueryResult - Enhanced response with metadata
396
+ * - ExpandConfig - Configuration for expanding related entities
397
+ * - ODataQueryParams - OData query parameters
398
+ */