@carecard/validate 3.1.16 → 3.1.17

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.
@@ -0,0 +1,114 @@
1
+ name: Auto Draft PR
2
+
3
+ # Creates a draft Pull Request automatically when a feature/fix/hotfix/etc.
4
+ # branch is pushed.
5
+ #
6
+ # Why not `workflow_run`?
7
+ # GitHub only dispatches `workflow_run` events for workflow files that
8
+ # exist on the repository's DEFAULT branch. Until this file is merged
9
+ # into the default branch, a `workflow_run`-based trigger silently does
10
+ # nothing. Triggering directly on `push` (with the same branch filters
11
+ # as CI) is more reliable and works from the branch itself.
12
+ #
13
+ # Routing rules:
14
+ # - If the branch name starts with "main-" (e.g. main-hotfix-x), open a
15
+ # draft PR targeting `main`.
16
+ # - For any other branch (except protected base branches), open a draft
17
+ # PR targeting `development`.
18
+ #
19
+ # Branch patterns mirror `.github/workflows/ci.yml` and support nested
20
+ # names such as `feature/foo/bar`, `fix/bug-1`, `hotfix/...`, etc.
21
+
22
+ on:
23
+ push:
24
+ branches:
25
+ - 'feature/**'
26
+ - 'releases/**'
27
+ - 'release*'
28
+ - 'hotfix/**'
29
+ - 'iss/**'
30
+ - 'fix/**'
31
+ - 'main-*'
32
+ - 'main-**'
33
+ paths-ignore:
34
+ - '**.md'
35
+ workflow_dispatch:
36
+
37
+ permissions:
38
+ contents: read
39
+ pull-requests: write
40
+
41
+ jobs:
42
+ open-draft-pr:
43
+ runs-on: ubuntu-latest
44
+
45
+ steps:
46
+ - name: Determine target base branch
47
+ id: target
48
+ env:
49
+ HEAD_BRANCH: ${{ github.ref_name }}
50
+ run: |
51
+ set -euo pipefail
52
+ echo "Head branch: $HEAD_BRANCH"
53
+
54
+ # Skip protected/base branches – we never open a PR from them to themselves.
55
+ case "$HEAD_BRANCH" in
56
+ main|master|development|develop)
57
+ echo "Branch '$HEAD_BRANCH' is a base branch – skipping PR creation."
58
+ echo "skip=true" >> "$GITHUB_OUTPUT"
59
+ exit 0
60
+ ;;
61
+ esac
62
+
63
+ if [[ "$HEAD_BRANCH" == main-* ]]; then
64
+ BASE="main"
65
+ else
66
+ BASE="development"
67
+ fi
68
+
69
+ echo "Target base branch: $BASE"
70
+ echo "base=$BASE" >> "$GITHUB_OUTPUT"
71
+ echo "head=$HEAD_BRANCH" >> "$GITHUB_OUTPUT"
72
+ echo "skip=false" >> "$GITHUB_OUTPUT"
73
+
74
+ - name: Checkout repository
75
+ if: steps.target.outputs.skip == 'false'
76
+ uses: actions/checkout@v4
77
+ with:
78
+ fetch-depth: 0
79
+
80
+ - name: Create draft Pull Request (if missing)
81
+ if: steps.target.outputs.skip == 'false'
82
+ env:
83
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
84
+ HEAD: ${{ steps.target.outputs.head }}
85
+ BASE: ${{ steps.target.outputs.base }}
86
+ REPO: ${{ github.repository }}
87
+ run: |
88
+ set -euo pipefail
89
+
90
+ # Avoid creating duplicates: check for an existing open PR for this head -> base.
91
+ # `gh pr list --head` expects the branch name (without owner prefix) for same-repo PRs.
92
+ EXISTING=$(gh pr list \
93
+ --repo "$REPO" \
94
+ --state open \
95
+ --head "$HEAD" \
96
+ --base "$BASE" \
97
+ --json number \
98
+ --jq '.[0].number' || true)
99
+
100
+ if [[ -n "${EXISTING:-}" ]]; then
101
+ echo "An open PR already exists for $HEAD -> $BASE (#$EXISTING). Nothing to do."
102
+ exit 0
103
+ fi
104
+
105
+ TITLE="Draft: merge \`$HEAD\` into \`$BASE\`"
106
+ BODY=$'Auto-created draft PR for branch `'"$HEAD"$'`.\n\nTarget base: `'"$BASE"$'`\n\nMark this PR as ready for review when you want it to be reviewed/merged.'
107
+
108
+ gh pr create \
109
+ --repo "$REPO" \
110
+ --draft \
111
+ --base "$BASE" \
112
+ --head "$HEAD" \
113
+ --title "$TITLE" \
114
+ --body "$BODY"
@@ -1,24 +1,24 @@
1
1
  name: CI
2
2
 
3
3
  on:
4
- push:
5
- pull_request:
4
+ push:
5
+ pull_request:
6
6
 
7
7
  jobs:
8
- test:
9
- runs-on: ubuntu-latest
8
+ test:
9
+ runs-on: ubuntu-latest
10
10
 
11
- steps:
12
- - uses: actions/checkout@v4
11
+ steps:
12
+ - uses: actions/checkout@v4
13
13
 
14
- - name: Use Node.js
15
- uses: actions/setup-node@v4
16
- with:
17
- node-version: '25'
18
- cache: 'npm'
14
+ - name: Use Node.js
15
+ uses: actions/setup-node@v4
16
+ with:
17
+ node-version: '25'
18
+ cache: 'npm'
19
19
 
20
- - name: Install dependencies
21
- run: npm ci
20
+ - name: Install dependencies
21
+ run: npm ci
22
22
 
23
- - name: Run tests and coverage
24
- run: npm run test:All
23
+ - name: Run tests and coverage
24
+ run: npm run test:All
@@ -1,34 +1,34 @@
1
1
  name: Publish to npm
2
2
 
3
3
  on:
4
- push:
5
- branches:
6
- - main
4
+ push:
5
+ branches:
6
+ - main
7
7
 
8
8
  permissions:
9
- id-token: write
10
- contents: read
9
+ id-token: write
10
+ contents: read
11
11
 
12
12
  jobs:
13
- publish:
14
- runs-on: ubuntu-latest
15
- steps:
16
- - name: Checkout code
17
- uses: actions/checkout@v4
13
+ publish:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - name: Checkout code
17
+ uses: actions/checkout@v4
18
18
 
19
- - name: Setup Node.js
20
- uses: actions/setup-node@v4
21
- with:
22
- node-version: '25'
23
- registry-url: 'https://registry.npmjs.org'
19
+ - name: Setup Node.js
20
+ uses: actions/setup-node@v4
21
+ with:
22
+ node-version: '25'
23
+ registry-url: 'https://registry.npmjs.org'
24
24
 
25
- - name: Install dependencies
26
- # Added HUSKY=0 to prevent the husky error in logs
27
- run: npm ci
28
- env:
29
- HUSKY: 0
25
+ - name: Install dependencies
26
+ # Added HUSKY=0 to prevent the husky error in logs
27
+ run: npm ci
28
+ env:
29
+ HUSKY: 0
30
30
 
31
- - name: Publish to npm
32
- run: npm publish --provenance --access public
33
- env:
34
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
31
+ - name: Publish to npm
32
+ run: npm publish --provenance --access public
33
+ env:
34
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/.prettierrc.js CHANGED
@@ -1,12 +1,13 @@
1
1
  module.exports = {
2
- arrowParens: 'avoid',
3
- bracketSameLine: true,
4
- bracketSpacing: true,
5
- singleQuote: true,
6
- trailingComma: 'all',
7
- printWidth: 140,
8
- useTabs: false,
9
- endOfLine: 'auto',
10
- importOrderSeparation: true,
11
- importOrderSortSpecifiers: true,
2
+ arrowParens: 'avoid',
3
+ bracketSameLine: true,
4
+ bracketSpacing: true,
5
+ singleQuote: true,
6
+ trailingComma: 'all',
7
+ printWidth: 140,
8
+ tabWidth: 4,
9
+ useTabs: false,
10
+ endOfLine: 'auto',
11
+ importOrderSeparation: true,
12
+ importOrderSortSpecifiers: true,
12
13
  };
package/eslint.config.mjs CHANGED
@@ -1,14 +1,14 @@
1
1
  import { defineConfig, globalIgnores } from 'eslint/config';
2
2
 
3
3
  const eslintConfig = defineConfig([
4
- globalIgnores([
5
- // Default ignores of eslint-config-next:
6
- '.next/**',
7
- 'out/**',
8
- 'build/**',
9
- 'next-env.d.ts',
10
- 'node_modules/**',
11
- ]),
4
+ globalIgnores([
5
+ // Default ignores of eslint-config-next:
6
+ '.next/**',
7
+ 'out/**',
8
+ 'build/**',
9
+ 'next-env.d.ts',
10
+ 'node_modules/**',
11
+ ]),
12
12
  ]);
13
13
 
14
14
  export default eslintConfig;
package/index.d.ts CHANGED
@@ -3,6 +3,52 @@
3
3
  */
4
4
  export function validateProperties(obj?: Record<string, any>): Record<string, any>;
5
5
 
6
+ /**
7
+ * Options for {@link validateWhitelistProperties}.
8
+ */
9
+ export interface ValidateWhitelistPropertiesOptions {
10
+ /** Properties allowed in the input but not required. */
11
+ optionalProperties?: string[];
12
+ /** When true, the returned object's keys are converted to snake_case. */
13
+ convertToSnakeCase?: boolean;
14
+ /**
15
+ * When true, the returned object is flattened so that every validated leaf
16
+ * becomes a top-level key, joined by `.` (e.g. `{ 'user.first_name': 'Jane' }`).
17
+ * No nested objects remain in the output. Applied after snake_case conversion.
18
+ */
19
+ flattenOutput?: boolean;
20
+ }
21
+
22
+ /**
23
+ * Validates and transforms whitelisted properties from an input object.
24
+ *
25
+ * - Supports nested objects via dot-notation paths (e.g. `"address.city"`),
26
+ * up to 5 levels deep. The function checks that each path resolves to an
27
+ * existing leaf property and validates the leaf value by its leaf segment.
28
+ * - Extracts only the whitelisted (required + optional) leaf properties and
29
+ * rebuilds the same nested shape in the result.
30
+ * - Validates values via {@link validateProperties}.
31
+ * - Throws a "Bad_Input" error when any required property is missing/invalid,
32
+ * when a provided optional property has an invalid value, when a path
33
+ * exceeds 5 levels of nesting, or when the combined count of
34
+ * `requiredProperties` and `options.optionalProperties` exceeds 5000.
35
+ * - Array values are supported: if a leaf value is an array, the per-leaf
36
+ * validator is applied to each element. The leaf is accepted only when every
37
+ * element passes validation, and the returned value is an array of the
38
+ * validated elements (e.g. `{ name: ["First", "Other"] }` is validated like
39
+ * `{ name: "First" }` and `{ name: "Other" }` individually).
40
+ * - Optionally converts the resulting keys (including nested keys) to snake_case.
41
+ *
42
+ * @param inputObject The input object (e.g. `req.body` or `req.params`).
43
+ * @param requiredProperties Leaf paths that must be present and valid. Dot-notation supported.
44
+ * @param options Optional list of additional allowed leaf paths and case-conversion flag.
45
+ */
46
+ export function validateWhitelistProperties(
47
+ inputObject: Record<string, any>,
48
+ requiredProperties?: string[],
49
+ options?: ValidateWhitelistPropertiesOptions,
50
+ ): Promise<Record<string, any>>;
51
+
6
52
  /** Checks if the string is a valid image URL format. */
7
53
  export function isImageUrl(imageUrl: any): boolean;
8
54
  /** Checks if the value is an integer. */
@@ -70,34 +116,34 @@ export function isValidArrayOfStrings(arr: any): boolean;
70
116
  * @deprecated Use direct imports instead.
71
117
  */
72
118
  export const validate: {
73
- isImageUrl: typeof isImageUrl;
74
- isInteger: typeof isInteger;
75
- isValidJsonString: typeof isValidJsonString;
76
- isValidIntegerString: typeof isValidIntegerString;
77
- isValidUuidString: typeof isValidUuidString;
78
- isCharactersString: typeof isCharactersString;
79
- isNameString: typeof isNameString;
80
- isSafeSearchString: typeof isSafeSearchString;
81
- isEmailString: typeof isEmailString;
82
- isJwtString: typeof isJwtString;
83
- isPasswordString: typeof isPasswordString;
84
- isSimplePasswordString: typeof isSimplePasswordString;
85
- isPasswordStringFailureMessage: typeof isPasswordStringFailureMessage;
86
- isSimplePasswordStringFailureMessage: typeof isSimplePasswordStringFailureMessage;
87
- isUsernameString: typeof isUsernameString;
88
- isPhoneNumber: typeof isPhoneNumber;
89
- isUrlSafeString: typeof isUrlSafeString;
90
- isString6To24CharacterLong: typeof isString6To24CharacterLong;
91
- isString6To16CharacterLong: typeof isString6To16CharacterLong;
92
- isProvinceString: typeof isProvinceString;
93
- isBoolValue: typeof isBoolValue;
94
- isPostalCodeString: typeof isPostalCodeString;
95
- isSafeString: typeof isSafeString;
96
- isInStringArray: typeof isInStringArray;
97
- isCountryCodeString: typeof isCountryCodeString;
98
- isValidDomainName: typeof isValidDomainName;
99
- isValidTimestampzString: typeof isValidTimestampzString;
100
- isValidTimestampString: typeof isValidTimestampString;
101
- isValidUrl: typeof isValidUrl;
102
- isValidArrayOfStrings: typeof isValidArrayOfStrings;
119
+ isImageUrl: typeof isImageUrl;
120
+ isInteger: typeof isInteger;
121
+ isValidJsonString: typeof isValidJsonString;
122
+ isValidIntegerString: typeof isValidIntegerString;
123
+ isValidUuidString: typeof isValidUuidString;
124
+ isCharactersString: typeof isCharactersString;
125
+ isNameString: typeof isNameString;
126
+ isSafeSearchString: typeof isSafeSearchString;
127
+ isEmailString: typeof isEmailString;
128
+ isJwtString: typeof isJwtString;
129
+ isPasswordString: typeof isPasswordString;
130
+ isSimplePasswordString: typeof isSimplePasswordString;
131
+ isPasswordStringFailureMessage: typeof isPasswordStringFailureMessage;
132
+ isSimplePasswordStringFailureMessage: typeof isSimplePasswordStringFailureMessage;
133
+ isUsernameString: typeof isUsernameString;
134
+ isPhoneNumber: typeof isPhoneNumber;
135
+ isUrlSafeString: typeof isUrlSafeString;
136
+ isString6To24CharacterLong: typeof isString6To24CharacterLong;
137
+ isString6To16CharacterLong: typeof isString6To16CharacterLong;
138
+ isProvinceString: typeof isProvinceString;
139
+ isBoolValue: typeof isBoolValue;
140
+ isPostalCodeString: typeof isPostalCodeString;
141
+ isSafeString: typeof isSafeString;
142
+ isInStringArray: typeof isInStringArray;
143
+ isCountryCodeString: typeof isCountryCodeString;
144
+ isValidDomainName: typeof isValidDomainName;
145
+ isValidTimestampzString: typeof isValidTimestampzString;
146
+ isValidTimestampString: typeof isValidTimestampString;
147
+ isValidUrl: typeof isValidUrl;
148
+ isValidArrayOfStrings: typeof isValidArrayOfStrings;
103
149
  };
package/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  const validate = require('./lib/validate');
2
2
  const validateProperties = require('./lib/validateProperties');
3
+ const validateWhitelistProperties = require('./lib/validateWhitelistProperties');
3
4
 
4
5
  module.exports = {
5
- validate,
6
- validateProperties,
7
- ...validate,
8
- ...validateProperties,
6
+ validate,
7
+ validateProperties,
8
+ validateWhitelistProperties,
9
+ ...validate,
10
+ ...validateProperties,
9
11
  };