@featurevisor/core 1.5.1 → 1.6.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.
Files changed (60) hide show
  1. package/.eslintcache +1 -1
  2. package/CHANGELOG.md +11 -0
  3. package/coverage/clover.xml +2 -2
  4. package/coverage/lcov-report/index.html +1 -1
  5. package/coverage/lcov-report/lib/builder/allocator.js.html +1 -1
  6. package/coverage/lcov-report/lib/builder/index.html +1 -1
  7. package/coverage/lcov-report/lib/builder/traffic.js.html +1 -1
  8. package/coverage/lcov-report/lib/tester/checkIfObjectsAreEqual.js.html +1 -1
  9. package/coverage/lcov-report/lib/tester/index.html +1 -1
  10. package/coverage/lcov-report/lib/tester/matrix.js.html +1 -1
  11. package/coverage/lcov-report/src/builder/allocator.ts.html +1 -1
  12. package/coverage/lcov-report/src/builder/index.html +1 -1
  13. package/coverage/lcov-report/src/builder/traffic.ts.html +1 -1
  14. package/coverage/lcov-report/src/tester/checkIfObjectsAreEqual.ts.html +1 -1
  15. package/coverage/lcov-report/src/tester/index.html +1 -1
  16. package/coverage/lcov-report/src/tester/matrix.ts.html +1 -1
  17. package/lib/linter/attributeSchema.d.ts +17 -2
  18. package/lib/linter/attributeSchema.js +13 -11
  19. package/lib/linter/attributeSchema.js.map +1 -1
  20. package/lib/linter/checkPercentageExceedingSlot.d.ts +3 -0
  21. package/lib/linter/checkPercentageExceedingSlot.js +86 -0
  22. package/lib/linter/checkPercentageExceedingSlot.js.map +1 -0
  23. package/lib/linter/conditionSchema.d.ts +2 -2
  24. package/lib/linter/conditionSchema.js +112 -57
  25. package/lib/linter/conditionSchema.js.map +1 -1
  26. package/lib/linter/featureSchema.d.ts +229 -2
  27. package/lib/linter/featureSchema.js +195 -139
  28. package/lib/linter/featureSchema.js.map +1 -1
  29. package/lib/linter/groupSchema.d.ts +32 -2
  30. package/lib/linter/groupSchema.js +28 -97
  31. package/lib/linter/groupSchema.js.map +1 -1
  32. package/lib/linter/lintProject.js +169 -118
  33. package/lib/linter/lintProject.js.map +1 -1
  34. package/lib/linter/printError.d.ts +2 -0
  35. package/lib/linter/printError.js +20 -0
  36. package/lib/linter/printError.js.map +1 -0
  37. package/lib/linter/segmentSchema.d.ts +14 -2
  38. package/lib/linter/segmentSchema.js +12 -10
  39. package/lib/linter/segmentSchema.js.map +1 -1
  40. package/lib/linter/testSchema.d.ts +90 -2
  41. package/lib/linter/testSchema.js +49 -38
  42. package/lib/linter/testSchema.js.map +1 -1
  43. package/lib/tester/cliFormat.d.ts +1 -0
  44. package/lib/tester/cliFormat.js +2 -1
  45. package/lib/tester/cliFormat.js.map +1 -1
  46. package/package.json +4 -4
  47. package/src/linter/attributeSchema.ts +11 -9
  48. package/src/linter/checkPercentageExceedingSlot.ts +41 -0
  49. package/src/linter/conditionSchema.ts +120 -97
  50. package/src/linter/featureSchema.ts +241 -177
  51. package/src/linter/groupSchema.ts +38 -54
  52. package/src/linter/lintProject.ts +144 -62
  53. package/src/linter/printError.ts +21 -0
  54. package/src/linter/segmentSchema.ts +10 -8
  55. package/src/linter/testSchema.ts +67 -50
  56. package/src/tester/cliFormat.ts +1 -0
  57. package/lib/linter/printJoiError.d.ts +0 -2
  58. package/lib/linter/printJoiError.js +0 -14
  59. package/lib/linter/printJoiError.js.map +0 -1
  60. package/src/linter/printJoiError.ts +0 -11
@@ -1,48 +1,59 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getTestsJoiSchema = void 0;
4
- var Joi = require("joi");
5
- function getTestsJoiSchema(projectConfig, availableFeatureKeys, availableSegmentKeys) {
6
- var _a, _b;
7
- var segmentTestJoiSchema = Joi.object({
8
- segment: (_a = Joi.string())
9
- .valid.apply(_a, availableSegmentKeys).required(),
10
- assertions: Joi.array().items(Joi.object({
11
- matrix: Joi.object().optional(),
12
- description: Joi.string().optional(),
13
- context: Joi.object(),
14
- expectedToMatch: Joi.boolean(),
15
- })),
16
- });
17
- var featureTestJoiSchema = Joi.object({
18
- feature: (_b = Joi.string())
19
- .valid.apply(_b, availableFeatureKeys).required(),
20
- assertions: Joi.array().items(Joi.object({
21
- matrix: Joi.object().optional(),
22
- description: Joi.string().optional(),
23
- at: Joi.alternatives().try(Joi.number().precision(3).min(0).max(100).required(),
24
- // because of supporting matrix
25
- Joi.string().required()),
26
- environment: Joi.string()
27
- .custom(function (value, helpers) {
3
+ exports.getTestsZodSchema = void 0;
4
+ var zod_1 = require("zod");
5
+ function getTestsZodSchema(projectConfig, availableFeatureKeys, availableSegmentKeys) {
6
+ var segmentTestZodSchema = zod_1.z
7
+ .object({
8
+ segment: zod_1.z.string().refine(function (value) { return availableSegmentKeys.includes(value); }, function (value) { return ({
9
+ message: "Unknown segment \"".concat(value, "\""),
10
+ }); }),
11
+ assertions: zod_1.z.array(zod_1.z
12
+ .object({
13
+ matrix: zod_1.z.record(zod_1.z.unknown()).optional(),
14
+ description: zod_1.z.string().optional(),
15
+ context: zod_1.z.record(zod_1.z.unknown()),
16
+ expectedToMatch: zod_1.z.boolean(),
17
+ })
18
+ .strict()),
19
+ })
20
+ .strict();
21
+ var featureTestZodSchema = zod_1.z
22
+ .object({
23
+ feature: zod_1.z.string().refine(function (value) { return availableFeatureKeys.includes(value); }, function (value) { return ({
24
+ message: "Unknown feature \"".concat(value, "\""),
25
+ }); }),
26
+ assertions: zod_1.z.array(zod_1.z
27
+ .object({
28
+ matrix: zod_1.z.record(zod_1.z.unknown()).optional(),
29
+ description: zod_1.z.string().optional(),
30
+ at: zod_1.z.union([
31
+ zod_1.z.number().min(0).max(100),
32
+ // because of supporting matrix
33
+ zod_1.z.string(),
34
+ ]),
35
+ environment: zod_1.z.string().refine(function (value) {
28
36
  if (value.indexOf("${{") === 0) {
29
37
  // allow unknown strings for matrix
30
- return value;
38
+ return true;
31
39
  }
32
40
  // otherwise only known environments should be passed
33
41
  if (projectConfig.environments.includes(value)) {
34
- return value;
42
+ return true;
35
43
  }
36
- return helpers.error("any.invalid");
37
- })
38
- .required(),
39
- context: Joi.object().required(),
40
- expectedToBeEnabled: Joi.boolean().required(),
41
- expectedVariation: Joi.alternatives().try(Joi.string(), Joi.number(), Joi.boolean()),
42
- expectedVariables: Joi.object(),
43
- })),
44
- });
45
- return Joi.alternatives().try(segmentTestJoiSchema, featureTestJoiSchema);
44
+ return false;
45
+ }, function (value) { return ({
46
+ message: "Unknown environment \"".concat(value, "\""),
47
+ }); }),
48
+ context: zod_1.z.record(zod_1.z.unknown()),
49
+ expectedToBeEnabled: zod_1.z.boolean(),
50
+ expectedVariation: zod_1.z.string().optional(),
51
+ expectedVariables: zod_1.z.record(zod_1.z.unknown()).optional(),
52
+ })
53
+ .strict()),
54
+ })
55
+ .strict();
56
+ return zod_1.z.union([segmentTestZodSchema, featureTestZodSchema]);
46
57
  }
47
- exports.getTestsJoiSchema = getTestsJoiSchema;
58
+ exports.getTestsZodSchema = getTestsZodSchema;
48
59
  //# sourceMappingURL=testSchema.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"testSchema.js","sourceRoot":"","sources":["../../src/linter/testSchema.ts"],"names":[],"mappings":";;;AAAA,yBAA2B;AAI3B,SAAgB,iBAAiB,CAC/B,aAA4B,EAC5B,oBAA8B,EAC9B,oBAA8B;;IAE9B,IAAM,oBAAoB,GAAG,GAAG,CAAC,MAAM,CAAC;QACtC,OAAO,EAAE,CAAA,KAAA,GAAG,CAAC,MAAM,EAAE,CAAA;aAClB,KAAK,WAAI,oBAAoB,EAC7B,QAAQ,EAAE;QACb,UAAU,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,KAAK,CAC3B,GAAG,CAAC,MAAM,CAAC;YACT,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAC/B,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YACpC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE;YACrB,eAAe,EAAE,GAAG,CAAC,OAAO,EAAE;SAC/B,CAAC,CACH;KACF,CAAC,CAAC;IAEH,IAAM,oBAAoB,GAAG,GAAG,CAAC,MAAM,CAAC;QACtC,OAAO,EAAE,CAAA,KAAA,GAAG,CAAC,MAAM,EAAE,CAAA;aAClB,KAAK,WAAI,oBAAoB,EAC7B,QAAQ,EAAE;QACb,UAAU,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,KAAK,CAC3B,GAAG,CAAC,MAAM,CAAC;YACT,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAC/B,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YACpC,EAAE,EAAE,GAAG,CAAC,YAAY,EAAE,CAAC,GAAG,CACxB,GAAG,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;YAEpD,+BAA+B;YAC/B,GAAG,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CACxB;YACD,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE;iBACtB,MAAM,CAAC,UAAC,KAAK,EAAE,OAAO;gBACrB,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE;oBAC9B,mCAAmC;oBACnC,OAAO,KAAK,CAAC;iBACd;gBAED,qDAAqD;gBACrD,IAAI,aAAa,CAAC,YAAY,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE;oBAC9C,OAAO,KAAK,CAAC;iBACd;gBAED,OAAO,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;YACtC,CAAC,CAAC;iBACD,QAAQ,EAAE;YACb,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAChC,mBAAmB,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;YAC7C,iBAAiB,EAAE,GAAG,CAAC,YAAY,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,GAAG,CAAC,MAAM,EAAE,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC;YACpF,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE;SAChC,CAAC,CACH;KACF,CAAC,CAAC;IAEH,OAAO,GAAG,CAAC,YAAY,EAAE,CAAC,GAAG,CAAC,oBAAoB,EAAE,oBAAoB,CAAC,CAAC;AAC5E,CAAC;AAzDD,8CAyDC"}
1
+ {"version":3,"file":"testSchema.js","sourceRoot":"","sources":["../../src/linter/testSchema.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
1
  export declare const CLI_FORMAT_RED = "\u001B[31m%s\u001B[0m";
2
2
  export declare const CLI_FORMAT_GREEN = "\u001B[32m%s\u001B[0m";
3
3
  export declare const CLI_FORMAT_BOLD = "\u001B[1m%s\u001B[0m";
4
+ export declare const CLI_FORMAT_UNDERLINE = "\u001B[4m%s\u001B[0m";
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CLI_FORMAT_BOLD = exports.CLI_FORMAT_GREEN = exports.CLI_FORMAT_RED = void 0;
3
+ exports.CLI_FORMAT_UNDERLINE = exports.CLI_FORMAT_BOLD = exports.CLI_FORMAT_GREEN = exports.CLI_FORMAT_RED = void 0;
4
4
  exports.CLI_FORMAT_RED = "\x1b[31m%s\x1b[0m";
5
5
  exports.CLI_FORMAT_GREEN = "\x1b[32m%s\x1b[0m";
6
6
  exports.CLI_FORMAT_BOLD = "\x1b[1m%s\x1b[0m";
7
+ exports.CLI_FORMAT_UNDERLINE = "\x1b[4m%s\x1b[0m";
7
8
  //# sourceMappingURL=cliFormat.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cliFormat.js","sourceRoot":"","sources":["../../src/tester/cliFormat.ts"],"names":[],"mappings":";;;AAAa,QAAA,cAAc,GAAG,mBAAmB,CAAC;AACrC,QAAA,gBAAgB,GAAG,mBAAmB,CAAC;AAEvC,QAAA,eAAe,GAAG,kBAAkB,CAAC"}
1
+ {"version":3,"file":"cliFormat.js","sourceRoot":"","sources":["../../src/tester/cliFormat.ts"],"names":[],"mappings":";;;AAAa,QAAA,cAAc,GAAG,mBAAmB,CAAC;AACrC,QAAA,gBAAgB,GAAG,mBAAmB,CAAC;AAEvC,QAAA,eAAe,GAAG,kBAAkB,CAAC;AACrC,QAAA,oBAAoB,GAAG,kBAAkB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@featurevisor/core",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "description": "Core package of Featurevisor for Node.js usage",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -48,14 +48,14 @@
48
48
  "@featurevisor/site": "^1.5.1",
49
49
  "@featurevisor/types": "^1.3.0",
50
50
  "axios": "^1.3.4",
51
- "joi": "^17.8.3",
52
51
  "js-yaml": "^4.1.0",
53
52
  "mkdirp": "^2.1.3",
54
- "tar": "^6.1.13"
53
+ "tar": "^6.1.13",
54
+ "zod": "^3.22.4"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@types/js-yaml": "^4.0.5",
58
58
  "@types/tar": "^6.1.4"
59
59
  },
60
- "gitHead": "1202fddb786e61079771c78f5a4a33dddd11bdfe"
60
+ "gitHead": "9d9a317843b028cc68ec0ff4ce060fe1404368d8"
61
61
  }
@@ -1,12 +1,14 @@
1
- import * as Joi from "joi";
1
+ import { z } from "zod";
2
2
 
3
- export function getAttributeJoiSchema() {
4
- const attributeJoiSchema = Joi.object({
5
- archived: Joi.boolean(),
6
- type: Joi.string().valid("boolean", "string", "integer", "double", "date", "semver").required(),
7
- description: Joi.string().required(),
8
- capture: Joi.boolean(),
9
- });
3
+ export function getAttributeZodSchema() {
4
+ const attributeZodSchema = z
5
+ .object({
6
+ archived: z.boolean().optional(),
7
+ type: z.enum(["boolean", "string", "integer", "double", "date", "semver"]),
8
+ description: z.string(),
9
+ capture: z.boolean().optional(),
10
+ })
11
+ .strict();
10
12
 
11
- return attributeJoiSchema;
13
+ return attributeZodSchema;
12
14
  }
@@ -0,0 +1,41 @@
1
+ import { Group } from "@featurevisor/types";
2
+
3
+ import { Datasource } from "../datasource";
4
+
5
+ // @TODO: ideally in future, this check should be done from Feature level,
6
+ // as well as Group level as done here
7
+ export async function checkForFeatureExceedingGroupSlotPercentage(
8
+ datasource: Datasource,
9
+ group: Group,
10
+ availableFeatureKeys: string[],
11
+ ) {
12
+ for (const slot of group.slots) {
13
+ const maxPercentageForRule = slot.percentage;
14
+
15
+ if (slot.feature) {
16
+ const featureKey = slot.feature;
17
+ const featureExists = availableFeatureKeys.indexOf(featureKey) > -1;
18
+
19
+ if (!featureExists) {
20
+ throw new Error(`Unknown feature "${featureKey}"`);
21
+ }
22
+
23
+ const parsedFeature = await datasource.readFeature(featureKey);
24
+
25
+ const environmentKeys = Object.keys(parsedFeature.environments);
26
+ for (const environmentKey of environmentKeys) {
27
+ const environment = parsedFeature.environments[environmentKey];
28
+ const rules = environment.rules;
29
+
30
+ for (const rule of rules) {
31
+ if (rule.percentage > maxPercentageForRule) {
32
+ // @TODO: this does not help with same feature belonging to multiple slots. fix that.
33
+ throw new Error(
34
+ `Feature ${featureKey}'s rule ${rule.key} in ${environmentKey} has a percentage of ${rule.percentage} which is greater than the maximum percentage of ${maxPercentageForRule} for the slot`,
35
+ );
36
+ }
37
+ }
38
+ }
39
+ }
40
+ }
41
+ }
@@ -1,114 +1,137 @@
1
- import * as Joi from "joi";
1
+ import { z } from "zod";
2
2
 
3
3
  import { ProjectConfig } from "../config";
4
4
 
5
- export function getConditionsJoiSchema(
5
+ const commonOperators: [string, ...string[]] = ["equals", "notEquals"];
6
+ const numericOperators = ["greaterThan", "greaterThanOrEquals", "lessThan", "lessThanOrEquals"];
7
+ const stringOperators = ["contains", "notContains", "startsWith", "endsWith"];
8
+ const semverOperators = [
9
+ "semverEquals",
10
+ "semverNotEquals",
11
+ "semverGreaterThan",
12
+ "semverGreaterThanOrEquals",
13
+ "semverLessThan",
14
+ "semverLessThanOrEquals",
15
+ ];
16
+ const dateOperators = ["before", "after"];
17
+ const arrayOperators = ["in", "notIn"];
18
+
19
+ export function getConditionsZodSchema(
6
20
  projectConfig: ProjectConfig,
7
- availableAttributeKeys: string[],
21
+ availableAttributeKeys: [string, ...string[]],
8
22
  ) {
9
- const plainConditionJoiSchema = Joi.object({
10
- attribute: Joi.string()
11
- .valid(...availableAttributeKeys)
12
- .required(),
13
- operator: Joi.string()
14
- .valid(
15
- "equals",
16
- "notEquals",
23
+ const plainConditionZodSchema = z
24
+ .object({
25
+ attribute: z.string().refine(
26
+ (value) => availableAttributeKeys.includes(value),
27
+ (value) => ({
28
+ message: `Unknown attribute "${value}"`,
29
+ }),
30
+ ),
31
+ operator: z.enum([
32
+ ...commonOperators,
33
+ ...numericOperators,
34
+ ...stringOperators,
35
+ ...semverOperators,
36
+ ...dateOperators,
37
+ ...arrayOperators,
38
+ ]),
39
+ value: z.union([
40
+ z.string(),
41
+ z.array(z.string()),
42
+ z.number(),
43
+ z.boolean(),
44
+ z.date(),
45
+ z.null(),
46
+ ]),
47
+ })
48
+ .superRefine((data, context) => {
49
+ // common
50
+ if (
51
+ commonOperators.includes(data.operator) &&
52
+ !(
53
+ data.value === null ||
54
+ typeof data.value === "string" ||
55
+ typeof data.value === "number" ||
56
+ typeof data.value === "boolean" ||
57
+ data.value instanceof Date ||
58
+ data.value === null
59
+ )
60
+ ) {
61
+ context.addIssue({
62
+ code: z.ZodIssueCode.custom,
63
+ message: `when operator is "${data.operator}", value has to be either a string, number, boolean, date or null`,
64
+ path: ["value"],
65
+ });
66
+ }
17
67
 
18
- // numeric
19
- "greaterThan",
20
- "greaterThanOrEquals",
21
- "lessThan",
22
- "lessThanOrEquals",
68
+ // numeric
69
+ if (numericOperators.includes(data.operator) && typeof data.value !== "number") {
70
+ context.addIssue({
71
+ code: z.ZodIssueCode.custom,
72
+ message: `when operator is "${data.operator}", value must be a number`,
73
+ path: ["value"],
74
+ });
75
+ }
23
76
 
24
- // string
25
- "contains",
26
- "notContains",
27
- "startsWith",
28
- "endsWith",
77
+ // string
78
+ if (stringOperators.includes(data.operator) && typeof data.value !== "string") {
79
+ context.addIssue({
80
+ code: z.ZodIssueCode.custom,
81
+ message: `when operator is "${data.operator}", value must be a string`,
82
+ path: ["value"],
83
+ });
84
+ }
29
85
 
30
- // semver (string)
31
- "semverEquals",
32
- "semverNotEquals",
33
- "semverGreaterThan",
34
- "semverGreaterThanOrEquals",
35
- "semverLessThan",
36
- "semverLessThanOrEquals",
86
+ // semver
87
+ if (semverOperators.includes(data.operator) && typeof data.value !== "string") {
88
+ context.addIssue({
89
+ code: z.ZodIssueCode.custom,
90
+ message: `when operator is "${data.operator}", value must be a string`,
91
+ path: ["value"],
92
+ });
93
+ }
37
94
 
38
- // date comparisons
39
- "before",
40
- "after",
95
+ // date
96
+ if (dateOperators.includes(data.operator) && !(data.value instanceof Date)) {
97
+ context.addIssue({
98
+ code: z.ZodIssueCode.custom,
99
+ message: `when operator is "${data.operator}", value must be a date`,
100
+ path: ["value"],
101
+ });
102
+ }
41
103
 
42
- // array of strings
43
- "in",
44
- "notIn",
45
- )
46
- .required(),
47
- value: Joi.when("operator", {
48
- is: Joi.string().valid("equals", "notEquals"),
49
- then: Joi.alternatives()
50
- .try(Joi.string(), Joi.number(), Joi.boolean(), Joi.date())
51
- .allow(null)
52
- .required(),
53
- })
54
- .when("operator", {
55
- is: Joi.string().valid(
56
- "greaterThan",
57
- "greaterThanOrEquals",
58
- "lessThan",
59
- "lessThanOrEquals",
60
- ),
61
- then: Joi.number().required(),
62
- })
63
- .when("operator", {
64
- is: Joi.string().valid("contains", "notContains", "startsWith", "endsWith"),
65
- then: Joi.string().required(),
104
+ // array
105
+ if (arrayOperators.includes(data.operator) && !Array.isArray(data.value)) {
106
+ context.addIssue({
107
+ code: z.ZodIssueCode.custom,
108
+ message: `when operator is "${data.operator}", value must be an array of strings`,
109
+ path: ["value"],
110
+ });
111
+ }
112
+ });
113
+
114
+ const andOrNotConditionZodSchema = z.union([
115
+ z
116
+ .object({
117
+ and: z.array(z.lazy(() => conditionZodSchema)),
66
118
  })
67
- .when("operator", {
68
- is: Joi.string().valid(
69
- "semverEquals",
70
- "semverNotEquals",
71
- "semverGreaterThan",
72
- "semverGreaterThanOrEquals",
73
- "semverLessThan",
74
- "semverLessThanOrEquals",
75
- ),
76
- then: Joi.string().required(),
119
+ .strict(),
120
+ z
121
+ .object({
122
+ or: z.array(z.lazy(() => conditionZodSchema)),
77
123
  })
78
- .when("operator", {
79
- is: Joi.string().valid("before", "after"),
80
- then: Joi.alternatives(Joi.date(), Joi.string()).required(),
124
+ .strict(),
125
+ z
126
+ .object({
127
+ not: z.array(z.lazy(() => conditionZodSchema)),
81
128
  })
82
- .when("operator", {
83
- is: Joi.string().valid("in", "notIn"),
84
- then: Joi.array().items(Joi.string()).required(),
85
- }),
86
- });
87
-
88
- const andOrNotConditionJoiSchema = Joi.alternatives()
89
- .try(
90
- Joi.object({
91
- and: Joi.array().items(Joi.link("#andOrNotCondition"), plainConditionJoiSchema),
92
- }),
93
- Joi.object({
94
- or: Joi.array().items(Joi.link("#andOrNotCondition"), plainConditionJoiSchema),
95
- }),
96
- Joi.object({
97
- // @TODO: allow plainConditionJoiSchema as well?
98
- not: Joi.array().items(Joi.link("#andOrNotCondition"), plainConditionJoiSchema),
99
- }),
100
- )
101
- .id("andOrNotCondition");
129
+ .strict(),
130
+ ]);
102
131
 
103
- const conditionJoiSchema = Joi.alternatives().try(
104
- andOrNotConditionJoiSchema,
105
- plainConditionJoiSchema,
106
- );
132
+ const conditionZodSchema = z.union([andOrNotConditionZodSchema, plainConditionZodSchema]);
107
133
 
108
- const conditionsJoiSchema = Joi.alternatives().try(
109
- conditionJoiSchema,
110
- Joi.array().items(conditionJoiSchema),
111
- );
134
+ const conditionsZodSchema = z.union([conditionZodSchema, z.array(conditionZodSchema)]);
112
135
 
113
- return conditionsJoiSchema;
136
+ return conditionsZodSchema;
114
137
  }