@specific.dev/spectest 0.109.0 → 0.109.1

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.
@@ -83,9 +83,11 @@ function invokeHelper(kind, name, member, fn, receiver, callArgs) {
83
83
  };
84
84
  let result;
85
85
  try {
86
+ // Reading a callable proxy's `.apply` can bind directly to its raw client
87
+ // and bypass instrumentation (SQL does this). Invoke the proxy itself.
86
88
  result = recording.run(() => kind === "fake"
87
- ? runUninstrumented(() => fn.apply(receiver, args))
88
- : fn.apply(receiver, args));
89
+ ? runUninstrumented(() => Reflect.apply(fn, receiver, args))
90
+ : Reflect.apply(fn, receiver, args));
89
91
  }
90
92
  catch (error) {
91
93
  return fail(error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specific.dev/spectest",
3
- "version": "0.109.0",
3
+ "version": "0.109.1",
4
4
  "description": "Spectest SDK for defining test environments in TypeScript.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -3,6 +3,7 @@ import { annotate } from "./annotate.js";
3
3
  import { expect as assertValue, type ServiceHandlesFor, type Wrapped } from "./index.js";
4
4
  import { readRaw, readTag, wrap } from "./inspect.js";
5
5
  import { trackHelpers } from "./helper-tracking.js";
6
+ import { SQL } from "./sql.js";
6
7
  import {
7
8
  recordHttp, recordWait, recorderEventCount, recorderMarkChildren, recorderTruncate,
8
9
  startRecording, stopRecording,
@@ -175,6 +176,41 @@ test("live objects, callable clients, function properties and receivers survive"
175
176
  expect<unknown>(readRaw(helper.latest.call({...raw, subject: "Other"}))).toEqual({subject: "Other"});
176
177
  });
177
178
 
179
+ test("a tracked SQL client keeps tagged queries and attached methods instrumented", async () => {
180
+ const client = new SQL("sqlite://:memory:", { label: "db" });
181
+ const helper = trackHelpers("service", "db", { client });
182
+ startRecording();
183
+ try {
184
+ const rows = await helper.client<{ id: number }>`SELECT ${7} AS id`;
185
+ assertValue(rows.transform("ids", (rows) => Array.from(rows, (row) => row.id))).toEqual([7]);
186
+ const other = await helper.client.unsafe<{ id: number }>("SELECT ? AS id", [8]);
187
+ assertValue(other[0]!.id).toBe(8);
188
+ const events = stopRecording();
189
+ expect(events.map((event) => event.kind)).toEqual(["db", "assertion", "db", "assertion"]);
190
+ expect(events[0]).toMatchObject({ service: "db", query: "SELECT $1 AS id", params: [7], rows: [{ id: 7 }] });
191
+ expect(events[1]).toMatchObject({ sourceSeq: events[0]!.seq, path: ["<ids>"] });
192
+ expect(events[2]).toMatchObject({ service: "db", query: "SELECT ? AS id", params: [8], rows: [{ id: 8 }] });
193
+ expect(events[3]).toMatchObject({ sourceSeq: events[2]!.seq, path: ["0", "id"] });
194
+ } finally {
195
+ await helper.client.close!();
196
+ }
197
+ });
198
+
199
+ test("a tracked SQL client preserves query failure diagnostics", async () => {
200
+ const client = new SQL("sqlite://:memory:", { label: "db" });
201
+ const helper = trackHelpers("service", "db", { client });
202
+ startRecording();
203
+ try {
204
+ await expect(helper.client`SELECT * FROM missing_table`).rejects.toThrow("no such table");
205
+ const events = stopRecording();
206
+ expect(events).toHaveLength(1);
207
+ expect(events[0]).toMatchObject({ kind: "db", service: "db", query: "SELECT * FROM missing_table",
208
+ error: expect.stringContaining("no such table") });
209
+ } finally {
210
+ await helper.client.close!();
211
+ }
212
+ });
213
+
178
214
  test("setup/eval unwrap annotated returns without recording", async () => {
179
215
  const scope = createInstrumentationScope(false);
180
216
  const helper = trackHelpers("service", "mail", { latest: () => annotate({subject: "Invoice"}, "email") });
@@ -93,9 +93,11 @@ function invokeHelper(
93
93
  };
94
94
  let result: unknown;
95
95
  try {
96
+ // Reading a callable proxy's `.apply` can bind directly to its raw client
97
+ // and bypass instrumentation (SQL does this). Invoke the proxy itself.
96
98
  result = recording.run(() => kind === "fake"
97
- ? runUninstrumented(() => fn.apply(receiver, args))
98
- : fn.apply(receiver, args));
99
+ ? runUninstrumented(() => Reflect.apply(fn, receiver, args))
100
+ : Reflect.apply(fn, receiver, args));
99
101
  } catch (error) {
100
102
  return fail(error);
101
103
  }