@featurevisor/core 2.18.0 → 2.20.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 (96) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/coverage/clover.xml +1581 -1119
  3. package/coverage/coverage-final.json +29 -22
  4. package/coverage/lcov-report/builder/allocator.ts.html +26 -26
  5. package/coverage/lcov-report/builder/buildDatafile.ts.html +2017 -0
  6. package/coverage/lcov-report/builder/buildScopedConditions.ts.html +35 -35
  7. package/coverage/lcov-report/builder/buildScopedDatafile.ts.html +213 -45
  8. package/coverage/lcov-report/builder/buildScopedSegments.ts.html +28 -28
  9. package/coverage/lcov-report/builder/convertToV1.ts.html +583 -0
  10. package/coverage/lcov-report/builder/getFeatureRanges.ts.html +268 -0
  11. package/coverage/lcov-report/builder/hashes.ts.html +412 -0
  12. package/coverage/lcov-report/builder/index.html +90 -30
  13. package/coverage/lcov-report/builder/mutateVariables.ts.html +66 -45
  14. package/coverage/lcov-report/builder/mutator.ts.html +95 -95
  15. package/coverage/lcov-report/builder/revision.ts.html +1 -1
  16. package/coverage/lcov-report/builder/traffic.ts.html +60 -57
  17. package/coverage/lcov-report/config/index.html +1 -1
  18. package/coverage/lcov-report/config/index.ts.html +2 -2
  19. package/coverage/lcov-report/config/projectConfig.ts.html +28 -28
  20. package/coverage/lcov-report/datasource/adapter.ts.html +2 -2
  21. package/coverage/lcov-report/datasource/datasource.ts.html +4 -4
  22. package/coverage/lcov-report/datasource/filesystemAdapter.ts.html +34 -34
  23. package/coverage/lcov-report/datasource/index.html +1 -1
  24. package/coverage/lcov-report/datasource/index.ts.html +1 -1
  25. package/coverage/lcov-report/index.html +39 -39
  26. package/coverage/lcov-report/linter/attributeSchema.ts.html +1 -1
  27. package/coverage/lcov-report/linter/checkCircularDependency.ts.html +1 -1
  28. package/coverage/lcov-report/linter/checkPercentageExceedingSlot.ts.html +1 -1
  29. package/coverage/lcov-report/linter/conditionSchema.ts.html +17 -17
  30. package/coverage/lcov-report/linter/featureSchema.ts.html +686 -401
  31. package/coverage/lcov-report/linter/groupSchema.ts.html +1 -1
  32. package/coverage/lcov-report/linter/index.html +27 -27
  33. package/coverage/lcov-report/linter/lintProject.ts.html +25 -25
  34. package/coverage/lcov-report/linter/mutationNotation.ts.html +191 -122
  35. package/coverage/lcov-report/linter/printError.ts.html +1 -1
  36. package/coverage/lcov-report/linter/schema.ts.html +72 -72
  37. package/coverage/lcov-report/linter/segmentSchema.ts.html +1 -1
  38. package/coverage/lcov-report/linter/testSchema.ts.html +5 -5
  39. package/coverage/lcov-report/list/index.html +1 -1
  40. package/coverage/lcov-report/list/matrix.ts.html +1 -1
  41. package/coverage/lcov-report/parsers/index.html +1 -1
  42. package/coverage/lcov-report/parsers/index.ts.html +4 -4
  43. package/coverage/lcov-report/parsers/json.ts.html +2 -2
  44. package/coverage/lcov-report/parsers/yml.ts.html +6 -6
  45. package/coverage/lcov-report/tester/cliFormat.ts.html +1 -1
  46. package/coverage/lcov-report/tester/helpers.ts.html +1 -1
  47. package/coverage/lcov-report/tester/index.html +1 -1
  48. package/coverage/lcov-report/utils/extractKeys.ts.html +493 -0
  49. package/coverage/lcov-report/utils/git.ts.html +3 -3
  50. package/coverage/lcov-report/utils/index.html +54 -9
  51. package/coverage/lcov-report/utils/index.ts.html +94 -0
  52. package/coverage/lcov-report/utils/pretty.ts.html +121 -0
  53. package/coverage/lcov.info +2955 -2095
  54. package/lib/builder/buildDatafile.js +50 -3
  55. package/lib/builder/buildDatafile.js.map +1 -1
  56. package/lib/builder/buildDatafile.spec.d.ts +1 -0
  57. package/lib/builder/buildDatafile.spec.js +233 -0
  58. package/lib/builder/buildDatafile.spec.js.map +1 -0
  59. package/lib/builder/buildScopedDatafile.js +37 -1
  60. package/lib/builder/buildScopedDatafile.js.map +1 -1
  61. package/lib/builder/buildScopedDatafile.spec.js +289 -0
  62. package/lib/builder/buildScopedDatafile.spec.js.map +1 -1
  63. package/lib/builder/mutateVariables.d.ts +1 -1
  64. package/lib/builder/mutateVariables.js +4 -1
  65. package/lib/builder/mutateVariables.js.map +1 -1
  66. package/lib/builder/mutateVariables.spec.js +29 -0
  67. package/lib/builder/mutateVariables.spec.js.map +1 -1
  68. package/lib/builder/traffic.js +1 -0
  69. package/lib/builder/traffic.js.map +1 -1
  70. package/lib/builder/traffic.spec.js +26 -0
  71. package/lib/builder/traffic.spec.js.map +1 -1
  72. package/lib/generate-code/typescript.js +1 -1
  73. package/lib/generate-code/typescript.js.map +1 -1
  74. package/lib/linter/featureSchema.d.ts +228 -22
  75. package/lib/linter/featureSchema.js +137 -76
  76. package/lib/linter/featureSchema.js.map +1 -1
  77. package/lib/linter/featureSchema.spec.js +175 -0
  78. package/lib/linter/featureSchema.spec.js.map +1 -1
  79. package/lib/linter/mutationNotation.js +35 -10
  80. package/lib/linter/mutationNotation.js.map +1 -1
  81. package/lib/linter/mutationNotation.spec.js +31 -0
  82. package/lib/linter/mutationNotation.spec.js.map +1 -1
  83. package/package.json +5 -5
  84. package/src/builder/buildDatafile.spec.ts +267 -0
  85. package/src/builder/buildDatafile.ts +85 -4
  86. package/src/builder/buildScopedDatafile.spec.ts +330 -0
  87. package/src/builder/buildScopedDatafile.ts +57 -1
  88. package/src/builder/mutateVariables.spec.ts +45 -0
  89. package/src/builder/mutateVariables.ts +7 -0
  90. package/src/builder/traffic.spec.ts +33 -0
  91. package/src/builder/traffic.ts +1 -0
  92. package/src/generate-code/typescript.ts +1 -1
  93. package/src/linter/featureSchema.spec.ts +197 -0
  94. package/src/linter/featureSchema.ts +204 -109
  95. package/src/linter/mutationNotation.spec.ts +39 -0
  96. package/src/linter/mutationNotation.ts +30 -7
@@ -956,6 +956,336 @@ describe("core: buildScopedDatafile", function () {
956
956
  });
957
957
  });
958
958
 
959
+ describe("features with traffic and variableOverrides", function () {
960
+ test("traffic variableOverride with matching segments becomes *", function () {
961
+ const datafile: DatafileContent = {
962
+ schemaVersion: "2",
963
+ revision: "unknown",
964
+ segments: {
965
+ mobile: {
966
+ conditions: [{ attribute: "device", operator: "equals", value: "mobile" }],
967
+ },
968
+ },
969
+ features: {
970
+ feature1: {
971
+ bucketBy: ["userId"],
972
+ traffic: [
973
+ {
974
+ key: "rule1",
975
+ segments: "*",
976
+ percentage: 100000,
977
+ variableOverrides: {
978
+ config: [{ segments: "mobile", value: { source: "mobile" } }],
979
+ },
980
+ },
981
+ ],
982
+ },
983
+ },
984
+ };
985
+
986
+ const result = buildScopedDatafile(datafile, { device: "mobile" });
987
+
988
+ expect(result.features.feature1.traffic[0].variableOverrides?.config[0].segments).toEqual(
989
+ "*",
990
+ );
991
+ });
992
+
993
+ test("traffic variableOverride with non-matching segments remains", function () {
994
+ const datafile: DatafileContent = {
995
+ schemaVersion: "2",
996
+ revision: "unknown",
997
+ segments: {
998
+ mobile: {
999
+ conditions: [{ attribute: "device", operator: "equals", value: "mobile" }],
1000
+ },
1001
+ },
1002
+ features: {
1003
+ feature1: {
1004
+ bucketBy: ["userId"],
1005
+ traffic: [
1006
+ {
1007
+ key: "rule1",
1008
+ segments: "*",
1009
+ percentage: 100000,
1010
+ variableOverrides: {
1011
+ config: [{ segments: "mobile", value: { source: "mobile" } }],
1012
+ },
1013
+ },
1014
+ ],
1015
+ },
1016
+ },
1017
+ };
1018
+
1019
+ const result = buildScopedDatafile(datafile, { device: "desktop" });
1020
+
1021
+ expect(result.features.feature1.traffic[0].variableOverrides?.config[0].segments).toEqual(
1022
+ "mobile",
1023
+ );
1024
+ });
1025
+
1026
+ test("traffic variableOverride with matching conditions becomes *", function () {
1027
+ const datafile: DatafileContent = {
1028
+ schemaVersion: "2",
1029
+ revision: "unknown",
1030
+ segments: {},
1031
+ features: {
1032
+ feature1: {
1033
+ bucketBy: ["userId"],
1034
+ traffic: [
1035
+ {
1036
+ key: "rule1",
1037
+ segments: "*",
1038
+ percentage: 100000,
1039
+ variableOverrides: {
1040
+ config: [
1041
+ {
1042
+ conditions: [{ attribute: "country", operator: "equals", value: "nl" }],
1043
+ value: { source: "nl" },
1044
+ },
1045
+ ],
1046
+ },
1047
+ },
1048
+ ],
1049
+ },
1050
+ },
1051
+ };
1052
+
1053
+ const result = buildScopedDatafile(datafile, { country: "nl" });
1054
+
1055
+ expect(result.features.feature1.traffic[0].variableOverrides?.config[0].conditions).toEqual(
1056
+ "*",
1057
+ );
1058
+ });
1059
+
1060
+ test("traffic variableOverride with non-matching conditions remains", function () {
1061
+ const datafile: DatafileContent = {
1062
+ schemaVersion: "2",
1063
+ revision: "unknown",
1064
+ segments: {},
1065
+ features: {
1066
+ feature1: {
1067
+ bucketBy: ["userId"],
1068
+ traffic: [
1069
+ {
1070
+ key: "rule1",
1071
+ segments: "*",
1072
+ percentage: 100000,
1073
+ variableOverrides: {
1074
+ config: [
1075
+ {
1076
+ conditions: [{ attribute: "country", operator: "equals", value: "nl" }],
1077
+ value: { source: "nl" },
1078
+ },
1079
+ ],
1080
+ },
1081
+ },
1082
+ ],
1083
+ },
1084
+ },
1085
+ };
1086
+
1087
+ const result = buildScopedDatafile(datafile, { country: "de" });
1088
+
1089
+ expect(result.features.feature1.traffic[0].variableOverrides?.config[0].conditions).toEqual([
1090
+ { attribute: "country", operator: "equals", value: "nl" },
1091
+ ]);
1092
+ });
1093
+
1094
+ test("traffic variableOverride supports array/group segments and conditions", function () {
1095
+ const datafile: DatafileContent = {
1096
+ schemaVersion: "2",
1097
+ revision: "unknown",
1098
+ segments: {
1099
+ mobile: {
1100
+ conditions: [{ attribute: "device", operator: "equals", value: "mobile" }],
1101
+ },
1102
+ touch: {
1103
+ conditions: [{ attribute: "touch", operator: "equals", value: true }],
1104
+ },
1105
+ },
1106
+ features: {
1107
+ feature1: {
1108
+ bucketBy: ["userId"],
1109
+ traffic: [
1110
+ {
1111
+ key: "rule1",
1112
+ segments: "*",
1113
+ percentage: 100000,
1114
+ variableOverrides: {
1115
+ config: [
1116
+ {
1117
+ segments: { and: ["mobile", "touch"] },
1118
+ value: { source: "group-segments" },
1119
+ },
1120
+ {
1121
+ conditions: {
1122
+ and: [
1123
+ { attribute: "country", operator: "equals", value: "nl" },
1124
+ { attribute: "tier", operator: "equals", value: "premium" },
1125
+ ],
1126
+ },
1127
+ value: { source: "group-conditions" },
1128
+ },
1129
+ ],
1130
+ },
1131
+ },
1132
+ ],
1133
+ },
1134
+ },
1135
+ };
1136
+
1137
+ const result = buildScopedDatafile(datafile, {
1138
+ device: "mobile",
1139
+ touch: true,
1140
+ country: "nl",
1141
+ tier: "premium",
1142
+ });
1143
+
1144
+ expect(result.features.feature1.traffic[0].variableOverrides?.config[0].segments).toEqual(
1145
+ "*",
1146
+ );
1147
+ expect(result.features.feature1.traffic[0].variableOverrides?.config[1].conditions).toEqual(
1148
+ "*",
1149
+ );
1150
+ });
1151
+
1152
+ test("traffic variableOverride supports stringified segments and conditions", function () {
1153
+ const datafile: DatafileContent = {
1154
+ schemaVersion: "2",
1155
+ revision: "unknown",
1156
+ segments: {
1157
+ mobile: {
1158
+ conditions: [{ attribute: "device", operator: "equals", value: "mobile" }],
1159
+ },
1160
+ touch: {
1161
+ conditions: [{ attribute: "touch", operator: "equals", value: true }],
1162
+ },
1163
+ },
1164
+ features: {
1165
+ feature1: {
1166
+ bucketBy: ["userId"],
1167
+ traffic: [
1168
+ {
1169
+ key: "rule1",
1170
+ segments: "*",
1171
+ percentage: 100000,
1172
+ variableOverrides: {
1173
+ config: [
1174
+ {
1175
+ segments: JSON.stringify({ and: ["mobile", "touch"] }),
1176
+ value: { source: "stringified-segments" },
1177
+ },
1178
+ {
1179
+ conditions: JSON.stringify([
1180
+ { attribute: "country", operator: "equals", value: "nl" },
1181
+ ]),
1182
+ value: { source: "stringified-conditions" },
1183
+ },
1184
+ ],
1185
+ },
1186
+ },
1187
+ ],
1188
+ },
1189
+ },
1190
+ };
1191
+
1192
+ const result = buildScopedDatafile(datafile, {
1193
+ device: "mobile",
1194
+ touch: true,
1195
+ country: "nl",
1196
+ });
1197
+
1198
+ expect(result.features.feature1.traffic[0].variableOverrides?.config[0].segments).toEqual(
1199
+ "*",
1200
+ );
1201
+ expect(result.features.feature1.traffic[0].variableOverrides?.config[1].conditions).toEqual(
1202
+ "*",
1203
+ );
1204
+ });
1205
+
1206
+ test("multiple traffic entries still apply consecutive * dedupe", function () {
1207
+ const datafile: DatafileContent = {
1208
+ schemaVersion: "2",
1209
+ revision: "unknown",
1210
+ segments: {
1211
+ web: {
1212
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
1213
+ },
1214
+ mobile: {
1215
+ conditions: [{ attribute: "device", operator: "equals", value: "mobile" }],
1216
+ },
1217
+ },
1218
+ features: {
1219
+ feature1: {
1220
+ bucketBy: ["userId"],
1221
+ traffic: [
1222
+ {
1223
+ key: "rule1",
1224
+ segments: "web",
1225
+ percentage: 50000,
1226
+ variableOverrides: {
1227
+ config: [{ segments: "mobile", value: { source: "rule1" } }],
1228
+ },
1229
+ },
1230
+ {
1231
+ key: "rule2",
1232
+ segments: "web",
1233
+ percentage: 50000,
1234
+ variableOverrides: {
1235
+ config: [{ segments: "mobile", value: { source: "rule2" } }],
1236
+ },
1237
+ },
1238
+ ],
1239
+ },
1240
+ },
1241
+ };
1242
+
1243
+ const result = buildScopedDatafile(datafile, {
1244
+ platform: "web",
1245
+ device: "mobile",
1246
+ });
1247
+
1248
+ expect(result.features.feature1.traffic).toHaveLength(1);
1249
+ expect(result.features.feature1.traffic[0].key).toEqual("rule1");
1250
+ expect(result.features.feature1.traffic[0].variableOverrides?.config[0].segments).toEqual(
1251
+ "*",
1252
+ );
1253
+ });
1254
+
1255
+ test("does not mutate original datafile traffic variableOverrides", function () {
1256
+ const datafile: DatafileContent = {
1257
+ schemaVersion: "2",
1258
+ revision: "unknown",
1259
+ segments: {
1260
+ mobile: {
1261
+ conditions: [{ attribute: "device", operator: "equals", value: "mobile" }],
1262
+ },
1263
+ },
1264
+ features: {
1265
+ feature1: {
1266
+ bucketBy: ["userId"],
1267
+ traffic: [
1268
+ {
1269
+ key: "rule1",
1270
+ segments: "*",
1271
+ percentage: 100000,
1272
+ variableOverrides: {
1273
+ config: [{ segments: "mobile", value: { source: "mobile" } }],
1274
+ },
1275
+ },
1276
+ ],
1277
+ },
1278
+ },
1279
+ };
1280
+
1281
+ const original = JSON.stringify(datafile);
1282
+
1283
+ buildScopedDatafile(datafile, { device: "mobile" });
1284
+
1285
+ expect(JSON.stringify(datafile)).toEqual(original);
1286
+ });
1287
+ });
1288
+
959
1289
  describe("features with variations and variableOverrides", function () {
960
1290
  test("variableOverride with matching segments becomes *", function () {
961
1291
  const datafile: DatafileContent = {
@@ -4,6 +4,23 @@ import { DatafileReader, createLogger } from "@featurevisor/sdk";
4
4
  import { buildScopedConditions } from "./buildScopedConditions";
5
5
  import { buildScopedSegments } from "./buildScopedSegments";
6
6
 
7
+ function parseIfStringified<T>(value: T): T {
8
+ if (typeof value !== "string" || value === "*") {
9
+ return value;
10
+ }
11
+
12
+ const firstChar = value[0];
13
+ if (firstChar !== "{" && firstChar !== "[") {
14
+ return value;
15
+ }
16
+
17
+ try {
18
+ return JSON.parse(value) as T;
19
+ } catch {
20
+ return value;
21
+ }
22
+ }
23
+
7
24
  export function buildScopedDatafile(
8
25
  originalDatafileContent: DatafileContent,
9
26
  context: Context,
@@ -76,13 +93,52 @@ export function buildScopedDatafile(
76
93
 
77
94
  // segments
78
95
  if (traffic.segments) {
96
+ const segments = parseIfStringified(traffic.segments);
79
97
  feature.traffic[trafficI].segments = buildScopedSegments(
80
98
  originalDatafileReader,
81
- traffic.segments,
99
+ segments,
82
100
  context,
83
101
  removeSegments,
84
102
  );
85
103
  }
104
+
105
+ // variable overrides
106
+ if (traffic.variableOverrides) {
107
+ for (const variableKey in traffic.variableOverrides) {
108
+ const variableOverrides = traffic.variableOverrides[variableKey];
109
+
110
+ for (
111
+ let variableOverrideI = 0;
112
+ variableOverrideI < variableOverrides.length;
113
+ variableOverrideI++
114
+ ) {
115
+ const variableOverride = variableOverrides[variableOverrideI];
116
+
117
+ // segments
118
+ if (variableOverride.segments) {
119
+ const segments = parseIfStringified(variableOverride.segments);
120
+ variableOverride.segments = buildScopedSegments(
121
+ originalDatafileReader,
122
+ segments,
123
+ context,
124
+ removeSegments,
125
+ );
126
+ }
127
+
128
+ // conditions
129
+ if (variableOverride.conditions) {
130
+ const conditions = parseIfStringified(variableOverride.conditions);
131
+ variableOverride.conditions = buildScopedConditions(
132
+ originalDatafileReader,
133
+ conditions,
134
+ context,
135
+ );
136
+ }
137
+
138
+ variableOverrides[variableOverrideI] = variableOverride;
139
+ }
140
+ }
141
+ }
86
142
  }
87
143
  }
88
144
 
@@ -987,6 +987,51 @@ describe("mutateVariables", function () {
987
987
  const result = resolveMutationsForSingleVariable(schema, "foo", {});
988
988
  expect(result).toEqual({});
989
989
  });
990
+
991
+ test("uses provided baseValue instead of defaultValue for path mutations", function () {
992
+ const schema: Record<string, VariableSchema> = {
993
+ cfg: {
994
+ type: "object",
995
+ defaultValue: { source: "default", nested: { value: 1 } },
996
+ },
997
+ };
998
+
999
+ const result = resolveMutationsForSingleVariable(
1000
+ schema,
1001
+ "cfg",
1002
+ {
1003
+ "nested.value": 2,
1004
+ },
1005
+ { source: "rule", nested: { value: 10 }, extra: true },
1006
+ );
1007
+
1008
+ expect(result).toEqual({
1009
+ source: "rule",
1010
+ nested: { value: 2 },
1011
+ extra: true,
1012
+ });
1013
+ });
1014
+
1015
+ test("baseValue with full replacement key still applies replacement first", function () {
1016
+ const schema: Record<string, VariableSchema> = {
1017
+ cfg: {
1018
+ type: "object",
1019
+ defaultValue: { a: 1, b: 2 },
1020
+ },
1021
+ };
1022
+
1023
+ const result = resolveMutationsForSingleVariable(
1024
+ schema,
1025
+ "cfg",
1026
+ {
1027
+ cfg: { a: 100, c: 300 },
1028
+ c: 999,
1029
+ },
1030
+ { a: 10, b: 20 },
1031
+ );
1032
+
1033
+ expect(result).toEqual({ a: 100, c: 999 });
1034
+ });
990
1035
  });
991
1036
 
992
1037
  describe("does not mutate input", function () {
@@ -89,14 +89,21 @@ export function resolveMutationsForSingleVariable(
89
89
  variablesSchema: Record<string, VariableSchema> | undefined,
90
90
  variableKey: string,
91
91
  overrideValue: VariableValue,
92
+ baseValue?: VariableValue,
92
93
  ): VariableValue {
93
94
  if (!variablesSchema || !variablesSchema[variableKey]) return overrideValue;
94
95
  if (overrideValue === null || overrideValue === undefined) return overrideValue;
95
96
  if (typeof overrideValue !== "object" || Array.isArray(overrideValue)) {
96
97
  return overrideValue;
97
98
  }
99
+
98
100
  const pathMap = overrideValue as Record<string, VariableValue>;
99
101
  const flat: Record<string, VariableValue> = {};
102
+
103
+ if (typeof baseValue !== "undefined") {
104
+ flat[variableKey] = JSON.parse(JSON.stringify(baseValue)) as VariableValue;
105
+ }
106
+
100
107
  for (const [k, v] of Object.entries(pathMap)) {
101
108
  flat[k === variableKey ? variableKey : variableKey + "." + k] = v;
102
109
  }
@@ -910,4 +910,37 @@ describe("core: Traffic", function () {
910
910
  },
911
911
  ]);
912
912
  });
913
+
914
+ it("should include variableOverrides from parsed rule in traffic", function () {
915
+ const variableOverrides = {
916
+ config: [
917
+ {
918
+ segments: "mobile",
919
+ value: {
920
+ "layout.width": 900,
921
+ },
922
+ },
923
+ ],
924
+ };
925
+
926
+ const result = getTraffic(
927
+ [
928
+ {
929
+ value: "control",
930
+ weight: 100,
931
+ },
932
+ ],
933
+ [
934
+ {
935
+ key: "1",
936
+ segments: "*",
937
+ percentage: 100,
938
+ variableOverrides,
939
+ },
940
+ ],
941
+ undefined,
942
+ );
943
+
944
+ expect(result[0].variableOverrides).toEqual(variableOverrides);
945
+ });
913
946
  });
@@ -91,6 +91,7 @@ export function getTraffic(
91
91
  percentage: rulePercentage * (MAX_BUCKETED_NUMBER / 100),
92
92
  allocation: [],
93
93
  variationWeights: parsedRule.variationWeights,
94
+ variableOverrides: parsedRule.variableOverrides,
94
95
  };
95
96
 
96
97
  // overrides
@@ -600,7 +600,7 @@ export async function generateTypeScriptCodeForProject(
600
600
  for (const attributeKey of attributeFiles) {
601
601
  const parsedAttribute = await datasource.readAttribute(attributeKey);
602
602
 
603
- if (typeof parsedAttribute.archived === "undefined") {
603
+ if (parsedAttribute.archived) {
604
604
  continue;
605
605
  }
606
606