@bagelink/vue 1.7.86 → 1.7.90

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.
@@ -204,7 +204,7 @@ export { useForm } from './BagelFormUtils'
204
204
  export { useLang } from './lang'
205
205
 
206
206
  export type { ComparisonOperator, FilterCondition, LogicalOperator, QueryConditions, QueryFilter } from './queryFilter'
207
- export { anyOf, query, range, search } from './queryFilter'
207
+ export { anyOf, parseQuery, query, range, search } from './queryFilter'
208
208
  export { formatString } from './strings'
209
209
 
210
210
  const URL_REGEX = /^https?:\/\/|^\/\//
@@ -364,3 +364,135 @@ export function search<T extends Record<string, any>>(
364
364
  }))
365
365
  return buildQuery(conditions)
366
366
  }
367
+
368
+ const OPERATORS: ComparisonOperator[] = ['eq', 'ne', 'gt', 'ge', 'lt', 'le', 'co', 'sw', 'ew', 'pr']
369
+ const CONNECTORS: LogicalOperator[] = ['and', 'or']
370
+
371
+ /**
372
+ * Parse a value string into a Primitive
373
+ */
374
+ function parseValue(valueStr: string): Primitive {
375
+ const trimmed = valueStr.trim()
376
+ if (trimmed === 'null') return null
377
+ if (trimmed === 'true') return true
378
+ if (trimmed === 'false') return false
379
+ if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
380
+ // Unescape quotes in strings
381
+ return trimmed.slice(1, -1).replace(/\\"/g, '"')
382
+ }
383
+ const num = Number(trimmed)
384
+ if (!Number.isNaN(num) && trimmed !== '') return num
385
+ return trimmed
386
+ }
387
+
388
+ /**
389
+ * Parse a SCIM 2.0 query string into conditions array
390
+ *
391
+ * @example
392
+ * ```typescript
393
+ * parseQuery('first_name eq "John" and age gt 18')
394
+ * // → [
395
+ * // { field: 'first_name', op: 'eq', value: 'John' },
396
+ * // { field: 'age', op: 'gt', value: 18, connector: 'and' },
397
+ * // ]
398
+ *
399
+ * parseQuery('email pr')
400
+ * // → [{ field: 'email', op: 'pr' }]
401
+ * ```
402
+ */
403
+ export function parseQuery<T extends Record<string, any> = Record<string, any>>(
404
+ queryString: string
405
+ ): QueryConditions<T> {
406
+ if (!queryString || !queryString.trim()) return []
407
+
408
+ const conditions: QueryConditions<T> = []
409
+ const tokens = tokenize(queryString)
410
+ let i = 0
411
+
412
+ while (i < tokens.length) {
413
+ let connector: LogicalOperator | undefined
414
+
415
+ // Check for connector (and/or)
416
+ if (conditions.length > 0 && CONNECTORS.includes(tokens[i]?.toLowerCase() as LogicalOperator)) {
417
+ connector = tokens[i].toLowerCase() as LogicalOperator
418
+ i++
419
+ }
420
+
421
+ if (i >= tokens.length) break
422
+
423
+ const field = tokens[i++]
424
+ if (i >= tokens.length) break
425
+
426
+ const op = tokens[i++]?.toLowerCase() as ComparisonOperator
427
+ if (!OPERATORS.includes(op)) {
428
+ throw new Error(`Invalid operator: ${op}`)
429
+ }
430
+
431
+ let value: Primitive | undefined
432
+ if (op !== 'pr') {
433
+ if (i >= tokens.length) {
434
+ throw new Error(`Missing value for operator ${op}`)
435
+ }
436
+ value = parseValue(tokens[i++])
437
+ }
438
+
439
+ const condition: FilterCondition<T> = { field, op, value }
440
+ if (connector) condition.connector = connector
441
+ conditions.push(condition)
442
+ }
443
+
444
+ return conditions
445
+ }
446
+
447
+ /**
448
+ * Tokenize a SCIM query string, handling quoted strings
449
+ */
450
+ function tokenize(query: string): string[] {
451
+ const tokens: string[] = []
452
+ let current = ''
453
+ let inQuote = false
454
+ let i = 0
455
+
456
+ while (i < query.length) {
457
+ const char = query[i]
458
+
459
+ if (inQuote) {
460
+ if (char === '\\' && query[i + 1] === '"') {
461
+ current += '\\"'
462
+ i += 2
463
+ continue
464
+ }
465
+ if (char === '"') {
466
+ current += '"'
467
+ tokens.push(current)
468
+ current = ''
469
+ inQuote = false
470
+ i++
471
+ continue
472
+ }
473
+ current += char
474
+ i++
475
+ } else {
476
+ if (char === '"') {
477
+ if (current) tokens.push(current)
478
+ current = '"'
479
+ inQuote = true
480
+ i++
481
+ continue
482
+ }
483
+ if (char === ' ' || char === '\t') {
484
+ if (current) {
485
+ tokens.push(current)
486
+ current = ''
487
+ }
488
+ i++
489
+ continue
490
+ }
491
+ current += char
492
+ i++
493
+ }
494
+ }
495
+
496
+ if (current) tokens.push(current)
497
+ return tokens
498
+ }