@ogcio/building-blocks-sdk 0.0.10 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -162,3 +162,58 @@ To fix the formatting and linting errors run:
162
162
  npm run fix:formatting
163
163
  npm run fix:linting
164
164
  ```
165
+
166
+ ### Testing
167
+
168
+ The project uses `vitest` for testing. To run the tests:
169
+
170
+ ```bash
171
+ npm test
172
+ ```
173
+
174
+ ## Feature Flags
175
+
176
+ ### Pre-requisites
177
+ For local development, you should have the Feature Flags service running.
178
+ Refer to the [unleash](https://github.com/ogcio/unleash) repository for instructions on how to run the service.
179
+ You can also find examples on how to run Unleash with different auth strategies in the [unleash-examples](https://github.com/ogcio/unleash-examples/tree/feat/oidc-auth) repository.
180
+
181
+ ### Usage
182
+
183
+ To use the Feature Flags service you will need:
184
+
185
+ - `baseUrl` A valid `Feature Flags` service URL. Use `http://localhost:4242` for local development.
186
+ - `token` A valid `Feature Flags` service token. Refer to [Client Tokens](https://docs.getunleash.io/reference/api-tokens-and-client-keys#client-tokens)
187
+ for instructions on how to generate a token.
188
+
189
+ Initialize the SDK with the `featureFlags` service:
190
+
191
+ ```typescript
192
+ const sdk = getBuildingBlockSDK({
193
+ services: {
194
+ featureFlags: {
195
+ baseUrl,
196
+ },
197
+ },
198
+ getTokenFn: () => token,
199
+ });
200
+ ```
201
+
202
+ Use the `featureFlags` service to check if a feature is enabled (without any context):
203
+
204
+ ```typescript
205
+ const isEnabled = await sdk.featureFlags.isFlagEnabled("feature-name");
206
+ ```
207
+
208
+ Use the `featureFlags` service to check if a feature is enabled with context:
209
+
210
+ ```typescript
211
+ const isEnabled = await sdk.featureFlags.isFlagEnabled("feature-name", {
212
+ userId: "userId",
213
+ sessionId: "sessionId",
214
+ });
215
+ ```
216
+
217
+ *Note*: The `isFlagEnabled` is asynchronous because if the client is not connected yet,
218
+ it will wait for the connection to be established before checking the flag.
219
+ Once the client is connected, the flag will be checked synchronously.
package/package.json CHANGED
@@ -1,11 +1,15 @@
1
1
  {
2
2
  "name": "@ogcio/building-blocks-sdk",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
7
7
  "scripts": {
8
- "test": "echo \"Error: no test specified\" && exit 1",
8
+ "test": "TAP_RCFILE=tap.yml tap --reporter junit --reporter-file results.xml",
9
+ "test:local": "TAP_RCFILE=tap.yml tap",
10
+ "test:e2e": "cd ./e2e && bru run --env local",
11
+ "test:smoke:e2e": "echo \"Error: no test specified\" && exit 0",
12
+ "test:regression:e2e": "cd ./e2e && mkdir -p test-results && bru run --env dev --output ./test-results/results.xml --format junit",
9
13
  "build": "rm -rf dist && tsc -p tsconfig.prod.json",
10
14
  "clients:update": "node --import=tsx src/cli/index.ts clients:update -c src/clients-configurations/clients-configuration.json",
11
15
  "clients:outdated": "node --import=tsx src/cli/index.ts clients:outdated -c src/clients-configurations/clients-configuration.json",
@@ -32,15 +36,22 @@
32
36
  "@types/http-errors": "^2.0.4",
33
37
  "commitlint": "^19.5.0",
34
38
  "husky": "^9.1.6",
39
+ "tap": "^21.0.1",
40
+ "testdouble": "^3.20.2",
41
+ "vitest": "^2.1.3",
35
42
  "tsx": "^4.19.2",
36
43
  "typescript": "^5.6.3"
37
44
  },
38
45
  "peerDependencies": {
39
- "@logto/node": "2.5.5"
46
+ "@logto/node": "2.5.5",
47
+ "unleash-client": "^6.1.1"
40
48
  },
41
49
  "peerDependenciesMeta": {
42
50
  "@logto/node": {
43
51
  "optional": true
52
+ },
53
+ "unleash-client": {
54
+ "optional": true
44
55
  }
45
56
  },
46
57
  "keywords": [],
@@ -0,0 +1,3 @@
1
+ const DEFAULT_PROJECT_ID = "default";
2
+
3
+ export { DEFAULT_PROJECT_ID };
@@ -0,0 +1,63 @@
1
+ import t from "tap";
2
+ import * as td from "testdouble";
3
+ import FeatureFlags from "./index.js";
4
+
5
+ let isEnabled = true;
6
+
7
+ await td.replaceEsm("unleash-client", {
8
+ initialize: td.func(),
9
+ InMemStorageProvider: td.func(),
10
+ });
11
+
12
+ let fetchResponse = {};
13
+
14
+ global.fetch = async () =>
15
+ ({
16
+ ok: true,
17
+ status: 200,
18
+ json: async () => fetchResponse,
19
+ headers: new Headers(),
20
+ }) as Response;
21
+
22
+ t.test("FeatureFlags", async (t) => {
23
+ const baseUrl = "http://fakehost";
24
+ const getTokenFn = () => "test-token";
25
+ let featureFlags: FeatureFlags;
26
+
27
+ t.beforeEach(async () => {
28
+ featureFlags = new FeatureFlags({ baseUrl, getTokenFn });
29
+ });
30
+
31
+ t.test(
32
+ "should initialize unleash client with correct parameters",
33
+ async (t) => {
34
+ t.ok(featureFlags.isConnected);
35
+ },
36
+ );
37
+
38
+ t.test("should return false if flag is not enabled", async (t) => {
39
+ isEnabled = false;
40
+ const result = await featureFlags.isFlagEnabled("test-flag");
41
+ t.equal(result, isEnabled);
42
+ });
43
+
44
+ t.test("should return true if flag is enabled", async (t) => {
45
+ isEnabled = true;
46
+ const result = await featureFlags.isFlagEnabled("test-flag");
47
+ t.equal(result, isEnabled);
48
+ });
49
+
50
+ t.test(
51
+ "should call GET method on client when getFeatureFlags is called",
52
+ async () => {
53
+ fetchResponse = { data: { features: [] }, metadata: {} };
54
+ const result = await featureFlags.getFeatureFlags();
55
+ t.ok(result);
56
+ t.same(result, {
57
+ data: [],
58
+ metadata: {},
59
+ error: null,
60
+ });
61
+ },
62
+ );
63
+ });
@@ -0,0 +1,74 @@
1
+ import type createClient from "openapi-fetch";
2
+ import {
3
+ type Context,
4
+ InMemStorageProvider,
5
+ type Unleash,
6
+ initialize,
7
+ } from "unleash-client";
8
+ import type { BaseApiClientParams } from "../../../types/index.js";
9
+ import { FEATURE_FLAGS } from "../../../types/index.js";
10
+ import BaseClient from "../../base-client.js";
11
+ import { DEFAULT_PROJECT_ID } from "./const.js";
12
+ import type { components, paths } from "./schema.js";
13
+ import { waitForConnection } from "./utils.js";
14
+
15
+ class FeatureFlags extends BaseClient<paths> {
16
+ declare client: ReturnType<typeof createClient<paths>>;
17
+ protected serviceName = FEATURE_FLAGS;
18
+
19
+ private unleashClient: Unleash | null = null;
20
+ public isConnected = false;
21
+
22
+ constructor({ baseUrl, getTokenFn }: BaseApiClientParams) {
23
+ super({ baseUrl, getTokenFn });
24
+ const token = getTokenFn ? (getTokenFn(FEATURE_FLAGS) as string) : "";
25
+ this.unleashClient = initialize({
26
+ appName: this.serviceName,
27
+ url: `${baseUrl}/api`,
28
+ refreshInterval: 1000,
29
+ customHeaders: {
30
+ Authorization: token,
31
+ },
32
+ storageProvider: new InMemStorageProvider(),
33
+ });
34
+ this.unleashClient.on("error", console.error);
35
+ this.unleashClient.on("synchronized", () => {
36
+ this.isConnected = true;
37
+ });
38
+ }
39
+
40
+ async isFlagEnabled(name: string, context?: Context) {
41
+ await this.waitForConnection();
42
+ return this.unleashClient?.isEnabled(name, context, () => false) ?? false;
43
+ }
44
+
45
+ async getFeatureFlags(projectId = DEFAULT_PROJECT_ID) {
46
+ return await this.client
47
+ .GET("/api/admin/projects/{projectId}/features", {
48
+ params: {
49
+ path: {
50
+ projectId,
51
+ },
52
+ },
53
+ })
54
+ .then(
55
+ (response) => {
56
+ // @ts-expect-error: TODO: fix me
57
+ const { data, metadata, error } = this.formatResponse(response);
58
+ return {
59
+ data: data?.features as components["schemas"]["projectFeatureSchema"][],
60
+ metadata,
61
+ error,
62
+ };
63
+ },
64
+ (reason) => this.formatError(reason),
65
+ );
66
+ }
67
+
68
+ private async waitForConnection(everyMs = 10) {
69
+ return waitForConnection(this, everyMs);
70
+ }
71
+ }
72
+
73
+ export default FeatureFlags;
74
+ export type { Context as FeatureFlagsEvaluationContext };