@carecard/validate 3.1.24 → 3.1.26
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/.agents/config.toml +2 -0
- package/.agents/skills/carecard-workspace-standards/SKILL.md +401 -0
- package/.agents/skills/carecard-workspace-standards/agents/openai.yaml +4 -0
- package/.agents/skills/github-pr-create-update/SKILL.md +188 -0
- package/.agents/skills/github-pr-create-update/agents/openai.yaml +5 -0
- package/.agents/skills/github-pr-merge-cleanup/SKILL.md +175 -0
- package/.agents/skills/github-pr-merge-cleanup/agents/openai.yaml +5 -0
- package/.agents/skills/logged-in-user-profile-page/SKILL.md +33 -0
- package/.agents/skills/logged-in-user-profile-page/agents/openai.yaml +4 -0
- package/.agents/skills/pkg-validate-validation-library/SKILL.md +200 -0
- package/.agents/skills/pkg-validate-validation-library/agents/openai.yaml +4 -0
- package/.agents/skills/software-design-patterns-and-clean-code/SKILL.md +73 -0
- package/.codex/config.toml +2 -0
- package/.github/workflows/ci.yml +2 -2
- package/index.d.ts +124 -75
- package/lib/validateProperties.js +7 -0
- package/package.json +6 -6
- package/readme.md +18 -3
package/index.d.ts
CHANGED
|
@@ -1,60 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime-compatible TypeScript declarations for the CommonJS
|
|
3
|
+
* `@carecard/validate` package.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type ValidatePropertiesInput = Record<string, unknown> | null | undefined;
|
|
7
|
+
export type ValidatePropertiesResult = Record<string, unknown>;
|
|
8
|
+
export type BoolValidator = (input: unknown) => boolean;
|
|
9
|
+
export type FailureMessageValidator = (input: unknown) => string | null;
|
|
10
|
+
export type InStringArrayValidator = (stringArray: readonly string[], input: unknown) => boolean;
|
|
11
|
+
|
|
1
12
|
/**
|
|
2
13
|
* Utility function to validate multiple properties of an object at once.
|
|
3
14
|
*/
|
|
4
|
-
export function validateProperties(obj?:
|
|
15
|
+
export function validateProperties(obj?: ValidatePropertiesInput): ValidatePropertiesResult;
|
|
16
|
+
|
|
17
|
+
export type ValidateWhitelistFlattenKeyStyle = 'path' | 'leaf';
|
|
5
18
|
|
|
6
19
|
/**
|
|
7
20
|
* Options for {@link validateWhitelistProperties}.
|
|
8
21
|
*/
|
|
9
22
|
export interface ValidateWhitelistPropertiesOptions {
|
|
10
23
|
/** Properties allowed in the input but not required. */
|
|
11
|
-
optionalProperties?: string[];
|
|
24
|
+
optionalProperties?: readonly string[] | null;
|
|
12
25
|
/** When true, the returned object's keys are converted to snake_case. */
|
|
13
26
|
convertToSnakeCase?: boolean;
|
|
14
27
|
/**
|
|
15
28
|
* When true, the returned object is flattened so that every validated leaf
|
|
16
|
-
* becomes a top-level key
|
|
17
|
-
*
|
|
29
|
+
* becomes a top-level key. No nested objects remain in the output. Applied
|
|
30
|
+
* after snake_case conversion.
|
|
18
31
|
*/
|
|
19
32
|
flattenOutput?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Controls flattened key naming when `flattenOutput` is true.
|
|
35
|
+
* - `path` uses full dot-notation paths, e.g. `{ "user.email": "Jane" }`.
|
|
36
|
+
* - `leaf` uses only leaf names, e.g. `{ email: "Jane" }`.
|
|
37
|
+
*
|
|
38
|
+
* Defaults to `path`.
|
|
39
|
+
*/
|
|
40
|
+
flattenKeyStyle?: ValidateWhitelistFlattenKeyStyle;
|
|
20
41
|
}
|
|
21
42
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
export interface ValidateWhitelistPropertiesFunction {
|
|
44
|
+
/**
|
|
45
|
+
* Validates and transforms whitelisted properties from an input object.
|
|
46
|
+
*
|
|
47
|
+
* - Supports nested objects via dot-notation paths (e.g. `"address.city"`),
|
|
48
|
+
* up to 5 levels deep. The function checks that each path resolves to an
|
|
49
|
+
* existing leaf property and validates the leaf value by its leaf segment.
|
|
50
|
+
* - Extracts only the whitelisted (required + optional) leaf properties and
|
|
51
|
+
* rebuilds the same nested shape in the result.
|
|
52
|
+
* - Validates values via {@link validateProperties}.
|
|
53
|
+
* - Throws a "Bad_Input" error when any required property is missing/invalid,
|
|
54
|
+
* when a provided optional property has an invalid value, when a path
|
|
55
|
+
* exceeds 5 levels of nesting, or when the combined count of
|
|
56
|
+
* `requiredProperties` and `options.optionalProperties` exceeds 5000.
|
|
57
|
+
* - Array values are supported: if a leaf value is an array, the per-leaf
|
|
58
|
+
* validator is applied to each element. The leaf is accepted only when every
|
|
59
|
+
* element passes validation, and the returned value is an array of the
|
|
60
|
+
* validated elements.
|
|
61
|
+
* - Optionally converts the resulting keys, including nested keys, to snake_case.
|
|
62
|
+
* - Optionally flattens the result after snake_case conversion.
|
|
63
|
+
*
|
|
64
|
+
* @param inputObject The input object, for example `req.body` or `req.params`.
|
|
65
|
+
* @param requiredProperties Leaf paths that must be present and valid. Dot notation is supported.
|
|
66
|
+
* @param options Optional additional leaf paths plus output transformation flags.
|
|
67
|
+
*/
|
|
68
|
+
(
|
|
69
|
+
inputObject?: ValidatePropertiesInput,
|
|
70
|
+
requiredProperties?: readonly string[] | null,
|
|
71
|
+
options?: ValidateWhitelistPropertiesOptions | null,
|
|
72
|
+
): Promise<ValidatePropertiesResult>;
|
|
73
|
+
validateWhitelistProperties: ValidateWhitelistPropertiesFunction;
|
|
74
|
+
MAX_NESTING_DEPTH: 5;
|
|
75
|
+
MAX_KEYS_PER_CALL: 5000;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const validateWhitelistProperties: ValidateWhitelistPropertiesFunction;
|
|
79
|
+
|
|
80
|
+
export type UserRoleRequestRole = 'student' | 'intern' | 'volunteer';
|
|
81
|
+
export type UserRoleRequestScopeRequirement = boolean | typeof REQUIRE_SCOPE_WHEN_ROLE_OR_SCOPE_PRESENT;
|
|
51
82
|
|
|
52
83
|
export const DEFAULT_USER_ROLE_REQUEST_ROLE: 'student';
|
|
53
84
|
export const REQUIRE_SCOPE_WHEN_ROLE_OR_SCOPE_PRESENT: 'whenRoleOrScopePresent';
|
|
54
85
|
|
|
55
86
|
export interface ValidateNewUserRoleRequestOptions {
|
|
56
|
-
defaultRole?:
|
|
57
|
-
requireScope?:
|
|
87
|
+
defaultRole?: UserRoleRequestRole | undefined;
|
|
88
|
+
requireScope?: UserRoleRequestScopeRequirement;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface ValidateNewUserRoleRequestInput extends Record<string, unknown> {
|
|
92
|
+
role_name?: unknown;
|
|
93
|
+
roleName?: unknown;
|
|
94
|
+
role?: unknown;
|
|
95
|
+
institution_id?: unknown;
|
|
96
|
+
institutionId?: unknown;
|
|
97
|
+
campus_id?: unknown;
|
|
98
|
+
campusId?: unknown;
|
|
99
|
+
program_id?: unknown;
|
|
100
|
+
programId?: unknown;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface ValidateNewUserRoleRequestPayload extends Record<string, unknown> {
|
|
104
|
+
role_name?: string;
|
|
105
|
+
institution_id?: string;
|
|
106
|
+
campus_id?: string;
|
|
107
|
+
program_id?: string;
|
|
58
108
|
}
|
|
59
109
|
|
|
60
110
|
/**
|
|
@@ -63,79 +113,78 @@ export interface ValidateNewUserRoleRequestOptions {
|
|
|
63
113
|
* both institution_id and campus_id must be provided.
|
|
64
114
|
*/
|
|
65
115
|
export function validateNewUserRoleRequestObject(
|
|
66
|
-
roleRequest?:
|
|
67
|
-
options?: ValidateNewUserRoleRequestOptions,
|
|
68
|
-
):
|
|
116
|
+
roleRequest?: ValidateNewUserRoleRequestInput | null,
|
|
117
|
+
options?: ValidateNewUserRoleRequestOptions | null,
|
|
118
|
+
): ValidateNewUserRoleRequestPayload;
|
|
69
119
|
|
|
70
120
|
/** Checks if the string is a valid image URL format. */
|
|
71
|
-
export
|
|
121
|
+
export const isImageUrl: BoolValidator;
|
|
72
122
|
/** Checks if the value is an integer. */
|
|
73
|
-
export
|
|
123
|
+
export const isInteger: BoolValidator;
|
|
74
124
|
/** Checks if the string is a valid JSON string and can be parsed into an object. */
|
|
75
|
-
export
|
|
125
|
+
export const isValidJsonString: BoolValidator;
|
|
76
126
|
/** Checks if the string represents a valid integer. */
|
|
77
|
-
export
|
|
127
|
+
export const isValidIntegerString: BoolValidator;
|
|
78
128
|
/** Checks if the string is a valid UUID. */
|
|
79
|
-
export
|
|
129
|
+
export const isValidUuidString: BoolValidator;
|
|
80
130
|
/** Checks if the string contains only alphanumeric characters, spaces, underscores, or hyphens. */
|
|
81
|
-
export
|
|
131
|
+
export const isCharactersString: BoolValidator;
|
|
82
132
|
/** Checks if the string is a valid street address format. */
|
|
83
|
-
export
|
|
133
|
+
export const isStreetString: BoolValidator;
|
|
84
134
|
/** Checks if the string is a valid name format. */
|
|
85
|
-
export
|
|
135
|
+
export const isNameString: BoolValidator;
|
|
86
136
|
/** Checks if the string is safe for search queries. */
|
|
87
|
-
export
|
|
137
|
+
export const isSafeSearchString: BoolValidator;
|
|
88
138
|
/** Checks if the string is a valid email address. */
|
|
89
|
-
export
|
|
139
|
+
export const isEmailString: BoolValidator;
|
|
90
140
|
/** Checks if the string is a valid JWT format. */
|
|
91
|
-
export
|
|
141
|
+
export const isJwtString: BoolValidator;
|
|
92
142
|
/** Checks if the string is a valid strong password. */
|
|
93
|
-
export
|
|
143
|
+
export const isPasswordString: BoolValidator;
|
|
94
144
|
/** Checks if the string is a valid simple password. */
|
|
95
|
-
export
|
|
145
|
+
export const isSimplePasswordString: BoolValidator;
|
|
96
146
|
/** Returns a failure message if the password is not strong enough. */
|
|
97
|
-
export
|
|
147
|
+
export const isPasswordStringFailureMessage: FailureMessageValidator;
|
|
98
148
|
/** Returns a failure message if the password is not valid as a simple password. */
|
|
99
|
-
export
|
|
149
|
+
export const isSimplePasswordStringFailureMessage: FailureMessageValidator;
|
|
100
150
|
/** Checks if the string is a valid username. */
|
|
101
|
-
export
|
|
151
|
+
export const isUsernameString: BoolValidator;
|
|
102
152
|
/** Checks if the string is a valid phone number. */
|
|
103
|
-
export
|
|
153
|
+
export const isPhoneNumber: BoolValidator;
|
|
104
154
|
/** Checks if the string is URL-safe. */
|
|
105
|
-
export
|
|
155
|
+
export const isUrlSafeString: BoolValidator;
|
|
106
156
|
/** Checks if the string length is between 6 and 24 characters. */
|
|
107
|
-
export
|
|
157
|
+
export const isString6To24CharacterLong: BoolValidator;
|
|
108
158
|
/** Checks if the string length is between 6 and 16 characters. */
|
|
109
|
-
export
|
|
159
|
+
export const isString6To16CharacterLong: BoolValidator;
|
|
110
160
|
/** Checks if the string is a valid Canadian province abbreviation (ON, QC). */
|
|
111
|
-
export
|
|
161
|
+
export const isProvinceString: BoolValidator;
|
|
112
162
|
/** Checks if the value is a boolean. */
|
|
113
|
-
export
|
|
163
|
+
export const isBoolValue: BoolValidator;
|
|
114
164
|
/** Checks if the string is a valid Canadian postal code. */
|
|
115
|
-
export
|
|
165
|
+
export const isPostalCodeString: BoolValidator;
|
|
116
166
|
/** Checks if the string contains only allowed "safe" characters. */
|
|
117
|
-
export
|
|
167
|
+
export const isSafeString: BoolValidator;
|
|
118
168
|
/** Checks if the value is non-empty text up to the supported maximum length. */
|
|
119
|
-
export
|
|
120
|
-
/** Checks if a string exists within a given array of strings
|
|
121
|
-
export
|
|
169
|
+
export const isTextString: BoolValidator;
|
|
170
|
+
/** Checks if a string exists within a given array of strings, case-insensitive. */
|
|
171
|
+
export const isInStringArray: InStringArrayValidator;
|
|
122
172
|
/** Checks if the string is one of the supported user role request statuses. */
|
|
123
|
-
export
|
|
173
|
+
export const isUserRoleRequestStatusString: BoolValidator;
|
|
124
174
|
/** Checks if the string is a supported new user role request role. */
|
|
125
|
-
export
|
|
126
|
-
/** Checks if the string is a valid country code
|
|
127
|
-
export
|
|
175
|
+
export const isUserRoleRequestRoleString: BoolValidator;
|
|
176
|
+
/** Checks if the string is a valid country code, for example +1. */
|
|
177
|
+
export const isCountryCodeString: BoolValidator;
|
|
128
178
|
/** Checks if the string is a valid domain name. */
|
|
129
|
-
export
|
|
179
|
+
export const isValidDomainName: BoolValidator;
|
|
130
180
|
/** Checks if the string is a valid ISO 8601 timestamp with time zone. */
|
|
131
|
-
export
|
|
181
|
+
export const isValidTimestampzString: BoolValidator;
|
|
132
182
|
/** Checks if the string is a valid ISO 8601 timestamp without time zone. */
|
|
133
|
-
export
|
|
134
|
-
|
|
183
|
+
export const isValidTimestampString: BoolValidator;
|
|
135
184
|
/** Checks if the string is a valid URL. */
|
|
136
|
-
export
|
|
185
|
+
export const isValidUrl: BoolValidator;
|
|
137
186
|
/** Checks if the array contains only safe strings. */
|
|
138
|
-
export
|
|
187
|
+
export const isValidArrayOfStrings: BoolValidator;
|
|
139
188
|
|
|
140
189
|
/**
|
|
141
190
|
* Utility functions for validating various types of strings and values.
|
|
@@ -23,6 +23,7 @@ const {
|
|
|
23
23
|
isTextString,
|
|
24
24
|
isUserRoleRequestStatusString,
|
|
25
25
|
isUserRoleRequestRoleString,
|
|
26
|
+
isCountryCodeString,
|
|
26
27
|
} = require('./validate');
|
|
27
28
|
|
|
28
29
|
function validateProperties(obj = {}) {
|
|
@@ -124,6 +125,12 @@ function validateProperties(obj = {}) {
|
|
|
124
125
|
returnObj[key] = value;
|
|
125
126
|
}
|
|
126
127
|
break;
|
|
128
|
+
case 'country_code':
|
|
129
|
+
case 'countryCode':
|
|
130
|
+
if (isCountryCodeString(value)) {
|
|
131
|
+
returnObj[key] = value;
|
|
132
|
+
}
|
|
133
|
+
break;
|
|
127
134
|
case 'token':
|
|
128
135
|
case 'email_confirm_token':
|
|
129
136
|
case 'emailConfirmToken':
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@carecard/validate",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.26",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/CareCard-ca/pkg-validate.git"
|
|
@@ -27,18 +27,18 @@
|
|
|
27
27
|
"license": "ISC",
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/mocha": "10.0.10",
|
|
30
|
-
"@types/node": "25.
|
|
30
|
+
"@types/node": "25.9.1",
|
|
31
31
|
"eslint": "9.39.4",
|
|
32
32
|
"husky": "9.1.7",
|
|
33
|
-
"lint-staged": "17.0.
|
|
34
|
-
"mocha": "11.7.
|
|
33
|
+
"lint-staged": "17.0.5",
|
|
34
|
+
"mocha": "11.7.6",
|
|
35
35
|
"nyc": "18.0.0",
|
|
36
36
|
"prettier": "3.8.3",
|
|
37
37
|
"ts-node": "10.9.2",
|
|
38
|
-
"typescript": "
|
|
38
|
+
"typescript": "6.0.3"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@carecard/common-util": "
|
|
41
|
+
"@carecard/common-util": "3.1.15"
|
|
42
42
|
},
|
|
43
43
|
"nyc": {
|
|
44
44
|
"all": true,
|
package/readme.md
CHANGED
|
@@ -106,6 +106,7 @@ where the package supports both.
|
|
|
106
106
|
| `isString6To16CharacterLong` and `isPasswordString` | `strong_password`, `strongPassword` |
|
|
107
107
|
| `isEmailString` | `email` |
|
|
108
108
|
| `isPhoneNumber` | `phone_number`, `phoneNumber` |
|
|
109
|
+
| `isCountryCodeString` | `country_code`, `countryCode` |
|
|
109
110
|
| `isUrlSafeString` | `token`, `email_confirm_token`, `emailConfirmToken`, `verification_token`, `verificationToken` |
|
|
110
111
|
| `isValidUuidString` | `uuid`, `item_id`, `itemId`, `user_id`, `userId`, `address_id`, `addressId`, `image_id`, `imageId`, `order_id`, `orderId`, `category_id`, `categoryId`, `parent_id`, `parentId`, `college_id`, `collegeId`, `campus_id`, `campusId`, `program_id`, `programId`, `id`, `institution_id`, `institutionId`, `role_assignment_id`, `roleAssignmentId`, `user_role_id`, `userRoleId`, `phone_number_id`, `phoneNumberId`, `entity_id`, `entityId`, `changed_by`, `changedBy`, `request_id`, `requestId` |
|
|
111
112
|
| `isValidIntegerString` | `offset_number`, `offsetNumber`, `number_of_orders`, `numberOfOrders`, `price`, `from`, `number`, `limit`, `offset` |
|
|
@@ -120,7 +121,7 @@ where the package supports both.
|
|
|
120
121
|
|
|
121
122
|
`validateWhitelistProperties` extracts only the required and optional property
|
|
122
123
|
paths you provide, validates each leaf through `validateProperties`, and returns
|
|
123
|
-
a `Promise<
|
|
124
|
+
a `Promise<ValidatePropertiesResult>` with the sanitized output.
|
|
124
125
|
|
|
125
126
|
```js
|
|
126
127
|
const body = {
|
|
@@ -332,10 +333,11 @@ await validateWhitelistProperties(input, ['name', 'user.name', 'user.email'], {
|
|
|
332
333
|
The package ships `index.d.ts` and declares types for the CommonJS exports.
|
|
333
334
|
|
|
334
335
|
```ts
|
|
335
|
-
import { validateWhitelistProperties, isEmailString } from '@carecard/validate';
|
|
336
|
+
import { validateWhitelistProperties, isEmailString, ValidatePropertiesResult } from '@carecard/validate';
|
|
336
337
|
|
|
337
338
|
const valid: boolean = isEmailString('jane@example.com');
|
|
338
|
-
const output:
|
|
339
|
+
const output: ValidatePropertiesResult = await validateWhitelistProperties({ first_name: 'Jane' }, ['first_name']);
|
|
340
|
+
const maxDepth: 5 = validateWhitelistProperties.MAX_NESTING_DEPTH;
|
|
339
341
|
```
|
|
340
342
|
|
|
341
343
|
## Project Layout
|
|
@@ -363,3 +365,16 @@ npm run format:check
|
|
|
363
365
|
|
|
364
366
|
CI runs on Node.js 25 and executes `npm run test:All`. Publishing to npm happens
|
|
365
367
|
from `main` through the `Publish to npm` GitHub workflow.
|
|
368
|
+
|
|
369
|
+
## Auth Boundary
|
|
370
|
+
|
|
371
|
+
Validation protects request shape, not authorization. `ms-auth` owns its
|
|
372
|
+
auth-table RLS contract: normal users are self-row only, JWT `roles: ["ad"]`
|
|
373
|
+
is the auth super-admin signal, and public auth flows use narrow system
|
|
374
|
+
contexts. Do not use validators as a replacement for service RLS or database
|
|
375
|
+
context checks.
|
|
376
|
+
|
|
377
|
+
Docs that mention `ms-auth` controller internals should use concise action
|
|
378
|
+
names such as `loginUser`, `registerUser`, `getUserDetail`, and `renewJwt`.
|
|
379
|
+
Access level is conveyed by route middleware and endpoint placement, not by
|
|
380
|
+
`public`/`protected`/`admin`/`Handler` suffixes.
|