Compare commits

...

319 Commits

Author SHA1 Message Date
Jonathan Norris 91ba360d36
feat(multi-provider): Add Track Method Support to Multi-Provider (#1323)
Signed-off-by: Jonathan Norris <jonathan@taplytics.com>
2025-07-11 13:39:13 -04:00
github-actions[bot] 0ff5c88135
chore(main): release config-cat-provider 0.7.6 (#1349)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-08 00:22:29 +02:00
renovate[bot] 601e7de199
fix(security): update dependency configcat-common to v9.4.0 (#1348)
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2025-07-04 15:44:05 -04:00
renovate[bot] fca83c925c
fix(security): update vulnerability-updates (#1342)
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2025-07-04 14:53:58 -04:00
Todd Baert e2404a480f
Update renovate.json
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-07-04 14:34:13 -04:00
Todd Baert 6776e9735a
Update renovate.json
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-07-04 14:29:19 -04:00
Todd Baert 0a6e44302d
Update renovate.json
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-07-04 14:24:19 -04:00
Todd Baert d6c4817948
Update renovate.json
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-07-04 13:55:54 -04:00
Todd Baert 1a913b8dba
chore: Update renovate.json
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-07-04 13:53:23 -04:00
Todd Baert 1b5ecc0959
chore: fix renovate.json
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-07-04 13:50:31 -04:00
Todd Baert 18268f8a21
chore: more renovate troubleshooting
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-07-04 13:34:15 -04:00
renovate[bot] ddbde614e9
fix(deps): update dependency @flipt-io/flipt-client-js to v0.2.0 (#1331)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-04 13:27:33 -04:00
renovate[bot] e501475536
fix(deps): update dependency axios to v1.10.0 (#1332)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-04 17:18:48 +00:00
renovate[bot] ea14c71e4d
chore(deps): update libs/shared/flagd-core/spec digest to a367871 (#1328)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-04 17:09:12 +00:00
Todd Baert 6a6d23ba32
chore: troubleshooting
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-07-04 13:01:52 -04:00
Todd Baert 82fbf1e042 chore: fix dep version
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-07-04 13:00:24 -04:00
renovate[bot] 375193081a
fix(deps): update dependency @aws-sdk/client-ssm to v3.840.0 (#1329)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-04 16:44:03 +00:00
renovate[bot] fbc5023097
chore(config): migrate renovate config (#1327)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-04 11:33:51 -04:00
github-actions[bot] e6766c07e5
chore(main): release flagd-core 1.1.0 (#1156)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-04 11:19:27 -04:00
renovate[bot] 9c0f895af5
chore(deps): update libs/providers/flagd/spec digest to a367871 (#1326)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-04 11:16:50 -04:00
Todd Baert cf9fe09cc1 chore: use dep dashboard
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-07-04 11:12:33 -04:00
github-actions[bot] 4e1202982e
chore(main): release ofrep-web-provider 0.3.3 (#1320)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-23 12:38:38 -04:00
Todd Baert 6ab7f1abfc
fix: remove incorrect undici dep (#1319)
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-06-23 12:17:32 -04:00
renovate[bot] 1ffe54af81
fix(deps): update dependency axios to v1.9.0 (#1314)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-08 00:58:04 +00:00
renovate[bot] ec5309a0a9
fix(deps): update dependency @aws-sdk/client-ssm to v3.826.0 (#1313)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-07 20:52:22 +00:00
renovate[bot] 02a671ca7c
chore(deps): update swc monorepo (#1312)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-07 16:52:28 +00:00
renovate[bot] 4f30cda7a3
chore(deps): update dependency testcontainers to v10.28.0 (#1311)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-07 12:38:51 +00:00
renovate[bot] 141a37df58
chore(deps): update dependency @smithy/types to v4.3.1 (#1310)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-07 10:33:14 +00:00
renovate[bot] d3ad93e3a9
fix(deps): update dependency @flipt-io/flipt-client-js to v0.0.6 (#1309)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-07 10:30:01 +00:00
renovate[bot] d495ee346f
chore(deps): update dependency ts-jest to v29.3.4 (#1308)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-07 05:59:41 +00:00
renovate[bot] b9ff7e1448
chore(deps): update libs/providers/flagd/spec digest to bb2dc2c (#1305)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-07 01:44:58 +00:00
renovate[bot] 70518ac4c1
chore(deps): update libs/shared/flagd-core/spec digest to bb2dc2c (#1306)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-06 23:58:24 +00:00
github-actions[bot] 6edf52e7c2
chore(main): release flipt-web-provider 0.1.5 (#1304)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-06 14:52:36 +00:00
renovate[bot] 61b46c9687
chore(deps): update dependency libs/shared/flagd-core/test-harness to v2 (#1218)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-06 14:52:14 +00:00
Philipp Jardas 9e8496a384
feat(flipt-web): update types to match flipt-client-js (#1303)
Signed-off-by: Philipp Jardas <philipp@jardas.de>
2025-06-06 10:48:46 -04:00
renovate[bot] 6dd71ac73a
chore(deps): update libs/shared/flagd-core/flagd-schemas digest to 2852d77 (#1301)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-05 16:11:27 +00:00
renovate[bot] 8cdd5b4524
chore(deps): update libs/providers/flagd/spec digest to f014806 (#1300)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-05 11:49:40 +00:00
renovate[bot] 4fe9c7e232
chore(deps): update libs/providers/flagd/schemas digest to 2852d77 (#1299)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-05 05:07:23 +00:00
renovate[bot] e83fa88790
chore(deps): update libs/providers/flagd-web/schemas digest to 2852d77 (#1298)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-05 02:26:46 +00:00
renovate[bot] 771dcd522b
chore(deps): update actions/setup-node digest to 49933ea (#1297)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-05 02:23:16 +00:00
renovate[bot] 182077cf32
chore(deps): update libs/shared/flagd-core/spec digest to f014806 (#1206)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-04 21:50:07 +00:00
Simon Schrottner fbd9f9155d
test(flagd): rework e2e tests to new format (#1129)
Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2025-06-04 15:59:55 -04:00
github-actions[bot] 24a7e0e3bb
chore(main): release flipt-web-provider 0.1.4 (#1295)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2025-06-04 14:54:27 -04:00
github-actions[bot] c1b00f69ee
chore(main): release aws-ssm-provider 0.1.3 (#1296)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-04 14:51:07 -04:00
Todd Baert 3045e0cac8
chore: remove non-feat-fix release config
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-06-04 14:23:49 -04:00
Todd Baert 4dbcbf1870
chore: add "type" import enforcement lint rule and apply (#1292)
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-05-30 13:06:18 -04:00
Todd Baert 8d2fd484db
chore: update ci to node 20+ (#1291)
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-05-29 15:36:27 -04:00
github-actions[bot] a0cdef59fd
chore(main): release go-feature-flag-web-provider 0.2.6 (#1262)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-28 08:56:07 +02:00
Thomas Poignant d9ffcec165
feat(goff web): Support tracking events (#1268)
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>
2025-05-26 22:35:44 +02:00
github-actions[bot] 136b1c9858
chore(main): release go-feature-flag-provider 0.7.8 (#1285)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-26 20:38:01 +02:00
simas.usas 7083655c78
feat(gofeatureflag): added cache option (#1284)
Signed-off-by: Simas Usas <simas.usas@vinted.com>
2025-05-26 08:17:38 +02:00
Todd Baert df1ec47c3f
chore: use publish env
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-04-16 12:31:06 -04:00
renovate[bot] de161ad178
fix(deps): update swc monorepo (#1280)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-12 10:57:06 +00:00
renovate[bot] a80f5ce3d7
fix(deps): update dependency lru-cache to v11.1.0 (#1279)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-12 01:42:00 +00:00
renovate[bot] afae82c1a1
fix(deps): update dependency @aws-sdk/client-ssm to v3.787.0 (#1278)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-12 01:38:56 +00:00
renovate[bot] b9231018c4
chore(deps): update dependency typescript to v5.8.3 (#1277)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-12 01:35:38 +00:00
renovate[bot] 4a48caa78a
chore(deps): update dependency ts-jest to v29.3.1 (#1276)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-11 22:24:37 +00:00
renovate[bot] 92c67281c8
chore(deps): update dependency testcontainers to v10.24.2 (#1275)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-11 18:11:16 +00:00
github-actions[bot] 358a04f3f0
chore(main): release go-feature-flag-provider 0.7.7 (#1265)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-11 15:32:00 +02:00
Christian Bilevits ce6a8e1a80
fix(gofeatureflag): Error for configurationHasChanged when slash was missing in endpoint (#1229)
Co-authored-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>
2025-04-11 15:30:13 +02:00
renovate[bot] ee676d8495
chore(deps): update dependency @smithy/types to v4.2.0 (#1274)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-11 12:46:31 +00:00
renovate[bot] 18a0cb906a
fix(deps): update dependency axios to v1.8.4 (#1273)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-11 12:43:06 +00:00
renovate[bot] d7f8746434
fix(deps): update dependency @flipt-io/flipt-client-js to v0.0.2 (#1272)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-11 06:52:29 +00:00
renovate[bot] fbe9adc832
chore(deps): update libs/shared/flagd-core/flagd-schemas digest to c707f56 (#1271)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-11 02:57:41 +00:00
renovate[bot] 29f8b84685
chore(deps): update libs/providers/flagd/spec digest to 27e4461 (#1270)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-11 02:54:31 +00:00
renovate[bot] ca98f7c6dc
chore(deps): update libs/providers/flagd/schemas digest to c707f56 (#1269)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-10 23:43:45 +00:00
renovate[bot] 241e36edf6
chore(deps): update libs/providers/flagd-web/schemas digest to c707f56 (#1254)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-10 15:16:48 +00:00
renovate[bot] e6d3e9902f
chore(deps): update actions/setup-node digest to cdca736 (#1248)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-10 15:13:43 +00:00
github-actions[bot] 7be62ae45f
chore(main): release flipt-web-provider 0.1.3 (#1253)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-10 17:13:41 +02:00
github-actions[bot] a4fa64ec24
chore(main): release flipt-web-provider 0.1.3 (#1250)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Lukas Reining <lukas.reining@codecentric.de>
2025-04-10 17:10:25 +02:00
github-actions[bot] 9323b14968
chore(main): release flipt-provider 0.1.3 (#1249)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-10 17:09:42 +02:00
Mark Phelps dba2a28001
chore(flipt): swap underlying flipt web sdk (#1244)
Signed-off-by: Mark Phelps <209477+markphelps@users.noreply.github.com>
Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>
Co-authored-by: adams85 <31276480+adams85@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Simon Schrottner <simon.schrottner@dynatrace.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2025-04-10 17:07:05 +02:00
Simon Schrottner 4180281715
chore: update codeownership for global maintainers (#1245)
Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2025-04-10 10:47:06 +02:00
github-actions[bot] e8774fdd8c
chore(main): release config-cat-provider 0.7.5 (#1246)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-09 20:51:01 +02:00
github-actions[bot] 650a598dbd
chore(main): release config-cat-web-provider 0.1.6 (#1247) 2025-04-09 19:57:59 +02:00
adams85 04256197bf
fix(config-cat): Rework error reporting (#1242) 2025-04-09 19:37:35 +02:00
github-actions[bot] a164bcaa2f
chore(main): release aws-ssm-provider 0.1.2 (#1243)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-27 15:41:45 -04:00
Giovanni De Giorgio 043be44de1
feat(aws-ssm): add decryption support for `SecureString` parameters (#1241)
Signed-off-by: Giovanni De Giorgio <giovannidegiorgio1999@gmail.com>
2025-03-27 13:22:52 -04:00
Giovanni De Giorgio ea9e62a8aa
fix(generator): update generated path `project.json`, `jest.config.ts`, `.eslintrc.json` (#1236)
Signed-off-by: Giovanni De Giorgio <giovannidegiorgio1999@gmail.com>
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2025-03-25 13:54:10 -04:00
github-actions[bot] d81ef890f3
chore(main): release aws-ssm-provider 0.1.1 (#1239)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-21 09:38:22 -04:00
Giovanni De Giorgio 819a247c41
feat(aws-ssm): implement AWS SSM provider (#1221)
Signed-off-by: Giovanni De Giorgio <giovannidegiorgio1999@gmail.com>
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2025-03-20 15:47:47 -04:00
Todd Baert 80ec86b938 chore: fix lint
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-03-20 15:18:30 -04:00
github-actions[bot] 7a145e3084
chore(main): release flagd-web-provider 0.7.3 (#998)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-20 14:10:59 -04:00
Giovanni De Giorgio 852257c31b
chore: add `.verdaccio` inside `.gitignore` (#1235)
Signed-off-by: Giovanni De Giorgio <giovannidegiorgio1999@gmail.com>
2025-03-19 08:13:10 -04:00
github-actions[bot] 7c0b6f10b2
chore(main): release flagsmith-client-provider 0.1.3 (#993)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-18 15:55:47 -04:00
Michael Beemer 84abd328a4
chore: add 1.13.0 as a valid grpc-js peer version (#1234)
Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2025-03-17 15:27:03 -04:00
github-actions[bot] 6609f5897c
chore(main): release config-cat-web-provider 0.1.5 (#1228)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-14 20:21:09 +00:00
github-actions[bot] 5394a70adb
chore(main): release config-cat-provider 0.7.4 (#1227)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-14 16:19:08 -04:00
Michael Beemer d8fc42f5d2
chore: bump the required core version
Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2025-03-14 16:17:52 -04:00
Michael Beemer 140839777b
chore: bump the required core version
Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2025-03-14 16:17:30 -04:00
github-actions[bot] 49cdbf7b7c
chore(main): release config-cat-core 0.1.1 (#1144)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-13 13:05:40 -04:00
github-actions[bot] ca4db10189
chore(main): release go-feature-flag-provider 0.7.6 (#1224)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2025-03-12 15:20:04 -04:00
github-actions[bot] 86f8d7cf8c
chore(main): release ofrep-core 1.0.1 (#1223)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2025-03-12 15:12:36 -04:00
renovate[bot] ed504968d9
fix(deps): update dependency axios to v1.8.2 [security] (#1222)
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2025-03-12 15:08:26 -04:00
Todd Baert f5e3f1f3ce
fix: improper inclusion of test deps (#1220)
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-03-12 14:58:49 -04:00
github-actions[bot] b6acc163ed
chore(main): release launchdarkly-client-provider 0.3.2 (#1174)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-12 13:46:05 -04:00
Tommy Josépovic e888d03fca
feat(launchdarkly-client-provider): Add tracking API (#1219)
Signed-off-by: Tommy Josépovic <44372776+tjosepo@users.noreply.github.com>
2025-03-12 11:30:43 -04:00
github-actions[bot] 0a87fd694b
chore(main): release config-cat-provider 0.7.3 (#1171)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Lukas Reining <lukas.reining@codecentric.de>
2025-03-04 11:09:43 +01:00
github-actions[bot] 55115118fc
chore(main): release config-cat-web-provider 0.1.4 (#1175)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-04 10:04:07 +01:00
adams85 9d14173cf0
fix(config-cat): Forward default value to underlying client (#1214)
Signed-off-by: Adam Simon <adam@configcat.com>
Co-authored-by: Adam Simon <adam@configcat.com>
Co-authored-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2025-03-04 10:01:14 +01:00
renovate[bot] 3dd1232330
chore(deps): update dependency testcontainers to v10.18.0 (#1217)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-20 06:27:00 +00:00
renovate[bot] 1fdb6c60a1
chore(deps): update dependency @swc/core to v1.10.18 (#1216)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-20 03:26:31 +00:00
renovate[bot] 8f08c48adf
chore(deps): update libs/providers/flagd/spec digest to a69f748 (#1215)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-19 23:28:54 +00:00
github-actions[bot] 3e1295c33a
chore(main): release ofrep-provider 0.2.1 (#1176)
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2025-02-13 15:31:58 -05:00
github-actions[bot] 3fa006de6e
chore(main): release ofrep-web-provider 0.3.2 (#1123)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-02-13 15:28:57 -05:00
Todd Baert b3abb3551c
chore: update ofrep peer (#1211)
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-02-13 15:25:23 -05:00
github-actions[bot] afa91affc2
chore(main): release ofrep-core 1.0.0 (#1180)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-02-13 15:16:58 -05:00
Todd Baert ce37b6adcd
feat: support metadata in errors in OFREP (#1203)
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-02-13 15:12:11 -05:00
github-actions[bot] 68585ae9d9
chore(main): release flipt-web-provider 0.1.2 (#1181)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-02-11 17:44:13 -05:00
Lucas Botingnon dbd23264e4
fix: Removed incorrect string case format from the options.authentica… (#1209)
Signed-off-by: lucasbotingnon <lucas.botingnon@gmail.com>
2025-02-11 17:40:49 -05:00
github-actions[bot] 38def1d65d
chore(main): release go-feature-flag-provider 0.7.5 (#1204)
Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2025-02-10 09:27:30 +01:00
Michael Beemer 9753545cd9
fix: correct openfeature core peer version
Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2025-02-07 16:49:30 -05:00
renovate[bot] 7af11c3bd4
chore(deps): update libs/providers/flagd/spec digest to 95fe981 (#1205)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-07 21:26:45 +00:00
renovate[bot] 5de7f8e04e
chore(deps): pin dependencies (#1130)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-07 16:23:53 -05:00
github-actions[bot] de63252239
chore(main): release flagd-provider 0.13.3 (#1191)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-02-07 16:20:41 -05:00
faisal.rafi 4e0db2abda
feat: support proxy routing via gRPC default_authority (#1202)
Signed-off-by: raflFaisal <faisal.rafi.mca@gmail.com>
2025-02-07 09:40:41 -05:00
Thomas Poignant 0dfffdc316
feat(go-feature-flag): Support exporter metadata during evaluation (#1186)
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>
2025-02-07 10:36:52 +01:00
Michael Beemer 9de707ac37
chore: add node 22 to test matrix (#1201)
Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2025-02-03 12:43:15 -05:00
Michael Beemer d88c5fe11f
feat(ofrep-core): add abort timeout to fetch call (#1200)
Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2025-02-03 12:38:00 -05:00
renovate[bot] 92c2bbe43f
chore(deps): update dependency testcontainers to v10.17.2 (#1198)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-29 00:30:21 +00:00
renovate[bot] 0f59028bd5
chore(deps): update bufbuild/buf-setup-action action to v1.50.0 (#1196)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-28 21:19:41 +00:00
renovate[bot] 8de214132d
fix(deps): update dependency json-logic-engine to v4.0.6 (#1195)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-28 17:39:03 +00:00
renovate[bot] 5b295d0368
chore(deps): update dependency @swc/core to v1.10.11 (#1194)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-28 13:56:22 +00:00
renovate[bot] 3555c50b51
chore(deps): update libs/shared/flagd-core/test-harness digest to 8931c86 (#1193)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-28 09:43:09 +00:00
renovate[bot] 0a910bc742
chore(deps): update libs/shared/flagd-core/spec digest to 5b07065 (#1192)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-28 09:40:17 +00:00
renovate[bot] 758d2678ff
chore(deps): update libs/shared/flagd-core/flagd-schemas digest to bb76343 (#1190)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-28 01:08:41 +00:00
renovate[bot] 215c983d7c
chore(deps): update libs/providers/flagd/spec digest to 5b07065 (#1189)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-28 01:06:10 +00:00
renovate[bot] 2304efebe4
chore(deps): update libs/providers/flagd/schemas digest to bb76343 (#1168)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-28 01:03:36 +00:00
renovate[bot] cb7bdb12a1
chore(deps): update libs/providers/flagd-web/schemas digest to bb76343 (#1167)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-28 01:00:56 +00:00
github-actions[bot] 95026ae20d
chore(main): release go-feature-flag-web-provider 0.2.5 (#1185)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-01-23 10:42:15 +01:00
Chris Price 928e43764a
fix(go-feature-flag): Support endpoint paths in data collector goff-api.ts (#1184)
Signed-off-by: Chris Price <6090660+crprice@users.noreply.github.com>
2025-01-23 10:37:13 +01:00
github-actions[bot] 62da1cb76a
chore(main): release go-feature-flag-provider 0.7.4 (#1169)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>
2025-01-16 19:33:26 +01:00
github-actions[bot] 6602f52850
chore(main): release go-feature-flag-web-provider 0.2.4 (#1173)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-01-16 18:45:07 +01:00
Thomas Poignant 0edf3f5516
feat(go-feature-flag): Support exporter metadata in web and server providers (#1183)
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>
2025-01-16 18:37:26 +01:00
github-actions[bot] 6d5309b118
chore(main): release unleash-web-provider 0.1.1 (#1166)
Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2025-01-10 12:16:52 -05:00
github-actions[bot] d489924cfa
chore(main): release growthbook-provider 0.1.2 (#1157)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2025-01-10 12:11:46 -05:00
github-actions[bot] d9e84cd244
chore(main): release flagd-provider 0.13.2 (#999)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-01-10 11:52:50 -05:00
Michael Beemer 995b13f368
chore: remove workspace plugin
This plugin is no longer necessary with the latest version of NX. NX verifies dependencies with a lint rule.

Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2025-01-10 11:47:52 -05:00
Todd Baert 7f310fe871
chore: update nx packages (#1147)
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2025-01-10 11:36:17 -05:00
jarebudev 19accf8387
feat: add unleash web provider (#1105)
Signed-off-by: jarebudev <23311805+jarebudev@users.noreply.github.com>
Co-authored-by: Lukas Reining <lukas.reining@codecentric.de>
2025-01-10 10:51:16 -05:00
Michael Beemer b1c6d23556
feat(flagd): add flag metadata (#1151)
Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2025-01-10 10:40:36 -05:00
renovate[bot] 5b2ac49e96
chore(deps): update dependency @swc/cli to ~0.6.0 (#1163)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-09 18:53:49 +00:00
renovate[bot] cd483ea93a
chore(deps): update bufbuild/buf-setup-action action to v1.49.0 (#1162)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-09 13:45:36 +00:00
renovate[bot] 337ae640a7
fix(deps): update dependency json-logic-engine to v4.0.5 (#1161)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-09 11:18:39 +00:00
renovate[bot] 45a9b030e1
chore(deps): update ghcr.io/open-feature/sync-testbed docker tag to v0.5.21 (#1160)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-09 05:45:26 +00:00
renovate[bot] 4e0c9839cc
chore(deps): update ghcr.io/open-feature/flagd-testbed-unstable docker tag to v0.5.21 (#1159)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-09 05:42:43 +00:00
renovate[bot] 5b09d51be2
chore(deps): update dependency typescript to v5.7.3 (#1158)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-09 02:14:27 +00:00
renovate[bot] 1150c9b818
chore(deps): update ghcr.io/open-feature/flagd-testbed docker tag to v0.5.21 (#1155)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-08 23:59:52 +00:00
Michael Samper 0e6a486136
feat: Create a Growthbook server side provider (#938)
Signed-off-by: Michael Samper <msamper@growthbook.io>
2025-01-08 18:57:29 -05:00
Todd Baert b4da066118
chore: fix manifest
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-01-08 15:01:53 -05:00
renovate[bot] 63adf97c0f
chore(deps): update dependency libs/shared/flagd-core/test-harness to v0.5.21 (#1154)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-08 19:06:39 +00:00
renovate[bot] f675664abd
chore(deps): update dependency @types/node to v20.17.12 (#1153)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-08 17:33:11 +00:00
renovate[bot] 40b7b0a4dd
chore(deps): update libs/shared/flagd-core/spec digest to d261f68 (#1152)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-08 15:04:55 +00:00
renovate[bot] 43667107c1
chore(deps): update libs/providers/flagd/spec digest to d261f68 (#1150)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-07 22:10:37 +00:00
renovate[bot] f3c2c8efc3
chore(deps): update libs/providers/flagd/schemas digest to 37baa2c (#1149)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-07 22:08:15 +00:00
renovate[bot] 36ec82a558
chore(deps): update libs/providers/flagd-web/schemas digest to 37baa2c (#1148)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-07 20:42:29 +00:00
renovate[bot] a18550e601
fix(deps): update swc monorepo (#1113)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-07 20:39:30 +00:00
Todd Baert f1a189b87d
chore: remove release-as for flagd core
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-01-07 14:17:03 -05:00
github-actions[bot] 14405a704b
chore(main): release flagd-core 1.0.0 (#1058)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-01-07 13:50:24 -05:00
Michael Beemer f1ed82acba
feat!: improve eval performance, restructure lib, support flag metadata (#1120)
Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2025-01-07 13:42:38 -05:00
renovate[bot] 1f8679e77f
chore(deps): update dependency flagsmith to v4.1.4 (#1124)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-30 18:49:11 +00:00
renovate[bot] d11f30649d
chore(deps): update dependency @openfeature/core to <=1.6.0 (#1126)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-30 18:46:21 +00:00
renovate[bot] a75886f667
fix(deps): update dependency tslib to v2.8.1 (#1099)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-30 16:31:39 +00:00
renovate[bot] 401b3102ff
chore(deps): update ghcr.io/open-feature/sync-testbed docker tag to v0.5.20 (#1139)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-30 13:01:58 +00:00
renovate[bot] d54c9a938c
chore(deps): update ghcr.io/open-feature/flagd-testbed-unstable docker tag to v0.5.20 (#1138)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-30 12:59:18 +00:00
renovate[bot] a0bc98034a
fix(deps): update dependency json-logic-engine to v1.3.9 (#1076)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-30 09:02:01 +00:00
renovate[bot] f5f110c1ac
chore(deps): update ghcr.io/open-feature/flagd-testbed docker tag to v0.5.20 (#1137)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-30 07:38:15 +00:00
renovate[bot] c69590d9c2
chore(deps): update dependency libs/shared/flagd-core/test-harness to v0.5.20 (#1136)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-30 07:35:56 +00:00
renovate[bot] 225a46532b
chore(deps): update libs/shared/flagd-core/spec digest to ed0f9ef (#1135)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-30 07:33:34 +00:00
renovate[bot] 7e40edda9f
chore(deps): update libs/shared/flagd-core/flagd-schemas digest to b81a56e (#1134)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-30 07:31:06 +00:00
renovate[bot] 535aba7d50
fix(deps): update dependency axios to v1.7.9 (#1110)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-30 03:41:27 +00:00
renovate[bot] 938d3a1084
chore(deps): update libs/providers/flagd/spec digest to ed0f9ef (#1133)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-30 01:43:10 +00:00
renovate[bot] cd069da13a
chore(deps): update libs/providers/flagd/schemas digest to b81a56e (#1132)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-30 01:40:33 +00:00
renovate[bot] 828145a89d
chore(deps): update libs/providers/flagd-web/schemas digest to b81a56e (#1131)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-30 01:37:57 +00:00
renovate[bot] f9dbde2e15
chore(deps): update dependency @types/node to v20.17.10 (#1125)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-29 21:20:50 +00:00
renovate[bot] dcd65dd9a6
chore(deps): update bufbuild/buf-setup-action action to v1.48.0 (#1094)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-29 19:54:23 +00:00
Simon Schrottner 232d9dd879
build(renovate): Utilize default OpenFeature Renovate configuration (#1128)
Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>
2024-12-29 14:51:19 -05:00
github-actions[bot] 55f19b5da6
chore(main): release multi-provider-web 0.0.3 (#1121)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-12-24 09:37:34 -05:00
github-actions[bot] 06ffd65fb3
chore(main): release multi-provider 0.1.2 (#1122)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-12-24 09:29:22 -05:00
Todd Baert 456be7c815
chore: update sdks, absorb changes (#1119)
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2024-12-23 10:22:20 -05:00
github-actions[bot] 929ff6de5f
chore(main): release launchdarkly-client-provider 0.3.1 (#935)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-12-17 12:24:49 -05:00
Nicholas Thomson be7851ce59
feat: Accept timeout for LD waitForInitialization (#1117)
Signed-off-by: Nicholas Thomson <RedbackThomson@users.noreply.github.com>
2024-12-17 12:22:41 -05:00
github-actions[bot] e773416a4a
chore(main): release go-feature-flag-web-provider 0.2.3 (#1116)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-12-13 11:55:13 -05:00
Thomas Poignant 2cc7483ea0
fix(go-feature-flag-web): avoid infinite loop in waitWebsocketFinalSt… (#1104)
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>
2024-12-13 17:20:30 +01:00
renovate[bot] 8069e410e9
chore(deps): update dependency typescript to v5.7.2 (#1112)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-09 09:00:22 +00:00
renovate[bot] 79bd064cde
chore(deps): update dependency testcontainers to v10.16.0 (#1111)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-09 03:37:18 +00:00
renovate[bot] 26fdcef8b3
chore(deps): update dependency @types/node to v20.17.9 (#1108)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-09 02:24:01 +00:00
github-actions[bot] 647ec79e0c
chore(main): release go-feature-flag-web-provider 0.2.2 (#1102)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-12-04 09:22:14 +01:00
Thomas Poignant 34fcecd78b
feat(go-feature-flag-web): Add support for data collection (#1101)
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>
2024-12-03 21:32:11 +01:00
github-actions[bot] 775a7c88d2
chore(main): release flipt-web-provider 0.1.1 (#1098)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-12-02 11:11:22 -05:00
Michael Beemer 54e782cdd3
ci: remove release add config from flipt web
Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2024-12-02 11:08:55 -05:00
Simon Schrottner 8a62ea8b17
ci(flagd): remove sync server as it is part of flagd by now (#1100)
Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>
2024-11-26 13:09:52 -05:00
Simon Schrottner 40abd8eca7
chore: various gherkin improvements for e2e tests (#1008)
Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2024-11-06 13:49:22 -05:00
renovate[bot] 9a4395b206
chore(deps): update dependency @types/node to v20.17.6 (#1097)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-05 04:53:28 +00:00
github-actions[bot] d47da7852f
chore(main): release flipt-web-provider 0.1.0 (#1066)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2024-11-04 20:37:40 -05:00
renovate[bot] d08f41dbe2
chore(deps): update dependency @types/node to v20.17.3 (#1092)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-29 22:33:55 +00:00
renovate[bot] 5a5f7629c4
chore(deps): update dependency @types/node to v20.17.2 (#1090)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-29 01:26:27 +00:00
renovate[bot] 1cbb4a861f
chore(deps): update dependency @swc/core to v1.7.40 (#1089)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-26 15:42:27 +00:00
renovate[bot] 27e1194a2d
chore(deps): update dependency @types/node to v20.17.1 (#1088)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-25 17:07:03 +00:00
github-actions[bot] 94057831a4
chore(main): release flipt-provider 0.1.2 (#1087)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-10-23 19:40:04 -04:00
Mark Phelps 9bbd1eb3f0
chore: update flipt-node provider (#1071)
Signed-off-by: Mark Phelps <209477+markphelps@users.noreply.github.com>
2024-10-23 18:00:41 -04:00
renovate[bot] e56f78b07d
chore(deps): update bufbuild/buf-setup-action action to v1.45.0 (#1077)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-23 15:28:09 -04:00
renovate[bot] ca6088b918
chore(deps): update dependency @types/node to v20.17.0 (#1086)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-23 19:05:21 +00:00
renovate[bot] 79477b1aff
chore(deps): update types (#1085)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-23 11:25:09 +00:00
renovate[bot] 1642905456
chore(deps): update dependency @swc/core to v1.7.39 (#1084)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-22 09:45:09 +00:00
renovate[bot] a53a668f8c
chore(deps): update dependency @types/node to v20.16.14 (#1083)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-22 06:31:58 +00:00
renovate[bot] 13a20ae720
chore(deps): update dependency typescript to v5.6.3 (#1079)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-19 10:33:32 +00:00
renovate[bot] 9df06855e3
chore(deps): update dependency @types/node to v20.16.13 (#1080)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-19 07:47:51 +00:00
renovate[bot] fba9db2fbd
chore(deps): update dependency testcontainers to v10.13.2 (#1078)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-19 04:39:02 +00:00
renovate[bot] 6f631464b9
fix(deps): update dependency lru-cache to v11 (#995)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-18 15:35:14 -04:00
renovate[bot] 376749dc19
chore(deps): update swc monorepo (#1009)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-18 15:34:59 -04:00
renovate[bot] 8809afb43d
chore(deps): update dependency @types/node to v20.16.12 (#1072)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-18 15:34:32 -04:00
renovate[bot] d600e72b6f
fix(deps): update dependency axios to v1.7.7 (#1063)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-18 15:33:24 -04:00
renovate[bot] 3a97b36bf7
chore(deps): update ghcr.io/open-feature/sync-testbed-unstable docker tag to v0.5.13 (#1075)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-18 15:24:25 -04:00
renovate[bot] 194bafbabc
chore(deps): update ghcr.io/open-feature/sync-testbed docker tag to v0.5.13 (#1074)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-18 15:20:43 -04:00
renovate[bot] bfadf65eeb
chore(deps): update ghcr.io/open-feature/flagd-testbed-unstable docker tag to v0.5.13 (#1073)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-18 15:18:31 -04:00
renovate[bot] 75c5b10fee
chore(deps): update ghcr.io/open-feature/flagd-testbed docker tag to v0.5.13 (#1068)
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2024-10-18 15:18:04 -04:00
renovate[bot] 6a84d050cc
chore(deps): update dependency axios-mock-adapter to v2 (#1044)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-18 14:41:34 -04:00
renovate[bot] 838fa52fc4
fix(deps): update dependency tslib to v2.8.0 (#927)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-18 14:39:29 -04:00
Mark Phelps 2d2f70d56f
chore: upgrade flipt-client-browser dep (#1070)
Signed-off-by: Mark Phelps <209477+markphelps@users.noreply.github.com>
2024-10-18 12:17:57 -04:00
renovate[bot] 768007f17a
chore(deps): update dependency @types/node to v20.16.11 (#1067)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-16 20:30:26 +00:00
renovate[bot] 1e8eca8409
chore(deps): update dependency @grpc/grpc-js to ~1.8.0 || ~1.9.0 || ~1.10.0 || ~1.11.0 || ~1.12.0 (#1064)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-16 15:18:11 +00:00
github-actions[bot] b0a3619496
chore(main): release flipt-web-provider 0.1.0 (#1018)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2024-10-16 11:14:45 -04:00
renovate[bot] ba90956a00
chore(deps): update types (#1062)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-04 03:09:37 +00:00
renovate[bot] c0d26971ee
chore(deps): update dependency ts-jest to v29.2.5 (#1061)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-04 01:41:58 +00:00
renovate[bot] 66f4805937
chore(deps): update dependency eslint to v8.57.1 (#1060)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-03 22:31:33 +00:00
renovate[bot] 14cc0516d5
chore(deps): update bufbuild/buf-setup-action action to v1.44.0 (#1039)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-03 14:06:42 -04:00
github-actions[bot] e2f0e7b903
chore(main): release growthbook-client-provider 0.1.2 (#1035)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-09-27 21:37:26 +02:00
github-actions[bot] 0267275f56
chore(main): release config-cat-provider 0.7.2 (#1056)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Lukas Reining <lukas.reining@codecentric.de>
2024-09-27 21:34:26 +02:00
github-actions[bot] 974d07a667
chore(main): release config-cat-web-provider 0.1.3 (#1051)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-09-27 21:33:58 +02:00
Michael Beemer 4e21b836b6
fix(flagd-core): use injected logger (#1057)
Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2024-09-20 15:14:17 -04:00
adams85 7e1dd72a14
fix(config-cat): Revise readme (#1054)
Signed-off-by: Adam Simon <adamo@configcat.com>
Signed-off-by: Adam Simon <adam@configcat.com>
Co-authored-by: Adam Simon <adamo@configcat.com>
Co-authored-by: Adam Simon <adam@configcat.com>
2024-09-18 15:09:04 +02:00
Michael 29c3669676
docs: fix README.md for flipt-web provider (#1053)
Signed-off-by: Michael <kidzki@users.noreply.github.com>
2024-09-16 15:04:20 -04:00
github-actions[bot] a4adca4a7c
chore(main): release flipt-provider 0.1.1 (#1047)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-09-04 14:37:56 -04:00
Roman Dmytrenko 28bffa5f91
fix(flipt): return the correct default value when provided in Flipt (#1052)
Signed-off-by: Roman Dmytrenko <rdmytrenko@gmail.com>
2024-09-04 14:31:01 -04:00
adams85 0b6179b9cb
fix(config-cat-web): Fix code examples in README.md (#1050) 2024-09-02 11:26:33 +02:00
github-actions[bot] f992e34a24
chore(main): release flagd-core 0.2.5 (#1049)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-08-30 09:08:33 -04:00
Michael Beemer 7d8264994b
fix(flagd): improve targeting key check in fractional operator (#1048)
Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2024-08-30 08:53:13 -04:00
Giovani Brollo Cunha 00c9e87398
docs: update flipt-io peer dependency (#1046)
Signed-off-by: Giovani Brollo Cunha <giovanibrollo@gmail.com>
2024-08-26 12:09:41 -04:00
renovate[bot] 46a06da4d0
chore(deps): update dependency @types/node to v20.16.1 (#1043)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-22 18:59:53 +00:00
renovate[bot] 46f69d3ccb
fix(deps): update dependency @flipt-io/flipt-client-browser to ^0.0.19 (#1038)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-22 17:55:42 +02:00
renovate[bot] 3a8090e43d
chore(deps): update dependency testcontainers to v10.11.0 (#1040)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-22 12:15:28 +00:00
github-actions[bot] 4bd2c1e19d
chore(main): release config-cat-web-provider 0.1.2 (#1042)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-08-22 14:06:35 +02:00
adams85 55e554d9fc
fix(config-cat-web): Update dependency configcat-js-ssr to v8.4.2 (#1041) 2024-08-22 13:19:22 +02:00
renovate[bot] f2247d3adf
chore(deps): update dependency @grpc/grpc-js to ~1.8.0 || ~1.9.0 || ~1.10.0 || ~1.11.0 (#1023)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-15 09:26:57 -04:00
renovate[bot] 93f602e3c4
fix(deps): update dependency json-logic-engine to v1.3.6 (#1022)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-15 09:26:06 -04:00
renovate[bot] 89c45a348a
chore(deps): update dependency ts-jest to v29.2.4 (#1037)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-14 21:03:29 +00:00
renovate[bot] 1292ba765a
chore(deps): update dependency @types/node to v20.14.15 (#1036)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-14 18:30:05 +00:00
renovate[bot] fa611fd92e
fix(deps): update dependency axios to v1.7.4 (#910)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-14 13:30:18 -04:00
Todd Baert 013251b7f4
fix: missing lodash dep (and tooling fix) (#1034)
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2024-08-14 08:43:32 -04:00
Peter Csajtai 62f7db8742
ci: add adams85 from ConfigCat as component owner (#1033)
Signed-off-by: Peter Csajtai <peter.csajtai@outlook.com>
2024-08-09 13:05:53 +02:00
github-actions[bot] 7ffff2d563
chore(main): release go-feature-flag-web-provider 0.2.1 (#1031)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-08-05 21:38:34 +02:00
Thomas Poignant 31a8adbedb
fix(go-feature-flag-web): Set API Key in headers (#1030)
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>
2024-08-05 21:35:58 +02:00
github-actions[bot] 533e21795f
chore(main): release flagd-core 0.2.4 (#997)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-08-02 17:09:15 -04:00
Michael Beemer 52aebedc00
feat(flagd-core): add bulk evaluation method (#1010)
Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2024-08-02 17:07:37 -04:00
github-actions[bot] a6768a0fad
chore(main): release config-cat-web-provider 0.1.1 (#1016) 2024-07-28 04:29:24 +02:00
github-actions[bot] 88515fcbe5
chore(main): release ofrep-web-provider 0.3.1 (#1028) 2024-07-28 04:28:26 +02:00
Roman Dmytrenko 23fa0a01ae
chore: fix readme examples (#1027) 2024-07-28 04:24:38 +02:00
renovate[bot] 7fbb2f042e
chore(deps): update dependency typescript to v5.5.4 (#1021)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-24 11:02:50 +00:00
renovate[bot] 273b2d3dc3
chore(deps): update dependency ts-jest to v29.2.3 (#1020)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-24 07:37:59 +00:00
renovate[bot] 4ab6cb8880
chore(deps): update dependency testcontainers to v10.10.4 (#1019)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-24 04:50:41 +00:00
renovate[bot] 6566302e2f
chore(deps): update dependency @types/node to v20.14.12 (#1017)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-24 02:02:33 +00:00
github-actions[bot] fc7163b98a
chore(main): release config-cat-provider 0.7.1 (#1015) 2024-07-24 00:12:00 +02:00
renovate[bot] 12184d834f
chore(deps): update bufbuild/buf-setup-action action to v1.35.0 (#912)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-23 14:46:25 -04:00
renovate[bot] 27af7fe2ab
fix(deps): update dependency @flipt-io/flipt-client-browser to ^0.0.18 (#1000)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-23 14:43:36 -04:00
renovate[bot] 3c8000ad16
chore(deps): update dyladan/component-owners digest to 58bd86e (#1004)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-23 14:41:57 -04:00
adams85 3b24653854
docs: A few corrections to ConfigCat providers' README.md (#1014) 2024-07-22 11:48:05 +02:00
Lukas Reining 0d59be0dfd fix: missing comma in release-please manifest
Signed-off-by: Lukas Reining <lukas.reining@codecentric.de>
2024-07-21 02:55:07 +02:00
github-actions[bot] 020b42e466
chore(main): release config-cat-provider 0.7.0 (#1011)
Signed-off-by: Lukas Reining <lukas.reining@codecentric.de>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Lukas Reining <lukas.reining@codecentric.de>
2024-07-21 02:50:02 +02:00
github-actions[bot] c1244c7cd6
chore(main): release config-cat-web-provider 0.1.0 (#1013)
Signed-off-by: Lukas Reining <lukas.reining@codecentric.de>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Lukas Reining <lukas.reining@codecentric.de>
2024-07-21 02:48:54 +02:00
github-actions[bot] fe81b12764
chore(main): release config-cat-core 0.1.0 (#1012)
Signed-off-by: Lukas Reining <lukas.reining@codecentric.de>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Lukas Reining <lukas.reining@codecentric.de>
2024-07-21 02:43:50 +02:00
Lukas Reining e280014f89
feat!: implement config cat web provider (#918)
Signed-off-by: Lukas Reining <lukas.reining@codecentric.de>
Co-authored-by: adams85 <31276480+adams85@users.noreply.github.com>
Co-authored-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2024-07-21 02:34:13 +02:00
github-actions[bot] c1092486b0
chore(main): release ofrep-provider 0.2.0 (#1005)
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2024-07-11 13:13:26 -04:00
github-actions[bot] e882ab3672
chore(main): release ofrep-web-provider 0.3.0 (#1006)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-07-11 13:02:04 -04:00
github-actions[bot] 5495a254de
chore(main): release ofrep-core 0.2.0 (#1007)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-07-11 12:50:29 -04:00
Todd Baert 383f4f310d
feat!: use native headers, optional query params (#1003)
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2024-07-11 12:37:11 -04:00
renovate[bot] 334fd84a8d
chore(deps): update dependency ts-jest to v29.2.2 (#1002)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-10 19:18:08 +00:00
github-actions[bot] 58624decf0
chore(main): release env-var-provider 0.3.1 (#985)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-07-10 21:09:43 +02:00
renovate[bot] 8eee1c3843
chore(deps): update dependency ts-jest to v29.2.1 (#1001)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-10 13:41:41 +00:00
renovate[bot] 070851bdfa
chore(deps): update dependency testcontainers to v10.10.3 (#996)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-09 18:14:01 +00:00
Simon Schrottner 2d6433101b
chore: removing build dependencies and using testcontainers for container spin up (#982)
Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>
Co-authored-by: Lukas Reining <lukas.reining@codecentric.de>
2024-07-09 18:05:47 +02:00
github-actions[bot] 6b09633773
chore(main): release flagd-web-provider 0.7.2 (#975)
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2024-07-08 15:43:30 -04:00
github-actions[bot] 850b82731f
chore(main): release flagd-provider 0.13.1 (#890)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2024-07-08 15:38:12 -04:00
github-actions[bot] 139835e647
chore(main): release ofrep-web-provider 0.2.0 (#992)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2024-07-08 15:33:42 -04:00
renovate[bot] e8e44c3f07
chore(deps): update dependency ts-jest to v29.2.0 (#991)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-08 18:21:16 +00:00
github-actions[bot] 431bf21cce
chore(main): release flagd-core 0.2.3 (#976)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-07-08 14:04:33 -04:00
Todd Baert 95fc808964
chore: update flagd json schema (#994)
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2024-07-08 13:59:17 -04:00
Rodrigo López Dato 1585b60672
docs(flagsmith): Show example traits and targeting key (#989)
Signed-off-by: Rodrigo López Dato <rodrigo.lopezdato@flagsmith.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2024-07-08 12:40:45 -04:00
Todd Baert deb67c15d0
feat!: resolver signatures changes for easier overrides, cache exposure (#990)
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2024-07-08 12:15:20 -04:00
renovate[bot] 6311bab791
chore(deps): update dependency @swc/core to v1.6.13 (#988)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-06 23:10:12 +00:00
renovate[bot] 8b7f14b709
chore(deps): update dependency @types/node to v20.14.10 (#987)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-06 02:42:21 +00:00
Michael Beemer 919761d892
fix(env-var): set runs on property to server (#981)
Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2024-07-04 14:40:35 -04:00
Todd Baert 6a54935f3b
chore: fix fractional tests (#984)
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2024-07-04 07:52:17 -04:00
renovate[bot] 925a20c98b
chore(deps): update dependency @swc/core to v1.6.7 (#983)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-03 19:14:07 +00:00
renovate[bot] 58f61fe52a
chore(deps): update swc monorepo (#979)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-03 01:07:44 +00:00
renovate[bot] 0e65ffa713
chore(deps): update dependency typescript to v5.5.3 (#978)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-02 21:32:55 +00:00
Todd Baert 29a673553f
chore: fix e2e test (#977)
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2024-07-02 12:20:12 -04:00
Simon Schrottner 0e9bc842cf
feat: Change fractional custom op from percentage-based to relative weighting. #946 (#954)
Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>
2024-07-02 08:56:03 -04:00
github-actions[bot] 4c017d5a3e
chore(main): release go-feature-flag-provider 0.7.3 (#974)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-06-29 11:00:14 +02:00
Thomas Poignant e17b99ec73
fix(gofeatureflag): Error when receiving an empty string errorCode (#973)
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>
2024-06-29 10:59:08 +02:00
renovate[bot] 6dcbdf7b5d
chore(deps): update dependency @swc-node/register to ~1.10.0 (#967)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-28 11:11:59 +00:00
github-actions[bot] d08f8d1f95
chore(main): release multi-provider-web 0.0.2 (#966)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-06-27 11:32:58 -04:00
Michael Beemer 6f61f71c27
chore: configure release please for the multi provider web (#965)
Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2024-06-27 11:27:57 -04:00
renovate[bot] 6d3f0d6943
fix(deps): update dependency json-logic-engine to v1.3.4 (#930)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-27 07:44:11 -04:00
renovate[bot] 8cf99eb2ae
chore(deps): update dyladan/component-owners digest to a0a1a67 (#931)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-27 07:40:38 -04:00
github-actions[bot] 1801837ea8
chore(main): release go-feature-flag-provider 0.7.2 (#963)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-06-27 09:00:45 +02:00
Thomas Poignant e448577b6b
fix(gofeatureflag): remove NodeJS.timeout because it fails eslint (#961) 2024-06-27 05:19:27 +00:00
emmawillis b7c4419999
fix: correct multi-provider-web dependency (#962)
Signed-off-by: Emma Willis <ehenriks@uwo.ca>
2024-06-26 22:25:37 +02:00
github-actions[bot] 89bdf64930
chore(main): release go-feature-flag-provider 0.7.1 (#934)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-06-26 09:32:51 +02:00
Thomas Poignant 338123fadb
feat(gofeatureflag): Clear cache if configuration changes + provider refactoring (#947)
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>
2024-06-26 09:12:43 +02:00
renovate[bot] 52d8445e28
chore(deps): update dependency @types/node to v20.14.9 (#960)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-26 00:03:20 +00:00
renovate[bot] 95bfd1adfd
chore(deps): update dependency @types/node to v20.14.8 (#959)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-22 13:14:28 +00:00
renovate[bot] 8c86268307
chore(deps): update dependency @swc/core to v1.6.5 (#958)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-22 06:03:31 +00:00
renovate[bot] ce0012bad9
chore(deps): update dependency @types/node to v20.14.7 (#957)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-21 05:22:03 +00:00
renovate[bot] aeb8be26db
chore(deps): update dependency typescript to v5.5.2 (#956)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-20 21:06:06 +00:00
renovate[bot] 19a8549127
chore(deps): update dependency @types/node to v20.14.6 (#955)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-19 19:18:06 +00:00
renovate[bot] 232e24524b
chore(deps): update dependency @swc/core to v1.6.3 (#953)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-19 11:57:45 +00:00
github-actions[bot] 0a4a8df83a
chore(main): release ofrep-web-provider 0.1.5 (#952)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-06-18 15:54:01 -04:00
Todd Baert 896406afbf
fix: update ofrep-code
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2024-06-18 15:49:25 -04:00
367 changed files with 24852 additions and 7875 deletions

View File

@ -2,14 +2,19 @@
"root": true,
"ignorePatterns": ["**/*"],
"plugins": ["@nx"],
"extends": [
"eslint:recommended",
"plugin:prettier/recommended"
],
"extends": ["eslint:recommended", "plugin:prettier/recommended"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {
"@typescript-eslint/consistent-type-imports": [
"error",
{
"disallowTypeAnnotations": true,
"fixStyle": "separate-type-imports",
"prefer": "type-imports"
}
],
"@nx/enforce-module-boundaries": [
"error",
{
@ -28,17 +33,41 @@
{
"files": ["*.ts", "*.tsx"],
"extends": ["plugin:@nx/typescript"],
"rules": {}
"rules": {
"@typescript-eslint/no-extra-semi": "error",
"no-extra-semi": "off"
}
},
{
"files": ["*.js", "*.jsx"],
"extends": ["plugin:@nx/javascript"],
"rules": {}
"rules": {
"@typescript-eslint/no-extra-semi": "error",
"no-extra-semi": "off"
}
},
{
"files": "*.json",
"parser": "jsonc-eslint-parser",
"rules": {}
},
{
"files": ["*.json"],
"parser": "jsonc-eslint-parser",
"rules": {
"@nx/dependency-checks": [
"error",
{
"buildTargets": ["lint"],
"includeTransitiveDependencies": false,
"checkMissingDependencies": true,
"checkObsoleteDependencies": true,
"checkVersionMismatches": true,
"ignoredDependencies": ["jest-cucumber", "jest"],
"ignoredFiles": ["**/test/**", "**/tests/*", "**/spec/**", "**/*.spec.ts", "**/*.spec.js", "**/*.test.ts", "**/*.test.js", "**/jest.*"]
}
]
}
}
]
}

View File

@ -3,8 +3,14 @@ components:
libs/hooks/open-telemetry:
- beeme1mr
- toddbaert
libs/providers/aws-ssm:
- gdegiorgio
libs/providers/config-cat:
- lukas-reining
- adams85
libs/providers/config-cat-web:
- lukas-reining
- adams85
libs/providers/env-var:
- beeme1mr
- toddbaert
@ -26,6 +32,8 @@ components:
- markphelps
libs/providers/flipt-web:
- markphelps
libs/providers/unleash-web:
- jarebudev
ignored-authors:
- renovate-bot

View File

@ -14,69 +14,42 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x, 20.x]
node-version: [20.x, 22.x, 24.x]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
fetch-depth: 0
submodules: recursive
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: ${{ matrix.node-version }}
- uses: bufbuild/buf-setup-action@v1.32.0
with:
github_token: ${{ github.token }}
- run: npm ci
- uses: nrwl/nx-set-shas@v3
# This line is needed for nx affected to work when CI is running on a PR
- run: git branch --track main origin/main || true
- run: npx nx workspace-lint
- run: if ! npx nx format:check ; then echo "Format check failed. Please run 'npx nx format:write'."; fi
- run: npx nx affected --target=lint --parallel=3
- run: npx nx affected --target=test --parallel=3 --ci --code-coverage
- run: npx nx affected --target=build --parallel=3
- run: npx nx affected --target=lint --parallel=3 --exclude=js-sdk-contrib
- run: npx nx affected --target=test --parallel=3 --ci --code-coverage --exclude=js-sdk-contrib
- run: npx nx affected --target=build --parallel=3 --exclude=js-sdk-contrib
e2e:
runs-on: ubuntu-latest
services:
flagd:
image: ghcr.io/open-feature/flagd-testbed:v0.5.4
ports:
- 8013:8013
flagd-unstable:
image: ghcr.io/open-feature/flagd-testbed-unstable:v0.5.4
ports:
- 8014:8013
sync:
image: ghcr.io/open-feature/sync-testbed:v0.5.4
ports:
- 9090:9090
sync-unstable:
image: ghcr.io/open-feature/sync-testbed-unstable:v0.5.4
ports:
- 9091:9090
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
fetch-depth: 0
submodules: recursive
- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
# we need 'fetch' for this test, which is only in 18+
node-version: 18
node-version: 20
cache: 'npm'
- name: Install
run: npm ci
- uses: bufbuild/buf-setup-action@v1.32.0
with:
github_token: ${{ github.token }}
- name: e2e
run: npm run e2e

View File

@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
name: Auto Assign Owners
steps:
- uses: dyladan/component-owners@cdaadffde64c918909ee081e3fe044b8910f56c2
- uses: dyladan/component-owners@58bd86e9814d23f1525d0a970682cead459fa783
with:
config-file: .github/component_owners.yml
repo-token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -12,12 +12,12 @@ jobs:
name: Validate PR title
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v5
- uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5
id: lint_pr_title
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: marocchino/sticky-pull-request-comment@v2
- uses: marocchino/sticky-pull-request-comment@52423e01640425a022ef5fd42c6fb5f633a02728 # v2
# When the previous steps fails, the workflow would stop. By adding this
# condition you can continue the execution with the populated error message.
if: always() && (steps.lint_pr_title.outputs.error_message != null)
@ -34,7 +34,7 @@ jobs:
```
# Delete a previous comment when the issue has been resolved
- if: ${{ steps.lint_pr_title.outputs.error_message == null }}
uses: marocchino/sticky-pull-request-comment@v2
uses: marocchino/sticky-pull-request-comment@52423e01640425a022ef5fd42c6fb5f633a02728 # v2
with:
header: pr-title-lint-error
delete: true

View File

@ -5,11 +5,12 @@ on:
name: Run Release Please
jobs:
release-please:
environment: publish
runs-on: ubuntu-latest
# Release-please creates a PR that tracks all changes
steps:
- uses: google-github-actions/release-please-action@v3
- uses: google-github-actions/release-please-action@db8f2c60ee802b3748b512940dde88eabd7b7e01 # v3
id: release
with:
command: manifest
@ -19,19 +20,19 @@ jobs:
# The logic below handles the npm publication:
- name: Checkout Repository
if: ${{ steps.release.outputs.releases_created }}
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
fetch-depth: 0
submodules: recursive
- uses: bufbuild/buf-setup-action@v1.32.0
- uses: bufbuild/buf-setup-action@a47c93e0b1648d5651a065437926377d060baa99 # v1.50.0
with:
github_token: ${{ github.token }}
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
if: ${{ steps.release.outputs.releases_created }}
with:
node-version: 18
node-version: 20
registry-url: "https://registry.npmjs.org"
- name: Build Packages
if: ${{ steps.release.outputs.releases_created }}

4
.gitignore vendored
View File

@ -46,3 +46,7 @@ proto
# yalc stuff
.yalc
yalc.lock
# Generated by @nx/js
.verdaccio

21
.gitmodules vendored
View File

@ -1,18 +1,19 @@
[submodule "libs/providers/flagd/schemas"]
path = libs/providers/flagd/schemas
url = https://github.com/open-feature/schemas.git
url = https://github.com/open-feature/flagd-schemas.git
[submodule "libs/providers/flagd-web/schemas"]
path = libs/providers/flagd-web/schemas
url = https://github.com/open-feature/schemas
url = https://github.com/open-feature/flagd-schemas.git
[submodule "libs/providers/flagd/spec"]
path = libs/providers/flagd/spec
url = https://github.com/open-feature/spec.git
[submodule "libs/providers/flagd-web/spec"]
path = libs/providers/flagd-web/spec
url = https://github.com/open-feature/spec.git
path = libs/providers/flagd/spec
url = https://github.com/open-feature/spec.git
[submodule "libs/shared/flagd-core/flagd-schemas"]
path = libs/shared/flagd-core/flagd-schemas
url = https://github.com/open-feature/flagd-schemas.git
[submodule "libs/providers/flagd/flagd-testbed"]
path = libs/providers/flagd/flagd-testbed
url = https://github.com/open-feature/flagd-testbed.git
[submodule "libs/shared/flagd-core/test-harness"]
path = libs/shared/flagd-core/test-harness
url = https://github.com/open-feature/flagd-testbed
branch = v2.8.0
[submodule "libs/shared/flagd-core/spec"]
path = libs/shared/flagd-core/spec
url = https://github.com/open-feature/spec

View File

@ -2,3 +2,6 @@
/dist
/coverage
/.nx/cache
/.nx/workspace-data

View File

@ -1,19 +1,25 @@
{
"libs/hooks/open-telemetry": "0.4.0",
"libs/providers/go-feature-flag": "0.7.0",
"libs/providers/flagd": "0.13.0",
"libs/providers/flagd-web": "0.7.1",
"libs/providers/env-var": "0.3.0",
"libs/providers/config-cat": "0.6.1",
"libs/providers/launchdarkly-client": "0.3.0",
"libs/providers/go-feature-flag-web": "0.2.0",
"libs/shared/flagd-core": "0.2.2",
"libs/shared/ofrep-core": "0.1.5",
"libs/providers/ofrep": "0.1.3",
"libs/providers/ofrep-web": "0.1.4",
"libs/providers/flipt": "0.1.0",
"libs/providers/flagsmith-client": "0.1.2",
"libs/providers/flipt-web": "0.1.0",
"libs/providers/multi-provider": "0.1.1",
"libs/providers/growthbook-client": "0.1.1"
"libs/providers/go-feature-flag": "0.7.8",
"libs/providers/flagd": "0.13.3",
"libs/providers/flagd-web": "0.7.3",
"libs/providers/env-var": "0.3.1",
"libs/providers/config-cat": "0.7.6",
"libs/providers/launchdarkly-client": "0.3.2",
"libs/providers/go-feature-flag-web": "0.2.6",
"libs/shared/flagd-core": "1.1.0",
"libs/shared/ofrep-core": "1.0.1",
"libs/providers/ofrep": "0.2.1",
"libs/providers/ofrep-web": "0.3.3",
"libs/providers/flipt": "0.1.3",
"libs/providers/flagsmith-client": "0.1.3",
"libs/providers/flipt-web": "0.1.5",
"libs/providers/multi-provider": "0.1.2",
"libs/providers/multi-provider-web": "0.0.3",
"libs/providers/growthbook-client": "0.1.2",
"libs/providers/config-cat-web": "0.1.6",
"libs/shared/config-cat-core": "0.1.1",
"libs/providers/unleash-web": "0.1.1",
"libs/providers/growthbook": "0.1.2",
"libs/providers/aws-ssm": "0.1.3"
}

View File

@ -3,4 +3,4 @@
#
# Managed by Peribolos: https://github.com/open-feature/community/blob/main/config/open-feature/sdk-javascript/workgroup.yaml
#
* @open-feature/sdk-javascript-maintainers
* @open-feature/sdk-javascript-maintainers @open-feature/maintainers

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

BIN
assets/aws-ssm/search.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
assets/aws-ssm/ssm-menu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -1,5 +1,5 @@
import { getJestProjects } from '@nx/jest';
import { getJestProjectsAsync } from '@nx/jest';
export default {
projects: getJestProjects(),
};
export default async () => ({
projects: await getJestProjectsAsync(),
});

View File

@ -1,3 +1,3 @@
{
"presets": [["minify", { "builtIns": false }]]
}
}

View File

@ -1,6 +1,7 @@
{
"name": "@openfeature/open-telemetry-hooks",
"version": "0.4.0",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/open-feature/js-sdk-contrib.git",
@ -16,6 +17,5 @@
"peerDependencies": {
"@openfeature/server-sdk": "^1.13.0",
"@opentelemetry/api": ">=1.3.0"
},
"license": "Apache-2.0"
}
}
}

View File

@ -6,9 +6,7 @@
"targets": {
"package": {
"executor": "@nx/rollup:rollup",
"outputs": [
"{options.outputPath}"
],
"outputs": ["{options.outputPath}"],
"options": {
"project": "libs/hooks/open-telemetry/package.json",
"outputPath": "dist/libs/hooks/open-telemetry",
@ -18,10 +16,7 @@
"generateExportsField": true,
"umdName": "OpenTelemetry",
"external": "all",
"format": [
"cjs",
"esm"
],
"format": ["cjs", "esm"],
"assets": [
{
"glob": "package.json",
@ -38,23 +33,17 @@
"input": "./libs/hooks/open-telemetry",
"output": "./"
}
],
"updateBuildableProjectDepsInPackageJson": true
]
}
},
"build": {
"executor": "@nx/js:tsc",
"outputs": [
"{options.outputPath}"
],
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/hooks/open-telemetry",
"main": "libs/hooks/open-telemetry/src/index.ts",
"tsConfig": "libs/hooks/open-telemetry/tsconfig.lib.json",
"assets": [
"libs/hooks/open-telemetry/*.md"
],
"updateBuildableProjectDepsInPackageJson": true
"assets": ["libs/hooks/open-telemetry/*.md"]
}
},
"publish": {
@ -70,24 +59,14 @@
]
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": [
"{options.outputFile}"
],
"options": {
"lintFilePatterns": [
"libs/hooks/open-telemetry/**/*.ts"
]
}
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"]
},
"test": {
"executor": "@nx/jest:jest",
"outputs": [
"{workspaceRoot}/coverage/libs/hooks/open-telemetry"
],
"outputs": ["{workspaceRoot}/coverage/libs/hooks/open-telemetry"],
"options": {
"jestConfig": "libs/hooks/open-telemetry/jest.config.ts",
"passWithNoTests": true,
"codeCoverage": true,
"coverageDirectory": "coverage/libs/hooks/open-telemetry"
}

View File

@ -1,6 +1,8 @@
import { BeforeHookContext, EvaluationDetails, HookContext, StandardResolutionReasons } from '@openfeature/server-sdk';
import type { BeforeHookContext, EvaluationDetails, HookContext } from '@openfeature/server-sdk';
import { StandardResolutionReasons } from '@openfeature/server-sdk';
import opentelemetry from '@opentelemetry/api';
import { DataPoint, MeterProvider, MetricReader, ScopeMetrics } from '@opentelemetry/sdk-metrics';
import type { DataPoint, ScopeMetrics } from '@opentelemetry/sdk-metrics';
import { MeterProvider, MetricReader } from '@opentelemetry/sdk-metrics';
import {
ACTIVE_COUNT_NAME,
ERROR_TOTAL_NAME,
@ -12,7 +14,7 @@ import {
VARIANT_ATTR,
} from '../conventions';
import { MetricsHook } from './metrics-hook';
import { AttributeMapper } from '../otel-hook';
import type { AttributeMapper } from '../otel-hook';
// no-op "in-memory" reader
class InMemoryMetricReader extends MetricReader {

View File

@ -1,19 +1,18 @@
import type { BeforeHookContext, Logger } from '@openfeature/server-sdk';
import {
BeforeHookContext,
Logger,
StandardResolutionReasons,
type EvaluationDetails,
type FlagValue,
type Hook,
type HookContext,
} from '@openfeature/server-sdk';
import { Attributes, Counter, UpDownCounter, ValueType, metrics } from '@opentelemetry/api';
import type { Attributes, Counter, UpDownCounter } from '@opentelemetry/api';
import { ValueType, metrics } from '@opentelemetry/api';
import type { EvaluationAttributes, ExceptionAttributes } from '../conventions';
import {
ACTIVE_COUNT_NAME,
ERROR_TOTAL_NAME,
EXCEPTION_ATTR,
EvaluationAttributes,
ExceptionAttributes,
KEY_ATTR,
PROVIDER_NAME_ATTR,
REASON_ATTR,
@ -21,7 +20,8 @@ import {
SUCCESS_TOTAL_NAME,
VARIANT_ATTR,
} from '../conventions';
import { OpenTelemetryHook, OpenTelemetryHookOptions } from '../otel-hook';
import type { OpenTelemetryHookOptions } from '../otel-hook';
import { OpenTelemetryHook } from '../otel-hook';
type ErrorEvaluationAttributes = EvaluationAttributes & ExceptionAttributes;

View File

@ -1,5 +1,5 @@
import { FlagMetadata, Logger } from '@openfeature/server-sdk';
import { Attributes } from '@opentelemetry/api';
import type { FlagMetadata, Logger } from '@openfeature/server-sdk';
import type { Attributes } from '@opentelemetry/api';
export type AttributeMapper = (flagMetadata: FlagMetadata) => Attributes;

View File

@ -1,4 +1,4 @@
import { EvaluationDetails, HookContext } from '@openfeature/server-sdk';
import type { EvaluationDetails, HookContext } from '@openfeature/server-sdk';
const addEvent = jest.fn();
const recordException = jest.fn();

View File

@ -1,7 +1,8 @@
import { Hook, HookContext, EvaluationDetails, FlagValue, Logger } from '@openfeature/server-sdk';
import type { Hook, HookContext, EvaluationDetails, FlagValue, Logger } from '@openfeature/server-sdk';
import { trace } from '@opentelemetry/api';
import { FEATURE_FLAG, KEY_ATTR, PROVIDER_NAME_ATTR, VARIANT_ATTR } from '../conventions';
import { OpenTelemetryHook, OpenTelemetryHookOptions } from '../otel-hook';
import type { OpenTelemetryHookOptions } from '../otel-hook';
import { OpenTelemetryHook } from '../otel-hook';
export type TracingHookOptions = OpenTelemetryHookOptions;

View File

@ -0,0 +1,30 @@
{
"extends": ["../../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.json"],
"parser": "jsonc-eslint-parser",
"rules": {
"@nx/dependency-checks": [
"error",
{
"ignoredFiles": ["{projectRoot}/eslint.config.{js,cjs,mjs}"]
}
]
}
}
]
}

View File

@ -0,0 +1,23 @@
# Changelog
## [0.1.3](https://github.com/open-feature/js-sdk-contrib/compare/aws-ssm-provider-v0.1.2...aws-ssm-provider-v0.1.3) (2025-06-04)
### 🐛 Bug Fixes
* **deps:** update dependency @aws-sdk/client-ssm to v3.787.0 ([#1278](https://github.com/open-feature/js-sdk-contrib/issues/1278)) ([afae82c](https://github.com/open-feature/js-sdk-contrib/commit/afae82c1a1472d33b884105edaac2976c19e7423))
* **deps:** update dependency lru-cache to v11.1.0 ([#1279](https://github.com/open-feature/js-sdk-contrib/issues/1279)) ([a80f5ce](https://github.com/open-feature/js-sdk-contrib/commit/a80f5ce3d7a6e74e762a75ba8fa9f5b70ca2a179))
## [0.1.2](https://github.com/open-feature/js-sdk-contrib/compare/aws-ssm-provider-v0.1.1...aws-ssm-provider-v0.1.2) (2025-03-27)
### ✨ New Features
* **aws-ssm:** add decryption support for `SecureString` parameters ([#1241](https://github.com/open-feature/js-sdk-contrib/issues/1241)) ([043be44](https://github.com/open-feature/js-sdk-contrib/commit/043be44de1442b89876e9857478afe619fcf0b04))
## [0.1.1](https://github.com/open-feature/js-sdk-contrib/compare/aws-ssm-provider-v0.1.0...aws-ssm-provider-v0.1.1) (2025-03-20)
### ✨ New Features
* **aws-ssm:** implement AWS SSM provider ([#1221](https://github.com/open-feature/js-sdk-contrib/issues/1221)) ([819a247](https://github.com/open-feature/js-sdk-contrib/commit/819a247c41112c2873aa025ac0abd3c62eb53aca))

View File

@ -0,0 +1,73 @@
# AWS SSM Provider
## What is AWS SSM?
AWS Systems Manager (SSM) is a service provided by Amazon Web Services (AWS) that enables users to manage and automate operational tasks across their AWS infrastructure. One of its key components is AWS Systems Manager Parameter Store, which allows users to store, retrieve, and manage configuration data and secrets securely.
SSM Parameter Store can be used to manage application configuration settings, database connection strings, API keys, and other sensitive information. It provides integration with AWS Identity and Access Management (IAM) to control access and encryption through AWS Key Management Service (KMS).
The aws-ssm provider for OpenFeature allows applications to fetch feature flag configurations from AWS SSM Parameter Store, enabling centralized and dynamic configuration management.
## Installation
```
$ npm install @openfeature/aws-ssm-provider
```
## Set AWS Provider
```
OpenFeature.setProvider(
new AwsSsmProvider({
ssmClientConfig: {
region: 'eu-west-1', // Change this to your desired AWS region
// You can setup your aws credentials here or it will be automatically retrieved from env vars
// See https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-credentials-node.html
},
// Use an LRUCache for improve performance and optimize AWS SDK Calls to SSM (cost awareness)
cacheOpts: {
enabled: true, // Enable caching
size: 1, // Cache size
ttl: 10, // Time-to-live in seconds
},
})
);
```
# AWS SSM Provider Configuration
## AwsSsmProviderConfig
| Property | Type | Description | Default |
|-----------------|--------------------|----------------------------------------------|---------|
| `ssmClientConfig` | `SSMClientConfig` | AWS SSM Client configuration options. | See [here](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/ssm/) |
| `enableDecryption` | `boolean` | Enable decryption for SecureString parameters | false |
| `cacheOpts` | `LRUCacheConfig` | Configuration for the local LRU cache. | See below |
## LRUCacheConfig
| Property | Type | Description | Default |
|-----------|--------|------------------------------------------------|---------|
| `enabled` | `boolean` | Whether caching is enabled. | `false` |
| `ttl` | `number` | Time-to-live (TTL) for cached items (in ms). | `300000` (5 minutes) |
| `size` | `number` | Maximum number of items in the cache. | `1000` |
## Retrieve Feature Flag!
Open your AWS Management Console and go to AWS System Manager service
![SSM-Menu](../../../assets/aws-ssm/search.png)
Go to Parameter Store
![Parameter-Store](../../../assets/aws-ssm/ssm-menu.png)
Create a new SSM Param called 'my-feature-flag' in your AWS Account and then retrieve it via OpenFeature Client!
![Create-Param](../../../assets/aws-ssm/create-param.png)
```
const featureFlags = OpenFeature.getClient();
const flagValue = await featureFlags.getBooleanValue('my-feature-flag', false);
console.log(`Feature flag value: ${flagValue}`);
```

View File

@ -0,0 +1,3 @@
{
"presets": [["minify", { "builtIns": false }]]
}

View File

@ -0,0 +1,16 @@
/* eslint-disable */
export default {
displayName: 'aws-ssm',
preset: '../../../jest.preset.js',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
},
},
transform: {
'^.+\\.[tj]s$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../../coverage/libs/providers/aws-ssm',
testEnvironment: 'node',
};

1514
libs/providers/aws-ssm/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
{
"name": "@openfeature/aws-ssm-provider",
"version": "0.1.3",
"dependencies": {
"@aws-sdk/client-ssm": "^3.759.0",
"lru-cache": "^11.0.2",
"tslib": "^2.3.0"
},
"main": "./src/index.js",
"typings": "./src/index.d.ts",
"scripts": {
"publish-if-not-exists": "cp $NPM_CONFIG_USERCONFIG .npmrc && if [ \"$(npm show $npm_package_name@$npm_package_version version)\" = \"$(npm run current-version -s)\" ]; then echo 'already published, skipping'; else npm publish --access public; fi",
"current-version": "echo $npm_package_version"
},
"license": "Apache-2.0",
"peerDependencies": {
"@openfeature/server-sdk": "^1.17.0"
},
"devDependencies": {
"@smithy/types": "^4.1.0",
"aws-sdk-client-mock": "^4.1.0"
}
}

View File

@ -0,0 +1,64 @@
{
"name": "providers-aws-ssm",
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/providers/aws-ssm/src",
"projectType": "library",
"targets": {
"publish": {
"executor": "nx:run-commands",
"options": {
"command": "npm run publish-if-not-exists",
"cwd": "dist/libs/providers/aws-ssm"
},
"dependsOn": [
{
"target": "package"
}
]
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"]
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/providers/aws-ssm/jest.config.ts"
}
},
"package": {
"executor": "@nx/rollup:rollup",
"outputs": ["{options.outputPath}"],
"options": {
"project": "libs/providers/aws-ssm/package.json",
"outputPath": "dist/libs/providers/aws-ssm",
"entryFile": "libs/providers/aws-ssm/src/index.ts",
"tsConfig": "libs/providers/aws-ssm/tsconfig.lib.json",
"compiler": "tsc",
"generateExportsField": true,
"umdName": "aws-ssm",
"external": "all",
"format": ["cjs", "esm"],
"assets": [
{
"glob": "package.json",
"input": "./assets",
"output": "./src/"
},
{
"glob": "LICENSE",
"input": "./",
"output": "./"
},
{
"glob": "README.md",
"input": "./libs/providers/aws-ssm",
"output": "./"
}
]
}
}
},
"tags": []
}

View File

@ -0,0 +1 @@
export * from './lib/aws-ssm-provider';

View File

@ -0,0 +1,86 @@
import { OpenFeature } from '@openfeature/server-sdk';
import { AwsSsmProvider } from '../lib/aws-ssm-provider';
import type { GetParameterCommandOutput } from '@aws-sdk/client-ssm';
import { GetParameterCommand, SSMClient } from '@aws-sdk/client-ssm';
import { mockClient } from 'aws-sdk-client-mock';
const ssmMock = mockClient(SSMClient);
describe('AWS SSM Provider E2E', () => {
const featureFlags = OpenFeature.getClient();
OpenFeature.setProvider(
new AwsSsmProvider({
ssmClientConfig: {
region: 'eu-west-1',
},
cacheOpts: {
enabled: true,
size: 1,
ttl: 10,
},
}),
);
describe('when using OpenFeature with AWS SSM Provider to retrieve a boolean', () => {
it('should use AWS SSM in order to retrieve the value', async () => {
const res: GetParameterCommandOutput = {
Parameter: {
Name: '/lambda/loggingEnabled',
Value: 'true',
},
$metadata: {},
};
ssmMock.on(GetParameterCommand).resolves(res);
const flagValue = await featureFlags.getBooleanValue('/lambda/loggingEnabled', false);
expect(flagValue).toBe(true);
});
});
describe('when using OpenFeature with AWS SSM Provider to retrieve a string', () => {
it('should use AWS SSM in order to retrieve the value', async () => {
const res: GetParameterCommandOutput = {
Parameter: {
Name: '/lambda/logLevel',
Value: 'ERROR',
},
$metadata: {},
};
ssmMock.on(GetParameterCommand).resolves(res);
const flagValue = await featureFlags.getStringValue('/lambda/logLevel', 'INFO');
expect(flagValue).toBe('ERROR');
});
});
describe('when using OpenFeature with AWS SSM Provider to retrieve a number', () => {
it('should use AWS SSM in order to retrieve the value', async () => {
const res: GetParameterCommandOutput = {
Parameter: {
Name: '/lambda/logRetentionInDays',
Value: '3',
},
$metadata: {},
};
ssmMock.on(GetParameterCommand).resolves(res);
const flagValue = await featureFlags.getNumberValue('/lambda/logRetentionInDays', 14);
expect(flagValue).toBe(3);
});
});
describe('when using OpenFeature with AWS SSM Provider to retrieve an object', () => {
it('should use AWS SSM in order to retrieve the value', async () => {
const res: GetParameterCommandOutput = {
Parameter: {
Name: '/lambda/env',
Value: JSON.stringify({
PROCESS_NUMBER: 3,
SOME_ENV_VAR: 4,
}),
},
$metadata: {},
};
ssmMock.on(GetParameterCommand).resolves(res);
const flagValue = await featureFlags.getObjectValue('/lambda/env', {});
expect(flagValue).toStrictEqual({
PROCESS_NUMBER: 3,
SOME_ENV_VAR: 4,
});
});
});
});

View File

@ -0,0 +1,203 @@
import type { SSMClientConfig } from '@aws-sdk/client-ssm';
import { AwsSsmProvider } from './aws-ssm-provider';
import { ErrorCode, StandardResolutionReasons } from '@openfeature/core';
const MOCK_SSM_CLIENT_CONFIG: SSMClientConfig = {
region: 'us-east-1',
credentials: {
accessKeyId: 'accessKeyId',
secretAccessKey: 'secretAccessKey',
},
};
const provider: AwsSsmProvider = new AwsSsmProvider({
ssmClientConfig: MOCK_SSM_CLIENT_CONFIG,
cacheOpts: {
enabled: true,
ttl: 1000,
size: 100,
},
});
describe(AwsSsmProvider.name, () => {
describe(AwsSsmProvider.prototype.resolveBooleanEvaluation.name, () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('when flag is cached', () => {
afterAll(() => {
provider.cache.clear();
});
it('should return cached value', async () => {
provider.cache.set('test', {
value: true,
reason: StandardResolutionReasons.STATIC,
});
await expect(provider.resolveBooleanEvaluation('test', false, {})).resolves.toEqual({
value: true,
reason: StandardResolutionReasons.CACHED,
});
});
});
describe('when flag is not cached', () => {
describe('when getBooleanValue rejects', () => {
it('should return default value', async () => {
jest.spyOn(provider.service, 'getBooleanValue').mockRejectedValue(new Error());
await expect(provider.resolveBooleanEvaluation('test', false, {})).resolves.toEqual({
value: false,
reason: StandardResolutionReasons.ERROR,
errorMessage: 'An unknown error occurred',
errorCode: ErrorCode.GENERAL,
});
});
});
describe('when getBooleanValue resolves', () => {
it('should resolve with expected value', async () => {
jest.spyOn(provider.service, 'getBooleanValue').mockResolvedValue({
value: true,
reason: StandardResolutionReasons.STATIC,
});
await expect(provider.resolveBooleanEvaluation('test', false, {})).resolves.toEqual({
value: true,
reason: StandardResolutionReasons.STATIC,
});
});
});
});
});
describe(AwsSsmProvider.prototype.resolveStringEvaluation.name, () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('when flag is cached', () => {
afterAll(() => {
provider.cache.clear();
});
it('should return cached value', async () => {
provider.cache.set('test', {
value: 'somestring',
reason: StandardResolutionReasons.STATIC,
});
await expect(provider.resolveStringEvaluation('test', 'default', {})).resolves.toEqual({
value: 'somestring',
reason: StandardResolutionReasons.CACHED,
});
});
});
describe('when flag is not cached', () => {
describe('when getStringValue rejects', () => {
it('should return default value', async () => {
jest.spyOn(provider.service, 'getStringValue').mockRejectedValue(new Error());
await expect(provider.resolveStringEvaluation('test', 'default', {})).resolves.toEqual({
value: 'default',
reason: StandardResolutionReasons.ERROR,
errorMessage: 'An unknown error occurred',
errorCode: ErrorCode.GENERAL,
});
});
});
describe('when getStringValue resolves', () => {
it('should resolve with expected value', async () => {
jest.spyOn(provider.service, 'getStringValue').mockResolvedValue({
value: 'somestring',
reason: StandardResolutionReasons.STATIC,
});
await expect(provider.resolveStringEvaluation('test', 'default', {})).resolves.toEqual({
value: 'somestring',
reason: StandardResolutionReasons.STATIC,
});
});
});
});
});
describe(AwsSsmProvider.prototype.resolveNumberEvaluation.name, () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('when flag is cached', () => {
afterAll(() => {
provider.cache.clear();
});
it('should return cached value', async () => {
provider.cache.set('test', {
value: 489,
reason: StandardResolutionReasons.STATIC,
});
await expect(provider.resolveNumberEvaluation('test', -1, {})).resolves.toEqual({
value: 489,
reason: StandardResolutionReasons.CACHED,
});
});
});
describe('when flag is not cached', () => {
describe('when getNumberValue rejects', () => {
it('should return default value', async () => {
jest.spyOn(provider.service, 'getNumberValue').mockRejectedValue(new Error());
await expect(provider.resolveNumberEvaluation('test', -1, {})).resolves.toEqual({
value: -1,
reason: StandardResolutionReasons.ERROR,
errorMessage: 'An unknown error occurred',
errorCode: ErrorCode.GENERAL,
});
});
});
describe('when getNumberValue resolves', () => {
it('should resolve with expected value', async () => {
jest.spyOn(provider.service, 'getNumberValue').mockResolvedValue({
value: 489,
reason: StandardResolutionReasons.STATIC,
});
await expect(provider.resolveNumberEvaluation('test', -1, {})).resolves.toEqual({
value: 489,
reason: StandardResolutionReasons.STATIC,
});
});
});
});
});
describe(AwsSsmProvider.prototype.resolveObjectEvaluation.name, () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('when flag is cached', () => {
afterAll(() => {
provider.cache.clear();
});
it('should return cached value', async () => {
provider.cache.set('test', {
value: { default: false },
reason: StandardResolutionReasons.STATIC,
});
await expect(provider.resolveObjectEvaluation('test', { default: true }, {})).resolves.toEqual({
value: { default: false },
reason: StandardResolutionReasons.CACHED,
});
});
});
describe('when flag is not cached', () => {
describe('when getObjectValue rejects', () => {
it('should return default value', async () => {
jest.spyOn(provider.service, 'getObjectValue').mockRejectedValue(new Error());
await expect(provider.resolveObjectEvaluation('test', { default: true }, {})).resolves.toEqual({
value: { default: true },
reason: StandardResolutionReasons.ERROR,
errorMessage: 'An unknown error occurred',
errorCode: ErrorCode.GENERAL,
});
});
});
describe('when getObjectValue resolves', () => {
it('should resolve with expected value', async () => {
jest.spyOn(provider.service, 'getObjectValue').mockResolvedValue({
value: { default: true },
reason: StandardResolutionReasons.STATIC,
});
await expect(provider.resolveObjectEvaluation('test', -1, {})).resolves.toEqual({
value: { default: true },
reason: StandardResolutionReasons.STATIC,
});
});
});
});
});
});

View File

@ -0,0 +1,146 @@
import type { EvaluationContext, Provider, JsonValue, ResolutionDetails } from '@openfeature/server-sdk';
import { StandardResolutionReasons, ErrorCode } from '@openfeature/server-sdk';
import { InternalServerError } from '@aws-sdk/client-ssm';
import type { AwsSsmProviderConfig } from './types';
import { SSMService } from './ssm-service';
import { Cache } from './cache';
export class AwsSsmProvider implements Provider {
metadata = {
name: AwsSsmProvider.name,
};
readonly runsOn = 'server';
readonly service: SSMService;
hooks = [];
cache: Cache;
constructor(config: AwsSsmProviderConfig) {
this.service = new SSMService(config.ssmClientConfig, config.enableDecryption);
this.cache = new Cache(config.cacheOpts);
}
async resolveBooleanEvaluation(
flagKey: string,
defaultValue: boolean,
context: EvaluationContext,
): Promise<ResolutionDetails<boolean>> {
const cachedValue = this.cache.get(flagKey);
if (cachedValue) {
return {
value: cachedValue.value,
reason: StandardResolutionReasons.CACHED,
};
}
try {
const res = await this.service.getBooleanValue(flagKey);
this.cache.set(flagKey, res);
return res;
} catch (err) {
let errMsg = 'An unknown error occurred';
if (err instanceof InternalServerError) {
errMsg = err.message;
}
return {
value: defaultValue,
reason: StandardResolutionReasons.ERROR,
errorCode: ErrorCode.GENERAL,
errorMessage: errMsg,
};
}
}
async resolveStringEvaluation(
flagKey: string,
defaultValue: string,
context: EvaluationContext,
): Promise<ResolutionDetails<string>> {
const cachedValue = this.cache.get(flagKey);
if (cachedValue) {
return {
value: cachedValue.value,
reason: StandardResolutionReasons.CACHED,
};
}
try {
const res = await this.service.getStringValue(flagKey);
this.cache.set(flagKey, res);
return res;
} catch (err) {
let errMsg = 'An unknown error occurred';
if (err instanceof InternalServerError) {
errMsg = err.message;
}
return {
value: defaultValue,
reason: StandardResolutionReasons.ERROR,
errorCode: ErrorCode.GENERAL,
errorMessage: errMsg,
};
}
}
async resolveNumberEvaluation(
flagKey: string,
defaultValue: number,
context: EvaluationContext,
): Promise<ResolutionDetails<number>> {
const cachedValue = this.cache.get(flagKey);
if (cachedValue) {
return {
value: cachedValue.value,
reason: StandardResolutionReasons.CACHED,
};
}
try {
return await this.service.getNumberValue(flagKey);
} catch (err) {
let errMsg = 'An unknown error occurred';
if (err instanceof InternalServerError) {
errMsg = err.message;
}
return {
value: defaultValue,
reason: StandardResolutionReasons.ERROR,
errorCode: ErrorCode.GENERAL,
errorMessage: errMsg,
};
}
}
async resolveObjectEvaluation<U extends JsonValue>(
flagKey: string,
defaultValue: U,
context: EvaluationContext,
): Promise<ResolutionDetails<U>> {
const cachedValue = this.cache.get(flagKey);
if (cachedValue) {
return {
value: cachedValue.value,
reason: StandardResolutionReasons.CACHED,
};
}
try {
return await this.service.getObjectValue(flagKey);
} catch (err) {
let errMsg = 'An unknown error occurred';
if (err instanceof InternalServerError) {
errMsg = err.message;
}
return {
value: defaultValue,
reason: StandardResolutionReasons.ERROR,
errorCode: ErrorCode.GENERAL,
errorMessage: errMsg,
};
}
}
}

View File

@ -0,0 +1,59 @@
import { Cache } from './cache';
describe(Cache.name, () => {
describe(Cache.prototype.get.name, () => {
describe('when cache is disabled', () => {
it('should return undefined', () => {
const cache = new Cache({ enabled: false, size: 1, ttl: 1 });
expect(cache.get('test')).toBeUndefined();
});
});
describe('when cache is enabled', () => {
describe('when key is not in cache', () => {
it('should return undefined', () => {
const cache = new Cache({ enabled: true, size: 1, ttl: 1 });
expect(cache.get('test')).toBeUndefined();
});
});
describe('when key is in cache', () => {
it('should return the value', () => {
const cache = new Cache({ enabled: true, size: 1, ttl: 1 });
cache.set('test', { value: true, reason: 'test' });
expect(cache.get('test')).toEqual({ value: true, reason: 'test' });
});
});
});
});
describe(Cache.prototype.set.name, () => {
describe('when cache is disabled', () => {
it('should not set the value', () => {
const spy = jest.spyOn(Cache.prototype, 'set');
expect(spy).not.toHaveBeenCalled();
});
});
describe('when cache is enabled', () => {
it('should set the value', () => {
const cache = new Cache({ enabled: true, size: 1, ttl: 1 });
cache.set('test', { value: true, reason: 'test' });
expect(cache.get('test')).toEqual({ value: true, reason: 'test' });
});
});
});
describe(Cache.prototype.clear.name, () => {
describe('when cache is disabled', () => {
it('should not clear the cache', () => {
const spy = jest.spyOn(Cache.prototype, 'clear');
expect(spy).not.toHaveBeenCalled();
});
});
describe('when cache is enabled', () => {
it('should clear the cache', () => {
const cache = new Cache({ enabled: true, size: 1, ttl: 1 });
cache.set('test', { value: true, reason: 'test' });
cache.clear();
expect(cache.get('test')).toBeUndefined();
});
});
});
});

View File

@ -0,0 +1,38 @@
import type { ResolutionDetails } from '@openfeature/core';
import type { LRUCacheConfig } from './types';
import { LRUCache } from 'lru-cache';
export class Cache {
private cache: LRUCache<string, ResolutionDetails<any>>;
private ttl: number;
private enabled: boolean;
constructor(opts: LRUCacheConfig) {
this.cache = new LRUCache({
maxSize: opts.size ?? 1000,
sizeCalculation: () => 1,
});
this.ttl = opts.ttl ?? 300000;
this.enabled = opts.enabled;
}
get(key: string): ResolutionDetails<any> | undefined {
if (!this.enabled) {
return undefined;
}
return this.cache.get(key);
}
set(key: string, value: ResolutionDetails<any>): void {
if (!this.enabled) {
return;
}
this.cache.set(key, value, { ttl: this.ttl });
}
clear() {
if (!this.enabled) {
return;
}
this.cache.clear();
}
}

View File

@ -0,0 +1,122 @@
import { ParseError, StandardResolutionReasons, TypeMismatchError } from '@openfeature/core';
import { SSMService } from './ssm-service';
describe(SSMService.name, () => {
describe(SSMService.prototype.getBooleanValue.name, () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe(`when _getParamFromSSM returns "true"`, () => {
it(`should return a ResolutionDetails with value true`, async () => {
jest
.spyOn(SSMService.prototype, '_getValueFromSSM')
.mockResolvedValue({ val: 'true', metadata: { httpStatusCode: 200 } });
const service = new SSMService({});
const result = await service.getBooleanValue('test');
expect(result).toEqual({
value: true,
reason: StandardResolutionReasons.STATIC,
flagMetadata: { httpStatusCode: 200 },
});
});
});
describe(`when _getParamFromSSM returns "false"`, () => {
it(`should return a ResolutionDetails with value true`, async () => {
jest
.spyOn(SSMService.prototype, '_getValueFromSSM')
.mockResolvedValue({ val: 'false', metadata: { httpStatusCode: 200 } });
const service = new SSMService({});
const result = await service.getBooleanValue('test');
expect(result).toEqual({
value: false,
reason: StandardResolutionReasons.STATIC,
flagMetadata: { httpStatusCode: 200 },
});
});
});
describe(`when _getParamFromSSM returns an invalid value`, () => {
it('should throw a TypeMismatchError', () => {
jest
.spyOn(SSMService.prototype, '_getValueFromSSM')
.mockResolvedValue({ val: 'invalid boolean', metadata: { httpStatusCode: 400 } });
const service = new SSMService({});
expect(() => service.getBooleanValue('test')).rejects.toThrow(TypeMismatchError);
});
});
});
describe(SSMService.prototype.getStringValue.name, () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe(`when _getParamFromSSM returns a valid value`, () => {
it(`should return a ResolutionDetails with that value`, async () => {
jest
.spyOn(SSMService.prototype, '_getValueFromSSM')
.mockResolvedValue({ val: 'example', metadata: { httpStatusCode: 200 } });
const service = new SSMService({});
const result = await service.getStringValue('example');
expect(result).toEqual({
value: 'example',
reason: StandardResolutionReasons.STATIC,
flagMetadata: { httpStatusCode: 200 },
});
});
});
});
describe(SSMService.prototype.getNumberValue.name, () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe(`when _getParamFromSSM returns a valid number`, () => {
it(`should return a ResolutionDetails with value true`, async () => {
jest
.spyOn(SSMService.prototype, '_getValueFromSSM')
.mockResolvedValue({ val: '1478', metadata: { httpStatusCode: 200 } });
const service = new SSMService({});
const result = await service.getNumberValue('test');
expect(result).toEqual({
value: 1478,
reason: StandardResolutionReasons.STATIC,
flagMetadata: { httpStatusCode: 200 },
});
});
});
describe(`when _getParamFromSSM returns a value that is not a number`, () => {
it(`should return a TypeMismatchError`, async () => {
jest
.spyOn(SSMService.prototype, '_getValueFromSSM')
.mockResolvedValue({ val: 'invalid number', metadata: { httpStatusCode: 400 } });
const service = new SSMService({});
expect(() => service.getNumberValue('test')).rejects.toThrow(TypeMismatchError);
});
});
});
describe(SSMService.prototype.getObjectValue.name, () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe(`when _getParamFromSSM returns a valid object`, () => {
it(`should return a ResolutionDetails with that object`, async () => {
jest
.spyOn(SSMService.prototype, '_getValueFromSSM')
.mockResolvedValue({ val: JSON.stringify({ test: true }), metadata: { httpStatusCode: 400 } });
const service = new SSMService({});
const result = await service.getObjectValue('test');
expect(result).toEqual({
value: { test: true },
reason: StandardResolutionReasons.STATIC,
flagMetadata: { httpStatusCode: 400 },
});
});
});
describe(`when _getParamFromSSM returns an invalid object`, () => {
it(`should return a ParseError`, async () => {
jest
.spyOn(SSMService.prototype, '_getValueFromSSM')
.mockResolvedValue({ val: 'invalid object', metadata: { httpStatusCode: 400 } });
const service = new SSMService({});
expect(() => service.getObjectValue('test')).rejects.toThrow(ParseError);
});
});
});
});

View File

@ -0,0 +1,118 @@
import type { SSMClientConfig, GetParameterCommandInput } from '@aws-sdk/client-ssm';
import { GetParameterCommand, SSMClient, DescribeParametersCommand } from '@aws-sdk/client-ssm';
import type { ResponseMetadata } from '@smithy/types';
import type { JsonValue, ResolutionDetails } from '@openfeature/core';
import { FlagNotFoundError, TypeMismatchError, ParseError, StandardResolutionReasons } from '@openfeature/core';
export class SSMService {
client: SSMClient;
enableDecryption: boolean;
constructor(config: SSMClientConfig, enableDecryption?: boolean) {
this.client = new SSMClient(config);
this.enableDecryption = enableDecryption ?? false;
}
async getBooleanValue(name: string): Promise<ResolutionDetails<boolean>> {
const res = await this._getValueFromSSM(name);
const { val, metadata } = res;
let result: boolean;
switch (val) {
case 'true':
result = true;
break;
case 'false':
result = false;
break;
default:
throw new TypeMismatchError(`${val} is not a valid boolean value`);
}
return {
value: result,
reason: StandardResolutionReasons.STATIC,
flagMetadata: { ...metadata },
};
}
async getStringValue(name: string): Promise<ResolutionDetails<string>> {
const res = await this._getValueFromSSM(name);
const { val, metadata } = res;
return {
value: val,
reason: StandardResolutionReasons.STATIC,
flagMetadata: { ...metadata },
};
}
async getNumberValue(name: string): Promise<ResolutionDetails<number>> {
const res = await this._getValueFromSSM(name);
const { val, metadata } = res;
if (Number.isNaN(Number(val))) {
throw new TypeMismatchError(`${val} is not a number`);
}
return {
value: Number(val),
reason: StandardResolutionReasons.STATIC,
flagMetadata: { ...metadata },
};
}
async getObjectValue<U extends JsonValue>(name: string): Promise<ResolutionDetails<U>> {
const res = await this._getValueFromSSM(name);
const { val, metadata } = res;
try {
return {
value: JSON.parse(val),
reason: StandardResolutionReasons.STATIC,
flagMetadata: { ...metadata },
};
} catch (e) {
throw new ParseError(`Unable to parse value as JSON: ${e}`);
}
}
async _isSecureString(name: string): Promise<boolean> {
const res = await this.client.send(
new DescribeParametersCommand({
ParameterFilters: [
{
Key: 'Name',
Values: [name],
},
],
}),
);
if (!res.Parameters) {
throw new FlagNotFoundError(`Unable to find an SSM Parameter with key ${name}`);
}
return res.Parameters[0].Type === 'SecureString';
}
async _getValueFromSSM(name: string): Promise<{ val: string; metadata: ResponseMetadata }> {
const param: GetParameterCommandInput = {
Name: name,
};
if (this.enableDecryption) {
param.WithDecryption = await this._isSecureString(name);
}
const command: GetParameterCommand = new GetParameterCommand(param);
const res = await this.client.send(command);
if (!res.Parameter) {
throw new FlagNotFoundError(`Unable to find an SSM Parameter with key ${name}`);
}
if (!res.Parameter.Value) {
throw new ParseError(`Value is empty`);
}
return { val: res.Parameter.Value, metadata: res.$metadata };
}
}

View File

@ -0,0 +1,13 @@
import type { SSMClientConfig } from '@aws-sdk/client-ssm';
export type AwsSsmProviderConfig = {
ssmClientConfig: SSMClientConfig;
cacheOpts: LRUCacheConfig;
enableDecryption?: boolean;
};
export type LRUCacheConfig = {
enabled: boolean;
ttl?: number;
size?: number;
};

View File

@ -0,0 +1,22 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"module": "ES6",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noPropertyAccessFromIndexSignature": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

View File

@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../dist/out-tsc",
"declaration": true,
"types": ["node"]
},
"include": ["src/**/*.ts"],
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"]
}

View File

@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../dist/out-tsc",
"module": "commonjs",
"moduleResolution": "node10",
"types": ["jest", "node"]
},
"include": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts"]
}

View File

@ -0,0 +1,18 @@
{
"extends": ["../../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

View File

@ -0,0 +1,60 @@
# Changelog
## [0.1.6](https://github.com/open-feature/js-sdk-contrib/compare/config-cat-web-provider-v0.1.5...config-cat-web-provider-v0.1.6) (2025-04-09)
### 🐛 Bug Fixes
* **config-cat:** Rework error reporting ([#1242](https://github.com/open-feature/js-sdk-contrib/issues/1242)) ([0425619](https://github.com/open-feature/js-sdk-contrib/commit/04256197bf6e7da70afd4ac1c31bdaf55ce4b789))
## [0.1.5](https://github.com/open-feature/js-sdk-contrib/compare/config-cat-web-provider-v0.1.4...config-cat-web-provider-v0.1.5) (2025-03-14)
### 🧹 Chore
* bump the required core version ([1408397](https://github.com/open-feature/js-sdk-contrib/commit/140839777b5cff8e624b23fc9eb2f8d2f4a977cb))
## [0.1.4](https://github.com/open-feature/js-sdk-contrib/compare/config-cat-web-provider-v0.1.3...config-cat-web-provider-v0.1.4) (2025-03-04)
### 🐛 Bug Fixes
* **config-cat:** Forward default value to underlying client ([#1214](https://github.com/open-feature/js-sdk-contrib/issues/1214)) ([9d14173](https://github.com/open-feature/js-sdk-contrib/commit/9d14173cf08da3030fc58fea8786b24bafd80403))
### 🧹 Chore
* update nx packages ([#1147](https://github.com/open-feature/js-sdk-contrib/issues/1147)) ([7f310fe](https://github.com/open-feature/js-sdk-contrib/commit/7f310fe87101b8aa793e1436e63c7602ccc202e3))
## [0.1.3](https://github.com/open-feature/js-sdk-contrib/compare/config-cat-web-provider-v0.1.2...config-cat-web-provider-v0.1.3) (2024-09-20)
### 🐛 Bug Fixes
* **config-cat-web:** Fix code examples in README.md ([#1050](https://github.com/open-feature/js-sdk-contrib/issues/1050)) ([0b6179b](https://github.com/open-feature/js-sdk-contrib/commit/0b6179b9cb16cce592be6c2fbe86dbacce5adc1f))
* **config-cat:** Revise readme ([#1054](https://github.com/open-feature/js-sdk-contrib/issues/1054)) ([7e1dd72](https://github.com/open-feature/js-sdk-contrib/commit/7e1dd72a1450a9982b340afda62d34379d1b3f16))
## [0.1.2](https://github.com/open-feature/js-sdk-contrib/compare/config-cat-web-provider-v0.1.1...config-cat-web-provider-v0.1.2) (2024-08-22)
### 🐛 Bug Fixes
* **config-cat-web:** Update dependency configcat-js-ssr to v8.4.2 ([#1041](https://github.com/open-feature/js-sdk-contrib/issues/1041)) ([55e554d](https://github.com/open-feature/js-sdk-contrib/commit/55e554d9fc9966d7d2b364da4776c478a2ba9bb1))
## [0.1.1](https://github.com/open-feature/js-sdk-contrib/compare/config-cat-web-provider-v0.1.0...config-cat-web-provider-v0.1.1) (2024-07-28)
### 📚 Documentation
* A few corrections to ConfigCat providers' README.md ([#1014](https://github.com/open-feature/js-sdk-contrib/issues/1014)) ([3b24653](https://github.com/open-feature/js-sdk-contrib/commit/3b24653854643c827bddccb12aeb59e61204202d))
## 0.1.0 (2024-07-21)
### ⚠ BREAKING CHANGES
* implement config cat web provider ([#918](https://github.com/open-feature/js-sdk-contrib/issues/918))
### ✨ New Features
* implement config cat web provider ([#918](https://github.com/open-feature/js-sdk-contrib/issues/918)) ([e280014](https://github.com/open-feature/js-sdk-contrib/commit/e280014f8998dd2e5f2b7700f0d24842eeafab5f))

View File

@ -0,0 +1,160 @@
# ConfigCat Web Provider
This is an OpenFeature provider implementation for using [ConfigCat](https://configcat.com), a managed feature flag service in JavaScript frontend applications.
## Installation
```
$ npm install @openfeature/config-cat-web-provider
```
#### Required peer dependencies
The OpenFeature SDK is required as peer dependency.
The minimum required version of `@openfeature/web-sdk` currently is `1.0.0`.
The minimum required version of `configcat-js-ssr` currently is `8.4.3`.
```
$ npm install @openfeature/web-sdk configcat-js-ssr
```
## Usage
The ConfigCat provider uses the [ConfigCat JavaScript SSR SDK](https://configcat.com/docs/sdk-reference/js-ssr/).
It can be created by passing the ConfigCat SDK options to ```ConfigCatWebProvider.create```.
The available options can be found in the [ConfigCat JavaScript SSR SDK](https://configcat.com/docs/sdk-reference/js-ssr/#creating-the-configcat-client).
The ConfigCat Web Provider only supports the `AutoPolling` mode because it caches all evaluation data to support synchronous evaluation of feature flags.
### Example using the default configuration
```javascript
import { OpenFeature } from "@openfeature/web-sdk";
import { ConfigCatWebProvider } from '@openfeature/config-cat-web-provider';
// Create and set the provider.
const provider = ConfigCatWebProvider.create('<sdk_key>');
await OpenFeature.setProviderAndWait(provider);
// Create a client instance to evaluate feature flags.
const client = OpenFeature.getClient();
const value = await client.getBooleanValue('isAwesomeFeatureEnabled', false);
console.log(`isAwesomeFeatureEnabled: ${value}`);
// On application shutdown, clean up the OpenFeature provider and the underlying ConfigCat client.
await OpenFeature.clearProviders();
```
### Example using custom configuration
```javascript
import { OpenFeature } from "@openfeature/web-sdk";
import { ConfigCatWebProvider } from '@openfeature/config-cat-web-provider';
import { createConsoleLogger, LogLevel } from 'configcat-js-ssr';
// Create and set the provider.
const provider = ConfigCatWebProvider.create('<sdk_key>', {
logger: createConsoleLogger(LogLevel.Info),
setupHooks: (hooks) => hooks.on('clientReady', () => console.log('Client is ready!')),
});
await OpenFeature.setProviderAndWait(provider);
// ...
```
## Evaluation Context
The OpenFeature Evaluation Context is mapped to the [ConfigCat User Object](https://configcat.com/docs/advanced/user-object/).
The [ConfigCat User Object](https://configcat.com/docs/advanced/user-object/) has three predefined attributes,
and allows for additional attributes.
The following shows how the attributes are mapped:
| OpenFeature EvaluationContext Field | ConfigCat User Field | Required |
|-------------------------------------|----------------------|----------|
| targetingKey | identifier | yes |
| email | email | no |
| country | country | no |
| _Any Other_ | custom | no |
The custom types are mapped the following way:
| OpenFeature EvaluationContext Field Type | ConfigCat User Field Type |
|------------------------------------------|---------------------------|
| string | string |
| number | number |
| boolean | string |
| Array<string> | Array<string> |
| Array | Array |
| object | string |
The following example shows the conversion between an OpenFeature Evaluation Context and the corresponding ConfigCat
User:
#### OpenFeature
```json
{
"targetingKey": "test",
"email": "email",
"country": "country",
"customString": "customString",
"customNumber": 1,
"customBoolean": true,
"customObject": {
"prop1": "1",
"prop2": 2
},
"customStringArray": [
"one",
"two"
],
"customArray": [
1,
"2",
false
]
}
```
#### ConfigCat
```json
{
"identifier": "test",
"email": "email",
"country": "country",
"custom": {
"customString": "customString",
"customBoolean": "true",
"customNumber": 1,
"customObject": "{\"prop1\":\"1\",\"prop2\":2}",
"customStringArray": [
"one",
"two"
],
"customArray": "[1,\"2\",false]"
}
}
```
## Events
The ConfigCat provider emits the
following [OpenFeature events](https://openfeature.dev/specification/types#provider-events):
- PROVIDER_READY
- PROVIDER_ERROR
- PROVIDER_CONFIGURATION_CHANGED
## Building
Run `nx package providers-config-cat-web` to build the library.
## Running unit tests
Run `nx test providers-config-cat-web` to execute the unit tests via [Jest](https://jestjs.io).

View File

@ -0,0 +1,3 @@
{
"presets": [["minify", { "builtIns": false }]]
}

View File

@ -0,0 +1,10 @@
/* eslint-disable */
export default {
displayName: 'providers-config-cat-web',
preset: '../../../jest.preset.js',
transform: {
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../../coverage/libs/providers/config-cat',
};

View File

@ -0,0 +1,156 @@
{
"name": "@openfeature/config-cat-web-provider",
"version": "0.1.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@openfeature/config-cat-web-provider",
"version": "0.1.6",
"peerDependencies": {
"@openfeature/web-sdk": "^1.0.0",
"configcat-js-ssr": "^8.4.3"
}
},
"node_modules/@openfeature/core": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@openfeature/core/-/core-1.2.0.tgz",
"integrity": "sha512-JyIiije5f+8Big1xz7UAmxqVmHBuFUI9Dh8DEFG2D1ocgjMm1tEzYXJDr3urCQGNnX9M/cYtNhEcGfyontIgJw==",
"peer": true
},
"node_modules/@openfeature/web-sdk": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@openfeature/web-sdk/-/web-sdk-1.1.0.tgz",
"integrity": "sha512-qfJBWVN0AzYGoZZUE4w4LrQc3Oq3MWaUys+bkBjkgyFFDJM4TrgRz+wz/f3TwRVKj2Bc0EZ0ouyfupdWjR7bsQ==",
"peer": true,
"peerDependencies": {
"@openfeature/core": "1.2.0"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"peer": true
},
"node_modules/axios": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz",
"integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
"peer": true,
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"peer": true,
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/configcat-common": {
"version": "9.3.1",
"resolved": "https://registry.npmjs.org/configcat-common/-/configcat-common-9.3.1.tgz",
"integrity": "sha512-yVkIbluksD/kZfVyKjLIOpwLrq3/ZRM7Lwrsz89JmbpQ6VtbnelrTQynSPElTtKjrPRZx56v3IZYk3nWTnnM6A==",
"peer": true,
"dependencies": {
"tslib": "^2.4.1"
}
},
"node_modules/configcat-js-ssr": {
"version": "8.4.3",
"resolved": "https://registry.npmjs.org/configcat-js-ssr/-/configcat-js-ssr-8.4.3.tgz",
"integrity": "sha512-9tNM61cgJOE9C1MO8wBK1QglrnlT8VpiAW/KgGdFdOuIPs3ky62EThgAE+HYSRYEv4JrRNB4i7G0v1Qgbf18Hw==",
"peer": true,
"dependencies": {
"axios": "^1.7.4",
"configcat-common": "9.3.1",
"tslib": "^2.4.1"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"peer": true,
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/follow-redirects": {
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"peer": true,
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"peer": true,
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"peer": true,
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"peer": true,
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"peer": true
},
"node_modules/tslib": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
"peer": true
}
}
}

View File

@ -0,0 +1,14 @@
{
"name": "@openfeature/config-cat-web-provider",
"version": "0.1.6",
"license": "Apache-2.0",
"scripts": {
"publish-if-not-exists": "cp $NPM_CONFIG_USERCONFIG .npmrc && if [ \"$(npm show $npm_package_name@$npm_package_version version)\" = \"$(npm run current-version -s)\" ]; then echo 'already published, skipping'; else npm publish --access public; fi",
"current-version": "echo $npm_package_version"
},
"peerDependencies": {
"@openfeature/web-sdk": "^1.0.0",
"configcat-js-ssr": "^8.4.3",
"@openfeature/config-cat-core": "0.1.1"
}
}

View File

@ -0,0 +1,64 @@
{
"name": "providers-config-cat-web",
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/providers/config-cat-web/src",
"projectType": "library",
"targets": {
"publish": {
"executor": "nx:run-commands",
"options": {
"command": "npm run publish-if-not-exists",
"cwd": "dist/libs/providers/config-cat-web"
},
"dependsOn": [
{
"target": "package"
}
]
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"]
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/providers/config-cat-web/jest.config.ts"
}
},
"package": {
"executor": "@nx/rollup:rollup",
"outputs": ["{options.outputPath}"],
"options": {
"project": "libs/providers/config-cat-web/package.json",
"outputPath": "dist/libs/providers/config-cat-web",
"entryFile": "libs/providers/config-cat-web/src/index.ts",
"tsConfig": "libs/providers/config-cat-web/tsconfig.lib.json",
"compiler": "tsc",
"generateExportsField": true,
"umdName": "config-cat",
"external": "all",
"format": ["cjs", "esm"],
"assets": [
{
"glob": "package.json",
"input": "./assets",
"output": "./src/"
},
{
"glob": "LICENSE",
"input": "./",
"output": "./"
},
{
"glob": "README.md",
"input": "./libs/providers/config-cat-web",
"output": "./"
}
]
}
}
},
"tags": []
}

View File

@ -0,0 +1 @@
export * from './lib/config-cat-web-provider';

View File

@ -0,0 +1,191 @@
import { ConfigCatWebProvider } from './config-cat-web-provider';
import type { HookEvents, IConfigCatCache, ISettingUnion } from 'configcat-js-ssr';
import { createConsoleLogger, createFlagOverridesFromMap, LogLevel, OverrideBehaviour } from 'configcat-js-ssr';
import type { EventEmitter } from 'events';
import { ProviderEvents, ParseError, FlagNotFoundError, TypeMismatchError } from '@openfeature/web-sdk';
describe('ConfigCatWebProvider', () => {
const targetingKey = 'abc';
let provider: ConfigCatWebProvider;
let configCatEmitter: EventEmitter<HookEvents>;
const values = {
booleanFalse: false,
booleanTrue: true,
number1: 1,
number2: 2,
stringTest: 'Test',
jsonValid: JSON.stringify({ valid: true }),
jsonInvalid: '{test:123',
jsonPrimitive: JSON.stringify(123),
};
beforeAll(async () => {
provider = ConfigCatWebProvider.create('__key__', {
logger: createConsoleLogger(LogLevel.Off),
offline: true,
flagOverrides: createFlagOverridesFromMap(values, OverrideBehaviour.LocalOnly),
});
await provider.initialize();
// Currently there is no option to get access to the event emitter
// eslint-disable-next-line @typescript-eslint/no-explicit-any
configCatEmitter = (provider.configCatClient as any).options.hooks;
});
afterAll(async () => {
await provider.onClose();
});
it('should be an instance of ConfigCatWebProvider', () => {
expect(provider).toBeInstanceOf(ConfigCatWebProvider);
});
it('should dispose the configcat client on provider closing', async () => {
const newProvider = ConfigCatWebProvider.create('__another_key__', {
logger: createConsoleLogger(LogLevel.Off),
offline: true,
flagOverrides: createFlagOverridesFromMap(values, OverrideBehaviour.LocalOnly),
});
await newProvider.initialize();
if (!newProvider.configCatClient) {
throw Error('No ConfigCat client');
}
const clientDisposeSpy = jest.spyOn(newProvider.configCatClient, 'dispose');
await newProvider.onClose();
expect(clientDisposeSpy).toHaveBeenCalled();
});
describe('events', () => {
it('should emit PROVIDER_CONFIGURATION_CHANGED event', () => {
const handler = jest.fn();
const eventData = { settings: { myFlag: {} as ISettingUnion }, salt: undefined, segments: [] };
provider.events.addHandler(ProviderEvents.ConfigurationChanged, handler);
configCatEmitter.emit('configChanged', eventData);
expect(handler).toHaveBeenCalledWith({
flagsChanged: ['myFlag'],
});
});
it("should emit PROVIDER_READY event when underlying client is initialized after provider's initialize", async () => {
const cacheValue = '253370761200000\nW/"12345678-90a"\n{"f":{"booleanTrue":{"t":0,"v":{"b":true}}}}';
const fakeSharedCache = new (class implements IConfigCatCache {
private _value?: string;
get(key: string) {
return this._value;
}
set(key: string, value: string) {
this._value = value;
}
})();
const provider = ConfigCatWebProvider.create('configcat-sdk-1/1234567890123456789012/1234567890123456789012', {
cache: fakeSharedCache,
logger: createConsoleLogger(LogLevel.Off),
offline: true,
maxInitWaitTimeSeconds: 1,
});
const readyHandler = jest.fn();
provider.events.addHandler(ProviderEvents.Ready, readyHandler);
try {
await provider.initialize();
} catch (err) {
expect((err as Error).message).toContain('underlying ConfigCat client could not initialize');
}
expect(readyHandler).toHaveBeenCalledTimes(0);
fakeSharedCache.set('', cacheValue);
// Make sure that the internal cache is refreshed.
await provider.configCatClient?.forceRefreshAsync();
provider.resolveBooleanEvaluation('booleanTrue', false, { targetingKey });
// Wait a little while for the Ready event to be emitted.
await new Promise((resolve) => setTimeout(resolve, 100));
expect(readyHandler).toHaveBeenCalled();
});
});
describe('method resolveBooleanEvaluation', () => {
it('should throw FlagNotFoundError if type is different than expected', () => {
expect(() => provider.resolveBooleanEvaluation('nonExistent', false, { targetingKey })).toThrow(
FlagNotFoundError,
);
});
it('should return right value if key exists', () => {
const value = provider.resolveBooleanEvaluation('booleanTrue', false, { targetingKey });
expect(value).toHaveProperty('value', values.booleanTrue);
});
it('should throw TypeMismatchError if type is different than expected', () => {
expect(() => provider.resolveBooleanEvaluation('number1', false, { targetingKey })).toThrow(TypeMismatchError);
});
});
describe('method resolveStringEvaluation', () => {
it('should throw FlagNotFoundError if type is different than expected', async () => {
expect(() => provider.resolveStringEvaluation('nonExistent', 'nonExistent', { targetingKey })).toThrow(
FlagNotFoundError,
);
});
it('should return right value if key exists', () => {
const value = provider.resolveStringEvaluation('stringTest', 'default', { targetingKey });
expect(value).toHaveProperty('value', values.stringTest);
});
it('should throw TypeMismatchError if type is different than expected', async () => {
expect(() => provider.resolveStringEvaluation('number1', 'default', { targetingKey })).toThrow(TypeMismatchError);
});
});
describe('method resolveNumberEvaluation', () => {
it('should throw FlagNotFoundError if type is different than expected', async () => {
expect(() => provider.resolveNumberEvaluation('nonExistent', 0, { targetingKey })).toThrow(FlagNotFoundError);
});
it('should return right value if key exists', () => {
const value = provider.resolveNumberEvaluation('number1', 0, { targetingKey });
expect(value).toHaveProperty('value', values.number1);
});
it('should throw TypeMismatchError if type is different than expected', () => {
expect(() => provider.resolveNumberEvaluation('stringTest', 0, { targetingKey })).toThrow(TypeMismatchError);
});
});
describe('method resolveObjectEvaluation', () => {
it('should throw FlagNotFoundError if type is different than expected', () => {
expect(() => provider.resolveObjectEvaluation('nonExistent', false, { targetingKey })).toThrow(FlagNotFoundError);
});
it('should return right value if key exists', () => {
const value = provider.resolveObjectEvaluation('jsonValid', {}, { targetingKey });
expect(value).toHaveProperty('value', JSON.parse(values.jsonValid));
});
it('should throw ParseError if string is not valid JSON', () => {
expect(() => provider.resolveObjectEvaluation('jsonInvalid', {}, { targetingKey })).toThrow(ParseError);
});
it('should return right value if key exists and value is only a JSON primitive', () => {
const value = provider.resolveObjectEvaluation('jsonPrimitive', {}, { targetingKey });
expect(value).toHaveProperty('value', JSON.parse(values.jsonPrimitive));
});
});
});

View File

@ -0,0 +1,167 @@
import type { EvaluationContext, JsonValue, Paradigm, Provider, ResolutionDetails } from '@openfeature/web-sdk';
import {
OpenFeatureEventEmitter,
ParseError,
ProviderEvents,
ProviderNotReadyError,
TypeMismatchError,
} from '@openfeature/web-sdk';
import type { PrimitiveType, PrimitiveTypeName } from '@openfeature/config-cat-core';
import { isType, parseError, toResolutionDetails, transformContext } from '@openfeature/config-cat-core';
import type { IConfig, IConfigCatClient, OptionsForPollingMode, SettingValue } from 'configcat-js-ssr';
import { ClientCacheState, getClient, PollingMode } from 'configcat-js-ssr';
export class ConfigCatWebProvider implements Provider {
public readonly events = new OpenFeatureEventEmitter();
private readonly _clientFactory: (provider: ConfigCatWebProvider) => IConfigCatClient;
private _isProviderReady = false;
private _client?: IConfigCatClient;
public runsOn: Paradigm = 'client';
public metadata = {
name: ConfigCatWebProvider.name,
};
protected constructor(clientFactory: (provider: ConfigCatWebProvider) => IConfigCatClient) {
this._clientFactory = clientFactory;
}
public static create(sdkKey: string, options?: OptionsForPollingMode<PollingMode.AutoPoll>) {
// Let's create a shallow copy to not mess up caller's options object.
options = options ? { ...options } : {};
return new ConfigCatWebProvider((provider) => {
const oldSetupHooks = options?.setupHooks;
options.setupHooks = (hooks) => {
oldSetupHooks?.(hooks);
hooks.on('configChanged', (config: IConfig) =>
provider.events.emit(ProviderEvents.ConfigurationChanged, {
flagsChanged: Object.keys(config.settings),
}),
);
};
return getClient(sdkKey, PollingMode.AutoPoll, options);
});
}
public async initialize(): Promise<void> {
const client = this._clientFactory(this);
const clientCacheState = await client.waitForReady();
this._client = client;
if (clientCacheState !== ClientCacheState.NoFlagData) {
this._isProviderReady = true;
} else {
// OpenFeature provider defines ready state like this: "The provider is ready to resolve flags."
// However, ConfigCat client's behavior is different: in some cases ready state may be reached
// even if the client's internal, in-memory cache hasn't been populated yet, that is,
// the client is not able to evaluate feature flags yet. In such cases we throw an error to
// prevent the provider from being set ready right away, and check for the ready state later.
throw Error('The underlying ConfigCat client could not initialize within maxInitWaitTimeSeconds.');
}
}
public get configCatClient() {
return this._client;
}
public async onClose(): Promise<void> {
this._client?.dispose();
}
public resolveBooleanEvaluation(
flagKey: string,
defaultValue: boolean,
context: EvaluationContext,
): ResolutionDetails<boolean> {
return this.evaluate(flagKey, 'boolean', defaultValue, context);
}
public resolveStringEvaluation(
flagKey: string,
defaultValue: string,
context: EvaluationContext,
): ResolutionDetails<string> {
return this.evaluate(flagKey, 'string', defaultValue, context);
}
public resolveNumberEvaluation(
flagKey: string,
defaultValue: number,
context: EvaluationContext,
): ResolutionDetails<number> {
return this.evaluate(flagKey, 'number', defaultValue, context);
}
public resolveObjectEvaluation<U extends JsonValue>(
flagKey: string,
defaultValue: U,
context: EvaluationContext,
): ResolutionDetails<U> {
const objectValue = this.evaluate(flagKey, 'object', defaultValue, context);
return objectValue as ResolutionDetails<U>;
}
protected evaluate<T extends PrimitiveTypeName>(
flagKey: string,
flagType: T,
defaultValue: PrimitiveType<T>,
context: EvaluationContext,
): ResolutionDetails<PrimitiveType<T>> {
if (!this._client) {
throw new ProviderNotReadyError('Provider is not initialized');
}
// Make sure that the user-provided `defaultValue` is compatible with `flagType` as there is
// no guarantee that it actually is. (User may bypass type checking or may not use TypeScript at all.)
if (!isType(flagType, defaultValue)) {
throw new TypeMismatchError();
}
const configCatDefaultValue = flagType !== 'object' ? (defaultValue as SettingValue) : JSON.stringify(defaultValue);
const snapshot = this._client.snapshot();
const { value, ...evaluationData } = snapshot.getValueDetails(
flagKey,
configCatDefaultValue,
transformContext(context),
);
if (!this._isProviderReady && snapshot.cacheState !== ClientCacheState.NoFlagData) {
// Ideally, we would check ConfigCat client's initialization state in its "background" polling loop.
// This is not possible at the moment, so as a workaround, we do the check on feature flag evaluation.
// There are plans to improve this situation, so let's revise this
// as soon as ConfigCat SDK implements the necessary event.
this._isProviderReady = true;
setTimeout(() => this.events.emit(ProviderEvents.Ready), 0);
}
if (evaluationData.isDefaultValue) {
throw parseError(evaluationData.errorMessage);
}
if (flagType !== 'object') {
// When `flagType` (more precisely, `configCatDefaultValue`) is boolean, string or number,
// ConfigCat SDK guarantees that the returned `value` is compatible with `PrimitiveType<T>`.
// See also: https://configcat.com/docs/sdk-reference/js-ssr/#setting-type-mapping
return toResolutionDetails(value as PrimitiveType<T>, evaluationData);
}
let json: JsonValue;
try {
// In this case we can be sure that `value` is string since `configCatDefaultValue` is string,
// which means that ConfigCat SDK is guaranteed to return a string value.
json = JSON.parse(value as string);
} catch (e) {
throw new ParseError(`Unable to parse "${value}" as JSON`);
}
return toResolutionDetails(json as PrimitiveType<T>, evaluationData);
}
}

View File

@ -0,0 +1,21 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"module": "ES6",
"forceConsistentCasingInFileNames": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

View File

@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"declaration": true,
"types": ["node"]
},
"include": ["src/**/*.ts"],
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"]
}

View File

@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts"]
}

View File

@ -1,5 +1,63 @@
# Changelog
## [0.7.6](https://github.com/open-feature/js-sdk-contrib/compare/config-cat-provider-v0.7.5...config-cat-provider-v0.7.6) (2025-07-04)
### 🐛 Bug Fixes
* **security:** update dependency configcat-common to v9.4.0 ([#1348](https://github.com/open-feature/js-sdk-contrib/issues/1348)) ([601e7de](https://github.com/open-feature/js-sdk-contrib/commit/601e7de19948bc826778a076f27b46a8cb1fabca))
## [0.7.5](https://github.com/open-feature/js-sdk-contrib/compare/config-cat-provider-v0.7.4...config-cat-provider-v0.7.5) (2025-04-09)
### 🐛 Bug Fixes
* **config-cat:** Rework error reporting ([#1242](https://github.com/open-feature/js-sdk-contrib/issues/1242)) ([0425619](https://github.com/open-feature/js-sdk-contrib/commit/04256197bf6e7da70afd4ac1c31bdaf55ce4b789))
## [0.7.4](https://github.com/open-feature/js-sdk-contrib/compare/config-cat-provider-v0.7.3...config-cat-provider-v0.7.4) (2025-03-14)
### 🧹 Chore
* bump the required core version ([d8fc42f](https://github.com/open-feature/js-sdk-contrib/commit/d8fc42f5d23f30f011a697610e65d83144c19fca))
## [0.7.3](https://github.com/open-feature/js-sdk-contrib/compare/config-cat-provider-v0.7.2...config-cat-provider-v0.7.3) (2025-03-04)
### 🐛 Bug Fixes
* **config-cat:** Forward default value to underlying client ([#1214](https://github.com/open-feature/js-sdk-contrib/issues/1214)) ([9d14173](https://github.com/open-feature/js-sdk-contrib/commit/9d14173cf08da3030fc58fea8786b24bafd80403))
### 🧹 Chore
* update nx packages ([#1147](https://github.com/open-feature/js-sdk-contrib/issues/1147)) ([7f310fe](https://github.com/open-feature/js-sdk-contrib/commit/7f310fe87101b8aa793e1436e63c7602ccc202e3))
## [0.7.2](https://github.com/open-feature/js-sdk-contrib/compare/config-cat-provider-v0.7.1...config-cat-provider-v0.7.2) (2024-09-20)
### 🐛 Bug Fixes
* **config-cat:** Revise readme ([#1054](https://github.com/open-feature/js-sdk-contrib/issues/1054)) ([7e1dd72](https://github.com/open-feature/js-sdk-contrib/commit/7e1dd72a1450a9982b340afda62d34379d1b3f16))
## [0.7.1](https://github.com/open-feature/js-sdk-contrib/compare/config-cat-provider-v0.7.0...config-cat-provider-v0.7.1) (2024-07-23)
### 📚 Documentation
* A few corrections to ConfigCat providers' README.md ([#1014](https://github.com/open-feature/js-sdk-contrib/issues/1014)) ([3b24653](https://github.com/open-feature/js-sdk-contrib/commit/3b24653854643c827bddccb12aeb59e61204202d))
## [0.7.0](https://github.com/open-feature/js-sdk-contrib/compare/config-cat-provider-v0.6.1...config-cat-provider-v0.7.0) (2024-07-21)
### ⚠ BREAKING CHANGES
* make interface similar to config-cat-web ([#918](https://github.com/open-feature/js-sdk-contrib/issues/918))
### ✨ New Features
* make interface similar to config-cat-web ([#918](https://github.com/open-feature/js-sdk-contrib/issues/918)) ([e280014](https://github.com/open-feature/js-sdk-contrib/commit/e280014f8998dd2e5f2b7700f0d24842eeafab5f))
## [0.6.1](https://github.com/open-feature/js-sdk-contrib/compare/config-cat-provider-v0.6.0...config-cat-provider-v0.6.1) (2024-04-05)

View File

@ -1,6 +1,6 @@
# ConfigCat Provider
This provider is an implementation for [ConfigCat](https://configcat.com) a managed feature flag service.
This is an OpenFeature provider implementation for using [ConfigCat](https://configcat.com), a managed feature flag service in Node.js applications.
## Installation
@ -12,54 +12,65 @@ $ npm install @openfeature/config-cat-provider
The OpenFeature SDK is required as peer dependency.
The minimum required version of `@openfeature/server-sdk` currently is `1.6.0`.
The minimum required version of `@openfeature/server-sdk` currently is `1.13.5`.
The minimum required version of `configcat-js-ssr` currently is `7.1.2`.
The minimum required version of `configcat-node` currently is `11.3.1`.
```
$ npm install @openfeature/server-sdk configcat-js-ssr
$ npm install @openfeature/server-sdk configcat-node
```
## Usage
The ConfigCat provider uses the [ConfigCat JavaScript (SSR) SDK](https://configcat.com/docs/sdk-reference/js-ssr/).
This means that the provider can be used in both server (e.g. Node.js) and client (browser) applications.
The ConfigCat provider uses the [ConfigCat Node.js SDK](https://configcat.com/docs/sdk-reference/node/).
It can either be created by passing the ConfigCat SDK options to ```ConfigCatProvider.create``` or
the ```ConfigCatProvider``` constructor.
It can be created by passing the ConfigCat SDK options to ```ConfigCatProvider.create```.
The available options can be found in the [ConfigCat JavaScript (SSR) SDK](https://configcat.com/docs/sdk-reference/js-ssr/).
The available options can be found in the [ConfigCat Node.js SDK](https://configcat.com/docs/sdk-reference/node/#creating-the-configcat-client).
### Example using the default configuration
```javascript
import { OpenFeature } from "@openfeature/server-sdk";
import { ConfigCatProvider } from '@openfeature/config-cat-provider';
// Create and set the provider.
const provider = ConfigCatProvider.create('<sdk_key>');
OpenFeature.setProvider(provider);
await OpenFeature.setProviderAndWait(provider);
// Obtain a client instance and evaluate feature flags.
const client = OpenFeature.getClient();
const value = await client.getBooleanValue('isAwesomeFeatureEnabled', false);
console.log(`isAwesomeFeatureEnabled: ${value}`);
// On application shutdown, clean up the OpenFeature provider and the underlying ConfigCat client.
await OpenFeature.clearProviders();
```
### Example using different polling options and a setupHook
### Example using a different polling mode and custom configuration
```javascript
import { OpenFeature } from "@openfeature/server-sdk";
import { ConfigCatProvider } from '@openfeature/config-cat-provider';
import { createConsoleLogger, LogLevel, PollingMode } from 'configcat-node';
// Create and set the provider.
const provider = ConfigCatProvider.create('<sdk_key>', PollingMode.LazyLoad, {
logger: createConsoleLogger(LogLevel.Info),
setupHooks: (hooks) => hooks.on('clientReady', () => console.log('Client is ready!')),
});
await OpenFeature.setProviderAndWait(provider);
OpenFeature.setProvider(provider);
// ...
```
## Evaluation Context
ConfigCat only supports string values in its "evaluation
context", [there known as User Object](https://configcat.com/docs/advanced/user-object/).
The OpenFeature Evaluation Context is mapped to the [ConfigCat User Object](https://configcat.com/docs/advanced/user-object/).
This means that every value is converted to a string. This is trivial for numbers and booleans. Objects and arrays are
converted to JSON strings that can be interpreted in ConfigCat.
ConfigCat has three known attributes, and allows for additional attributes.
The [ConfigCat User Object](https://configcat.com/docs/advanced/user-object/) has three predefined attributes,
and allows for additional attributes.
The following shows how the attributes are mapped:
| OpenFeature EvaluationContext Field | ConfigCat User Field | Required |
@ -69,6 +80,17 @@ The following shows how the attributes are mapped:
| country | country | no |
| _Any Other_ | custom | no |
The custom types are mapped the following way:
| OpenFeature EvaluationContext Field Type | ConfigCat User Field Type |
|-----------------------------------------------|---------------------------|
| string | string |
| number | number |
| boolean | string |
| Array<string> | Array<string> |
| Array | Array |
| object | string |
The following example shows the conversion between an OpenFeature Evaluation Context and the corresponding ConfigCat
User:
@ -86,6 +108,7 @@ User:
"prop1": "1",
"prop2": 2
},
"customStringArray": ["one", "two"],
"customArray": [
1,
"2",
@ -104,8 +127,9 @@ User:
"custom": {
"customString": "customString",
"customBoolean": "true",
"customNumber": "1",
"customNumber": 1,
"customObject": "{\"prop1\":\"1\",\"prop2\":2}",
"customStringArray": ["one", "two"],
"customArray": "[1,\"2\",false]"
}
}

View File

@ -0,0 +1,72 @@
{
"name": "@openfeature/config-cat-provider",
"version": "0.7.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@openfeature/config-cat-provider",
"version": "0.7.6",
"peerDependencies": {
"@openfeature/server-sdk": "^1.13.5",
"configcat-node": "^11.3.1"
}
},
"node_modules/@openfeature/core": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@openfeature/core/-/core-1.2.0.tgz",
"integrity": "sha512-JyIiije5f+8Big1xz7UAmxqVmHBuFUI9Dh8DEFG2D1ocgjMm1tEzYXJDr3urCQGNnX9M/cYtNhEcGfyontIgJw==",
"peer": true
},
"node_modules/@openfeature/server-sdk": {
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/@openfeature/server-sdk/-/server-sdk-1.14.0.tgz",
"integrity": "sha512-PGPI6OZdyAy2FZVUiH1suw/WuWZJsIlK2xd1KbRl5rlMLawYk2bKGBGgZYX9rcozsGKOZM6/vaFjCSB6QCjCfw==",
"peer": true,
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@openfeature/core": "1.2.0"
}
},
"node_modules/configcat-common": {
"version": "9.3.1",
"resolved": "https://registry.npmjs.org/configcat-common/-/configcat-common-9.3.1.tgz",
"integrity": "sha512-yVkIbluksD/kZfVyKjLIOpwLrq3/ZRM7Lwrsz89JmbpQ6VtbnelrTQynSPElTtKjrPRZx56v3IZYk3nWTnnM6A==",
"peer": true,
"dependencies": {
"tslib": "^2.4.1"
}
},
"node_modules/configcat-node": {
"version": "11.3.1",
"resolved": "https://registry.npmjs.org/configcat-node/-/configcat-node-11.3.1.tgz",
"integrity": "sha512-7XJbgBpcxlwzlRLmvCtHTkO247Ban2ZkBqlmk+T0wVEt5tXfltgd53SYLYpw7RBWX0ma/QyP5E+/k/UDdMrOCw==",
"peer": true,
"dependencies": {
"configcat-common": "9.3.1",
"tslib": "^2.4.1",
"tunnel": "0.0.6"
},
"engines": {
"node": ">=14"
}
},
"node_modules/tslib": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
"peer": true
},
"node_modules/tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
"peer": true,
"engines": {
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
}
}
}
}

View File

@ -1,12 +1,15 @@
{
"name": "@openfeature/config-cat-provider",
"version": "0.6.1",
"version": "0.7.6",
"license": "Apache-2.0",
"scripts": {
"publish-if-not-exists": "cp $NPM_CONFIG_USERCONFIG .npmrc && if [ \"$(npm show $npm_package_name@$npm_package_version version)\" = \"$(npm run current-version -s)\" ]; then echo 'already published, skipping'; else npm publish --access public; fi",
"current-version": "echo $npm_package_version"
},
"peerDependencies": {
"@openfeature/server-sdk": "^1.13.0",
"configcat-js-ssr": ">=7.1.2"
"@openfeature/server-sdk": "^1.13.5",
"configcat-node": "^11.3.1",
"@openfeature/config-cat-core": "0.1.1",
"configcat-common": "9.4.0"
}
}
}

View File

@ -17,51 +17,29 @@
]
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": [
"{options.outputFile}"
],
"options": {
"lintFilePatterns": [
"libs/providers/config-cat/**/*.ts"
]
}
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"]
},
"test": {
"executor": "@nx/jest:jest",
"outputs": [
"{workspaceRoot}/coverage/{projectRoot}"
],
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/providers/config-cat/jest.config.ts",
"passWithNoTests": true
},
"configurations": {
"ci": {
"ci": true,
"codeCoverage": true
}
"jestConfig": "libs/providers/config-cat/jest.config.ts"
}
},
"package": {
"executor": "@nx/rollup:rollup",
"outputs": [
"{options.outputPath}"
],
"outputs": ["{options.outputPath}"],
"options": {
"project": "libs/providers/config-cat/package.json",
"outputPath": "dist/libs/providers/config-cat",
"entryFile": "libs/providers/config-cat/src/index.ts",
"tsConfig": "libs/providers/config-cat/tsconfig.lib.json",
"buildableProjectDepsInPackageJsonType": "dependencies",
"compiler": "tsc",
"generateExportsField": true,
"umdName": "config-cat",
"external": "all",
"format": [
"cjs",
"esm"
],
"format": ["cjs", "esm"],
"assets": [
{
"glob": "package.json",
@ -78,8 +56,7 @@
"input": "./libs/providers/config-cat",
"output": "./"
}
],
"updateBuildableProjectDepsInPackageJson": true
]
}
}
},

View File

@ -1,16 +1,14 @@
import { ConfigCatProvider } from './config-cat-provider';
import { ParseError, ProviderEvents, ProviderStatus, TypeMismatchError } from '@openfeature/server-sdk';
import { ProviderEvents, ParseError, FlagNotFoundError, TypeMismatchError } from '@openfeature/web-sdk';
import type { HookEvents, IConfigCatCache, ISettingUnion } from 'configcat-js-ssr';
import {
ClientCacheState,
createConsoleLogger,
createFlagOverridesFromMap,
HookEvents,
ISettingUnion,
LogLevel,
OverrideBehaviour,
PollingMode,
} from 'configcat-js-ssr';
import { EventEmitter } from 'events';
import type { EventEmitter } from 'events';
describe('ConfigCatProvider', () => {
const targetingKey = 'abc';
@ -70,66 +68,7 @@ describe('ConfigCatProvider', () => {
expect(clientDisposeSpy).toHaveBeenCalled();
});
describe('status', () => {
it('should be NOT_READY before initialization and READY after successful initialization', async () => {
const newProvider = ConfigCatProvider.create('wrong_key', PollingMode.ManualPoll, {
logger: createConsoleLogger(LogLevel.Off),
offline: true,
flagOverrides: createFlagOverridesFromMap(values, OverrideBehaviour.LocalOnly),
});
expect(newProvider.status).toEqual(ProviderStatus.NOT_READY);
await newProvider.initialize();
expect(newProvider.status).toEqual(ProviderStatus.READY);
});
it('should set status to ERROR if an error occurs', async () => {
configCatEmitter.emit('clientError', 'Error');
expect(provider.status).toEqual(ProviderStatus.ERROR);
});
it('should set status back to READY if client switches back to ready after an error occured', async () => {
configCatEmitter.emit('clientError', 'Error');
expect(provider.status).toEqual(ProviderStatus.ERROR);
configCatEmitter.emit('clientReady', ClientCacheState.HasCachedFlagDataOnly);
expect(provider.status).toEqual(ProviderStatus.READY);
});
});
describe('events', () => {
it('should emit PROVIDER_READY event', () => {
const handler = jest.fn();
provider.events.addHandler(ProviderEvents.Ready, handler);
configCatEmitter.emit('clientReady', ClientCacheState.HasCachedFlagDataOnly);
expect(handler).toHaveBeenCalled();
});
it('should emit PROVIDER_READY event on initialization', async () => {
const newProvider = ConfigCatProvider.create('__another_key__', PollingMode.ManualPoll, {
logger: createConsoleLogger(LogLevel.Off),
offline: true,
flagOverrides: createFlagOverridesFromMap(values, OverrideBehaviour.LocalOnly),
});
const handler = jest.fn();
newProvider.events.addHandler(ProviderEvents.Ready, handler);
await newProvider.initialize();
expect(handler).toHaveBeenCalled();
});
it('should emit PROVIDER_READY event without options', async () => {
const newProvider = ConfigCatProvider.create('__yet_another_key__', PollingMode.ManualPoll, {
flagOverrides: createFlagOverridesFromMap(values, OverrideBehaviour.LocalOnly),
});
const handler = jest.fn();
newProvider.events.addHandler(ProviderEvents.Ready, handler);
await newProvider.initialize();
expect(handler).toHaveBeenCalled();
});
it('should emit PROVIDER_CONFIGURATION_CHANGED event', () => {
const handler = jest.fn();
const eventData = { settings: { myFlag: {} as ISettingUnion }, salt: undefined, segments: [] };
@ -142,24 +81,60 @@ describe('ConfigCatProvider', () => {
});
});
it('should emit PROVIDER_ERROR event', () => {
const handler = jest.fn();
const eventData: [string, unknown] = ['error', { error: 'error' }];
it("should emit PROVIDER_READY event when underlying client is initialized after provider's initialize", async () => {
const cacheValue = '253370761200000\nW/"12345678-90a"\n{"f":{"booleanTrue":{"t":0,"v":{"b":true}}}}';
provider.events.addHandler(ProviderEvents.Error, handler);
configCatEmitter.emit('clientError', ...eventData);
const fakeSharedCache = new (class implements IConfigCatCache {
private _value?: string;
get(key: string) {
return this._value;
}
set(key: string, value: string) {
this._value = value;
}
})();
expect(handler).toHaveBeenCalledWith({
message: eventData[0],
metadata: eventData[1],
});
const provider = ConfigCatProvider.create(
'configcat-sdk-1/1234567890123456789012/1234567890123456789012',
PollingMode.AutoPoll,
{
cache: fakeSharedCache,
logger: createConsoleLogger(LogLevel.Off),
offline: true,
maxInitWaitTimeSeconds: 1,
},
);
const readyHandler = jest.fn();
provider.events.addHandler(ProviderEvents.Ready, readyHandler);
try {
await provider.initialize();
} catch (err) {
expect((err as Error).message).toContain('underlying ConfigCat client could not initialize');
}
expect(readyHandler).toHaveBeenCalledTimes(0);
fakeSharedCache.set('', cacheValue);
// Make sure that the internal cache is refreshed.
await provider.configCatClient?.forceRefreshAsync();
provider.resolveBooleanEvaluation('booleanTrue', false, { targetingKey });
// Wait a little while for the Ready event to be emitted.
await new Promise((resolve) => setTimeout(resolve, 100));
expect(readyHandler).toHaveBeenCalled();
});
});
describe('method resolveBooleanEvaluation', () => {
it('should return default value for missing value', async () => {
const value = await provider.resolveBooleanEvaluation('nonExistent', false, { targetingKey });
expect(value).toHaveProperty('value', false);
it('should throw FlagNotFoundError if type is different than expected', async () => {
await expect(provider.resolveBooleanEvaluation('nonExistent', false, { targetingKey })).rejects.toThrow(
FlagNotFoundError,
);
});
it('should return right value if key exists', async () => {
@ -175,9 +150,10 @@ describe('ConfigCatProvider', () => {
});
describe('method resolveStringEvaluation', () => {
it('should return default value for missing value', async () => {
const value = await provider.resolveStringEvaluation('nonExistent', 'default', { targetingKey });
expect(value).toHaveProperty('value', 'default');
it('should throw FlagNotFoundError if type is different than expected', async () => {
await expect(provider.resolveStringEvaluation('nonExistent', 'nonExistent', { targetingKey })).rejects.toThrow(
FlagNotFoundError,
);
});
it('should return right value if key exists', async () => {
@ -193,9 +169,10 @@ describe('ConfigCatProvider', () => {
});
describe('method resolveNumberEvaluation', () => {
it('should return default value for missing value', async () => {
const value = await provider.resolveNumberEvaluation('nonExistent', 0, { targetingKey });
expect(value).toHaveProperty('value', 0);
it('should throw FlagNotFoundError if type is different than expected', async () => {
await expect(provider.resolveNumberEvaluation('nonExistent', 0, { targetingKey })).rejects.toThrow(
FlagNotFoundError,
);
});
it('should return right value if key exists', async () => {
@ -211,9 +188,10 @@ describe('ConfigCatProvider', () => {
});
describe('method resolveObjectEvaluation', () => {
it('should return default value for missing value', async () => {
const value = await provider.resolveObjectEvaluation('nonExistent', {}, { targetingKey });
expect(value).toHaveProperty('value', {});
it('should throw FlagNotFoundError if type is different than expected', async () => {
await expect(provider.resolveObjectEvaluation('nonExistent', false, { targetingKey })).rejects.toThrow(
FlagNotFoundError,
);
});
it('should return right value if key exists', async () => {
@ -225,10 +203,9 @@ describe('ConfigCatProvider', () => {
await expect(provider.resolveObjectEvaluation('jsonInvalid', {}, { targetingKey })).rejects.toThrow(ParseError);
});
it('should throw TypeMismatchError if string is only a JSON primitive', async () => {
await expect(provider.resolveObjectEvaluation('jsonPrimitive', {}, { targetingKey })).rejects.toThrow(
TypeMismatchError,
);
it('should return right value if key exists and value is only a JSON primitive', async () => {
const value = await provider.resolveObjectEvaluation('jsonPrimitive', {}, { targetingKey });
expect(value).toHaveProperty('value', JSON.parse(values.jsonPrimitive));
});
});
});

View File

@ -1,82 +1,75 @@
import type { EvaluationContext, JsonValue, Provider, ResolutionDetails, Paradigm } from '@openfeature/server-sdk';
import {
EvaluationContext,
GeneralError,
JsonValue,
OpenFeatureEventEmitter,
ParseError,
Provider,
ProviderEvents,
ProviderStatus,
ResolutionDetails,
ResolutionReason,
StandardResolutionReasons,
ProviderNotReadyError,
TypeMismatchError,
ParseError,
} from '@openfeature/server-sdk';
import { getClient, IConfig, IConfigCatClient, IEvaluationDetails, SettingValue } from 'configcat-js-ssr';
import { transformContext } from './context-transformer';
import type { PrimitiveType, PrimitiveTypeName } from '@openfeature/config-cat-core';
import { isType, parseError, toResolutionDetails, transformContext } from '@openfeature/config-cat-core';
import type { SettingValue } from 'configcat-common';
import { ClientCacheState, PollingMode } from 'configcat-common';
import type { IConfigCatClient, IConfig, OptionsForPollingMode } from 'configcat-node';
import { getClient } from 'configcat-node';
export class ConfigCatProvider implements Provider {
public readonly events = new OpenFeatureEventEmitter();
private _status: ProviderStatus = ProviderStatus.NOT_READY;
private readonly _clientParameters: Parameters<typeof getClient>;
private readonly _clientFactory: (provider: ConfigCatProvider) => IConfigCatClient;
private readonly _pollingMode: PollingMode;
private _isProviderReady = false;
private _client?: IConfigCatClient;
public runsOn: Paradigm = 'server';
public metadata = {
name: ConfigCatProvider.name,
};
constructor(...params: Parameters<typeof getClient>) {
this._clientParameters = params;
protected constructor(clientFactory: (provider: ConfigCatProvider) => IConfigCatClient, pollingMode: PollingMode) {
this._clientFactory = clientFactory;
this._pollingMode = pollingMode;
}
public static create(...params: Parameters<typeof getClient>) {
return new ConfigCatProvider(...params);
}
get status() {
return this._status;
}
public async initialize(): Promise<void> {
return new Promise((resolve) => {
const originalParameters = this._clientParameters;
originalParameters[2] ??= {};
const options = originalParameters[2];
const oldSetupHooks = options.setupHooks;
public static create<TMode extends PollingMode>(
sdkKey: string,
pollingMode?: TMode,
options?: OptionsForPollingMode<TMode>,
): ConfigCatProvider {
// Let's create a shallow copy to not mess up caller's options object.
options = options ? { ...options } : ({} as OptionsForPollingMode<TMode>);
return new ConfigCatProvider((provider) => {
const oldSetupHooks = options?.setupHooks;
options.setupHooks = (hooks) => {
oldSetupHooks?.(hooks);
// After resolving, once, we can simply emit events the next time
const onProviderReady = () => {
this.events.emit(ProviderEvents.Ready);
this._status = ProviderStatus.READY;
};
hooks.once('clientReady', () => {
hooks.on('clientReady', onProviderReady);
onProviderReady();
resolve();
});
hooks.on('configChanged', (projectConfig: IConfig | undefined) =>
this.events.emit(ProviderEvents.ConfigurationChanged, {
flagsChanged: projectConfig ? Object.keys(projectConfig.settings) : undefined,
hooks.on('configChanged', (config: IConfig) =>
provider.events.emit(ProviderEvents.ConfigurationChanged, {
flagsChanged: Object.keys(config.settings),
}),
);
hooks.on('clientError', (message: string, error) => {
this._status = ProviderStatus.ERROR;
this.events.emit(ProviderEvents.Error, {
message: message,
metadata: error,
});
});
};
this._client = getClient(...originalParameters);
});
return getClient(sdkKey, pollingMode, options);
}, pollingMode ?? PollingMode.AutoPoll);
}
public async initialize(): Promise<void> {
const client = this._clientFactory(this);
const clientCacheState = await client.waitForReady();
this._client = client;
if (this._pollingMode !== PollingMode.AutoPoll || clientCacheState !== ClientCacheState.NoFlagData) {
this._isProviderReady = true;
} else {
// OpenFeature provider defines ready state like this: "The provider is ready to resolve flags."
// However, ConfigCat client's behavior is different: in some cases ready state may be reached
// even if the client's internal, in-memory cache hasn't been populated yet, that is,
// the client is not able to evaluate feature flags yet. In such cases we throw an error to
// prevent the provider from being set ready right away, and check for the ready state later.
throw Error('The underlying ConfigCat client could not initialize within maxInitWaitTimeSeconds.');
}
}
public get configCatClient() {
@ -92,21 +85,7 @@ export class ConfigCatProvider implements Provider {
defaultValue: boolean,
context: EvaluationContext,
): Promise<ResolutionDetails<boolean>> {
if (!this._client) {
throw new GeneralError('Provider is not initialized');
}
const { value, ...evaluationData } = await this._client.getValueDetailsAsync<SettingValue>(
flagKey,
undefined,
context.targetingKey != null ? transformContext(context) : undefined,
);
const validatedValue = validateFlagType('boolean', value);
return validatedValue
? toResolutionDetails(validatedValue, evaluationData)
: toResolutionDetails(defaultValue, evaluationData, StandardResolutionReasons.DEFAULT);
return this.evaluate(flagKey, 'boolean', defaultValue, context);
}
public async resolveStringEvaluation(
@ -114,21 +93,7 @@ export class ConfigCatProvider implements Provider {
defaultValue: string,
context: EvaluationContext,
): Promise<ResolutionDetails<string>> {
if (!this._client) {
throw new GeneralError('Provider is not initialized');
}
const { value, ...evaluationData } = await this._client.getValueDetailsAsync<SettingValue>(
flagKey,
undefined,
context.targetingKey != null ? transformContext(context) : undefined,
);
const validatedValue = validateFlagType('string', value);
return validatedValue
? toResolutionDetails(validatedValue, evaluationData)
: toResolutionDetails(defaultValue, evaluationData, StandardResolutionReasons.DEFAULT);
return this.evaluate(flagKey, 'string', defaultValue, context);
}
public async resolveNumberEvaluation(
@ -136,21 +101,7 @@ export class ConfigCatProvider implements Provider {
defaultValue: number,
context: EvaluationContext,
): Promise<ResolutionDetails<number>> {
if (!this._client) {
throw new GeneralError('Provider is not initialized');
}
const { value, ...evaluationData } = await this._client.getValueDetailsAsync<SettingValue>(
flagKey,
undefined,
context.targetingKey != null ? transformContext(context) : undefined,
);
const validatedValue = validateFlagType('number', value);
return validatedValue
? toResolutionDetails(validatedValue, evaluationData)
: toResolutionDetails(defaultValue, evaluationData, StandardResolutionReasons.DEFAULT);
return this.evaluate(flagKey, 'number', defaultValue, context);
}
public async resolveObjectEvaluation<U extends JsonValue>(
@ -158,83 +109,64 @@ export class ConfigCatProvider implements Provider {
defaultValue: U,
context: EvaluationContext,
): Promise<ResolutionDetails<U>> {
const objectValue = await this.evaluate(flagKey, 'object', defaultValue, context);
return objectValue as ResolutionDetails<U>;
}
protected async evaluate<T extends PrimitiveTypeName>(
flagKey: string,
flagType: T,
defaultValue: PrimitiveType<T>,
context: EvaluationContext,
): Promise<ResolutionDetails<PrimitiveType<T>>> {
if (!this._client) {
throw new GeneralError('Provider is not initialized');
throw new ProviderNotReadyError('Provider is not initialized');
}
// Make sure that the user-provided `defaultValue` is compatible with `flagType` as there is
// no guarantee that it actually is. (User may bypass type checking or may not use TypeScript at all.)
if (!isType(flagType, defaultValue)) {
throw new TypeMismatchError();
}
const configCatDefaultValue = flagType !== 'object' ? (defaultValue as SettingValue) : JSON.stringify(defaultValue);
const { value, ...evaluationData } = await this._client.getValueDetailsAsync(
flagKey,
undefined,
context.targetingKey != null ? transformContext(context) : undefined,
configCatDefaultValue,
transformContext(context),
);
if (typeof value === 'undefined') {
return toResolutionDetails(defaultValue, evaluationData, StandardResolutionReasons.DEFAULT);
if (!this._isProviderReady && this._client.snapshot().cacheState !== ClientCacheState.NoFlagData) {
// Ideally, we would check ConfigCat client's initialization state in its "background" polling loop.
// This is not possible at the moment, so as a workaround, we do the check on feature flag evaluation.
// There are plans to improve this situation, so let's revise this
// as soon as ConfigCat SDK implements the necessary event.
this._isProviderReady = true;
setTimeout(() => this.events.emit(ProviderEvents.Ready), 0);
}
if (!isType('string', value)) {
throw new TypeMismatchError(`Requested object flag but the actual value is not a JSON string`);
if (evaluationData.isDefaultValue) {
throw parseError(evaluationData.errorMessage);
}
if (flagType !== 'object') {
// When `flagType` (more precisely, `configCatDefaultValue`) is boolean, string or number,
// ConfigCat SDK guarantees that the returned `value` is compatible with `PrimitiveType<T>`.
// See also: https://configcat.com/docs/sdk-reference/node/#setting-type-mapping
return toResolutionDetails(value as PrimitiveType<T>, evaluationData);
}
let json: JsonValue;
try {
const object = JSON.parse(value);
if (typeof object !== 'object') {
throw new TypeMismatchError(`Requested object flag but the actual value is ${typeof value}`);
}
return toResolutionDetails(object, evaluationData);
// In this case we can be sure that `value` is string since `configCatDefaultValue` is string,
// which means that ConfigCat SDK is guaranteed to return a string value.
json = JSON.parse(value as string);
} catch (e) {
if (e instanceof TypeMismatchError) {
throw e;
}
throw new ParseError(`Unable to parse '${value}' as JSON`);
throw new ParseError(`Unable to parse "${value}" as JSON`);
}
return toResolutionDetails(json as PrimitiveType<T>, evaluationData);
}
}
function toResolutionDetails<U extends JsonValue>(
value: U,
data: Omit<IEvaluationDetails, 'value'>,
reason?: ResolutionReason,
): ResolutionDetails<U> {
const matchedTargeting = 'matchedEvaluationRule' in data ? data.matchedEvaluationRule : data.matchedTargetingRule;
const matchedPercentage =
'matchedEvaluationPercentageRule' in data ? data.matchedEvaluationPercentageRule : data.matchedPercentageOption;
const matchedRule = Boolean(matchedTargeting || matchedPercentage);
const evaluatedReason = matchedRule ? StandardResolutionReasons.TARGETING_MATCH : StandardResolutionReasons.STATIC;
return {
value,
reason: reason ?? evaluatedReason,
errorMessage: data.errorMessage,
variant: data.variationId ?? undefined,
};
}
type PrimitiveTypeName = 'string' | 'boolean' | 'number' | 'object' | 'undefined';
type PrimitiveType<T> = T extends 'string'
? string
: T extends 'boolean'
? boolean
: T extends 'number'
? number
: T extends 'object'
? object
: T extends 'undefined'
? undefined
: unknown;
function isType<T extends PrimitiveTypeName>(type: T, value: unknown): value is PrimitiveType<T> {
return typeof value === type;
}
function validateFlagType<T extends PrimitiveTypeName>(type: T, value: unknown): PrimitiveType<T> | undefined {
if (typeof value !== 'undefined' && !isType(type, value)) {
throw new TypeMismatchError(`Requested ${type} flag but the actual value is ${typeof value}`);
}
return value;
}

View File

@ -1,65 +0,0 @@
import { EvaluationContext, EvaluationContextValue, TargetingKeyMissingError } from '@openfeature/server-sdk';
import { User as ConfigCatUser } from 'configcat-js-ssr';
function contextValueToString(contextValue: EvaluationContextValue): string | undefined {
if (typeof contextValue === 'string') {
return contextValue;
}
if (typeof contextValue === 'boolean' || typeof contextValue === 'number' || contextValue === null) {
return String(contextValue);
}
if (typeof contextValue === 'undefined') {
return contextValue;
}
if (contextValue instanceof Date) {
return contextValue.toISOString();
}
return JSON.stringify(contextValue);
}
function transformContextValues(contextValue: EvaluationContextValue): ConfigCatUser['custom'] {
if (contextValue === null) {
return {};
}
if (typeof contextValue !== 'object' || Array.isArray(contextValue)) {
const value = contextValueToString(contextValue);
return value ? { value } : {};
}
if (contextValue instanceof Date) {
return { value: contextValue.toISOString() };
}
return Object.entries(contextValue).reduce<ConfigCatUser['custom']>((context, [key, value]) => {
const transformedValue = contextValueToString(value);
return transformedValue ? { ...context, [key]: transformedValue } : context;
}, {});
}
function stringOrUndefined(param?: unknown): string | undefined {
if (typeof param === 'string') {
return param;
}
return undefined;
}
export function transformContext(context: EvaluationContext): ConfigCatUser | never {
const { targetingKey, email, country, ...attributes } = context;
if (!targetingKey) {
throw new TargetingKeyMissingError('ConfigCat evaluation context can only be used if a targetingKey is given');
}
return {
identifier: targetingKey,
email: stringOrUndefined(email),
country: stringOrUndefined(country),
custom: transformContextValues(attributes),
};
}

View File

@ -3,7 +3,6 @@
"compilerOptions": {
"module": "ES6",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,

View File

@ -1,5 +1,12 @@
# Changelog
## [0.3.1](https://github.com/open-feature/js-sdk-contrib/compare/env-var-provider-v0.3.0...env-var-provider-v0.3.1) (2024-07-10)
### 🐛 Bug Fixes
* **env-var:** set runs on property to server ([#981](https://github.com/open-feature/js-sdk-contrib/issues/981)) ([919761d](https://github.com/open-feature/js-sdk-contrib/commit/919761d8926fc102c84b11288d4c6d1ff3e3fc05))
## [0.3.0](https://github.com/open-feature/js-sdk-contrib/compare/env-var-provider-v0.2.0...env-var-provider-v0.3.0) (2024-03-25)

View File

@ -1,6 +1,7 @@
{
"name": "@openfeature/env-var-provider",
"version": "0.3.0",
"version": "0.3.1",
"license": "Apache-2.0",
"scripts": {
"publish-if-not-exists": "cp $NPM_CONFIG_USERCONFIG .npmrc && if [ \"$(npm show $npm_package_name@$npm_package_version version)\" = \"$(npm run current-version -s)\" ]; then echo 'already published, skipping'; else npm publish --access public; fi",
"current-version": "echo $npm_package_version"
@ -8,4 +9,4 @@
"peerDependencies": {
"@openfeature/server-sdk": "^1.13.0"
}
}
}

View File

@ -17,51 +17,29 @@
]
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": [
"{options.outputFile}"
],
"options": {
"lintFilePatterns": [
"libs/providers/env-var/**/*.ts"
]
}
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"]
},
"test": {
"executor": "@nx/jest:jest",
"outputs": [
"{workspaceRoot}/coverage/{projectRoot}"
],
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/providers/env-var/jest.config.ts",
"passWithNoTests": true
},
"configurations": {
"ci": {
"ci": true,
"codeCoverage": true
}
"jestConfig": "libs/providers/env-var/jest.config.ts"
}
},
"package": {
"executor": "@nx/rollup:rollup",
"outputs": [
"{options.outputPath}"
],
"outputs": ["{options.outputPath}"],
"options": {
"project": "libs/providers/env-var/package.json",
"outputPath": "dist/libs/providers/env-var",
"entryFile": "libs/providers/env-var/src/index.ts",
"tsConfig": "libs/providers/env-var/tsconfig.lib.json",
"buildableProjectDepsInPackageJsonType": "dependencies",
"compiler": "tsc",
"generateExportsField": true,
"umdName": "Env Var",
"external": "all",
"format": [
"cjs",
"esm"
],
"format": ["cjs", "esm"],
"assets": [
{
"glob": "package.json",
@ -78,8 +56,7 @@
"input": "./libs/providers/env-var",
"output": "./"
}
],
"updateBuildableProjectDepsInPackageJson": true
]
}
}
},

View File

@ -1,11 +1,5 @@
import {
FlagNotFoundError,
JsonValue,
ParseError,
Provider,
ResolutionDetails,
StandardResolutionReasons,
} from '@openfeature/server-sdk';
import type { JsonValue, Provider, ResolutionDetails } from '@openfeature/server-sdk';
import { FlagNotFoundError, ParseError, StandardResolutionReasons } from '@openfeature/server-sdk';
import { constantCase } from './constant-case';
export type Config = {
@ -25,6 +19,8 @@ export class EnvVarProvider implements Provider {
name: 'environment variable',
};
readonly runsOn = 'server';
private readonly options: Config;
// use the constructor for provider-specific configuration

View File

@ -1,6 +1,6 @@
{
"extends": ["../../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"ignorePatterns": ["!**/*", "schemas/**"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],

View File

@ -1,5 +1,29 @@
# Changelog
## [0.7.3](https://github.com/open-feature/js-sdk-contrib/compare/flagd-web-provider-v0.7.2...flagd-web-provider-v0.7.3) (2025-03-19)
### ✨ New Features
* **flagd:** add flag metadata ([#1151](https://github.com/open-feature/js-sdk-contrib/issues/1151)) ([b1c6d23](https://github.com/open-feature/js-sdk-contrib/commit/b1c6d235565f6cce02519d7c08bb6ad2dd791332))
### 🧹 Chore
* **deps:** update libs/providers/flagd-web/schemas digest to 37baa2c ([#1148](https://github.com/open-feature/js-sdk-contrib/issues/1148)) ([36ec82a](https://github.com/open-feature/js-sdk-contrib/commit/36ec82a5581e436b699d4b8238908fa4d5817deb))
* **deps:** update libs/providers/flagd-web/schemas digest to b81a56e ([#1131](https://github.com/open-feature/js-sdk-contrib/issues/1131)) ([828145a](https://github.com/open-feature/js-sdk-contrib/commit/828145a89da13bbd90bca352a6488aecb62b764b))
* **deps:** update libs/providers/flagd-web/schemas digest to bb76343 ([#1167](https://github.com/open-feature/js-sdk-contrib/issues/1167)) ([cb7bdb1](https://github.com/open-feature/js-sdk-contrib/commit/cb7bdb12a19ea403eb834d0eb552f30b42921b6b))
* removing build dependencies and using testcontainers for container spin up ([#982](https://github.com/open-feature/js-sdk-contrib/issues/982)) ([2d64331](https://github.com/open-feature/js-sdk-contrib/commit/2d6433101b76ba9ad266095fe31b58314f82a105))
* update nx packages ([#1147](https://github.com/open-feature/js-sdk-contrib/issues/1147)) ([7f310fe](https://github.com/open-feature/js-sdk-contrib/commit/7f310fe87101b8aa793e1436e63c7602ccc202e3))
* various gherkin improvements for e2e tests ([#1008](https://github.com/open-feature/js-sdk-contrib/issues/1008)) ([40abd8e](https://github.com/open-feature/js-sdk-contrib/commit/40abd8eca76b47bb5c084b377302821968acd19c))
## [0.7.2](https://github.com/open-feature/js-sdk-contrib/compare/flagd-web-provider-v0.7.1...flagd-web-provider-v0.7.2) (2024-07-08)
### ✨ New Features
* Change fractional custom op from percentage-based to relative weighting. [#946](https://github.com/open-feature/js-sdk-contrib/issues/946) ([#954](https://github.com/open-feature/js-sdk-contrib/issues/954)) ([0e9bc84](https://github.com/open-feature/js-sdk-contrib/commit/0e9bc842cf09de12e8445dcb4e0e8b3623c66099))
## [0.7.1](https://github.com/open-feature/js-sdk-contrib/compare/flagd-web-provider-v0.7.0...flagd-web-provider-v0.7.1) (2024-04-26)

View File

@ -2,13 +2,10 @@
export default {
displayName: 'providers-flagd-web',
preset: '../../../jest.preset.js',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
},
},
transform: {
'^.+\\.[tj]s$': 'ts-jest',
'^.+\\.[tj]s$': ['ts-jest', {
tsconfig: '<rootDir>/tsconfig.spec.json'
}]
},
testEnvironment: 'jsdom',
moduleFileExtensions: ['ts', 'js', 'html'],

View File

@ -1,12 +1,12 @@
{
"name": "@openfeature/flagd-web-provider",
"version": "0.7.1",
"version": "0.7.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@openfeature/flagd-web-provider",
"version": "0.7.1"
"version": "0.7.3"
}
}
}

View File

@ -1,11 +1,18 @@
{
"name": "@openfeature/flagd-web-provider",
"version": "0.7.1",
"version": "0.7.3",
"license": "Apache-2.0",
"scripts": {
"publish-if-not-exists": "cp $NPM_CONFIG_USERCONFIG .npmrc && if [ \"$(npm show $npm_package_name@$npm_package_version version)\" = \"$(npm run current-version -s)\" ]; then echo 'already published, skipping'; else npm publish --access public; fi",
"current-version": "echo $npm_package_version"
},
"peerDependencies": {
"@openfeature/web-sdk": "^1.0.0"
},
"dependencies": {
"@openfeature/flagd-core": "1.1.0",
"@connectrpc/connect": "^1.4.0",
"@connectrpc/connect-web": "^1.4.0",
"@bufbuild/protobuf": "^1.2.0"
}
}

View File

@ -21,24 +21,15 @@
"options": {
"commands": [
"git submodule update --init schemas",
"rm -f -r ./src/proto",
"cd schemas && buf generate buf.build/open-feature/flagd --template protobuf/buf.gen.ts-connect.yaml",
"mv -v ./proto ./src"
"npx buf generate buf.build/open-feature/flagd --template schemas/protobuf/buf.gen.ts-connect.yaml --output ./src/lib"
],
"cwd": "libs/providers/flagd-web",
"parallel": false
}
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": [
"{options.outputFile}"
],
"options": {
"lintFilePatterns": [
"libs/providers/flagd-web/**/*.ts"
]
},
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"],
"dependsOn": [
{
"target": "generate"
@ -47,12 +38,9 @@
},
"test": {
"executor": "@nx/jest:jest",
"outputs": [
"{workspaceRoot}/coverage/libs/providers/flagd-web"
],
"outputs": ["{workspaceRoot}/coverage/libs/providers/flagd-web"],
"options": {
"jestConfig": "libs/providers/flagd-web/jest.config.ts",
"passWithNoTests": true
"jestConfig": "libs/providers/flagd-web/jest.config.ts"
},
"dependsOn": [
{
@ -60,25 +48,11 @@
}
]
},
"pullTestHarness": {
"executor": "nx:run-commands",
"options": {
"commands": [
"git submodule update --init spec",
"rm -f -r ./src/e2e/features/*",
"cp -v ./spec/specification/assets/gherkin/evaluation.feature ./src/e2e/features/"
],
"cwd": "libs/providers/flagd-web",
"parallel": false
}
},
"e2e": {
"executor": "nx:run-commands",
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/libs/providers/flagd-web"],
"options": {
"commands": [
"npx jest"
],
"cwd": "libs/providers/flagd-web/src/e2e",
"jestConfig": "libs/providers/flagd-web/src/e2e/jest.config.ts",
"parallel": false
},
"dependsOn": [
@ -86,15 +60,13 @@
"target": "generate"
},
{
"target": "pullTestHarness"
"target": "flagd-core:pullTestHarness"
}
]
},
"package": {
"executor": "@nx/rollup:rollup",
"outputs": [
"{options.outputPath}"
],
"outputs": ["{options.outputPath}"],
"options": {
"project": "libs/providers/flagd-web/package.json",
"outputPath": "dist/libs/providers/flagd-web",
@ -102,13 +74,9 @@
"tsConfig": "libs/providers/flagd-web/tsconfig.lib.json",
"compiler": "tsc",
"generateExportsField": true,
"buildableProjectDepsInPackageJsonType": "dependencies",
"umdName": "flagd-web",
"external": "all",
"format": [
"cjs",
"esm"
],
"format": ["cjs", "esm"],
"assets": [
{
"glob": "package.json",
@ -125,8 +93,7 @@
"input": "./libs/providers/flagd-web",
"output": "./"
}
],
"updateBuildableProjectDepsInPackageJson": true
]
},
"dependsOn": [
{

@ -1 +1 @@
Subproject commit 8c72c14eebff2a5b20fe2afb90c7ad44c1184ae8
Subproject commit 2852d7772e6b8674681a6ee6b88db10dbe3f6899

@ -1 +0,0 @@
Subproject commit 1513e9a0212448a19808bff695180cca9b7b6656

View File

@ -0,0 +1,8 @@
import { getGherkinTestPath } from '@openfeature/flagd-core';
export const FLAGD_NAME = 'flagd';
export const GHERKIN_EVALUATION_FEATURE = getGherkinTestPath(
'evaluation.feature',
'spec/specification/assets/gherkin/',
);

View File

@ -1 +0,0 @@
*.feature

View File

@ -0,0 +1,2 @@
export * from './constants';
export * from './step-definitions';

View File

@ -1,15 +1,14 @@
export default {
import type { Config } from 'jest';
const config: Config = {
displayName: 'providers-flagd-web-e2e',
transform: {
'^.+\\.[tj]s$': ['ts-jest', { tsConfig: './tsconfig.lib.json' }],
},
moduleNameMapper: {
'^(.*)\\.js$': ['$1.js', '$1.ts', '$1'],
},
testEnvironment: 'node',
preset: 'ts-jest',
clearMocks: true,
setupFiles: ['./setup.ts'],
preset: 'ts-jest',
moduleNameMapper: {
'@openfeature/flagd-core': ['<rootDir>/../../../../shared/flagd-core/src'],
'(.+)\\.js$': '$1',
},
verbose: true,
silent: false,
};
export default config;

View File

@ -1,21 +0,0 @@
import assert from 'assert';
import { FlagdWebProvider } from '../lib/flagd-web-provider';
import { OpenFeature } from '@openfeature/web-sdk';
const FLAGD_WEB_NAME = 'flagd-web';
// register the flagd provider before the tests.
console.log('Setting flagd web provider...');
OpenFeature.setProvider(
new FlagdWebProvider({
host: 'localhost',
port: 8013,
tls: false,
maxRetries: -1,
}),
);
assert(
OpenFeature.providerMetadata.name === FLAGD_WEB_NAME,
new Error(`Expected ${FLAGD_WEB_NAME} provider to be configured, instead got: ${OpenFeature.providerMetadata.name}`),
);
console.log('flagd web provider configured!');

View File

@ -1,341 +0,0 @@
import {
EvaluationContext,
EvaluationDetails,
JsonObject,
JsonValue,
OpenFeature,
ProviderEvents,
ResolutionDetails,
StandardResolutionReasons,
} from '@openfeature/web-sdk';
import { defineFeature, loadFeature } from 'jest-cucumber';
// load the feature file.
const feature = loadFeature('features/evaluation.feature');
// get a client (flagd provider registered in setup)
const client = OpenFeature.getClient();
const givenAnOpenfeatureClientIsRegistered = (
given: (stepMatcher: string, stepDefinitionCallback: () => void) => void,
) => {
given('a provider is registered', () => undefined);
};
defineFeature(feature, (test) => {
beforeAll((done) => {
client.addHandler(ProviderEvents.Ready, async () => {
done();
});
});
afterAll(async () => {
await OpenFeature.close();
});
test('Resolves boolean value', ({ given, when, then }) => {
let value: boolean;
givenAnOpenfeatureClientIsRegistered(given);
when(
/^a boolean flag with key "(.*)" is evaluated with default value "(.*)"$/,
(key: string, defaultValue: string) => {
value = client.getBooleanValue(key, defaultValue === 'true');
},
);
then(/^the resolved boolean value should be "(.*)"$/, (expectedValue: string) => {
expect(value).toEqual(expectedValue === 'true');
});
});
test('Resolves string value', ({ given, when, then }) => {
let value: string;
givenAnOpenfeatureClientIsRegistered(given);
when(
/^a string flag with key "(.*)" is evaluated with default value "(.*)"$/,
(key: string, defaultValue: string) => {
value = client.getStringValue(key, defaultValue);
},
);
then(/^the resolved string value should be "(.*)"$/, (expectedValue: string) => {
expect(value).toEqual(expectedValue);
});
});
test('Resolves integer value', ({ given, when, then }) => {
let value: number;
givenAnOpenfeatureClientIsRegistered(given);
when(
/^an integer flag with key "(.*)" is evaluated with default value (\d+)$/,
(key: string, defaultValue: number) => {
value = client.getNumberValue(key, defaultValue);
},
);
then(/^the resolved integer value should be (\d+)$/, (expectedStringValue: string) => {
const expectedNumberValue = parseInt(expectedStringValue);
expect(value).toEqual(expectedNumberValue);
});
});
test('Resolves float value', ({ given, when, then }) => {
let value: number;
givenAnOpenfeatureClientIsRegistered(given);
when(
/^a float flag with key "(.*)" is evaluated with default value (\d+\.?\d*)$/,
(key: string, defaultValue: string) => {
value = client.getNumberValue(key, Number.parseFloat(defaultValue));
},
);
then(/^the resolved float value should be (\d+\.?\d*)$/, (expectedValue: string) => {
expect(value).toEqual(Number.parseFloat(expectedValue));
});
});
test('Resolves object value', ({ given, when, then }) => {
let value: JsonValue;
givenAnOpenfeatureClientIsRegistered(given);
when(/^an object flag with key "(.*)" is evaluated with a null default value$/, (key: string) => {
value = client.getObjectValue<JsonValue>(key, {});
});
then(
/^the resolved object value should be contain fields "(.*)", "(.*)", and "(.*)", with values "(.*)", "(.*)" and (\d+), respectively$/,
(field1: string, field2: string, field3: string, boolVal: string, strVal: string, intVal: string) => {
const jsonObject = value as JsonObject;
expect(jsonObject[field1]).toEqual(boolVal === 'true');
expect(jsonObject[field2]).toEqual(strVal);
expect(jsonObject[field3]).toEqual(Number.parseInt(intVal));
},
);
});
test('Resolves boolean details', ({ given, when, then }) => {
let details: EvaluationDetails<boolean>;
givenAnOpenfeatureClientIsRegistered(given);
when(
/^a boolean flag with key "(.*)" is evaluated with details and default value "(.*)"$/,
(key: string, defaultValue: string) => {
details = client.getBooleanDetails(key, defaultValue === 'true');
},
);
then(
/^the resolved boolean details value should be "(.*)", the variant should be "(.*)", and the reason should be "(.*)"$/,
(expectedValue: string, expectedVariant: string, expectedReason: string) => {
expect(details.value).toEqual(expectedValue === 'true');
expect(details.variant).toEqual(expectedVariant);
expect(details.reason).toEqual(expectedReason);
},
);
});
test('Resolves string details', ({ given, when, then }) => {
let details: EvaluationDetails<string>;
givenAnOpenfeatureClientIsRegistered(given);
when(
/^a string flag with key "(.*)" is evaluated with details and default value "(.*)"$/,
(key: string, defaultValue: string) => {
details = client.getStringDetails(key, defaultValue);
},
);
then(
/^the resolved string details value should be "(.*)", the variant should be "(.*)", and the reason should be "(.*)"$/,
(expectedValue: string, expectedVariant: string, expectedReason: string) => {
expect(details.value).toEqual(expectedValue);
expect(details.variant).toEqual(expectedVariant);
expect(details.reason).toEqual(expectedReason);
},
);
});
test('Resolves integer details', ({ given, when, then }) => {
let details: EvaluationDetails<number>;
givenAnOpenfeatureClientIsRegistered(given);
when(
/^an integer flag with key "(.*)" is evaluated with details and default value (\d+)$/,
(key: string, defaultValue: string) => {
details = client.getNumberDetails(key, Number.parseInt(defaultValue));
},
);
then(
/^the resolved integer details value should be (\d+), the variant should be "(.*)", and the reason should be "(.*)"$/,
(expectedValue: string, expectedVariant: string, expectedReason: string) => {
expect(details.value).toEqual(Number.parseInt(expectedValue));
expect(details.variant).toEqual(expectedVariant);
expect(details.reason).toEqual(expectedReason);
},
);
});
test('Resolves float details', ({ given, when, then }) => {
let details: EvaluationDetails<number>;
givenAnOpenfeatureClientIsRegistered(given);
when(
/^a float flag with key "(.*)" is evaluated with details and default value (\d+\.?\d*)$/,
(key: string, defaultValue: string) => {
details = client.getNumberDetails(key, Number.parseFloat(defaultValue));
},
);
then(
/^the resolved float details value should be (\d+\.?\d*), the variant should be "(.*)", and the reason should be "(.*)"$/,
(expectedValue: string, expectedVariant: string, expectedReason: string) => {
expect(details.value).toEqual(Number.parseFloat(expectedValue));
expect(details.variant).toEqual(expectedVariant);
expect(details.reason).toEqual(expectedReason);
},
);
});
test('Resolves object details', ({ given, when, then, and }) => {
let details: EvaluationDetails<JsonValue>; // update this after merge
givenAnOpenfeatureClientIsRegistered(given);
when(/^an object flag with key "(.*)" is evaluated with details and a null default value$/, (key: string) => {
details = client.getObjectDetails(key, {}); // update this after merge
});
then(
/^the resolved object details value should be contain fields "(.*)", "(.*)", and "(.*)", with values "(.*)", "(.*)" and (\d+), respectively$/,
(field1: string, field2: string, field3: string, boolValue: string, stringValue: string, intValue: string) => {
const jsonObject = details.value as JsonObject;
expect(jsonObject[field1]).toEqual(boolValue === 'true');
expect(jsonObject[field2]).toEqual(stringValue);
expect(jsonObject[field3]).toEqual(Number.parseInt(intValue));
},
);
and(
/^the variant should be "(.*)", and the reason should be "(.*)"$/,
(expectedVariant: string, expectedReason: string) => {
expect(details.variant).toEqual(expectedVariant);
expect(details.reason).toEqual(expectedReason);
},
);
});
test('Resolves based on context', ({ given, when, and, then }) => {
const context: EvaluationContext = {};
let value: string;
let flagKey: string;
givenAnOpenfeatureClientIsRegistered(given);
when(
/^context contains keys "(.*)", "(.*)", "(.*)", "(.*)" with values "(.*)", "(.*)", (\d+), "(.*)"$/,
async (
stringField1: string,
stringField2: string,
intField: string,
boolField: string,
stringValue1: string,
stringValue2: string,
intValue: string,
boolValue: string,
) => {
context[stringField1] = stringValue1;
context[stringField2] = stringValue2;
context[intField] = Number.parseInt(intValue);
context[boolField] = boolValue === 'true';
await OpenFeature.setContext(context);
},
);
and(/^a flag with key "(.*)" is evaluated with default value "(.*)"$/, (key: string, defaultValue: string) => {
flagKey = key;
value = client.getStringValue(flagKey, defaultValue);
});
then(/^the resolved string response should be "(.*)"$/, (expectedValue: string) => {
expect(value).toEqual(expectedValue);
});
and(/^the resolved flag value is "(.*)" when the context is empty$/, async (expectedValue) => {
await OpenFeature.setContext({});
const emptyContextValue = client.getStringValue(flagKey, 'nope', {});
expect(emptyContextValue).toEqual(expectedValue);
});
});
test('Flag not found', ({ given, when, then, and }) => {
let flagKey: string;
let fallbackValue: string;
let details: ResolutionDetails<string>;
givenAnOpenfeatureClientIsRegistered(given);
when(
/^a non-existent string flag with key "(.*)" is evaluated with details and a default value "(.*)"$/,
(key: string, defaultValue: string) => {
flagKey = key;
fallbackValue = defaultValue;
details = client.getStringDetails(flagKey, defaultValue);
},
);
then(/^the default string value should be returned$/, () => {
expect(details.value).toEqual(fallbackValue);
});
and(
/^the reason should indicate an error and the error code should indicate a missing flag with "(.*)"$/,
(errorCode: string) => {
expect(details.reason).toEqual(StandardResolutionReasons.ERROR);
expect(details.errorCode).toEqual(errorCode);
},
);
});
test('Type error', ({ given, when, then, and }) => {
let flagKey: string;
let fallbackValue: number;
let details: ResolutionDetails<number>;
givenAnOpenfeatureClientIsRegistered(given);
when(
/^a string flag with key "(.*)" is evaluated as an integer, with details and a default value (\d+)$/,
(key: string, defaultValue: string) => {
flagKey = key;
fallbackValue = Number.parseInt(defaultValue);
details = client.getNumberDetails(flagKey, Number.parseInt(defaultValue));
},
);
then(/^the default integer value should be returned$/, () => {
expect(details.value).toEqual(fallbackValue);
});
and(
/^the reason should indicate an error and the error code should indicate a type mismatch with "(.*)"$/,
(errorCode: string) => {
expect(details.reason).toEqual(StandardResolutionReasons.ERROR);
expect(details.errorCode).toEqual(errorCode);
},
);
});
});

View File

@ -0,0 +1,364 @@
import type { StepDefinitions } from 'jest-cucumber';
import type { EvaluationContext, EvaluationDetails, FlagValue, JsonObject } from '@openfeature/web-sdk';
import { OpenFeature, ProviderEvents, StandardResolutionReasons } from '@openfeature/web-sdk';
import { E2E_CLIENT_NAME } from '@openfeature/flagd-core';
export const flagStepDefinitions: StepDefinitions = ({ given, and, when, then }) => {
let flagKey: string;
let value: FlagValue;
let details: EvaluationDetails<FlagValue>;
let fallback: FlagValue;
let context: EvaluationContext;
const client = OpenFeature.getClient(E2E_CLIENT_NAME);
beforeAll((done) => {
client.addHandler(ProviderEvents.Ready, () => {
done();
});
});
given('a stable provider', () => undefined);
given('a flagd provider is set', () => undefined);
when(
/^a boolean flag with key "(.*)" is evaluated with default value "(.*)"$/,
(key: string, defaultValue: string) => {
flagKey = key;
fallback = defaultValue;
value = client.getBooleanValue(key, defaultValue === 'true');
},
);
then(/^the resolved boolean value should be "(.*)"$/, (expectedValue: string) => {
expect(value).toEqual(expectedValue === 'true');
});
when(
/^a string flag with key "(.*)" is evaluated with default value "(.*)"$/,
(key: string, defaultValue: string) => {
flagKey = key;
fallback = defaultValue;
value = client.getStringValue(key, defaultValue);
},
);
then(/^the resolved string value should be "(.*)"$/, (expectedValue: string) => {
expect(value).toEqual(expectedValue);
});
when(
/^an integer flag with key "(.*)" is evaluated with default value (\d+)$/,
async (key: string, defaultValue: string) => {
flagKey = key;
fallback = Number(defaultValue);
value = client.getNumberValue(key, Number.parseInt(defaultValue));
},
);
then(/^the resolved integer value should be (\d+)$/, (expectedValue: string) => {
expect(value).toEqual(Number.parseInt(expectedValue));
});
when(
/^a float flag with key "(.*)" is evaluated with default value (\d+\.?\d*)$/,
async (key: string, defaultValue: string) => {
flagKey = key;
fallback = Number(defaultValue);
value = client.getNumberValue(key, Number.parseFloat(defaultValue));
},
);
then(/^the resolved float value should be (\d+\.?\d*)$/, (expectedValue: string) => {
expect(value).toEqual(Number.parseFloat(expectedValue));
});
when(/^an object flag with key "(.*)" is evaluated with a null default value$/, async (key: string) => {
const defaultValue = {};
flagKey = key;
fallback = '';
value = client.getObjectValue(key, defaultValue);
});
and(/^a flag with key "(.*)" is evaluated with default value "(.*)"$/, async (key, defaultValue) => {
await OpenFeature.setContext(context);
flagKey = key;
fallback = defaultValue;
value = client.getStringValue(flagKey, fallback as string);
});
when(
/^context contains keys "(.*)", "(.*)", "(.*)", "(.*)" with values "(.*)", "(.*)", (\d+), "(.*)"$/,
(key0, key1, key2, key3, stringVal1, stringVal2, intVal, boolVal) => {
context = {
[key0]: stringVal1,
[key1]: stringVal2,
[key2]: Number.parseInt(intVal),
[key3]: boolVal === true,
};
},
);
and(/^the resolved flag value is "(.*)" when the context is empty$/, async (expectedValue) => {
context = {};
await OpenFeature.setContext(context);
value = client.getStringValue(flagKey, fallback as string);
expect(value).toEqual(expectedValue);
});
then(
/^the resolved object value should be contain fields "(.*)", "(.*)", and "(.*)", with values "(.*)", "(.*)" and (\d+), respectively$/,
(field1: string, field2: string, field3: string, boolValue: string, stringValue: string, intValue: string) => {
const jsonObject = value as JsonObject;
expect(jsonObject[field1]).toEqual(boolValue === 'true');
expect(jsonObject[field2]).toEqual(stringValue);
expect(jsonObject[field3]).toEqual(Number.parseInt(intValue));
},
);
when(
/^a boolean flag with key "(.*)" is evaluated with details and default value "(.*)"$/,
async (key: string, defaultValue: string) => {
flagKey = key;
fallback = defaultValue;
details = client.getBooleanDetails(key, defaultValue === 'true');
},
);
then(
/^the resolved boolean details value should be "(.*)", the variant should be "(.*)", and the reason should be "(.*)"$/,
(expectedValue: string, expectedVariant: string, expectedReason: string) => {
expect(details).toBeDefined();
expect(details.value).toEqual(expectedValue === 'true');
expect(details.variant).toEqual(expectedVariant);
expect(details.reason).toEqual(expectedReason);
},
);
when(
/^a string flag with key "(.*)" is evaluated with details and default value "(.*)"$/,
(key: string, defaultValue: string) => {
flagKey = key;
fallback = defaultValue;
details = client.getStringDetails(key, defaultValue);
},
);
then(
/^the resolved string details value should be "(.*)", the variant should be "(.*)", and the reason should be "(.*)"$/,
(expectedValue: string, expectedVariant: string, expectedReason: string) => {
expect(details).toBeDefined();
expect(details.value).toEqual(expectedValue);
expect(details.variant).toEqual(expectedVariant);
expect(details.reason).toEqual(expectedReason);
},
);
when(
/^an integer flag with key "(.*)" is evaluated with details and default value (\d+)$/,
(key: string, defaultValue: string) => {
flagKey = key;
fallback = defaultValue;
details = client.getNumberDetails(key, Number.parseInt(defaultValue));
},
);
then(
/^the resolved integer details value should be (\d+), the variant should be "(.*)", and the reason should be "(.*)"$/,
(expectedValue: string, expectedVariant: string, expectedReason: string) => {
expect(details).toBeDefined();
expect(details.value).toEqual(Number.parseInt(expectedValue));
expect(details.variant).toEqual(expectedVariant);
expect(details.reason).toEqual(expectedReason);
},
);
when(
/^a float flag with key "(.*)" is evaluated with details and default value (\d+\.?\d*)$/,
(key: string, defaultValue: string) => {
flagKey = key;
fallback = defaultValue;
details = client.getNumberDetails(key, Number.parseFloat(defaultValue));
},
);
then(
/^the resolved float details value should be (\d+\.?\d*), the variant should be "(.*)", and the reason should be "(.*)"$/,
(expectedValue: string, expectedVariant: string, expectedReason: string) => {
expect(details).toBeDefined();
expect(details.value).toEqual(Number.parseFloat(expectedValue));
expect(details.variant).toEqual(expectedVariant);
expect(details.reason).toEqual(expectedReason);
},
);
when(/^an object flag with key "(.*)" is evaluated with details and a null default value$/, (key: string) => {
flagKey = key;
fallback = {};
details = client.getObjectDetails(key, {});
});
then(
/^the resolved object details value should be contain fields "(.*)", "(.*)", and "(.*)", with values "(.*)", "(.*)" and (\d+), respectively$/,
(field1: string, field2: string, field3: string, boolValue: string, stringValue: string, intValue: string) => {
expect(details).toBeDefined();
const jsonObject = details.value as JsonObject;
expect(jsonObject[field1]).toEqual(boolValue === 'true');
expect(jsonObject[field2]).toEqual(stringValue);
expect(jsonObject[field3]).toEqual(Number.parseInt(intValue));
},
);
and(
/^the variant should be "(.*)", and the reason should be "(.*)"$/,
(expectedVariant: string, expectedReason: string) => {
expect(details).toBeDefined();
expect(details.variant).toEqual(expectedVariant);
expect(details.reason).toEqual(expectedReason);
},
);
then(/^the resolved string response should be "(.*)"$/, (expectedValue: string) => {
expect(value).toEqual(expectedValue);
});
when(
/^a non-existent string flag with key "(.*)" is evaluated with details and a default value "(.*)"$/,
(key: string, defaultValue: string) => {
flagKey = key;
fallback = defaultValue;
details = client.getStringDetails(flagKey, defaultValue);
},
);
then(/^the default string value should be returned$/, () => {
expect(details).toBeDefined();
expect(details.value).toEqual(fallback);
});
and(
/^the reason should indicate an error and the error code should indicate a missing flag with "(.*)"$/,
(errorCode: string) => {
expect(details).toBeDefined();
expect(details.reason).toEqual(StandardResolutionReasons.ERROR);
expect(details.errorCode).toEqual(errorCode);
},
);
when(
/^a string flag with key "(.*)" is evaluated as an integer, with details and a default value (\d+)$/,
(key: string, defaultValue: string) => {
flagKey = key;
fallback = Number.parseInt(defaultValue);
details = client.getNumberDetails(flagKey, Number.parseInt(defaultValue));
},
);
then(/^the default integer value should be returned$/, () => {
expect(details).toBeDefined();
expect(details.value).toEqual(fallback);
});
and(
/^the reason should indicate an error and the error code should indicate a type mismatch with "(.*)"$/,
(errorCode: string) => {
expect(details).toBeDefined();
expect(details.reason).toEqual(StandardResolutionReasons.ERROR);
expect(details.errorCode).toEqual(errorCode);
},
);
let ran: boolean;
when('a PROVIDER_READY handler is added', () => {
client.addHandler(ProviderEvents.Ready, async () => {
ran = true;
});
});
then('the PROVIDER_READY handler must run', () => {
expect(ran).toBeTruthy();
});
when('a PROVIDER_CONFIGURATION_CHANGED handler is added', () => {
client.addHandler(ProviderEvents.ConfigurationChanged, async () => {
// file writes are not atomic, so we get a few events in quick succession from the testbed
// some will not contain changes, this tolerates that; at least 1 should have our change
// TODO: enable this for testing of issue
//if (details?.flagsChanged?.length) {
// flagsChanged = details?.flagsChanged;
// ran = true;
//}
// TODO: remove this for testing of issue
ran = true;
});
});
and(/^a flag with key "(.*)" is modified$/, async () => {
// this happens every 1s in the associated container, so wait 3s
await new Promise((resolve) => setTimeout(resolve, 3000));
});
then('the PROVIDER_CONFIGURATION_CHANGED handler must run', async () => {
expect(ran).toBeTruthy();
});
and(/^the event details must indicate "(.*)" was altered$/, () => {
// TODO: enable this for testing of issue
//expect(flagsChanged).toContain(flagName);
});
when(
/^a zero-value boolean flag with key "(.*)" is evaluated with default value "(.*)"$/,
(key, defaultVal: string) => {
flagKey = key;
fallback = defaultVal === 'true';
},
);
then(/^the resolved boolean zero-value should be "(.*)"$/, (expectedVal: string) => {
const expectedValue = expectedVal === 'true';
const value = client.getBooleanValue(flagKey, fallback as boolean);
expect(value).toEqual(expectedValue);
});
when(/^a zero-value string flag with key "(.*)" is evaluated with default value "(.*)"$/, (key, defaultVal) => {
flagKey = key;
fallback = defaultVal;
});
then('the resolved string zero-value should be ""', () => {
const value = client.getStringValue(flagKey, fallback as string);
expect(value).toEqual('');
});
when(/^a zero-value integer flag with key "(.*)" is evaluated with default value (\d+)$/, (key, defaultVal) => {
flagKey = key;
fallback = defaultVal;
});
then(/^the resolved integer zero-value should be (\d+)$/, (expectedValueString) => {
const expectedValue = Number.parseInt(expectedValueString);
const value = client.getNumberValue(flagKey, fallback as number);
expect(value).toEqual(expectedValue);
});
when(
/^a zero-value float flag with key "(.*)" is evaluated with default value (\d+\.\d+)$/,
(key, defaultValueString) => {
flagKey = key;
fallback = Number.parseFloat(defaultValueString);
},
);
then(/^the resolved float zero-value should be (\d+\.\d+)$/, (expectedValueString) => {
const expectedValue = Number.parseFloat(expectedValueString);
const value = client.getNumberValue(flagKey, fallback as number);
expect(value).toEqual(expectedValue);
});
then(/^the returned reason should be "(.*)"$/, (expectedReason) => {
expect(details.reason).toEqual(expectedReason);
});
};

View File

@ -0,0 +1 @@
export * from './flag';

View File

@ -0,0 +1,52 @@
import assert from 'assert';
import { OpenFeature } from '@openfeature/web-sdk';
import type { StartedTestContainer } from 'testcontainers';
import { GenericContainer } from 'testcontainers';
import { FlagdWebProvider } from '../../lib/flagd-web-provider';
import { autoBindSteps, loadFeature } from 'jest-cucumber';
import { FLAGD_NAME, GHERKIN_EVALUATION_FEATURE } from '../constants';
import { flagStepDefinitions } from '../step-definitions';
import { E2E_CLIENT_NAME, IMAGE_VERSION } from '@openfeature/flagd-core';
// register the flagd provider before the tests.
async function setup() {
const containers: StartedTestContainer[] = [];
console.log('Setting flagd provider...');
const stable = await new GenericContainer(`ghcr.io/open-feature/flagd-testbed:${IMAGE_VERSION}`)
.withExposedPorts(8013)
.start();
containers.push(stable);
const flagdWebProvider = new FlagdWebProvider({
host: stable.getHost(),
port: stable.getMappedPort(8013),
tls: false,
maxRetries: -1,
});
await OpenFeature.setProviderAndWait(E2E_CLIENT_NAME, flagdWebProvider);
assert(
OpenFeature.getProviderMetadata(E2E_CLIENT_NAME).name === FLAGD_NAME,
new Error(
`Expected ${E2E_CLIENT_NAME} provider to be configured, instead got: ${OpenFeature.providerMetadata.name}`,
),
);
console.log('flagd provider configured!');
return containers;
}
describe('web provider', () => {
let containers: StartedTestContainer[] = [];
beforeAll(async () => {
containers = await setup();
}, 60000);
afterAll(async () => {
await OpenFeature.close();
for (const container of containers) {
container.stop();
}
});
const features = [loadFeature(GHERKIN_EVALUATION_FEATURE)];
autoBindSteps(features, [flagStepDefinitions]);
});

View File

@ -1,11 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"lib": ["ES2015", "DOM"],
"outDir": "../../../dist/out-tsc",
"declaration": true,
"types": ["jest"],
"allowSyntheticDefaultImports": true,
"allowJs" :true
}
}

View File

@ -1,17 +1,11 @@
import { CallbackClient, Code, ConnectError, PromiseClient } from '@connectrpc/connect';
import type { CallbackClient, ConnectError, PromiseClient } from '@connectrpc/connect';
import { Code } from '@connectrpc/connect';
import { Struct } from '@bufbuild/protobuf';
import {
Client,
ErrorCode,
JsonValue,
OpenFeature,
ProviderEvents,
ProviderStatus,
StandardResolutionReasons,
} from '@openfeature/web-sdk';
import type { Client, JsonValue } from '@openfeature/web-sdk';
import { ErrorCode, OpenFeature, ProviderEvents, StandardResolutionReasons } from '@openfeature/web-sdk';
import fetchMock from 'jest-fetch-mock';
import { Service } from '../proto/ts/flagd/evaluation/v1/evaluation_connect';
import { AnyFlag, EventStreamResponse, ResolveAllResponse } from '../proto/ts/flagd/evaluation/v1/evaluation_pb';
import type { Service } from '../proto/ts/flagd/evaluation/v1/evaluation_connect';
import type { AnyFlag, EventStreamResponse, ResolveAllResponse } from '../proto/ts/flagd/evaluation/v1/evaluation_pb';
import { FlagdWebProvider } from './flagd-web-provider';
const EVENT_CONFIGURATION_CHANGE = 'configuration_change';
@ -193,21 +187,12 @@ describe(FlagdWebProvider.name, () => {
describe(ProviderEvents.Ready, () => {
it('should fire as soon as client subscribes, if ready', (done) => {
try {
// should start NOT_READY
expect(provider.status).toEqual(ProviderStatus.NOT_READY);
done();
} catch (err) {
done(err);
}
mockCallbackClient.mockMessage({
type: EVENT_PROVIDER_READY,
});
client.addHandler(ProviderEvents.Ready, () => {
try {
expect(provider.status).toEqual(ProviderStatus.READY);
done();
} catch (err) {
done(err);
@ -216,22 +201,14 @@ describe(FlagdWebProvider.name, () => {
});
it('should fire and be ready if message received', (done) => {
try {
// should start NOT_READY
expect(provider.status).toEqual(ProviderStatus.NOT_READY);
done();
} catch (err) {
done(err);
}
client.addHandler(ProviderEvents.Ready, () => {
try {
expect(provider.status).toEqual(ProviderStatus.READY);
done();
} catch (err) {
done(err);
}
});
mockCallbackClient.mockMessage({
type: EVENT_PROVIDER_READY,
});
@ -269,9 +246,9 @@ describe(FlagdWebProvider.name, () => {
describe(ProviderEvents.Error, () => {
it('should fire if message received', (done) => {
client.addHandler(ProviderEvents.Error, () => {
client.addHandler(ProviderEvents.Error, (event) => {
try {
expect(provider.status).toEqual(ProviderStatus.ERROR);
expect(event?.providerName).toBe('flagd');
done();
} catch (err) {
done(err);

View File

@ -1,24 +1,27 @@
import { CallbackClient, createCallbackClient, createPromiseClient, PromiseClient } from '@connectrpc/connect';
import type { CallbackClient, PromiseClient } from '@connectrpc/connect';
import { createCallbackClient, createPromiseClient } from '@connectrpc/connect';
import { createConnectTransport } from '@connectrpc/connect-web';
import { Struct } from '@bufbuild/protobuf';
import {
import type {
EvaluationContext,
FlagNotFoundError,
FlagValue,
JsonValue,
Logger,
Provider,
ResolutionDetails,
} from '@openfeature/web-sdk';
import {
FlagNotFoundError,
OpenFeature,
OpenFeatureEventEmitter,
Provider,
ProviderEvents,
ProviderStatus,
ResolutionDetails,
StandardResolutionReasons,
TypeMismatchError,
} from '@openfeature/web-sdk';
import { Service } from '../proto/ts/flagd/evaluation/v1/evaluation_connect';
import { AnyFlag } from '../proto/ts/flagd/evaluation/v1/evaluation_pb';
import { FlagdProviderOptions, getOptions } from './options';
import type { AnyFlag } from '../proto/ts/flagd/evaluation/v1/evaluation_pb';
import type { FlagdProviderOptions } from './options';
import { getOptions } from './options';
export const ERROR_DISABLED = 'DISABLED';
@ -31,10 +34,12 @@ type AnyFlagResolutionType = typeof AnyFlag.prototype.value.case;
export class FlagdWebProvider implements Provider {
metadata = {
name: 'flagd-web',
name: 'flagd',
};
private _status = ProviderStatus.NOT_READY;
readonly runsOn = 'client';
readonly events = new OpenFeatureEventEmitter();
private _connected = false;
private _promiseClient: PromiseClient<typeof Service>;
private _callbackClient: CallbackClient<typeof Service>;
@ -64,12 +69,6 @@ export class FlagdWebProvider implements Provider {
this._logger = logger;
}
get status() {
return this._status;
}
events = new OpenFeatureEventEmitter();
async onContextChange(oldContext: EvaluationContext, newContext: EvaluationContext): Promise<void> {
await this.fetchAll(newContext);
}
@ -112,6 +111,7 @@ export class FlagdWebProvider implements Provider {
reason: this._connected ? resolved.reason : StandardResolutionReasons.CACHED,
variant: resolved.variant,
value: resolved.value as T,
flagMetadata: resolved.flagMetadata,
};
}
@ -129,7 +129,6 @@ export class FlagdWebProvider implements Provider {
case EVENT_PROVIDER_READY:
this.fetchAll(currentContext).then(() => {
this.resetConnectionState();
this._status = ProviderStatus.READY;
resolve();
});
return;
@ -142,8 +141,7 @@ export class FlagdWebProvider implements Provider {
}
},
(err) => {
this._status = ProviderStatus.ERROR;
this._logger?.error(`${FlagdWebProvider.name}: could not establish connection to flagd, ${err?.message}`);
this._logger?.error(`${FlagdWebProvider.name}: could not establish connection, ${err?.message}`);
this._logger?.debug(err?.stack);
if (this._retry < this._maxRetries) {
this._retry++;
@ -160,12 +158,12 @@ export class FlagdWebProvider implements Provider {
private async fetchAll(context: EvaluationContext) {
const transformedContext = this.transformContext(context);
const allResolved = await this._promiseClient.resolveAll({ context: transformedContext });
this._flags = Object.keys(allResolved.flags).reduce((accumuated, currentKey) => {
this._flags = Object.keys(allResolved.flags).reduce((accumulated, currentKey) => {
const resolved = allResolved.flags[currentKey];
// reducer to store the resolved bulk response in a map of ResolutionDetails,
// with an addition annotation for the type (typeof AnyFlag.prototype.value.case)
return {
...accumuated,
...accumulated,
[currentKey]: {
type: resolved.value.case,
reason: resolved.reason,

View File

@ -9,8 +9,17 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true
},
"files": [],
"include": []
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

View File

@ -5,9 +5,8 @@
"outDir": "../../../dist/out-tsc",
"declaration": true,
"types": [],
"allowSyntheticDefaultImports": true,
"allowSyntheticDefaultImports": true
},
"include": ["**/*.ts"],
"exclude": ["jest.config.ts", "**/*.spec.ts", "**/*.test.ts"]
"exclude": ["jest.config.ts", "**/*.spec.ts", "**/*.test.ts", "src/e2e/"]
}

View File

@ -4,7 +4,7 @@
"outDir": "../../../dist/out-tsc",
"module": "commonjs",
"types": ["jest"],
"allowJs": true,
"allowJs": true
},
"include": ["jest.config.ts", "**/*.test.ts", "**/*.spec.ts", "**/*.d.ts"]
"include": ["jest.config.ts", "**/*.test.ts", "**/*.spec.ts", "**/*.d.ts", "./src/e2e/"]
}

View File

@ -1,6 +1,6 @@
{
"extends": ["../../../.eslintrc.json"],
"ignorePatterns": ["!**/*", "node_modules", "src/proto/**"],
"ignorePatterns": ["!**/*", "node_modules", "src/proto/**", "schemas/**", "spec/**"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],

View File

@ -1,5 +1,69 @@
# Changelog
## [0.13.3](https://github.com/open-feature/js-sdk-contrib/compare/flagd-provider-v0.13.2...flagd-provider-v0.13.3) (2025-02-07)
### ✨ New Features
* support proxy routing via gRPC default_authority ([#1202](https://github.com/open-feature/js-sdk-contrib/issues/1202)) ([4e0db2a](https://github.com/open-feature/js-sdk-contrib/commit/4e0db2abda828151603ab943c0046536c8d2aa8d))
### 🧹 Chore
* **deps:** update libs/providers/flagd/schemas digest to bb76343 ([#1168](https://github.com/open-feature/js-sdk-contrib/issues/1168)) ([2304efe](https://github.com/open-feature/js-sdk-contrib/commit/2304efebe4c05672b479e983adba1526239b1a1d))
* **deps:** update libs/providers/flagd/spec digest to 5b07065 ([#1189](https://github.com/open-feature/js-sdk-contrib/issues/1189)) ([215c983](https://github.com/open-feature/js-sdk-contrib/commit/215c983d7c71c0f9d4e711d3e359171a36b7a59a))
## [0.13.2](https://github.com/open-feature/js-sdk-contrib/compare/flagd-provider-v0.13.1...flagd-provider-v0.13.2) (2025-01-10)
### ✨ New Features
* **flagd:** add flag metadata ([#1151](https://github.com/open-feature/js-sdk-contrib/issues/1151)) ([b1c6d23](https://github.com/open-feature/js-sdk-contrib/commit/b1c6d235565f6cce02519d7c08bb6ad2dd791332))
### 🧹 Chore
* **deps:** update dependency @grpc/grpc-js to ~1.8.0 || ~1.9.0 || ~1.10.0 || ~1.11.0 ([#1023](https://github.com/open-feature/js-sdk-contrib/issues/1023)) ([f2247d3](https://github.com/open-feature/js-sdk-contrib/commit/f2247d3adfa33b0b5bcc7d07184d6c6bca534ee6))
* **deps:** update dependency @grpc/grpc-js to ~1.8.0 || ~1.9.0 || ~1.10.0 || ~1.11.0 || ~1.12.0 ([#1064](https://github.com/open-feature/js-sdk-contrib/issues/1064)) ([1e8eca8](https://github.com/open-feature/js-sdk-contrib/commit/1e8eca8409e4e09196b9044a97268972ddd66c2f))
* **deps:** update ghcr.io/open-feature/flagd-testbed docker tag to v0.5.13 ([#1068](https://github.com/open-feature/js-sdk-contrib/issues/1068)) ([75c5b10](https://github.com/open-feature/js-sdk-contrib/commit/75c5b10feec2165c6f2f176bcde011e78f9791d0))
* **deps:** update ghcr.io/open-feature/flagd-testbed docker tag to v0.5.20 ([#1137](https://github.com/open-feature/js-sdk-contrib/issues/1137)) ([f5f110c](https://github.com/open-feature/js-sdk-contrib/commit/f5f110c1ac41cd1a556de5aaa4c198f4fcba3ccf))
* **deps:** update ghcr.io/open-feature/flagd-testbed docker tag to v0.5.21 ([#1155](https://github.com/open-feature/js-sdk-contrib/issues/1155)) ([1150c9b](https://github.com/open-feature/js-sdk-contrib/commit/1150c9b818fc9e9a2fac542bca08377cdb90211e))
* **deps:** update ghcr.io/open-feature/flagd-testbed-unstable docker tag to v0.5.13 ([#1073](https://github.com/open-feature/js-sdk-contrib/issues/1073)) ([bfadf65](https://github.com/open-feature/js-sdk-contrib/commit/bfadf65eebf8002bf4761a7ae223027295cc545c))
* **deps:** update ghcr.io/open-feature/flagd-testbed-unstable docker tag to v0.5.20 ([#1138](https://github.com/open-feature/js-sdk-contrib/issues/1138)) ([d54c9a9](https://github.com/open-feature/js-sdk-contrib/commit/d54c9a938c5456d7e1bde684cf6bfed8b08a759d))
* **deps:** update ghcr.io/open-feature/flagd-testbed-unstable docker tag to v0.5.21 ([#1159](https://github.com/open-feature/js-sdk-contrib/issues/1159)) ([4e0c983](https://github.com/open-feature/js-sdk-contrib/commit/4e0c9839cc631f62d29778005bb5751b5e770f9e))
* **deps:** update ghcr.io/open-feature/sync-testbed docker tag to v0.5.13 ([#1074](https://github.com/open-feature/js-sdk-contrib/issues/1074)) ([194bafb](https://github.com/open-feature/js-sdk-contrib/commit/194bafbabcdea7e02001754377ba72c16d10d759))
* **deps:** update ghcr.io/open-feature/sync-testbed docker tag to v0.5.20 ([#1139](https://github.com/open-feature/js-sdk-contrib/issues/1139)) ([401b310](https://github.com/open-feature/js-sdk-contrib/commit/401b3102ffe9803584344ad50ff77ceed7ecf472))
* **deps:** update ghcr.io/open-feature/sync-testbed docker tag to v0.5.21 ([#1160](https://github.com/open-feature/js-sdk-contrib/issues/1160)) ([45a9b03](https://github.com/open-feature/js-sdk-contrib/commit/45a9b030e15efe8121fa05aca58fec76b2c63b84))
* **deps:** update ghcr.io/open-feature/sync-testbed-unstable docker tag to v0.5.13 ([#1075](https://github.com/open-feature/js-sdk-contrib/issues/1075)) ([3a97b36](https://github.com/open-feature/js-sdk-contrib/commit/3a97b36bf77b813effbf80d00e4a87b54e7de320))
* **deps:** update libs/providers/flagd/schemas digest to 37baa2c ([#1149](https://github.com/open-feature/js-sdk-contrib/issues/1149)) ([f3c2c8e](https://github.com/open-feature/js-sdk-contrib/commit/f3c2c8efc30dc3c55c87446b30b4bb462b0220a0))
* **deps:** update libs/providers/flagd/schemas digest to b81a56e ([#1132](https://github.com/open-feature/js-sdk-contrib/issues/1132)) ([cd069da](https://github.com/open-feature/js-sdk-contrib/commit/cd069da13aecec9abe44113802d70a44dfebc8d6))
* **deps:** update libs/providers/flagd/spec digest to d261f68 ([#1150](https://github.com/open-feature/js-sdk-contrib/issues/1150)) ([4366710](https://github.com/open-feature/js-sdk-contrib/commit/43667107c1e9202b0dbcb2e44abbf5de0feb1e06))
* **deps:** update libs/providers/flagd/spec digest to ed0f9ef ([#1133](https://github.com/open-feature/js-sdk-contrib/issues/1133)) ([938d3a1](https://github.com/open-feature/js-sdk-contrib/commit/938d3a108488016df14d82abaa8a2c400bbc75f3))
* removing build dependencies and using testcontainers for container spin up ([#982](https://github.com/open-feature/js-sdk-contrib/issues/982)) ([2d64331](https://github.com/open-feature/js-sdk-contrib/commit/2d6433101b76ba9ad266095fe31b58314f82a105))
* update nx packages ([#1147](https://github.com/open-feature/js-sdk-contrib/issues/1147)) ([7f310fe](https://github.com/open-feature/js-sdk-contrib/commit/7f310fe87101b8aa793e1436e63c7602ccc202e3))
* various gherkin improvements for e2e tests ([#1008](https://github.com/open-feature/js-sdk-contrib/issues/1008)) ([40abd8e](https://github.com/open-feature/js-sdk-contrib/commit/40abd8eca76b47bb5c084b377302821968acd19c))
## [0.13.1](https://github.com/open-feature/js-sdk-contrib/compare/flagd-provider-v0.13.0...flagd-provider-v0.13.1) (2024-07-08)
### 🐛 Bug Fixes
* **deps:** update dependency @openfeature/flagd-core to ~0.2.0 ([#880](https://github.com/open-feature/js-sdk-contrib/issues/880)) ([f15909f](https://github.com/open-feature/js-sdk-contrib/commit/f15909ff6fb32fd423233ad77d57f21b265ec61a))
### ✨ New Features
* Change fractional custom op from percentage-based to relative weighting. [#946](https://github.com/open-feature/js-sdk-contrib/issues/946) ([#954](https://github.com/open-feature/js-sdk-contrib/issues/954)) ([0e9bc84](https://github.com/open-feature/js-sdk-contrib/commit/0e9bc842cf09de12e8445dcb4e0e8b3623c66099))
* Default port to 8015 if in-process resolver is used. [#936](https://github.com/open-feature/js-sdk-contrib/issues/936) ([#937](https://github.com/open-feature/js-sdk-contrib/issues/937)) ([53c4077](https://github.com/open-feature/js-sdk-contrib/commit/53c4077f84a1976d69c3846a0049619a1dfa6607))
### 🧹 Chore
* fix e2e test ([#977](https://github.com/open-feature/js-sdk-contrib/issues/977)) ([29a6735](https://github.com/open-feature/js-sdk-contrib/commit/29a673553f93ecae1adcec0d3d23a6e77363d3f5))
* fix fractional tests ([#984](https://github.com/open-feature/js-sdk-contrib/issues/984)) ([6a54935](https://github.com/open-feature/js-sdk-contrib/commit/6a54935f3bbff99d1abc8599f667cda7b0a6efe4))
* loosen some test assertions, fix e2e matcher ([#933](https://github.com/open-feature/js-sdk-contrib/issues/933)) ([8def607](https://github.com/open-feature/js-sdk-contrib/commit/8def6072c5d29eaf81d7262b6878cb3d6ff40483))
* remove explicit dep, use root ([#917](https://github.com/open-feature/js-sdk-contrib/issues/917)) ([a8c0be1](https://github.com/open-feature/js-sdk-contrib/commit/a8c0be1810a4baef62fcd453a57acd3edd3155d0))
## [0.13.0](https://github.com/open-feature/js-sdk-contrib/compare/flagd-provider-v0.12.0...flagd-provider-v0.13.0) (2024-04-17)

View File

@ -38,6 +38,7 @@ Options can be defined in the constructor or as environment variables. Construct
| selector | FLAGD_SOURCE_SELECTOR | string | - | |
| cache | FLAGD_CACHE | string | lru | lru, disabled |
| maxCacheSize | FLAGD_MAX_CACHE_SIZE | int | 1000 | |
| defaultAuthority | FLAGD_DEFAULT_AUTHORITY | string | - | rpc, in-process |
#### Resolver type-specific Defaults
@ -61,7 +62,7 @@ In the above example, the provider expects flagd to be available at `localhost:8
Alternatively, you can use socket paths to connect to flagd.
```
```ts
OpenFeature.setProvider(new FlagdProvider({
socketPath: "/tmp/flagd.socks",
}))
@ -72,7 +73,7 @@ Alternatively, you can use socket paths to connect to flagd.
This mode performs flag evaluations locally (in-process).
Flag configurations for evaluation are obtained via gRPC protocol using [sync protobuf schema](https://buf.build/open-feature/flagd/file/main:sync/v1/sync_service.proto) service definition.
```
```ts
OpenFeature.setProvider(new FlagdProvider({
resolverType: 'in-process',
}))
@ -83,7 +84,7 @@ In the above example, the provider expects a flag sync service implementation to
In-process resolver can also work in an offline mode.
To enable this mode, you should provide a valid flag configuration file with the option `offlineFlagSourcePath`.
```
```ts
OpenFeature.setProvider(new FlagdProvider({
resolverType: 'in-process',
offlineFlagSourcePath: './flags.json',
@ -93,6 +94,18 @@ To enable this mode, you should provide a valid flag configuration file with the
Offline mode uses `fs.watchFile` and polls every 5 seconds for changes to the file.
This mode is useful for local development, test cases, and for offline applications.
### Default Authority usage (optional)
This is useful for complex routing or service-discovery use cases that involve a proxy (e.g., Envoy).
Please refer to this [GitHub issue](https://github.com/open-feature/js-sdk-contrib/issues/1187) for more information.
```ts
OpenFeature.setProvider(new FlagdProvider({
resolverType: 'in-process',
defaultAuthority: 'b-target-api.service',
}))
```
### Supported Events
The flagd provider emits `PROVIDER_READY`, `PROVIDER_ERROR` and `PROVIDER_CONFIGURATION_CHANGED` events.
@ -107,9 +120,8 @@ For general information on events, see the [official documentation](https://open
### Flag Metadata
| Field | Type | Value |
| ------- | ------ | ------------------------------------------------- |
| `scope` | string | "selector" set for the associated source in flagd |
[Flag metadata](https://flagd.dev/reference/flag-definitions/#metadata) is a set of key-value pairs that can be associated with a flag.
The values come from the flag definition in flagd.
## Building

View File

@ -1,17 +0,0 @@
services:
flagd:
image: ghcr.io/open-feature/flagd-testbed:v0.5.4
ports:
- 8013:8013
flagd-unstable:
image: ghcr.io/open-feature/flagd-testbed-unstable:v0.5.4
ports:
- 8014:8013
flagd-sync:
image: ghcr.io/open-feature/sync-testbed:v0.5.4
ports:
- 9090:9090
flagd-sync-unstable:
image: ghcr.io/open-feature/sync-testbed-unstable:v0.5.4
ports:
- 9091:9090

@ -1 +0,0 @@
Subproject commit efcbf72d34593be47e03ea920b77db29050e47eb

View File

@ -1,12 +1,17 @@
{
"name": "@openfeature/flagd-provider",
"version": "0.13.0",
"version": "0.13.3",
"license": "Apache-2.0",
"scripts": {
"publish-if-not-exists": "cp $NPM_CONFIG_USERCONFIG .npmrc && if [ \"$(npm show $npm_package_name@$npm_package_version version)\" = \"$(npm run current-version -s)\" ]; then echo 'already published, skipping'; else npm publish --access public; fi",
"current-version": "echo $npm_package_version"
},
"peerDependencies": {
"@grpc/grpc-js": "~1.8.0 || ~1.9.0 || ~1.10.0",
"@openfeature/server-sdk": "^1.13.0"
"@grpc/grpc-js": "~1.8.0 || ~1.9.0 || ~1.10.0 || ~1.11.0 || ~1.12.0 || ~1.13.0",
"@openfeature/server-sdk": "^1.17.0"
},
"dependencies": {
"lru-cache": "^11.0.0",
"@openfeature/flagd-core": "^1.0.0"
}
}
}

Some files were not shown because too many files have changed in this diff Show More