@finsys/core 1.0.0 → 1.1.0
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/README.md +10 -0
- package/dist/catalogs.d.ts +13 -0
- package/dist/catalogs.js +35 -0
- package/dist/catalogs.test.d.ts +1 -0
- package/dist/catalogs.test.js +207 -0
- package/dist/data/form-field-base-specs.json +7353 -0
- package/dist/data/form-field-types.json +83 -0
- package/dist/data/form-field-validators.json +341 -0
- package/dist/form-field-category.d.ts +12 -0
- package/dist/form-field-category.js +21 -0
- package/dist/form-field-validator.d.ts +18 -0
- package/dist/form-field-validator.js +41 -0
- package/dist/form-field.d.ts +81 -0
- package/dist/form-field.js +197 -0
- package/dist/form-field.test.d.ts +1 -0
- package/dist/form-field.test.js +482 -0
- package/dist/form-spec.d.ts +39 -0
- package/dist/form-spec.js +278 -0
- package/dist/form-spec.test.d.ts +1 -0
- package/dist/form-spec.test.js +322 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +22 -0
- package/dist/rhf-generator.js +15 -0
- package/dist/rhf-generator.test.js +15 -0
- package/dist/survey-generator.js +13 -4
- package/dist/survey-generator.test.js +15 -0
- package/dist/utils.js +15 -0
- package/dist/utils.test.js +15 -0
- package/dist/validator.js +15 -0
- package/dist/validator.test.js +15 -0
- package/package.json +4 -2
- package/dist/schema/schema/unified-form.schema.json +0 -266
package/README.md
CHANGED
|
@@ -215,6 +215,16 @@ npm run lint # Type-check without emitting
|
|
|
215
215
|
|
|
216
216
|
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
217
217
|
|
|
218
|
+
## Security
|
|
219
|
+
|
|
220
|
+
### Expression Evaluation
|
|
221
|
+
|
|
222
|
+
The `evaluateExpression()` function uses `new Function()` internally to evaluate SurveyJS-style visibility expressions. Expression strings **must come from trusted, developer-authored sources** (e.g., your own form configuration files). Never pass untrusted or user-supplied strings as expressions, as this could allow arbitrary code execution.
|
|
223
|
+
|
|
224
|
+
## Disclaimer
|
|
225
|
+
|
|
226
|
+
This library provides form configuration utilities and does not constitute financial, legal, or investment advice. It does not perform financial calculations, risk assessments, or lending decisions. The creators and contributors accept no liability for decisions made using forms generated by this library. See the [LICENSE](./LICENSE) file for warranty and liability terms.
|
|
227
|
+
|
|
218
228
|
## License
|
|
219
229
|
|
|
220
230
|
[Apache-2.0](./LICENSE)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FieldData, Category } from "./survey-generator.js";
|
|
2
|
+
import type { FormFieldTypeDefinitions } from "./form-field.js";
|
|
3
|
+
import type { FormValidatorDefinitions } from "./form-field-validator.js";
|
|
4
|
+
export declare const BASE_FIELD_SPECS: {
|
|
5
|
+
schemaVersion: string;
|
|
6
|
+
categories: Category[];
|
|
7
|
+
fields: FieldData[];
|
|
8
|
+
};
|
|
9
|
+
export declare const FIELD_TYPE_DEFINITIONS: FormFieldTypeDefinitions;
|
|
10
|
+
export declare const DEFAULT_VALIDATOR_DEFINITIONS: FormValidatorDefinitions;
|
|
11
|
+
export declare function getBaseFieldSpecs(): FieldData[];
|
|
12
|
+
export declare function getBaseCategories(): Category[];
|
|
13
|
+
export declare function getBaseFieldNames(): string[];
|
package/dist/catalogs.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import FormField from "./form-field.js";
|
|
2
|
+
import baseSpecsData from "./data/form-field-base-specs.json" with { type: "json" };
|
|
3
|
+
import typeDefsData from "./data/form-field-types.json" with { type: "json" };
|
|
4
|
+
import validatorDefsData from "./data/form-field-validators.json" with { type: "json" };
|
|
5
|
+
export const BASE_FIELD_SPECS = baseSpecsData;
|
|
6
|
+
export const FIELD_TYPE_DEFINITIONS = typeDefsData;
|
|
7
|
+
export const DEFAULT_VALIDATOR_DEFINITIONS = {
|
|
8
|
+
version: validatorDefsData.version,
|
|
9
|
+
options: validatorDefsData.options,
|
|
10
|
+
};
|
|
11
|
+
export function getBaseFieldSpecs() {
|
|
12
|
+
return BASE_FIELD_SPECS.fields;
|
|
13
|
+
}
|
|
14
|
+
export function getBaseCategories() {
|
|
15
|
+
return BASE_FIELD_SPECS.categories;
|
|
16
|
+
}
|
|
17
|
+
let cachedFieldNames = null;
|
|
18
|
+
export function getBaseFieldNames() {
|
|
19
|
+
if (cachedFieldNames) {
|
|
20
|
+
return cachedFieldNames;
|
|
21
|
+
}
|
|
22
|
+
const fields = BASE_FIELD_SPECS.fields;
|
|
23
|
+
const names = [];
|
|
24
|
+
for (const field of fields) {
|
|
25
|
+
try {
|
|
26
|
+
const formField = FormField.fromObject(field);
|
|
27
|
+
names.push(...formField.getFieldNames());
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Skip fields that fail to parse
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
cachedFieldNames = [...new Set(names)];
|
|
34
|
+
return cachedFieldNames;
|
|
35
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { generateRHFSchema } from "./rhf-generator.js";
|
|
3
|
+
import { BASE_FIELD_SPECS, FIELD_TYPE_DEFINITIONS, DEFAULT_VALIDATOR_DEFINITIONS, getBaseFieldSpecs, getBaseCategories, getBaseFieldNames, } from "./catalogs.js";
|
|
4
|
+
describe("BASE_FIELD_SPECS", () => {
|
|
5
|
+
it("has schemaVersion, categories, and fields", () => {
|
|
6
|
+
expect(BASE_FIELD_SPECS.schemaVersion).toBeDefined();
|
|
7
|
+
expect(Array.isArray(BASE_FIELD_SPECS.categories)).toBe(true);
|
|
8
|
+
expect(Array.isArray(BASE_FIELD_SPECS.fields)).toBe(true);
|
|
9
|
+
expect(BASE_FIELD_SPECS.categories.length).toBeGreaterThan(0);
|
|
10
|
+
expect(BASE_FIELD_SPECS.fields.length).toBeGreaterThan(0);
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
describe("FIELD_TYPE_DEFINITIONS", () => {
|
|
14
|
+
it("has version, types, and input_types", () => {
|
|
15
|
+
expect(FIELD_TYPE_DEFINITIONS.version).toBeDefined();
|
|
16
|
+
expect(Array.isArray(FIELD_TYPE_DEFINITIONS.types)).toBe(true);
|
|
17
|
+
expect(Array.isArray(FIELD_TYPE_DEFINITIONS.input_types)).toBe(true);
|
|
18
|
+
expect(FIELD_TYPE_DEFINITIONS.types.length).toBeGreaterThan(0);
|
|
19
|
+
expect(FIELD_TYPE_DEFINITIONS.input_types.length).toBeGreaterThan(0);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
describe("DEFAULT_VALIDATOR_DEFINITIONS", () => {
|
|
23
|
+
it("has version and options", () => {
|
|
24
|
+
expect(DEFAULT_VALIDATOR_DEFINITIONS.version).toBeDefined();
|
|
25
|
+
expect(Array.isArray(DEFAULT_VALIDATOR_DEFINITIONS.options)).toBe(true);
|
|
26
|
+
expect(DEFAULT_VALIDATOR_DEFINITIONS.options.length).toBeGreaterThan(0);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
describe("getBaseFieldSpecs", () => {
|
|
30
|
+
it("returns field array", () => {
|
|
31
|
+
const fields = getBaseFieldSpecs();
|
|
32
|
+
expect(Array.isArray(fields)).toBe(true);
|
|
33
|
+
expect(fields.length).toBeGreaterThan(0);
|
|
34
|
+
expect(fields[0].name).toBeDefined();
|
|
35
|
+
expect(fields[0].type).toBeDefined();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
describe("getBaseCategories", () => {
|
|
39
|
+
it("returns category array", () => {
|
|
40
|
+
const categories = getBaseCategories();
|
|
41
|
+
expect(Array.isArray(categories)).toBe(true);
|
|
42
|
+
expect(categories.length).toBeGreaterThan(0);
|
|
43
|
+
expect(categories[0].id).toBeDefined();
|
|
44
|
+
expect(categories[0].name).toBeDefined();
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
describe("getBaseFieldNames", () => {
|
|
48
|
+
it("returns unique field names", () => {
|
|
49
|
+
const names = getBaseFieldNames();
|
|
50
|
+
expect(Array.isArray(names)).toBe(true);
|
|
51
|
+
expect(names.length).toBeGreaterThan(0);
|
|
52
|
+
// Should be unique
|
|
53
|
+
expect(new Set(names).size).toBe(names.length);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
describe("Base Specs Zod Validation", () => {
|
|
57
|
+
it("all field definitions should be valid for RHF schema generation", () => {
|
|
58
|
+
const fields = BASE_FIELD_SPECS.fields;
|
|
59
|
+
const categories = BASE_FIELD_SPECS.categories;
|
|
60
|
+
const failedFields = [];
|
|
61
|
+
fields.forEach((field) => {
|
|
62
|
+
try {
|
|
63
|
+
const config = {
|
|
64
|
+
schemaVersion: "v2.0.0",
|
|
65
|
+
displayName: "Test",
|
|
66
|
+
categories: categories,
|
|
67
|
+
fields: {
|
|
68
|
+
[field.name]: field,
|
|
69
|
+
},
|
|
70
|
+
pages: [
|
|
71
|
+
{
|
|
72
|
+
id: "test-page",
|
|
73
|
+
fields: [field.name],
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
const { zodSchema, defaultValues } = generateRHFSchema(config);
|
|
78
|
+
const result = zodSchema.safeParse(defaultValues);
|
|
79
|
+
if (!result.success) {
|
|
80
|
+
const hasRequirementError = result.error.issues.some((i) => i.message === "This field is required" || i.code === "too_small");
|
|
81
|
+
const isRequired = field.isRequired !== false && field.required !== false;
|
|
82
|
+
if (!(isRequired && hasRequirementError && !field.defaultValue)) {
|
|
83
|
+
failedFields.push({
|
|
84
|
+
name: field.name,
|
|
85
|
+
error: `Validation failed: ${JSON.stringify(result.error.issues)}`,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
failedFields.push({
|
|
92
|
+
name: field.name,
|
|
93
|
+
error: `Exception: ${error.message}`,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
if (failedFields.length > 0) {
|
|
98
|
+
const errorMsg = failedFields.map((f) => `[${f.name}]: ${f.error}`).join("\n");
|
|
99
|
+
expect.unreachable(`Found ${failedFields.length} invalid field definitions:\n${errorMsg.substring(0, 2000)}`);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
describe("Field Types Zod Validation", () => {
|
|
104
|
+
it("all supported type combinations should be valid for RHF schema generation", () => {
|
|
105
|
+
const types = FIELD_TYPE_DEFINITIONS.types;
|
|
106
|
+
const inputTypes = FIELD_TYPE_DEFINITIONS.input_types;
|
|
107
|
+
const failedCombinations = [];
|
|
108
|
+
for (const typeObj of types) {
|
|
109
|
+
const baseType = typeObj.name;
|
|
110
|
+
const targetInputTypes = baseType === "text" ? inputTypes : [{ name: undefined }];
|
|
111
|
+
for (const inputTypeObj of targetInputTypes) {
|
|
112
|
+
const inputType = inputTypeObj.name;
|
|
113
|
+
try {
|
|
114
|
+
const field = {
|
|
115
|
+
name: "testField",
|
|
116
|
+
displayName: "Test Field",
|
|
117
|
+
category: "1",
|
|
118
|
+
type: baseType,
|
|
119
|
+
};
|
|
120
|
+
if (inputType)
|
|
121
|
+
field.inputType = inputType;
|
|
122
|
+
const config = {
|
|
123
|
+
schemaVersion: "v2.0.0",
|
|
124
|
+
displayName: "Test",
|
|
125
|
+
categories: [{ id: "1", name: "Test" }],
|
|
126
|
+
fields: { [field.name]: field },
|
|
127
|
+
pages: [{ id: "test-page", fields: [field.name] }],
|
|
128
|
+
};
|
|
129
|
+
const { zodSchema, defaultValues } = generateRHFSchema(config);
|
|
130
|
+
const result = zodSchema.safeParse(defaultValues);
|
|
131
|
+
if (!result.success) {
|
|
132
|
+
const errors = result.error.issues.filter((i) => i.message !== "This field is required" && i.code !== "too_small");
|
|
133
|
+
if (errors.length > 0) {
|
|
134
|
+
failedCombinations.push({
|
|
135
|
+
type: baseType,
|
|
136
|
+
inputType,
|
|
137
|
+
error: `Validation failed: ${JSON.stringify(errors)}`,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
failedCombinations.push({
|
|
144
|
+
type: baseType,
|
|
145
|
+
inputType,
|
|
146
|
+
error: `Exception: ${error.message}`,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (failedCombinations.length > 0) {
|
|
152
|
+
const errorMsg = failedCombinations
|
|
153
|
+
.map((c) => `[${c.type}${c.inputType ? "/" + c.inputType : ""}]: ${c.error}`)
|
|
154
|
+
.join("\n");
|
|
155
|
+
expect.unreachable(`Found ${failedCombinations.length} invalid type combinations:\n${errorMsg}`);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
describe("Field Validators Zod Validation", () => {
|
|
160
|
+
it("all shared validator definitions should be valid for RHF schema generation", () => {
|
|
161
|
+
const options = DEFAULT_VALIDATOR_DEFINITIONS.options;
|
|
162
|
+
const failedValidators = [];
|
|
163
|
+
options.forEach((option) => {
|
|
164
|
+
try {
|
|
165
|
+
const field = {
|
|
166
|
+
name: "testField",
|
|
167
|
+
displayName: "Test Field",
|
|
168
|
+
category: "1",
|
|
169
|
+
type: "text",
|
|
170
|
+
validators: option.validators || [],
|
|
171
|
+
};
|
|
172
|
+
if (option.validators?.some((v) => v.type === "numeric")) {
|
|
173
|
+
field.type = "number";
|
|
174
|
+
field.inputType = "number";
|
|
175
|
+
}
|
|
176
|
+
const config = {
|
|
177
|
+
schemaVersion: "v2.0.0",
|
|
178
|
+
displayName: "Test",
|
|
179
|
+
categories: [{ id: "1", name: "Test" }],
|
|
180
|
+
fields: { [field.name]: field },
|
|
181
|
+
pages: [{ id: "test-page", fields: [field.name] }],
|
|
182
|
+
};
|
|
183
|
+
const { zodSchema, defaultValues } = generateRHFSchema(config);
|
|
184
|
+
const result = zodSchema.safeParse(defaultValues);
|
|
185
|
+
if (!result.success) {
|
|
186
|
+
const errors = result.error.issues.filter((i) => i.message !== "This field is required");
|
|
187
|
+
if (errors.length > 0) {
|
|
188
|
+
failedValidators.push({
|
|
189
|
+
name: option.displayName,
|
|
190
|
+
error: `Validation failed: ${JSON.stringify(errors)}`,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
failedValidators.push({
|
|
197
|
+
name: option.displayName,
|
|
198
|
+
error: `Exception: ${error.message}`,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
if (failedValidators.length > 0) {
|
|
203
|
+
const errorMsg = failedValidators.map((f) => `[${f.name}]: ${f.error}`).join("\n");
|
|
204
|
+
expect.unreachable(`Found ${failedValidators.length} invalid validator definitions:\n${errorMsg.substring(0, 2000)}`);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
});
|