nitro-web 0.0.157 → 0.0.159
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/client/index.ts +1 -1
- package/components/partials/styleguide.tsx +39 -23
- package/package.json +2 -2
- package/types/util.d.ts +88 -43
- package/types/util.d.ts.map +1 -1
- package/util.js +156 -63
package/client/index.ts
CHANGED
|
@@ -26,7 +26,7 @@ export { Avatar } from '../components/partials/element/avatar'
|
|
|
26
26
|
export { Button } from '../components/partials/element/button'
|
|
27
27
|
export { Calendar, type CalendarProps } from '../components/partials/element/calendar'
|
|
28
28
|
export { Dropdown, type DropdownProps, type DropdownOption } from '../components/partials/element/dropdown'
|
|
29
|
-
export { Filters, type
|
|
29
|
+
export { Filters, type FilterType, usePushChangesToPath } from '../components/partials/element/filters'
|
|
30
30
|
export { GithubLink } from '../components/partials/element/github-link'
|
|
31
31
|
export { Initials } from '../components/partials/element/initials'
|
|
32
32
|
export { Message } from '../components/partials/element/message'
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Drop, Dropdown, Field, Select, Button as ButtonNitro, Checkbox, GithubLink, Modal, Calendar, injectedConfig, TimePicker,
|
|
3
|
-
Filters,
|
|
3
|
+
Filters, FilterType, Table, TableColumn, usePushChangesToPath,
|
|
4
4
|
} from 'nitro-web'
|
|
5
|
-
import { date,
|
|
5
|
+
import { date, getCurrencyOptions, onChange, ucFirst } from 'nitro-web/util'
|
|
6
6
|
import { Check, EllipsisVerticalIcon, FileEditIcon } from 'lucide-react'
|
|
7
|
+
import React from 'react'
|
|
7
8
|
|
|
8
9
|
const perPage = 10
|
|
9
10
|
const statusColors = function(status: string) {
|
|
@@ -237,7 +238,12 @@ export function Styleguide({ className, elements, children, currencies }: Styleg
|
|
|
237
238
|
<h1 class="h1">{injectedConfig.isDemo ? 'Design System' : 'Style Guide'}</h1>
|
|
238
239
|
<p class="mb-3">
|
|
239
240
|
Components are styled using
|
|
240
|
-
<a href="https://v3.tailwindcss.com/docs/configuration" class="underline" target="_blank" rel="noreferrer">TailwindCSS</a>.
|
|
241
|
+
<a href="https://v3.tailwindcss.com/docs/configuration" class="underline" target="_blank" rel="noreferrer">TailwindCSS</a>.
|
|
242
|
+
{injectedConfig.isDemo &&
|
|
243
|
+
<React.Fragment>
|
|
244
|
+
<a href="#" class="underline" onClick={indirectlyChangeTheState}>Click here</a> to indirectly change the state
|
|
245
|
+
</React.Fragment>
|
|
246
|
+
}
|
|
241
247
|
</p>
|
|
242
248
|
</div>
|
|
243
249
|
|
|
@@ -266,7 +272,13 @@ export function Styleguide({ className, elements, children, currencies }: Styleg
|
|
|
266
272
|
// menuIsOpen={true}
|
|
267
273
|
dir="bottom-right"
|
|
268
274
|
minWidth="330px"
|
|
269
|
-
options={[
|
|
275
|
+
options={[
|
|
276
|
+
{
|
|
277
|
+
label: <React.Fragment><b>New Customer</b> / Add <b>Bruce Lee</b></React.Fragment>,
|
|
278
|
+
className: 'border-bottom-with-space',
|
|
279
|
+
},
|
|
280
|
+
...options,
|
|
281
|
+
]}
|
|
270
282
|
>
|
|
271
283
|
<Button color="white" IconRight="v" class="gap-x-3">Dropdown bottom-right</Button>
|
|
272
284
|
</Dropdown>
|
|
@@ -429,10 +441,10 @@ export function Styleguide({ className, elements, children, currencies }: Styleg
|
|
|
429
441
|
fixed: true,
|
|
430
442
|
value: '0',
|
|
431
443
|
label: (
|
|
432
|
-
|
|
444
|
+
<React.Fragment>
|
|
433
445
|
<b>New Customer</b> (and clear select)
|
|
434
|
-
{customerSearch ?
|
|
435
|
-
|
|
446
|
+
{customerSearch ? <React.Fragment> / Add <b>{ucFirst(customerSearch)}</b></React.Fragment> : ''}
|
|
447
|
+
</React.Fragment>
|
|
436
448
|
),
|
|
437
449
|
},
|
|
438
450
|
{ value: '1', label: 'Iron Man Industries' },
|
|
@@ -446,7 +458,10 @@ export function Styleguide({ className, elements, children, currencies }: Styleg
|
|
|
446
458
|
<Select
|
|
447
459
|
name="currency"
|
|
448
460
|
state={state}
|
|
449
|
-
options={useMemo(() => (currencies ? getCurrencyOptions(currencies) : [
|
|
461
|
+
options={useMemo(() => (currencies ? getCurrencyOptions(currencies) : [
|
|
462
|
+
{ value: 'nzd', label: 'New Zealand Dollar' },
|
|
463
|
+
{ value: 'aud', label: 'Australian Dollar' },
|
|
464
|
+
]), [])}
|
|
450
465
|
onChange={(e) => onChange(e, setState)}
|
|
451
466
|
/>
|
|
452
467
|
</div>
|
|
@@ -522,7 +537,7 @@ export function Styleguide({ className, elements, children, currencies }: Styleg
|
|
|
522
537
|
// DropdownProps={{ menuIsOpen: true }}
|
|
523
538
|
/>
|
|
524
539
|
</div>
|
|
525
|
-
|
|
540
|
+
<div>
|
|
526
541
|
<label for="dateRange">Date range (with prefix & disabled days)</label>
|
|
527
542
|
<Field
|
|
528
543
|
name="dateRange"
|
|
@@ -532,13 +547,14 @@ export function Styleguide({ className, elements, children, currencies }: Styleg
|
|
|
532
547
|
state={state}
|
|
533
548
|
onChange={(e) => onChange(e, setState)}
|
|
534
549
|
DayPickerProps={{
|
|
535
|
-
disabled: { after: new Date(Date.now() + 1000 * 60 * 60 * 24 * 45) }
|
|
550
|
+
disabled: { after: new Date(Date.now() + 1000 * 60 * 60 * 24 * 45) },
|
|
536
551
|
}}
|
|
537
552
|
/>
|
|
538
553
|
</div>
|
|
539
554
|
<div>
|
|
540
555
|
<label for="dateMultiple">Date multi-select (right aligned)</label>
|
|
541
|
-
<Field name="dateMultiple" type="date" mode="multiple" state={state}
|
|
556
|
+
<Field name="dateMultiple" type="date" mode="multiple" state={state}
|
|
557
|
+
onChange={(e) => onChange(e, setState)} dir="bottom-right" />
|
|
542
558
|
</div>
|
|
543
559
|
<div>
|
|
544
560
|
<label for="time">Time: {date(state.time, 'dd MMM hh:mmaa')}</label>
|
|
@@ -583,14 +599,14 @@ export function Styleguide({ className, elements, children, currencies }: Styleg
|
|
|
583
599
|
<div>
|
|
584
600
|
<label for="time">TimePicker</label>
|
|
585
601
|
{/* <div className="mt-2.5 mb-6 mt-input-before mb-input-after pt-2"> */}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
602
|
+
<TimePicker
|
|
603
|
+
value={state.time}
|
|
604
|
+
className="min-h-[150] mt-2.5 mb-6 mt-input-before mb-input-after pt-2"
|
|
605
|
+
onChange={(value) => onChange({ target: { name: 'time', value: value }}, setState)}
|
|
606
|
+
// Testing timezone support:
|
|
607
|
+
// tz="Pacific/Honolulu"
|
|
608
|
+
// referenceTimestamp={new Date().getTime() + 1000 * 60 * 60 * 24 * 5}
|
|
609
|
+
/>
|
|
594
610
|
{/* </div> */}
|
|
595
611
|
</div>
|
|
596
612
|
</div>
|
|
@@ -650,7 +666,7 @@ export function Styleguide({ className, elements, children, currencies }: Styleg
|
|
|
650
666
|
)}
|
|
651
667
|
|
|
652
668
|
{groups.includes('Modals') && (
|
|
653
|
-
|
|
669
|
+
<React.Fragment>
|
|
654
670
|
<div>
|
|
655
671
|
<h2 class="h3">Modals</h2>
|
|
656
672
|
<div class="mb-6"><Button color="primary" onClick={() => setShowModal1(true)}>Modal (default)</Button></div>
|
|
@@ -673,13 +689,13 @@ export function Styleguide({ className, elements, children, currencies }: Styleg
|
|
|
673
689
|
<Button color="primary" onClick={() => setShowModal1(false)}>Save</Button>
|
|
674
690
|
</div>
|
|
675
691
|
</Modal>
|
|
676
|
-
|
|
692
|
+
</React.Fragment>
|
|
677
693
|
)}
|
|
678
694
|
|
|
679
695
|
{groups.includes('Custom Components') && (
|
|
680
|
-
|
|
696
|
+
<React.Fragment>
|
|
681
697
|
{children}
|
|
682
|
-
|
|
698
|
+
</React.Fragment>
|
|
683
699
|
)}
|
|
684
700
|
|
|
685
701
|
<GithubLink filename={__filename} />
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nitro-web",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.159",
|
|
4
4
|
"repository": "github:boycce/nitro-web",
|
|
5
5
|
"homepage": "https://boycce.github.io/nitro-web/",
|
|
6
6
|
"description": "Nitro is a battle-tested, modular base project to turbocharge your projects, styled using Tailwind 🚀",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"@stripe/stripe-js": "^1.34.0",
|
|
60
|
-
"monastery": "
|
|
60
|
+
"monastery": "^3.5.9",
|
|
61
61
|
"stripe": "^9.16.0"
|
|
62
62
|
},
|
|
63
63
|
"_peers-are-packages-that-will-be-used-in-the-host-repo-too": "",
|
package/types/util.d.ts
CHANGED
|
@@ -190,6 +190,22 @@ export function deepSetWithInfo<T>(_obj: T, path: string, value: unknown | Funct
|
|
|
190
190
|
parent: T;
|
|
191
191
|
fieldName: string;
|
|
192
192
|
};
|
|
193
|
+
/**
|
|
194
|
+
* Recursively traverses arrays and plain objects and replaces
|
|
195
|
+
* every value strictly equal to "{VALUE}".
|
|
196
|
+
*
|
|
197
|
+
* - Does not mutate the original input
|
|
198
|
+
* - Only traverses plain objects and arrays
|
|
199
|
+
* - Throws if a circular reference is detected
|
|
200
|
+
*
|
|
201
|
+
* @template T
|
|
202
|
+
* @param {T} input
|
|
203
|
+
* @param {any} matchingValue
|
|
204
|
+
* @param {any} value
|
|
205
|
+
* @returns {T}
|
|
206
|
+
* @throws {Error}
|
|
207
|
+
*/
|
|
208
|
+
export function deepSetWithMatch<T>(input: T, matchingValue: any, value: any): T;
|
|
193
209
|
/**
|
|
194
210
|
* Iterates over an object or array
|
|
195
211
|
* @param {{[key: string]: any}|[]|null} obj
|
|
@@ -528,63 +544,86 @@ export function onChange<T>(eventOrPathValue: EventOrPathValue, setState: React.
|
|
|
528
544
|
export function pad(num?: number, padLeft?: number, fixedRight?: number): string;
|
|
529
545
|
/**
|
|
530
546
|
* Validates req.query "filters" against a config object, and returns a MongoDB-compatible query object.
|
|
547
|
+
* - `{ rule: 'search' }` is only supported with supported $search operations (e.g. aggregate).
|
|
548
|
+
* - `{ rule: 'text', numberFields: string[] }` number fields require indexes to work.
|
|
531
549
|
* @param {{ [key: string]: unknown }} query - req.query
|
|
532
|
-
*
|
|
533
|
-
*
|
|
534
|
-
*
|
|
535
|
-
*
|
|
536
|
-
*
|
|
537
|
-
*
|
|
538
|
-
*
|
|
539
|
-
*
|
|
540
|
-
*
|
|
541
|
-
*
|
|
542
|
-
* }
|
|
543
|
-
*
|
|
544
|
-
*
|
|
545
|
-
*
|
|
546
|
-
*
|
|
547
|
-
*
|
|
548
|
-
*
|
|
549
|
-
*
|
|
550
|
-
*
|
|
551
|
-
*
|
|
552
|
-
*
|
|
553
|
-
*
|
|
554
|
-
*
|
|
555
|
-
*
|
|
556
|
-
*
|
|
557
|
-
* @example
|
|
558
|
-
*
|
|
559
|
-
*
|
|
560
|
-
*
|
|
561
|
-
*
|
|
562
|
-
*
|
|
563
|
-
*
|
|
564
|
-
*
|
|
565
|
-
*
|
|
566
|
-
*
|
|
567
|
-
*
|
|
568
|
-
*
|
|
550
|
+
* @param {{ [key: string]: (
|
|
551
|
+
* 'string'
|
|
552
|
+
* | 'number'
|
|
553
|
+
* | 'boolean'
|
|
554
|
+
* | 'dateRange'
|
|
555
|
+
* | EnumArray
|
|
556
|
+
* | { rule: 'ids', parseId: parseId }
|
|
557
|
+
* | 'text'
|
|
558
|
+
* | { rule: 'text', numberFields: string[] }
|
|
559
|
+
* | { rule: 'search' } & SearchOperators
|
|
560
|
+
* )}} config - object of allowed filter/rule pairs.
|
|
561
|
+
*
|
|
562
|
+
* Examples:
|
|
563
|
+
*
|
|
564
|
+
* @example query = {
|
|
565
|
+
* location: '10-RS',
|
|
566
|
+
* age: '33',
|
|
567
|
+
* isDeleted: 'false',
|
|
568
|
+
* createdAt: '1749038400000,1749729600000',
|
|
569
|
+
* status: 'incomplete',
|
|
570
|
+
* customer.0: '69214ce7ab121fb3726965a1', // splayed array items
|
|
571
|
+
* text: 'John Doe',
|
|
572
|
+
* text: '15',
|
|
573
|
+
* search: 'John Doe',
|
|
574
|
+
* }
|
|
575
|
+
* @example config = {
|
|
576
|
+
* location: 'string',
|
|
577
|
+
* age: 'number',
|
|
578
|
+
* isDeleted: 'boolean',
|
|
579
|
+
* createdAt: 'dateRange',
|
|
580
|
+
* status: ['incomplete', 'complete'], // EnumArray [string|boolean|number]
|
|
581
|
+
* customer: { rule: 'ids', ObjectId: ObjectIdConstructor },
|
|
582
|
+
* text: 'text',
|
|
583
|
+
* text: { rule: 'text', numberFields: ['age'] }, // search with numeric fields
|
|
584
|
+
* search: { rule: 'search', text: { query: "{VALUE}", path: ['firstName'] } },
|
|
585
|
+
* }
|
|
586
|
+
* @example returned MongoDB query object = {
|
|
587
|
+
* location: '10-RS',
|
|
588
|
+
* age: 33,
|
|
589
|
+
* isDeleted: false,
|
|
590
|
+
* createdAt: { $gte: 1749038400000, $lte: 1749729600000 },
|
|
591
|
+
* status: 'incomplete',
|
|
592
|
+
* customer: { $in: [new ObjectId('1234567890')] },
|
|
593
|
+
* $text: { $search: 'John Doe' },
|
|
594
|
+
* $or: [{ $text: { $search: '15' }}, { age: 15 }],
|
|
595
|
+
* $search: { text: { query: "John Doe", path: ['firstName'] }},
|
|
596
|
+
* }
|
|
569
597
|
*/
|
|
570
598
|
export function parseFilters(query: {
|
|
571
599
|
[key: string]: unknown;
|
|
572
600
|
}, config: {
|
|
573
|
-
[key: string]: "string" | "number" | "boolean" | "
|
|
601
|
+
[key: string]: ("string" | "number" | "boolean" | "dateRange" | EnumArray | {
|
|
574
602
|
rule: "ids";
|
|
575
603
|
parseId: parseId;
|
|
576
|
-
}
|
|
604
|
+
} | "text" | {
|
|
605
|
+
rule: "text";
|
|
606
|
+
numberFields: string[];
|
|
607
|
+
} | ({
|
|
608
|
+
rule: "search";
|
|
609
|
+
} & SearchOperators));
|
|
577
610
|
}): {
|
|
578
|
-
[key: string]: string | number | boolean | {
|
|
579
|
-
$search: string;
|
|
580
|
-
} | {
|
|
611
|
+
[key: string]: string | number | boolean | SearchOperators | {
|
|
581
612
|
$gte?: number;
|
|
582
613
|
$gt?: number;
|
|
583
614
|
$lte?: number;
|
|
584
615
|
$lt?: number;
|
|
585
616
|
} | {
|
|
586
617
|
$in: ObjectId[];
|
|
587
|
-
}
|
|
618
|
+
} | {
|
|
619
|
+
$search: string;
|
|
620
|
+
} | ({
|
|
621
|
+
$text: {
|
|
622
|
+
$search: string;
|
|
623
|
+
};
|
|
624
|
+
} | {
|
|
625
|
+
[key: string]: number;
|
|
626
|
+
})[];
|
|
588
627
|
};
|
|
589
628
|
/**
|
|
590
629
|
* Parses req.query "pagination" and "sorting" fields and returns a monastery-compatible options object.
|
|
@@ -879,6 +918,12 @@ export type parseId = (value: string) => ObjectId;
|
|
|
879
918
|
* - an array of strings, numbers or booleans
|
|
880
919
|
*/
|
|
881
920
|
export type EnumArray = (string | number | boolean)[];
|
|
921
|
+
/**
|
|
922
|
+
* - https://mongodb.com/docs/atlas/atlas-search/operators-and-collectors/
|
|
923
|
+
*/
|
|
924
|
+
export type SearchOperators = {
|
|
925
|
+
[key: string]: any;
|
|
926
|
+
};
|
|
882
927
|
export type NitroError = {
|
|
883
928
|
title: string;
|
|
884
929
|
detail: string;
|
package/types/util.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../util.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../util.js"],"names":[],"mappings":"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"}
|
package/util.js
CHANGED
|
@@ -29,6 +29,7 @@ export { TZDate } from '@date-fns/tz'
|
|
|
29
29
|
/** @typedef {object} ObjectId */
|
|
30
30
|
/** @typedef {(value: string) => ObjectId} parseId */
|
|
31
31
|
/** @typedef {(string|number|boolean)[]} EnumArray - an array of strings, numbers or booleans */
|
|
32
|
+
/** @typedef {{ [key: string]: any }} SearchOperators - https://mongodb.com/docs/atlas/atlas-search/operators-and-collectors/ */
|
|
32
33
|
/** @typedef {{ title: string, detail: string }} NitroError */
|
|
33
34
|
/** @typedef {{ toJSON: () => { message: string } }} MongoError */
|
|
34
35
|
/** @typedef {{ response: { data: { errors?: NitroError[], error?: string, error_description?: string } } }} AxiosWithErrors */
|
|
@@ -540,6 +541,56 @@ export function deepSetWithInfo(_obj, path, value) {
|
|
|
540
541
|
}
|
|
541
542
|
}
|
|
542
543
|
|
|
544
|
+
/**
|
|
545
|
+
* Recursively traverses arrays and plain objects and replaces
|
|
546
|
+
* every value strictly equal to "{VALUE}".
|
|
547
|
+
*
|
|
548
|
+
* - Does not mutate the original input
|
|
549
|
+
* - Only traverses plain objects and arrays
|
|
550
|
+
* - Throws if a circular reference is detected
|
|
551
|
+
*
|
|
552
|
+
* @template T
|
|
553
|
+
* @param {T} input
|
|
554
|
+
* @param {any} matchingValue
|
|
555
|
+
* @param {any} value
|
|
556
|
+
* @returns {T}
|
|
557
|
+
* @throws {Error}
|
|
558
|
+
*/
|
|
559
|
+
export function deepSetWithMatch(input, matchingValue, value) {
|
|
560
|
+
const seen = new WeakMap()
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* @param {*} input2
|
|
564
|
+
* @returns {*}
|
|
565
|
+
*/
|
|
566
|
+
function walk(input2) {
|
|
567
|
+
if (input2 === matchingValue) return value
|
|
568
|
+
|
|
569
|
+
if (Array.isArray(input2)) {
|
|
570
|
+
return input2.map(walk)
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if (input2 && typeof input2 === 'object') {
|
|
574
|
+
const proto = Object.getPrototypeOf(input2)
|
|
575
|
+
if (proto !== Object.prototype && proto !== null) return input2
|
|
576
|
+
if (seen.has(input2)) throw new Error('Circular reference detected')
|
|
577
|
+
|
|
578
|
+
/** @type {{ [key: string]: any }} */
|
|
579
|
+
const result = {}
|
|
580
|
+
seen.set(input2, result)
|
|
581
|
+
|
|
582
|
+
for (const key of Object.keys(input2)) {
|
|
583
|
+
result[key] = (walk(input2[key]))
|
|
584
|
+
}
|
|
585
|
+
return result
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return input2
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
return /** @type {T} */ (walk(input))
|
|
592
|
+
}
|
|
593
|
+
|
|
543
594
|
/**
|
|
544
595
|
* Iterates over an object or array
|
|
545
596
|
* @param {{[key: string]: any}|[]|null} obj
|
|
@@ -1282,54 +1333,77 @@ export function pad (num=0, padLeft=0, fixedRight) {
|
|
|
1282
1333
|
|
|
1283
1334
|
/**
|
|
1284
1335
|
* Validates req.query "filters" against a config object, and returns a MongoDB-compatible query object.
|
|
1336
|
+
* - `{ rule: 'search' }` is only supported with supported $search operations (e.g. aggregate).
|
|
1337
|
+
* - `{ rule: 'text', numberFields: string[] }` number fields require indexes to work.
|
|
1285
1338
|
* @param {{ [key: string]: unknown }} query - req.query
|
|
1286
|
-
*
|
|
1287
|
-
*
|
|
1288
|
-
*
|
|
1289
|
-
*
|
|
1290
|
-
*
|
|
1291
|
-
*
|
|
1292
|
-
*
|
|
1293
|
-
*
|
|
1294
|
-
*
|
|
1295
|
-
*
|
|
1296
|
-
* }
|
|
1297
|
-
*
|
|
1298
|
-
*
|
|
1299
|
-
*
|
|
1300
|
-
*
|
|
1301
|
-
*
|
|
1302
|
-
*
|
|
1303
|
-
*
|
|
1304
|
-
*
|
|
1305
|
-
*
|
|
1306
|
-
*
|
|
1307
|
-
*
|
|
1308
|
-
*
|
|
1309
|
-
*
|
|
1310
|
-
*
|
|
1311
|
-
* @example
|
|
1312
|
-
*
|
|
1313
|
-
*
|
|
1314
|
-
*
|
|
1315
|
-
*
|
|
1316
|
-
*
|
|
1317
|
-
*
|
|
1318
|
-
*
|
|
1319
|
-
*
|
|
1320
|
-
*
|
|
1321
|
-
*
|
|
1322
|
-
*
|
|
1339
|
+
* @param {{ [key: string]: (
|
|
1340
|
+
* 'string'
|
|
1341
|
+
* | 'number'
|
|
1342
|
+
* | 'boolean'
|
|
1343
|
+
* | 'dateRange'
|
|
1344
|
+
* | EnumArray
|
|
1345
|
+
* | { rule: 'ids', parseId: parseId }
|
|
1346
|
+
* | 'text'
|
|
1347
|
+
* | { rule: 'text', numberFields: string[] }
|
|
1348
|
+
* | { rule: 'search' } & SearchOperators
|
|
1349
|
+
* )}} config - object of allowed filter/rule pairs.
|
|
1350
|
+
*
|
|
1351
|
+
* Examples:
|
|
1352
|
+
*
|
|
1353
|
+
* @example query = {
|
|
1354
|
+
* location: '10-RS',
|
|
1355
|
+
* age: '33',
|
|
1356
|
+
* isDeleted: 'false',
|
|
1357
|
+
* createdAt: '1749038400000,1749729600000',
|
|
1358
|
+
* status: 'incomplete',
|
|
1359
|
+
* customer.0: '69214ce7ab121fb3726965a1', // splayed array items
|
|
1360
|
+
* text: 'John Doe',
|
|
1361
|
+
* text: '15',
|
|
1362
|
+
* search: 'John Doe',
|
|
1363
|
+
* }
|
|
1364
|
+
* @example config = {
|
|
1365
|
+
* location: 'string',
|
|
1366
|
+
* age: 'number',
|
|
1367
|
+
* isDeleted: 'boolean',
|
|
1368
|
+
* createdAt: 'dateRange',
|
|
1369
|
+
* status: ['incomplete', 'complete'], // EnumArray [string|boolean|number]
|
|
1370
|
+
* customer: { rule: 'ids', ObjectId: ObjectIdConstructor },
|
|
1371
|
+
* text: 'text',
|
|
1372
|
+
* text: { rule: 'text', numberFields: ['age'] }, // search with numeric fields
|
|
1373
|
+
* search: { rule: 'search', text: { query: "{VALUE}", path: ['firstName'] } },
|
|
1374
|
+
* }
|
|
1375
|
+
* @example returned MongoDB query object = {
|
|
1376
|
+
* location: '10-RS',
|
|
1377
|
+
* age: 33,
|
|
1378
|
+
* isDeleted: false,
|
|
1379
|
+
* createdAt: { $gte: 1749038400000, $lte: 1749729600000 },
|
|
1380
|
+
* status: 'incomplete',
|
|
1381
|
+
* customer: { $in: [new ObjectId('1234567890')] },
|
|
1382
|
+
* $text: { $search: 'John Doe' },
|
|
1383
|
+
* $or: [{ $text: { $search: '15' }}, { age: 15 }],
|
|
1384
|
+
* $search: { text: { query: "John Doe", path: ['firstName'] }},
|
|
1385
|
+
* }
|
|
1323
1386
|
*/
|
|
1324
1387
|
export function parseFilters(query, config) {
|
|
1325
|
-
/**
|
|
1388
|
+
/**
|
|
1326
1389
|
* Should match the example returned object above
|
|
1327
1390
|
* @type {{
|
|
1328
|
-
* [key: string]:
|
|
1329
|
-
*
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1391
|
+
* [key: string]: (
|
|
1392
|
+
* string
|
|
1393
|
+
* | number
|
|
1394
|
+
* | boolean
|
|
1395
|
+
* | { $gte?: number; $gt?: number; $lte?: number; $lt?: number; }
|
|
1396
|
+
* | (string|number|boolean)
|
|
1397
|
+
* | { $in: ObjectId[] }
|
|
1398
|
+
* | { $search: string } // key=$text
|
|
1399
|
+
* | ({ $text: { $search: string } } | { [key: string]: number })[] // key=$or
|
|
1400
|
+
* | SearchOperators // key=$search
|
|
1401
|
+
* )
|
|
1402
|
+
* }} */
|
|
1403
|
+
const returnedMongoQuery = {}
|
|
1404
|
+
|
|
1405
|
+
// Convert splayed array items into a unified array objects,
|
|
1406
|
+
// E.g. 'customer.0' = '1' and 'customer.1' = '2' -> 'customer' = '1,2'
|
|
1333
1407
|
for (const key in query) {
|
|
1334
1408
|
if (key.match(/\.\d+$/)) {
|
|
1335
1409
|
const baseKey = key.replace(/\.\d+$/, '')
|
|
@@ -1341,7 +1415,7 @@ export function parseFilters(query, config) {
|
|
|
1341
1415
|
|
|
1342
1416
|
for (const key in query) {
|
|
1343
1417
|
if (typeof query[key] !== 'string') continue
|
|
1344
|
-
const val = query[key]
|
|
1418
|
+
const val = query[key].trim()
|
|
1345
1419
|
const rule = config[key]
|
|
1346
1420
|
|
|
1347
1421
|
if (!rule) {
|
|
@@ -1349,24 +1423,26 @@ export function parseFilters(query, config) {
|
|
|
1349
1423
|
|
|
1350
1424
|
} else if (rule === 'string') {
|
|
1351
1425
|
if (typeof val !== 'string') throw new Error(`The "${key}" filter has an invalid value "${val}". Expected a string.`)
|
|
1352
|
-
|
|
1426
|
+
returnedMongoQuery[key] = val
|
|
1353
1427
|
|
|
1354
1428
|
} else if (rule === 'number') {
|
|
1355
1429
|
const num = parseFloat(val)
|
|
1356
1430
|
if (isNaN(num)) throw new Error(`The "${key}" filter should be a number, but received "${val}".`)
|
|
1357
|
-
|
|
1431
|
+
returnedMongoQuery[key] = num
|
|
1358
1432
|
|
|
1359
1433
|
} else if (rule === 'boolean') {
|
|
1360
1434
|
const bool = val === 'true' ? true : val === 'false' ? false : undefined
|
|
1361
1435
|
if (bool === undefined) throw new Error(`The "${key}" filter should be a boolean, but received "${val}".`)
|
|
1362
|
-
|
|
1436
|
+
returnedMongoQuery[key] = bool
|
|
1363
1437
|
|
|
1364
|
-
} else if (rule === '
|
|
1365
|
-
|
|
1366
|
-
|
|
1438
|
+
} else if (rule === 'dateRange') {
|
|
1439
|
+
const [start, end] = val.split(',').map(Number)
|
|
1440
|
+
if (isNaN(start) && isNaN(end)) throw new Error(`The "${key}" filter has an invalid value "${val}". Expected a date range.`)
|
|
1441
|
+
else if (isNaN(start)) returnedMongoQuery[key] = { $gte: 0, $lte: end }
|
|
1442
|
+
else if (isNaN(end)) returnedMongoQuery[key] = { $gte: start }
|
|
1443
|
+
else returnedMongoQuery[key] = { $gte: start, $lte: end }
|
|
1367
1444
|
|
|
1368
|
-
//
|
|
1369
|
-
} else if (Array.isArray(rule)) {
|
|
1445
|
+
} else if (Array.isArray(rule)) { // Enum
|
|
1370
1446
|
// Detetect the entire array's type from the first item
|
|
1371
1447
|
const type = typeof rule[0]
|
|
1372
1448
|
if (!['string', 'number', 'boolean'].includes(type)) {
|
|
@@ -1377,11 +1453,10 @@ export function parseFilters(query, config) {
|
|
|
1377
1453
|
let valParsed = /** @type {string|number|boolean|undefined} */(val)
|
|
1378
1454
|
if (type === 'number') valParsed = parseFloat(val)
|
|
1379
1455
|
else if (type === 'boolean') valParsed = val === 'true' ? true : val === 'false' ? false : undefined
|
|
1380
|
-
if (valParsed === ruleItem)
|
|
1456
|
+
if (valParsed === ruleItem) returnedMongoQuery[key] = valParsed
|
|
1381
1457
|
}
|
|
1382
1458
|
|
|
1383
|
-
//
|
|
1384
|
-
} else if (typeof rule === 'object' && 'rule' in rule && rule.rule === 'ids') {
|
|
1459
|
+
} else if (typeof rule === 'object' && 'rule' in rule && rule.rule === 'ids') { // ids
|
|
1385
1460
|
if (!rule.parseId) {
|
|
1386
1461
|
throw new Error(`The "${key}" filter has an invalid rule. Expected a parseId function.`)
|
|
1387
1462
|
}
|
|
@@ -1390,21 +1465,39 @@ export function parseFilters(query, config) {
|
|
|
1390
1465
|
else return rule.parseId(id)
|
|
1391
1466
|
})
|
|
1392
1467
|
if (!ids.length) throw new Error(`Please pass at least one id to the "${key}" filter.`)
|
|
1393
|
-
|
|
1468
|
+
returnedMongoQuery[key] = { $in: ids }
|
|
1394
1469
|
|
|
1395
|
-
} else if (rule === '
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
else if (isNaN(start)) mongoQuery[key] = { $gte: 0, $lte: end }
|
|
1399
|
-
else if (isNaN(end)) mongoQuery[key] = { $gte: start }
|
|
1400
|
-
else mongoQuery[key] = { $gte: start, $lte: end }
|
|
1470
|
+
} else if (rule === 'text') {
|
|
1471
|
+
if (typeof val !== 'string') throw new Error(`The "${key}" filter has an invalid value "${val}". Expected a string.`)
|
|
1472
|
+
returnedMongoQuery['$text'] = { $search: '"' + val + '"' }
|
|
1401
1473
|
|
|
1474
|
+
} else if (typeof rule === 'object' && 'rule' in rule && rule.rule === 'text') {
|
|
1475
|
+
if (typeof val !== 'string') {
|
|
1476
|
+
throw new Error(`The "${key}" filter has an invalid value "${val}". Expected a string.`)
|
|
1477
|
+
} else if (!Array.isArray(rule.numberFields) || !rule.numberFields.length) {
|
|
1478
|
+
throw new Error(`The "${key}" filter has an invalid rule. Expected an numberFields to be a non-empty array.`)
|
|
1479
|
+
}
|
|
1480
|
+
const ors = []
|
|
1481
|
+
const num = parseFloat(val)
|
|
1482
|
+
ors.push({ $text: { $search: val } })
|
|
1483
|
+
if (!isNaN(num)) {
|
|
1484
|
+
for (const field of rule.numberFields) {
|
|
1485
|
+
ors.push({ [field]: num })
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
returnedMongoQuery.$or = ors
|
|
1489
|
+
|
|
1490
|
+
} else if (typeof rule === 'object' && 'rule' in rule && rule.rule === 'search') {
|
|
1491
|
+
if (typeof val !== 'string') throw new Error(`The "${key}" filter has an invalid value "${val}". Expected a string.`)
|
|
1492
|
+
const output = /** @type {SearchOperators} */(deepSetWithMatch(rule, '{VALUE}', val)) // replace {VALUE} with the value
|
|
1493
|
+
delete output.rule // need to remove rule from the object
|
|
1494
|
+
returnedMongoQuery['$search'] = output
|
|
1402
1495
|
} else {
|
|
1403
1496
|
throw new Error(`Unknown filter type "${rule}" in the config.`)
|
|
1404
1497
|
}
|
|
1405
1498
|
}
|
|
1406
1499
|
|
|
1407
|
-
return
|
|
1500
|
+
return returnedMongoQuery
|
|
1408
1501
|
}
|
|
1409
1502
|
|
|
1410
1503
|
/**
|