Skip to content

Commit 4f6c547

Browse files
authored
Merge branch 'main' into chris/schy-148-schematic-node140-breaks-in-cloudflare-workers
2 parents 7f0fa17 + ef7adab commit 4f6c547

File tree

4 files changed

+124
-11
lines changed

4 files changed

+124
-11
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@schematichq/schematic-typescript-node",
3-
"version": "1.4.0",
3+
"version": "1.4.1",
44
"private": false,
55
"repository": {
66
"type": "git",

src/wrapper.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -300,9 +300,13 @@ export class SchematicClient extends BaseClient {
300300
value: response.data.value,
301301
};
302302

303-
for (const provider of this.flagCheckCacheProviders) {
304-
this.logger.debug(`Caching value for flag ${key} in ${provider.constructor.name}`);
305-
await provider.set(cacheKey, result);
303+
try {
304+
for (const provider of this.flagCheckCacheProviders) {
305+
this.logger.debug(`Caching value for flag ${key} in ${provider.constructor.name}`);
306+
await provider.set(cacheKey, result);
307+
}
308+
} catch (cacheErr) {
309+
this.logger.warn(`Cache write failed for flag ${key}: ${cacheErr}`);
306310
}
307311

308312
return result;
@@ -406,9 +410,13 @@ export class SchematicClient extends BaseClient {
406410
userId: apiResult.userId,
407411
value: apiResult.value,
408412
};
409-
for (const provider of this.flagCheckCacheProviders) {
410-
this.logger.debug(`Caching value for flag ${cacheKey} in ${provider.constructor.name}`);
411-
await provider.set(cacheKey, cacheEntry);
413+
try {
414+
for (const provider of this.flagCheckCacheProviders) {
415+
this.logger.debug(`Caching value for flag ${cacheKey} in ${provider.constructor.name}`);
416+
await provider.set(cacheKey, cacheEntry);
417+
}
418+
} catch (cacheErr) {
419+
this.logger.warn(`Cache write failed for flag ${key}: ${cacheErr}`);
412420
}
413421
results.push(apiResult);
414422
} else {
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { SchematicClient } from "../../../src/wrapper";
2+
import type { CacheProvider } from "../../../src/cache";
3+
import type { CheckFlagWithEntitlementResponse } from "../../../src/wrapper";
4+
5+
/**
6+
* A cache provider that always throws on set() to simulate failures
7+
* like Cloudflare KV 429 Too Many Requests.
8+
*/
9+
class FailingCacheProvider implements CacheProvider<CheckFlagWithEntitlementResponse> {
10+
async get(): Promise<CheckFlagWithEntitlementResponse | null> {
11+
return null; // Always miss
12+
}
13+
async set(): Promise<void> {
14+
throw new Error("429 Too Many Requests");
15+
}
16+
async delete(): Promise<void> {}
17+
}
18+
19+
// Mock the features.checkFlag API call
20+
jest.mock("../../../src/Client", () => {
21+
class MockBaseClient {
22+
features = {
23+
checkFlag: jest.fn().mockResolvedValue({
24+
data: {
25+
value: true,
26+
flag: "test-flag",
27+
reason: "match",
28+
companyId: "comp-1",
29+
},
30+
}),
31+
checkFlags: jest.fn().mockResolvedValue({
32+
data: {
33+
flags: [
34+
{
35+
value: true,
36+
flag: "test-flag",
37+
reason: "match",
38+
companyId: "comp-1",
39+
},
40+
],
41+
},
42+
}),
43+
};
44+
events = {};
45+
}
46+
return { SchematicClient: MockBaseClient };
47+
});
48+
49+
// Mock the EventBuffer to avoid side effects
50+
jest.mock("../../../src/events", () => {
51+
return {
52+
EventBuffer: jest.fn().mockImplementation(() => ({
53+
push: jest.fn(),
54+
stop: jest.fn().mockResolvedValue(undefined),
55+
})),
56+
};
57+
});
58+
59+
describe("Cache write failure should not discard API result", () => {
60+
let client: SchematicClient;
61+
const warnSpy = jest.fn();
62+
63+
beforeEach(() => {
64+
client = new SchematicClient({
65+
apiKey: "test-api-key",
66+
cacheProviders: {
67+
flagChecks: [new FailingCacheProvider()],
68+
},
69+
logger: {
70+
error: jest.fn(),
71+
warn: warnSpy,
72+
info: jest.fn(),
73+
debug: jest.fn(),
74+
},
75+
});
76+
});
77+
78+
afterEach(async () => {
79+
await client.close();
80+
});
81+
82+
it("checkFlag returns the API value even when cache set() throws", async () => {
83+
const result = await client.checkFlag({ company: { id: "comp-1" } }, "test-flag");
84+
expect(result).toBe(true);
85+
});
86+
87+
it("checkFlagWithEntitlement returns the API value even when cache set() throws", async () => {
88+
const result = await client.checkFlagWithEntitlement({ company: { id: "comp-1" } }, "test-flag");
89+
expect(result.value).toBe(true);
90+
expect(result.reason).toBe("match");
91+
});
92+
93+
it("logs a warning when cache write fails", async () => {
94+
await client.checkFlag({ company: { id: "comp-1" } }, "test-flag");
95+
expect(warnSpy).toHaveBeenCalledWith(
96+
expect.stringContaining("Cache write failed for flag test-flag"),
97+
);
98+
});
99+
100+
it("checkFlags returns API values even when cache set() throws", async () => {
101+
const results = await client.checkFlags({ company: { id: "comp-1" } }, ["test-flag"]);
102+
expect(results).toHaveLength(1);
103+
expect(results[0].value).toBe(true);
104+
});
105+
});
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
/* eslint @typescript-eslint/no-explicit-any: 0 */
22

3-
import { EventBuffer } from "./events";
4-
import { EventsClient } from "./api/resources/events/client/Client";
5-
import { CreateEventRequestBody } from "./api";
6-
import { Logger } from "./logger";
3+
import { EventBuffer } from "../../src/events";
4+
import { EventsClient } from "../../src/api/resources/events/client/Client";
5+
import { CreateEventRequestBody } from "../../src/api";
6+
import { Logger } from "../../src/logger";
77

88
process.env.NODE_ENV = "test";
99

0 commit comments

Comments
 (0)