@goharvest/simforge 0.5.0 → 0.5.2
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/dist/client.d.ts +27 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +81 -43
- package/dist/client.js.map +1 -1
- package/dist/client.test.js +282 -62
- package/dist/client.test.js.map +1 -1
- package/dist/http.d.ts +0 -9
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +0 -7
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/serialize.test.d.ts +8 -0
- package/dist/serialize.test.d.ts.map +1 -0
- package/dist/serialize.test.js +226 -0
- package/dist/serialize.test.js.map +1 -0
- package/dist/tracing.d.ts.map +1 -1
- package/dist/tracing.js +10 -5
- package/dist/tracing.js.map +1 -1
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/package.json +1 -1
package/dist/client.test.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
2
|
import * as baml from "./baml.js";
|
|
3
|
-
import {
|
|
3
|
+
import { getCurrentSpan, Simforge } from "./client";
|
|
4
4
|
// Mock fetch globally
|
|
5
5
|
globalThis.fetch = vi.fn();
|
|
6
6
|
// Mock BAML execution
|
|
@@ -16,9 +16,30 @@ describe("Simforge Client", () => {
|
|
|
16
16
|
const client = new Simforge({ apiKey: "test-key" });
|
|
17
17
|
expect(client).toBeInstanceOf(Simforge);
|
|
18
18
|
});
|
|
19
|
-
it("should
|
|
19
|
+
it("should auto-disable tracing with empty apiKey and log warning", () => {
|
|
20
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => { });
|
|
20
21
|
const client = new Simforge({ apiKey: "" });
|
|
21
22
|
expect(client).toBeInstanceOf(Simforge);
|
|
23
|
+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("apiKey is empty"));
|
|
24
|
+
// Should behave as disabled — withSpan returns the original function
|
|
25
|
+
const fn = client.withSpan("test-key", () => "result");
|
|
26
|
+
expect(fn()).toBe("result");
|
|
27
|
+
warnSpy.mockRestore();
|
|
28
|
+
});
|
|
29
|
+
it("should auto-disable tracing with whitespace apiKey", () => {
|
|
30
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => { });
|
|
31
|
+
const client = new Simforge({ apiKey: " " });
|
|
32
|
+
const fn = client.withSpan("test-key", () => "result");
|
|
33
|
+
expect(fn()).toBe("result");
|
|
34
|
+
warnSpy.mockRestore();
|
|
35
|
+
});
|
|
36
|
+
it("should not warn when explicitly disabled with empty apiKey", () => {
|
|
37
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => { });
|
|
38
|
+
const client = new Simforge({ apiKey: "", enabled: false });
|
|
39
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
40
|
+
const fn = client.withSpan("test-key", () => "result");
|
|
41
|
+
expect(fn()).toBe("result");
|
|
42
|
+
warnSpy.mockRestore();
|
|
22
43
|
});
|
|
23
44
|
it("should use custom serviceUrl", () => {
|
|
24
45
|
const client = new Simforge({
|
|
@@ -41,14 +62,7 @@ describe("Simforge Client", () => {
|
|
|
41
62
|
});
|
|
42
63
|
expect(client).toBeInstanceOf(Simforge);
|
|
43
64
|
});
|
|
44
|
-
|
|
45
|
-
const client = new Simforge({
|
|
46
|
-
apiKey: "test-key",
|
|
47
|
-
executeLocally: false,
|
|
48
|
-
});
|
|
49
|
-
expect(client).toBeInstanceOf(Simforge);
|
|
50
|
-
});
|
|
51
|
-
describe("call method - local execution", () => {
|
|
65
|
+
describe("call method", () => {
|
|
52
66
|
beforeEach(() => {
|
|
53
67
|
// Reset BAML mock
|
|
54
68
|
vi.mocked(baml.runFunctionWithBaml).mockReset();
|
|
@@ -118,7 +132,6 @@ function ExtractName(text: string) -> Name {
|
|
|
118
132
|
});
|
|
119
133
|
const client = new Simforge({
|
|
120
134
|
apiKey: "test-key",
|
|
121
|
-
executeLocally: true,
|
|
122
135
|
envVars: { OPENAI_API_KEY: "test-openai-key" },
|
|
123
136
|
});
|
|
124
137
|
const result = await client.call("ExtractName", {
|
|
@@ -151,7 +164,6 @@ function ExtractName(text: string) -> Name {
|
|
|
151
164
|
vi.mocked(baml.runFunctionWithBaml).mockRejectedValueOnce(new Error("Invalid argument: Expected type String, got Number(12345)"));
|
|
152
165
|
const client = new Simforge({
|
|
153
166
|
apiKey: "test-key",
|
|
154
|
-
executeLocally: true,
|
|
155
167
|
});
|
|
156
168
|
await expect(
|
|
157
169
|
// biome-ignore lint/suspicious/noExplicitAny: Testing wrong type handling
|
|
@@ -175,7 +187,6 @@ function ExtractName(text: string) -> Name {
|
|
|
175
187
|
vi.mocked(baml.runFunctionWithBaml).mockRejectedValueOnce(new Error("Missing required parameter: text"));
|
|
176
188
|
const client = new Simforge({
|
|
177
189
|
apiKey: "test-key",
|
|
178
|
-
executeLocally: true,
|
|
179
190
|
});
|
|
180
191
|
await expect(client.call("ExtractName", {})).rejects.toThrow("Missing required parameter");
|
|
181
192
|
});
|
|
@@ -232,7 +243,6 @@ function ExtractPerson(text: string) -> Person {}
|
|
|
232
243
|
});
|
|
233
244
|
const client = new Simforge({
|
|
234
245
|
apiKey: "test-key",
|
|
235
|
-
executeLocally: true,
|
|
236
246
|
});
|
|
237
247
|
const result = (await client.call("ExtractPerson", {
|
|
238
248
|
text: "John Doe, 30, john@example.com, 123 Main St, New York, 10001",
|
|
@@ -262,7 +272,6 @@ function ExtractPerson(text: string) -> Person {}
|
|
|
262
272
|
});
|
|
263
273
|
const client = new Simforge({
|
|
264
274
|
apiKey: "test-key",
|
|
265
|
-
executeLocally: true,
|
|
266
275
|
});
|
|
267
276
|
await expect(client.call("ExtractName", { text: "test" })).rejects.toThrow("has no prompt");
|
|
268
277
|
});
|
|
@@ -295,7 +304,6 @@ function ExtractPerson(text: string) -> Person {}
|
|
|
295
304
|
});
|
|
296
305
|
const client = new Simforge({
|
|
297
306
|
apiKey: "test-key",
|
|
298
|
-
executeLocally: true,
|
|
299
307
|
envVars: {
|
|
300
308
|
OPENAI_API_KEY: "test-key",
|
|
301
309
|
},
|
|
@@ -309,50 +317,6 @@ function ExtractPerson(text: string) -> Person {}
|
|
|
309
317
|
});
|
|
310
318
|
});
|
|
311
319
|
});
|
|
312
|
-
describe("call method - server-side execution", () => {
|
|
313
|
-
it("should make successful API call", async () => {
|
|
314
|
-
const mockResponse = "success";
|
|
315
|
-
globalThis.fetch.mockResolvedValueOnce({
|
|
316
|
-
ok: true,
|
|
317
|
-
json: async () => ({ result: mockResponse }),
|
|
318
|
-
});
|
|
319
|
-
const client = new Simforge({
|
|
320
|
-
apiKey: "test-key",
|
|
321
|
-
executeLocally: false,
|
|
322
|
-
});
|
|
323
|
-
const result = await client.call("testMethod", { input: "test" });
|
|
324
|
-
expect(result).toEqual(mockResponse);
|
|
325
|
-
expect(globalThis.fetch).toHaveBeenCalledWith(expect.stringContaining("/api/sdk/call"), expect.objectContaining({
|
|
326
|
-
method: "POST",
|
|
327
|
-
headers: expect.objectContaining({
|
|
328
|
-
Authorization: "Bearer test-key",
|
|
329
|
-
"Content-Type": "application/json",
|
|
330
|
-
}),
|
|
331
|
-
}));
|
|
332
|
-
});
|
|
333
|
-
it("should handle API errors", async () => {
|
|
334
|
-
;
|
|
335
|
-
globalThis.fetch.mockResolvedValueOnce({
|
|
336
|
-
ok: false,
|
|
337
|
-
status: 400,
|
|
338
|
-
json: async () => ({ error: "Bad request" }),
|
|
339
|
-
});
|
|
340
|
-
const client = new Simforge({
|
|
341
|
-
apiKey: "test-key",
|
|
342
|
-
executeLocally: false,
|
|
343
|
-
});
|
|
344
|
-
await expect(client.call("testMethod", {})).rejects.toThrow(SimforgeError);
|
|
345
|
-
});
|
|
346
|
-
it("should handle network errors", async () => {
|
|
347
|
-
;
|
|
348
|
-
globalThis.fetch.mockRejectedValueOnce(new Error("Network error"));
|
|
349
|
-
const client = new Simforge({
|
|
350
|
-
apiKey: "test-key",
|
|
351
|
-
executeLocally: false,
|
|
352
|
-
});
|
|
353
|
-
await expect(client.call("testMethod", {})).rejects.toThrow(SimforgeError);
|
|
354
|
-
});
|
|
355
|
-
});
|
|
356
320
|
describe("trace creation with source field", () => {
|
|
357
321
|
it("should include source='typescript-sdk' by default when creating traces", async () => {
|
|
358
322
|
const mockFetch = vi.mocked(fetch);
|
|
@@ -387,7 +351,6 @@ function ExtractPerson(text: string) -> Person {}
|
|
|
387
351
|
});
|
|
388
352
|
const client = new Simforge({
|
|
389
353
|
apiKey: "test-key",
|
|
390
|
-
executeLocally: true,
|
|
391
354
|
});
|
|
392
355
|
await client.call("testMethod", {});
|
|
393
356
|
// Verify trace creation was called with source field
|
|
@@ -440,7 +403,6 @@ function ExtractPerson(text: string) -> Person {}
|
|
|
440
403
|
});
|
|
441
404
|
const client = new Simforge({
|
|
442
405
|
apiKey: "test-key",
|
|
443
|
-
executeLocally: true,
|
|
444
406
|
});
|
|
445
407
|
await client.call("testMethod", {});
|
|
446
408
|
// Verify trace creation includes both source and rawCollector
|
|
@@ -912,6 +874,264 @@ function ExtractPerson(text: string) -> Person {}
|
|
|
912
874
|
expect(traceBody.rawSpan.span_data.output).toBe(8);
|
|
913
875
|
});
|
|
914
876
|
});
|
|
877
|
+
describe("enabled option", () => {
|
|
878
|
+
it("should default enabled to true", () => {
|
|
879
|
+
const mockFetch = vi.mocked(fetch);
|
|
880
|
+
mockFetch.mockResolvedValueOnce({
|
|
881
|
+
ok: true,
|
|
882
|
+
status: 200,
|
|
883
|
+
json: async () => ({ traceId: "enabled-default-trace" }),
|
|
884
|
+
});
|
|
885
|
+
const client = new Simforge({ apiKey: "test-key" });
|
|
886
|
+
const fn = async () => "result";
|
|
887
|
+
const wrappedFn = client.withSpan("test-service", fn);
|
|
888
|
+
// If enabled defaults to true, the wrapped function should send spans
|
|
889
|
+
// We can verify by checking that it's not the same reference as the original
|
|
890
|
+
expect(wrappedFn).not.toBe(fn);
|
|
891
|
+
});
|
|
892
|
+
it("should return unwrapped function when enabled is false", () => {
|
|
893
|
+
const client = new Simforge({ apiKey: "test-key", enabled: false });
|
|
894
|
+
const fn = async () => "result";
|
|
895
|
+
const wrappedFn = client.withSpan("test-service", fn);
|
|
896
|
+
// When disabled, withSpan should return the original function
|
|
897
|
+
expect(wrappedFn).toBe(fn);
|
|
898
|
+
});
|
|
899
|
+
it("should not send spans when enabled is false", async () => {
|
|
900
|
+
const mockFetch = vi.mocked(fetch);
|
|
901
|
+
mockFetch.mockClear();
|
|
902
|
+
const client = new Simforge({ apiKey: "test-key", enabled: false });
|
|
903
|
+
const fn = async (x) => x * 2;
|
|
904
|
+
const wrappedFn = client.withSpan("test-service", fn);
|
|
905
|
+
const result = await wrappedFn(5);
|
|
906
|
+
expect(result).toBe(10);
|
|
907
|
+
// Wait for any potential background traces
|
|
908
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
909
|
+
// No fetch calls should have been made
|
|
910
|
+
const spanCalls = mockFetch.mock.calls.filter((call) => call[0].toString().includes("/sdk/externalSpans"));
|
|
911
|
+
expect(spanCalls.length).toBe(0);
|
|
912
|
+
});
|
|
913
|
+
it("should return unwrapped function via getFunction when enabled is false", () => {
|
|
914
|
+
const client = new Simforge({ apiKey: "test-key", enabled: false });
|
|
915
|
+
const func = client.getFunction("my-function");
|
|
916
|
+
const fn = async () => "result";
|
|
917
|
+
const wrappedFn = func.withSpan(fn);
|
|
918
|
+
// When disabled, should return the original function
|
|
919
|
+
expect(wrappedFn).toBe(fn);
|
|
920
|
+
});
|
|
921
|
+
it("should return unwrapped function with options when enabled is false", () => {
|
|
922
|
+
const client = new Simforge({ apiKey: "test-key", enabled: false });
|
|
923
|
+
const fn = async () => "result";
|
|
924
|
+
const wrappedFn = client.withSpan("test-service", { type: "function" }, fn);
|
|
925
|
+
expect(wrappedFn).toBe(fn);
|
|
926
|
+
});
|
|
927
|
+
});
|
|
928
|
+
describe("metadata", () => {
|
|
929
|
+
it("metadata is included in span_data when provided", async () => {
|
|
930
|
+
const mockFetch = vi.mocked(fetch);
|
|
931
|
+
mockFetch.mockResolvedValue({
|
|
932
|
+
ok: true,
|
|
933
|
+
status: 200,
|
|
934
|
+
json: async () => ({ traceId: "metadata-trace" }),
|
|
935
|
+
});
|
|
936
|
+
const client = new Simforge({ apiKey: "test-key" });
|
|
937
|
+
async function processOrder(id) {
|
|
938
|
+
return { id };
|
|
939
|
+
}
|
|
940
|
+
const wrapped = client.withSpan("my-service", {
|
|
941
|
+
type: "function",
|
|
942
|
+
metadata: { user_id: "u-123", region: "us-east" },
|
|
943
|
+
}, processOrder);
|
|
944
|
+
await wrapped("123");
|
|
945
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
946
|
+
const spanCall = mockFetch.mock.calls.find((c) => String(c[0]).includes("/sdk/externalSpans"));
|
|
947
|
+
expect(spanCall).toBeDefined();
|
|
948
|
+
const traceBody = JSON.parse(spanCall[1]?.body ?? "{}");
|
|
949
|
+
const spanData = traceBody.rawSpan
|
|
950
|
+
.span_data;
|
|
951
|
+
expect(spanData.metadata).toEqual({ user_id: "u-123", region: "us-east" });
|
|
952
|
+
});
|
|
953
|
+
it("metadata is omitted from span_data when not provided", async () => {
|
|
954
|
+
const mockFetch = vi.mocked(fetch);
|
|
955
|
+
mockFetch.mockResolvedValue({
|
|
956
|
+
ok: true,
|
|
957
|
+
status: 200,
|
|
958
|
+
json: async () => ({ traceId: "no-metadata-trace" }),
|
|
959
|
+
});
|
|
960
|
+
const client = new Simforge({ apiKey: "test-key" });
|
|
961
|
+
async function myFn() {
|
|
962
|
+
return "result";
|
|
963
|
+
}
|
|
964
|
+
const wrapped = client.withSpan("my-service", myFn);
|
|
965
|
+
await wrapped();
|
|
966
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
967
|
+
const spanCall = mockFetch.mock.calls.find((c) => String(c[0]).includes("/sdk/externalSpans"));
|
|
968
|
+
expect(spanCall).toBeDefined();
|
|
969
|
+
const traceBody = JSON.parse(spanCall[1]?.body ?? "{}");
|
|
970
|
+
const spanData = traceBody.rawSpan
|
|
971
|
+
.span_data;
|
|
972
|
+
expect(spanData.metadata).toBeUndefined();
|
|
973
|
+
});
|
|
974
|
+
it("metadata works with getFunction fluent API", async () => {
|
|
975
|
+
const mockFetch = vi.mocked(fetch);
|
|
976
|
+
mockFetch.mockResolvedValue({
|
|
977
|
+
ok: true,
|
|
978
|
+
status: 200,
|
|
979
|
+
json: async () => ({ traceId: "fluent-metadata-trace" }),
|
|
980
|
+
});
|
|
981
|
+
const client = new Simforge({ apiKey: "test-key" });
|
|
982
|
+
const service = client.getFunction("order-processing");
|
|
983
|
+
async function processOrder(id) {
|
|
984
|
+
return { id };
|
|
985
|
+
}
|
|
986
|
+
const wrapped = service.withSpan({
|
|
987
|
+
type: "function",
|
|
988
|
+
metadata: { env: "staging", version: "1.2.3" },
|
|
989
|
+
}, processOrder);
|
|
990
|
+
await wrapped("123");
|
|
991
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
992
|
+
const spanCall = mockFetch.mock.calls.find((c) => String(c[0]).includes("/sdk/externalSpans"));
|
|
993
|
+
expect(spanCall).toBeDefined();
|
|
994
|
+
const traceBody = JSON.parse(spanCall[1]?.body ?? "{}");
|
|
995
|
+
const spanData = traceBody.rawSpan
|
|
996
|
+
.span_data;
|
|
997
|
+
expect(spanData.metadata).toEqual({ env: "staging", version: "1.2.3" });
|
|
998
|
+
});
|
|
999
|
+
});
|
|
1000
|
+
describe("runtime metadata (getCurrentSpan)", () => {
|
|
1001
|
+
it("sets metadata at runtime from inside a span", async () => {
|
|
1002
|
+
const mockFetch = vi.mocked(fetch);
|
|
1003
|
+
mockFetch.mockResolvedValue({
|
|
1004
|
+
ok: true,
|
|
1005
|
+
status: 200,
|
|
1006
|
+
json: async () => ({ traceId: "runtime-meta-trace" }),
|
|
1007
|
+
});
|
|
1008
|
+
const client = new Simforge({ apiKey: "test-key" });
|
|
1009
|
+
async function processOrder(id) {
|
|
1010
|
+
getCurrentSpan()?.setMetadata({
|
|
1011
|
+
request_id: "req-789",
|
|
1012
|
+
user_id: "u-456",
|
|
1013
|
+
});
|
|
1014
|
+
return { id };
|
|
1015
|
+
}
|
|
1016
|
+
const wrapped = client.withSpan("my-service", { type: "function" }, processOrder);
|
|
1017
|
+
await wrapped("123");
|
|
1018
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1019
|
+
const spanCall = mockFetch.mock.calls.find((c) => String(c[0]).includes("/sdk/externalSpans"));
|
|
1020
|
+
expect(spanCall).toBeDefined();
|
|
1021
|
+
const traceBody = JSON.parse(spanCall[1]?.body ?? "{}");
|
|
1022
|
+
const spanData = traceBody.rawSpan
|
|
1023
|
+
.span_data;
|
|
1024
|
+
expect(spanData.metadata).toEqual({
|
|
1025
|
+
request_id: "req-789",
|
|
1026
|
+
user_id: "u-456",
|
|
1027
|
+
});
|
|
1028
|
+
});
|
|
1029
|
+
it("merges runtime metadata with definition-time metadata (runtime wins)", async () => {
|
|
1030
|
+
const mockFetch = vi.mocked(fetch);
|
|
1031
|
+
mockFetch.mockResolvedValue({
|
|
1032
|
+
ok: true,
|
|
1033
|
+
status: 200,
|
|
1034
|
+
json: async () => ({ traceId: "merge-meta-trace" }),
|
|
1035
|
+
});
|
|
1036
|
+
const client = new Simforge({ apiKey: "test-key" });
|
|
1037
|
+
async function processOrder(id) {
|
|
1038
|
+
getCurrentSpan()?.setMetadata({
|
|
1039
|
+
region: "eu-west",
|
|
1040
|
+
request_id: "req-789",
|
|
1041
|
+
});
|
|
1042
|
+
return { id };
|
|
1043
|
+
}
|
|
1044
|
+
const wrapped = client.withSpan("my-service", { type: "function", metadata: { user_id: "u-123", region: "us-east" } }, processOrder);
|
|
1045
|
+
await wrapped("123");
|
|
1046
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1047
|
+
const spanCall = mockFetch.mock.calls.find((c) => String(c[0]).includes("/sdk/externalSpans"));
|
|
1048
|
+
expect(spanCall).toBeDefined();
|
|
1049
|
+
const traceBody = JSON.parse(spanCall[1]?.body ?? "{}");
|
|
1050
|
+
const spanData = traceBody.rawSpan
|
|
1051
|
+
.span_data;
|
|
1052
|
+
expect(spanData.metadata).toEqual({
|
|
1053
|
+
user_id: "u-123",
|
|
1054
|
+
region: "eu-west",
|
|
1055
|
+
request_id: "req-789",
|
|
1056
|
+
});
|
|
1057
|
+
});
|
|
1058
|
+
it("getCurrentSpan returns null outside of a span", () => {
|
|
1059
|
+
expect(getCurrentSpan()).toBeNull();
|
|
1060
|
+
});
|
|
1061
|
+
it("works with getFunction fluent API", async () => {
|
|
1062
|
+
const mockFetch = vi.mocked(fetch);
|
|
1063
|
+
mockFetch.mockResolvedValue({
|
|
1064
|
+
ok: true,
|
|
1065
|
+
status: 200,
|
|
1066
|
+
json: async () => ({ traceId: "fluent-runtime-meta" }),
|
|
1067
|
+
});
|
|
1068
|
+
const client = new Simforge({ apiKey: "test-key" });
|
|
1069
|
+
const service = client.getFunction("order-processing");
|
|
1070
|
+
async function processOrder(id) {
|
|
1071
|
+
getCurrentSpan()?.setMetadata({ computed_at: "2024-01-01" });
|
|
1072
|
+
return { id };
|
|
1073
|
+
}
|
|
1074
|
+
const wrapped = service.withSpan({ type: "function" }, processOrder);
|
|
1075
|
+
await wrapped("123");
|
|
1076
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1077
|
+
const spanCall = mockFetch.mock.calls.find((c) => String(c[0]).includes("/sdk/externalSpans"));
|
|
1078
|
+
expect(spanCall).toBeDefined();
|
|
1079
|
+
const traceBody = JSON.parse(spanCall[1]?.body ?? "{}");
|
|
1080
|
+
const spanData = traceBody.rawSpan
|
|
1081
|
+
.span_data;
|
|
1082
|
+
expect(spanData.metadata).toEqual({ computed_at: "2024-01-01" });
|
|
1083
|
+
});
|
|
1084
|
+
});
|
|
1085
|
+
describe("error resilience", () => {
|
|
1086
|
+
it("should not crash the host app when span sending throws synchronously", async () => {
|
|
1087
|
+
const mockFetch = vi.mocked(fetch);
|
|
1088
|
+
// Make fetch throw synchronously (simulating serialization failure)
|
|
1089
|
+
mockFetch.mockImplementation(() => {
|
|
1090
|
+
throw new Error("Serialization error");
|
|
1091
|
+
});
|
|
1092
|
+
const client = new Simforge({ apiKey: "test-key" });
|
|
1093
|
+
const fn = async (x) => x * 2;
|
|
1094
|
+
const wrappedFn = client.withSpan("resilient-service", fn);
|
|
1095
|
+
// Function should still return normally
|
|
1096
|
+
const result = await wrappedFn(5);
|
|
1097
|
+
expect(result).toBe(10);
|
|
1098
|
+
});
|
|
1099
|
+
it("should propagate user errors even when SDK has internal errors", async () => {
|
|
1100
|
+
const mockFetch = vi.mocked(fetch);
|
|
1101
|
+
mockFetch.mockImplementation(() => {
|
|
1102
|
+
throw new Error("SDK internal error");
|
|
1103
|
+
});
|
|
1104
|
+
const client = new Simforge({ apiKey: "test-key" });
|
|
1105
|
+
const failingFn = async () => {
|
|
1106
|
+
throw new Error("User error");
|
|
1107
|
+
};
|
|
1108
|
+
const wrappedFn = client.withSpan("error-service", failingFn);
|
|
1109
|
+
await expect(wrappedFn()).rejects.toThrow("User error");
|
|
1110
|
+
});
|
|
1111
|
+
it("should not crash with sync functions when span sending throws", () => {
|
|
1112
|
+
const mockFetch = vi.mocked(fetch);
|
|
1113
|
+
mockFetch.mockImplementation(() => {
|
|
1114
|
+
throw new Error("Serialization error");
|
|
1115
|
+
});
|
|
1116
|
+
const client = new Simforge({ apiKey: "test-key" });
|
|
1117
|
+
const fn = (a, b) => a + b;
|
|
1118
|
+
const wrappedFn = client.withSpan("sync-resilient", fn);
|
|
1119
|
+
const result = wrappedFn(5, 3);
|
|
1120
|
+
expect(result).toBe(8);
|
|
1121
|
+
});
|
|
1122
|
+
it("should propagate user errors from sync functions even when SDK has internal errors", () => {
|
|
1123
|
+
const mockFetch = vi.mocked(fetch);
|
|
1124
|
+
mockFetch.mockImplementation(() => {
|
|
1125
|
+
throw new Error("SDK internal error");
|
|
1126
|
+
});
|
|
1127
|
+
const client = new Simforge({ apiKey: "test-key" });
|
|
1128
|
+
const failingFn = () => {
|
|
1129
|
+
throw new Error("Sync user error");
|
|
1130
|
+
};
|
|
1131
|
+
const wrappedFn = client.withSpan("sync-error-service", failingFn);
|
|
1132
|
+
expect(() => wrappedFn()).toThrow("Sync user error");
|
|
1133
|
+
});
|
|
1134
|
+
});
|
|
915
1135
|
describe("getFunction", () => {
|
|
916
1136
|
it("should return a SimforgeFunction instance", () => {
|
|
917
1137
|
const client = new Simforge({ apiKey: "test-key" });
|