batchkit 0.2.0-beta.1 → 0.3.0

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.
@@ -0,0 +1,3 @@
1
+ import type { Batcher, BatchFn, BatchOptions, Match } from './types';
2
+ export declare function batch<K, V>(fn: BatchFn<K, V>, match: Match<K, V>, options?: BatchOptions<K>): Batcher<K, V>;
3
+ //# sourceMappingURL=batch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["../src/batch.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,OAAO,EACP,OAAO,EACP,YAAY,EAEZ,KAAK,EAGN,MAAM,SAAS,CAAC;AAEjB,wBAAgB,KAAK,CAAC,CAAC,EAAE,CAAC,EACxB,EAAE,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,EACjB,KAAK,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAClB,OAAO,GAAE,YAAY,CAAC,CAAC,CAAM,GAC5B,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CA8Rf"}
1
+ {"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["../src/batch.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,OAAO,EACP,OAAO,EACP,YAAY,EAEZ,KAAK,EAIN,MAAM,SAAS,CAAC;AAEjB,wBAAgB,KAAK,CAAC,CAAC,EAAE,CAAC,EACxB,EAAE,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,EACjB,KAAK,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAClB,OAAO,GAAE,YAAY,CAAC,CAAC,CAAM,GAC5B,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAkTf"}
@@ -0,0 +1,4 @@
1
+ export declare class BatchError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,UAAW,SAAQ,KAAK;gBACvB,OAAO,EAAE,MAAM;CAI5B"}
@@ -0,0 +1,8 @@
1
+ export { batch } from './batch';
2
+ export { BatchError } from './errors';
3
+ export { indexed } from './indexed';
4
+ export { onAnimationFrame, onIdle } from './schedulers';
5
+ export type { DevtoolsHook } from './trace';
6
+ export { __setDevtoolsHook } from './trace';
7
+ export type { Batcher, BatchFn, BatchOptions, GetOptions, Match, MatchFn, Scheduler, TraceEvent, TraceEventData, TraceHandler, } from './types';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACxD,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAE5C,YAAY,EACV,OAAO,EACP,OAAO,EACP,YAAY,EACZ,UAAU,EACV,KAAK,EACL,OAAO,EACP,SAAS,EACT,UAAU,EACV,cAAc,EACd,YAAY,GACb,MAAM,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ function isIndexed(match) {
14
14
  return match === indexed;
15
15
  }
16
16
  function isKeyMatch(match) {
17
- return typeof match === "string";
17
+ return typeof match === "string" || typeof match === "number";
18
18
  }
19
19
  function normalizeMatch(match) {
20
20
  if (isIndexed(match)) {
@@ -66,15 +66,15 @@ function onIdle(options) {
66
66
  }
67
67
 
68
68
  // src/trace.ts
69
- function getDevtools() {
70
- if (typeof window !== "undefined" && window.__BATCHKIT_DEVTOOLS__) {
71
- return window.__BATCHKIT_DEVTOOLS__;
72
- }
73
- return;
69
+ var devtoolsHook = null;
70
+ function __setDevtoolsHook(hook) {
71
+ devtoolsHook = hook;
72
+ }
73
+ function getDevtoolsHook() {
74
+ return devtoolsHook;
74
75
  }
75
- function createTracer(name, handler) {
76
+ function createTracer(name, handler, getDevtoolsEmitter) {
76
77
  let batchCounter = 0;
77
- let registeredWithDevtools = false;
78
78
  function emit(event) {
79
79
  const timestamp = performance.now();
80
80
  const fullEvent = {
@@ -84,13 +84,9 @@ function createTracer(name, handler) {
84
84
  if (handler) {
85
85
  handler(fullEvent);
86
86
  }
87
- const devtools = getDevtools();
88
- if (devtools && name) {
89
- if (!registeredWithDevtools) {
90
- devtools.register({ name, registeredAt: timestamp });
91
- registeredWithDevtools = true;
92
- }
93
- devtools.emit(name, fullEvent);
87
+ const devtoolsEmitter = getDevtoolsEmitter?.();
88
+ if (devtoolsEmitter) {
89
+ devtoolsEmitter(fullEvent);
94
90
  }
95
91
  }
96
92
  function nextBatchId() {
@@ -110,7 +106,13 @@ function batch(fn, match, options = {}) {
110
106
  trace: traceHandler
111
107
  } = options;
112
108
  const scheduler = schedule ?? (waitMs ? wait(waitMs) : microtask);
113
- const tracer = createTracer(name, traceHandler);
109
+ let devtoolsEmitter;
110
+ const hook = getDevtoolsHook();
111
+ if (hook) {
112
+ const stack = new Error().stack;
113
+ devtoolsEmitter = hook.onBatcherCreated({ fn, name, stack });
114
+ }
115
+ const tracer = createTracer(name, traceHandler, () => devtoolsEmitter);
114
116
  const matchFn = normalizeMatch(match);
115
117
  const isIndexedMatch = isIndexed(match);
116
118
  const indexedMatcher = isIndexedMatch ? createIndexedMatcher() : null;
@@ -119,6 +121,7 @@ function batch(fn, match, options = {}) {
119
121
  let cleanup = null;
120
122
  let isScheduled = false;
121
123
  let currentAbortController = null;
124
+ let inFlightRequests = [];
122
125
  function scheduleDispatch() {
123
126
  if (isScheduled || queue.length === 0)
124
127
  return;
@@ -179,6 +182,7 @@ function batch(fn, match, options = {}) {
179
182
  }
180
183
  if (uniqueKeys.length === 0)
181
184
  return;
185
+ inFlightRequests = chunk;
182
186
  tracer.emit({
183
187
  type: "dispatch",
184
188
  batchId,
@@ -259,6 +263,7 @@ function batch(fn, match, options = {}) {
259
263
  }
260
264
  } finally {
261
265
  currentAbortController = null;
266
+ inFlightRequests = [];
262
267
  }
263
268
  }
264
269
  function getSingle(key, options2) {
@@ -286,8 +291,9 @@ function batch(fn, match, options = {}) {
286
291
  const onAbort = () => {
287
292
  request.aborted = true;
288
293
  reject(new DOMException("Aborted", "AbortError"));
289
- const allAborted = queue.every((r) => r.aborted);
290
- if (allAborted && currentAbortController) {
294
+ const allPendingAborted = queue.every((r) => r.aborted);
295
+ const allInFlightAborted = inFlightRequests.length > 0 && inFlightRequests.every((r) => r.aborted);
296
+ if (allPendingAborted && allInFlightAborted && currentAbortController) {
291
297
  currentAbortController.abort();
292
298
  }
293
299
  };
@@ -338,5 +344,6 @@ export {
338
344
  onAnimationFrame,
339
345
  indexed,
340
346
  batch,
347
+ __setDevtoolsHook,
341
348
  BatchError
342
349
  };
@@ -0,0 +1,2 @@
1
+ export declare const indexed: unique symbol;
2
+ //# sourceMappingURL=indexed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexed.d.ts","sourceRoot":"","sources":["../src/indexed.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,EAAE,OAAO,MAA0B,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { Match, MatchFn } from './types';
2
+ export declare function isIndexed<K, V>(match: Match<K, V>): match is symbol;
3
+ export declare function isKeyMatch<K, V>(match: Match<K, V>): match is keyof V;
4
+ export declare function normalizeMatch<K, V>(match: Match<K, V>): MatchFn<K, V> | null;
5
+ export declare function createIndexedMatcher<K, V>(): (results: Record<string, V>, key: K) => V | undefined;
6
+ //# sourceMappingURL=match.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"match.d.ts","sourceRoot":"","sources":["../src/match.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAE9C,wBAAgB,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,IAAI,MAAM,CAEnE;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,IAAI,MAAM,CAAC,CAErE;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAkB7E;AAED,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,CAAC,KAAK,CAC5C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,EAC1B,GAAG,EAAE,CAAC,KACH,CAAC,GAAG,SAAS,CAEjB"}
@@ -0,0 +1,8 @@
1
+ import type { Scheduler } from './types';
2
+ export declare const microtask: Scheduler;
3
+ export declare function wait(ms: number): Scheduler;
4
+ export declare const onAnimationFrame: Scheduler;
5
+ export declare function onIdle(options?: {
6
+ timeout?: number;
7
+ }): Scheduler;
8
+ //# sourceMappingURL=schedulers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schedulers.d.ts","sourceRoot":"","sources":["../src/schedulers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,eAAO,MAAM,SAAS,EAAE,SAYvB,CAAC;AAEF,wBAAgB,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,CAK1C;AAED,eAAO,MAAM,gBAAgB,EAAE,SAG9B,CAAC;AAEF,wBAAgB,MAAM,CAAC,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAahE"}
@@ -0,0 +1,17 @@
1
+ import type { TraceEvent, TraceEventData, TraceHandler } from './types';
2
+ export interface DevtoolsHook {
3
+ onBatcherCreated(info: {
4
+ fn: {
5
+ toString(): string;
6
+ };
7
+ name: string | undefined;
8
+ stack: string | undefined;
9
+ }): ((event: TraceEvent) => void) | undefined;
10
+ }
11
+ export declare function __setDevtoolsHook(hook: DevtoolsHook | null): void;
12
+ export declare function getDevtoolsHook(): DevtoolsHook | null;
13
+ export declare function createTracer<K>(name: string | undefined, handler: TraceHandler<K> | undefined, getDevtoolsEmitter: (() => ((event: TraceEvent<K>) => void) | undefined) | undefined): {
14
+ emit: (event: TraceEventData<K>) => void;
15
+ nextBatchId: () => string;
16
+ };
17
+ //# sourceMappingURL=trace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace.d.ts","sourceRoot":"","sources":["../src/trace.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAExE,MAAM,WAAW,YAAY;IAC3B,gBAAgB,CAAC,IAAI,EAAE;QACrB,EAAE,EAAE;YAAE,QAAQ,IAAI,MAAM,CAAA;SAAE,CAAC;QAC3B,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;QACzB,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;KAC3B,GAAG,CAAC,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;CAC/C;AAID,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,GAAG,IAAI,CAEjE;AAED,wBAAgB,eAAe,IAAI,YAAY,GAAG,IAAI,CAErD;AAED,wBAAgB,YAAY,CAAC,CAAC,EAC5B,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,SAAS,EACpC,kBAAkB,EACd,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC,GACpD,SAAS;kBAIQ,eAAe,CAAC,CAAC;uBAiBd,MAAM;EAK/B"}
@@ -0,0 +1,61 @@
1
+ export type BatchFn<K, V> = (keys: K[], signal: AbortSignal) => Promise<V[] | Record<string, V>>;
2
+ export type Match<K, V> = keyof V | symbol | MatchFn<K, V>;
3
+ export type MatchFn<K, V> = (results: V[], key: K) => V | undefined;
4
+ export type IndexedMatchFn<K, V> = (results: Record<string, V>, key: K) => V | undefined;
5
+ export type Scheduler = (dispatch: () => void) => () => void;
6
+ export type TraceHandler<K = unknown> = (event: TraceEvent<K>) => void;
7
+ export type TraceEventData<K = unknown> = {
8
+ type: 'get';
9
+ key: K;
10
+ } | {
11
+ type: 'dedup';
12
+ key: K;
13
+ } | {
14
+ type: 'schedule';
15
+ batchId: string;
16
+ size: number;
17
+ } | {
18
+ type: 'dispatch';
19
+ batchId: string;
20
+ keys: K[];
21
+ } | {
22
+ type: 'resolve';
23
+ batchId: string;
24
+ duration: number;
25
+ } | {
26
+ type: 'error';
27
+ batchId: string;
28
+ error: Error;
29
+ } | {
30
+ type: 'abort';
31
+ batchId: string;
32
+ };
33
+ export type TraceEvent<K = unknown> = TraceEventData<K> & {
34
+ timestamp: number;
35
+ };
36
+ export interface BatchOptions<K = unknown> {
37
+ wait?: number;
38
+ schedule?: Scheduler;
39
+ max?: number;
40
+ key?: (k: K) => unknown;
41
+ name?: string;
42
+ trace?: TraceHandler<K>;
43
+ }
44
+ export interface GetOptions {
45
+ signal?: AbortSignal;
46
+ }
47
+ export interface Batcher<K, V> {
48
+ get(key: K, options?: GetOptions): Promise<V>;
49
+ get(keys: K[], options?: GetOptions): Promise<V[]>;
50
+ flush(): Promise<void>;
51
+ abort(): void;
52
+ readonly name?: string;
53
+ }
54
+ export interface PendingRequest<K, V> {
55
+ key: K;
56
+ resolve: (value: V) => void;
57
+ reject: (error: Error) => void;
58
+ signal?: AbortSignal;
59
+ aborted: boolean;
60
+ }
61
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "batchkit",
3
- "version": "0.2.0-beta.1",
3
+ "version": "0.3.0",
4
4
  "description": "A modern TypeScript library for batching async operations",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -28,6 +28,11 @@
28
28
  ],
29
29
  "author": "",
30
30
  "license": "MIT",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/SirajChokshi/batchkit.git",
34
+ "directory": "packages/core"
35
+ },
31
36
  "devDependencies": {
32
37
  "bun-types": "latest",
33
38
  "typescript": "5.3.3"
package/src/batch.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { BatchError } from './errors';
2
2
  import { createIndexedMatcher, isIndexed, normalizeMatch } from './match';
3
3
  import { microtask, wait } from './schedulers';
4
- import { createTracer } from './trace';
4
+ import { createTracer, getDevtoolsHook } from './trace';
5
5
  import type {
6
6
  Batcher,
7
7
  BatchFn,
@@ -10,6 +10,7 @@ import type {
10
10
  Match,
11
11
  PendingRequest,
12
12
  Scheduler,
13
+ TraceEvent,
13
14
  } from './types';
14
15
 
15
16
  export function batch<K, V>(
@@ -27,7 +28,16 @@ export function batch<K, V>(
27
28
  } = options;
28
29
 
29
30
  const scheduler: Scheduler = schedule ?? (waitMs ? wait(waitMs) : microtask);
30
- const tracer = createTracer(name, traceHandler);
31
+
32
+ let devtoolsEmitter: ((event: TraceEvent) => void) | undefined;
33
+ const hook = getDevtoolsHook();
34
+
35
+ if (hook) {
36
+ const stack = new Error().stack;
37
+ devtoolsEmitter = hook.onBatcherCreated({ fn, name, stack });
38
+ }
39
+
40
+ const tracer = createTracer(name, traceHandler, () => devtoolsEmitter);
31
41
 
32
42
  const matchFn = normalizeMatch(match);
33
43
  const isIndexedMatch = isIndexed(match);
@@ -38,6 +48,7 @@ export function batch<K, V>(
38
48
  let cleanup: (() => void) | null = null;
39
49
  let isScheduled = false;
40
50
  let currentAbortController: AbortController | null = null;
51
+ let inFlightRequests: PendingRequest<K, V>[] = [];
41
52
 
42
53
  function scheduleDispatch(): void {
43
54
  if (isScheduled || queue.length === 0) return;
@@ -115,6 +126,8 @@ export function batch<K, V>(
115
126
 
116
127
  if (uniqueKeys.length === 0) return;
117
128
 
129
+ inFlightRequests = chunk;
130
+
118
131
  tracer.emit({
119
132
  type: 'dispatch',
120
133
  batchId,
@@ -210,6 +223,7 @@ export function batch<K, V>(
210
223
  }
211
224
  } finally {
212
225
  currentAbortController = null;
226
+ inFlightRequests = [];
213
227
  }
214
228
  }
215
229
 
@@ -246,8 +260,15 @@ export function batch<K, V>(
246
260
  request.aborted = true;
247
261
  reject(new DOMException('Aborted', 'AbortError'));
248
262
 
249
- const allAborted = queue.every((r) => r.aborted);
250
- if (allAborted && currentAbortController) {
263
+ const allPendingAborted = queue.every((r) => r.aborted);
264
+ const allInFlightAborted =
265
+ inFlightRequests.length > 0 &&
266
+ inFlightRequests.every((r) => r.aborted);
267
+ if (
268
+ allPendingAborted &&
269
+ allInFlightAborted &&
270
+ currentAbortController
271
+ ) {
251
272
  currentAbortController.abort();
252
273
  }
253
274
  };
package/src/index.ts CHANGED
@@ -2,6 +2,8 @@ export { batch } from './batch';
2
2
  export { BatchError } from './errors';
3
3
  export { indexed } from './indexed';
4
4
  export { onAnimationFrame, onIdle } from './schedulers';
5
+ export type { DevtoolsHook } from './trace';
6
+ export { __setDevtoolsHook } from './trace';
5
7
 
6
8
  export type {
7
9
  Batcher,
@@ -12,5 +14,6 @@ export type {
12
14
  MatchFn,
13
15
  Scheduler,
14
16
  TraceEvent,
17
+ TraceEventData,
15
18
  TraceHandler,
16
19
  } from './types';
package/src/match.ts CHANGED
@@ -6,7 +6,7 @@ export function isIndexed<K, V>(match: Match<K, V>): match is symbol {
6
6
  }
7
7
 
8
8
  export function isKeyMatch<K, V>(match: Match<K, V>): match is keyof V {
9
- return typeof match === 'string';
9
+ return typeof match === 'string' || typeof match === 'number';
10
10
  }
11
11
 
12
12
  export function normalizeMatch<K, V>(match: Match<K, V>): MatchFn<K, V> | null {
@@ -16,11 +16,11 @@ export function normalizeMatch<K, V>(match: Match<K, V>): MatchFn<K, V> | null {
16
16
  }
17
17
 
18
18
  if (isKeyMatch<K, V>(match)) {
19
- const key = match;
19
+ const key = match as string | number;
20
20
  return (results: V[], requestedKey: K) => {
21
21
  return results.find(
22
22
  (item) =>
23
- (item as Record<string, unknown>)[key as string] === requestedKey,
23
+ (item as Record<string | number, unknown>)[key] === requestedKey,
24
24
  );
25
25
  };
26
26
  }
package/src/trace.ts CHANGED
@@ -1,30 +1,34 @@
1
1
  import type { TraceEvent, TraceEventData, TraceHandler } from './types';
2
2
 
3
- interface DevtoolsRegistry {
4
- register(info: { name: string; registeredAt: number }): void;
5
- emit(name: string, event: unknown): void;
3
+ export interface DevtoolsHook {
4
+ onBatcherCreated(info: {
5
+ fn: { toString(): string };
6
+ name: string | undefined;
7
+ stack: string | undefined;
8
+ }): ((event: TraceEvent) => void) | undefined;
6
9
  }
7
10
 
8
- declare const window: { __BATCHKIT_DEVTOOLS__?: DevtoolsRegistry } | undefined;
11
+ let devtoolsHook: DevtoolsHook | null = null;
9
12
 
10
- function getDevtools(): DevtoolsRegistry | undefined {
11
- // biome-ignore lint/complexity/useOptionalChain: need typeof check for Node.js
12
- if (typeof window !== 'undefined' && window.__BATCHKIT_DEVTOOLS__) {
13
- return window.__BATCHKIT_DEVTOOLS__;
14
- }
15
- return undefined;
13
+ export function __setDevtoolsHook(hook: DevtoolsHook | null): void {
14
+ devtoolsHook = hook;
15
+ }
16
+
17
+ export function getDevtoolsHook(): DevtoolsHook | null {
18
+ return devtoolsHook;
16
19
  }
17
20
 
18
21
  export function createTracer<K>(
19
22
  name: string | undefined,
20
23
  handler: TraceHandler<K> | undefined,
24
+ getDevtoolsEmitter:
25
+ | (() => ((event: TraceEvent<K>) => void) | undefined)
26
+ | undefined,
21
27
  ) {
22
28
  let batchCounter = 0;
23
- let registeredWithDevtools = false;
24
29
 
25
30
  function emit(event: TraceEventData<K>) {
26
31
  const timestamp = performance.now();
27
-
28
32
  const fullEvent = {
29
33
  ...event,
30
34
  timestamp,
@@ -34,13 +38,9 @@ export function createTracer<K>(
34
38
  handler(fullEvent);
35
39
  }
36
40
 
37
- const devtools = getDevtools();
38
- if (devtools && name) {
39
- if (!registeredWithDevtools) {
40
- devtools.register({ name, registeredAt: timestamp });
41
- registeredWithDevtools = true;
42
- }
43
- devtools.emit(name, fullEvent);
41
+ const devtoolsEmitter = getDevtoolsEmitter?.();
42
+ if (devtoolsEmitter) {
43
+ devtoolsEmitter(fullEvent);
44
44
  }
45
45
  }
46
46