@playfast/reform-profiler 1.0.2 → 1.2.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.
Files changed (53) hide show
  1. package/dist/index.d.ts +6 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +6 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/noop.d.ts +23 -0
  6. package/dist/noop.d.ts.map +1 -0
  7. package/dist/noop.js +41 -0
  8. package/dist/noop.js.map +1 -0
  9. package/dist/profileScene.d.ts +12 -0
  10. package/dist/profileScene.d.ts.map +1 -0
  11. package/dist/profileScene.js +32 -0
  12. package/dist/profileScene.js.map +1 -0
  13. package/dist/react/index.d.ts +2 -0
  14. package/dist/react/index.d.ts.map +1 -0
  15. package/dist/react/index.js +2 -0
  16. package/dist/react/index.js.map +1 -0
  17. package/dist/react/noop.d.ts +5 -0
  18. package/dist/react/noop.d.ts.map +1 -0
  19. package/dist/react/noop.js +3 -0
  20. package/dist/react/noop.js.map +1 -0
  21. package/dist/react/overlay.d.ts +5 -0
  22. package/dist/react/overlay.d.ts.map +1 -0
  23. package/dist/react/overlay.js +70 -0
  24. package/dist/react/overlay.js.map +1 -0
  25. package/dist/react/styles.d.ts +23 -0
  26. package/dist/react/styles.d.ts.map +1 -0
  27. package/dist/react/styles.js +91 -0
  28. package/dist/react/styles.js.map +1 -0
  29. package/dist/recorder.d.ts +38 -0
  30. package/dist/recorder.d.ts.map +1 -0
  31. package/dist/recorder.js +82 -0
  32. package/dist/recorder.js.map +1 -0
  33. package/dist/registry.d.ts +12 -0
  34. package/dist/registry.d.ts.map +1 -0
  35. package/dist/registry.js +57 -0
  36. package/dist/registry.js.map +1 -0
  37. package/dist/report.d.ts +3 -0
  38. package/dist/report.d.ts.map +1 -0
  39. package/dist/report.js +48 -0
  40. package/dist/report.js.map +1 -0
  41. package/dist/trace.d.ts +36 -0
  42. package/dist/trace.d.ts.map +1 -0
  43. package/dist/trace.js +58 -0
  44. package/dist/trace.js.map +1 -0
  45. package/package.json +42 -24
  46. package/src/index.ts +1 -1
  47. package/src/noop.ts +48 -12
  48. package/src/profileScene.ts +85 -19
  49. package/src/profiler.test.ts +38 -10
  50. package/src/react/overlay.tsx +3 -1
  51. package/src/recorder.ts +2 -3
  52. package/src/registry.ts +4 -1
  53. package/src/trace.ts +19 -2
@@ -0,0 +1,57 @@
1
+ import { GlobalValue, Option } from 'effect';
2
+ const state = GlobalValue.globalValue(Symbol.for('reform-profiler/registry'), () => ({
3
+ entries: new Map(),
4
+ listeners: new Set(),
5
+ version: { current: 0 },
6
+ overlay: { owner: Option.none() },
7
+ }));
8
+ const notify = () => {
9
+ state.version.current += 1;
10
+ [...state.listeners].forEach((listener) => listener());
11
+ };
12
+ export const registerProfiler = (registration) => {
13
+ const existing = state.entries.get(registration.handle);
14
+ state.entries.set(registration.handle, {
15
+ registration,
16
+ count: existing === undefined ? 1 : existing.count + 1,
17
+ });
18
+ if (existing === undefined) {
19
+ notify();
20
+ }
21
+ return () => {
22
+ const current = state.entries.get(registration.handle);
23
+ if (current === undefined) {
24
+ return;
25
+ }
26
+ if (current.count <= 1) {
27
+ state.entries.delete(registration.handle);
28
+ notify();
29
+ }
30
+ else {
31
+ state.entries.set(registration.handle, { ...current, count: current.count - 1 });
32
+ }
33
+ };
34
+ };
35
+ export const listProfilers = () => [...state.entries.values()].map((entry) => entry.registration);
36
+ export const subscribeProfilers = (listener) => {
37
+ state.listeners.add(listener);
38
+ return () => {
39
+ state.listeners.delete(listener);
40
+ };
41
+ };
42
+ export const profilersVersion = () => state.version.current;
43
+ // First mounted overlay claims render; later instances no-op until claim released.
44
+ export const claimOverlay = (owner) => {
45
+ if (Option.isNone(state.overlay.owner)) {
46
+ state.overlay.owner = Option.some(owner);
47
+ return true;
48
+ }
49
+ return Option.contains(state.overlay.owner, owner);
50
+ };
51
+ export const releaseOverlay = (owner) => {
52
+ if (Option.contains(state.overlay.owner, owner)) {
53
+ state.overlay.owner = Option.none();
54
+ notify();
55
+ }
56
+ };
57
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { ProfilerHandle } from './recorder.js';
2
+ export declare const formatReport: (profiler: ProfilerHandle) => string;
3
+ //# sourceMappingURL=report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAe,MAAM,YAAY,CAAA;AAiD7D,eAAO,MAAM,YAAY,GAAI,UAAU,cAAc,KAAG,MAqBvD,CAAA"}
package/dist/report.js ADDED
@@ -0,0 +1,48 @@
1
+ const sortedRows = (stats) => [...stats.entries()]
2
+ .map(([name, stat]) => ({ name, stat }))
3
+ .toSorted((left, right) => right.stat.totalMs - left.stat.totalMs || right.stat.count - left.stat.count);
4
+ const MS_DECIMALS = 2;
5
+ const formatMs = (durationMs) => durationMs.toFixed(MS_DECIMALS);
6
+ const COUNT_WIDTH = 7;
7
+ const TOTAL_WIDTH = 9;
8
+ const AVG_WIDTH = 8;
9
+ const ruleOverhead = '── '.length + ' '.length;
10
+ const section = ({ title, rows, timed }) => {
11
+ if (rows.length === 0) {
12
+ return [];
13
+ }
14
+ const nameWidth = Math.max('name'.length, ...rows.map((row) => row.name.length));
15
+ const header = timed
16
+ ? `${'name'.padEnd(nameWidth)} ${'count'.padStart(COUNT_WIDTH)} ${'total ms'.padStart(TOTAL_WIDTH)} ${'avg ms'.padStart(AVG_WIDTH)}`
17
+ : `${'name'.padEnd(nameWidth)} ${'count'.padStart(COUNT_WIDTH)}`;
18
+ const lines = rows.map((row) => timed
19
+ ? `${row.name.padEnd(nameWidth)} ${String(row.stat.count).padStart(COUNT_WIDTH)} ${formatMs(row.stat.totalMs).padStart(TOTAL_WIDTH)} ${formatMs(row.stat.count === 0 ? 0 : row.stat.totalMs / row.stat.count).padStart(AVG_WIDTH)}`
20
+ : `${row.name.padEnd(nameWidth)} ${String(row.stat.count).padStart(COUNT_WIDTH)}`);
21
+ return [
22
+ `── ${title} ${'─'.repeat(Math.max(0, header.length - title.length - ruleOverhead))}`,
23
+ header,
24
+ ...lines,
25
+ '',
26
+ ];
27
+ };
28
+ export const formatReport = (profiler) => {
29
+ const snapshot = profiler.snapshot();
30
+ const frameRows = snapshot.frames.count === 0 ? [] : [{ name: 'frame', stat: snapshot.frames }];
31
+ const dropped = snapshot.dropped === 0
32
+ ? []
33
+ : [`(${snapshot.dropped} early records evicted from the ring buffer)`, ''];
34
+ return [
35
+ `reform profile — ${profiler.label}`,
36
+ '',
37
+ ...section({ title: 'Events', rows: sortedRows(snapshot.events), timed: false }),
38
+ ...section({ title: 'Reducers', rows: sortedRows(snapshot.reducers), timed: true }),
39
+ ...section({ title: 'State updates', rows: sortedRows(snapshot.states), timed: false }),
40
+ ...section({ title: 'Calcs', rows: sortedRows(snapshot.calcs), timed: true }),
41
+ ...section({ title: 'Queries', rows: sortedRows(snapshot.queries), timed: true }),
42
+ ...section({ title: 'Procedures', rows: sortedRows(snapshot.procedures), timed: true }),
43
+ ...section({ title: 'Renders', rows: sortedRows(snapshot.renders), timed: true }),
44
+ ...section({ title: 'Frames', rows: frameRows, timed: true }),
45
+ ...dropped,
46
+ ].join('\n');
47
+ };
48
+ //# sourceMappingURL=report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAOA,MAAM,UAAU,GAAG,CAAC,KAAuC,EAAsB,EAAE,CACjF,CAAC,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC;KACjB,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,EAAO,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;KAC5C,QAAQ,CACP,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAC9F,CAAA;AAEL,MAAM,WAAW,GAAG,CAAC,CAAA;AACrB,MAAM,QAAQ,GAAG,CAAC,UAAkB,EAAU,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;AAEhF,MAAM,WAAW,GAAG,CAAC,CAAA;AACrB,MAAM,WAAW,GAAG,CAAC,CAAA;AACrB,MAAM,SAAS,GAAG,CAAC,CAAA;AACnB,MAAM,YAAY,GAAG,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAA;AAQ9C,MAAM,OAAO,GAAG,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAW,EAAyB,EAAE;IACzE,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,EAAE,CAAA;IACX,CAAC;IACD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA;IAChF,MAAM,MAAM,GAAG,KAAK;QAClB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,KAAK,UAAU,CAAC,QAAQ,CAAC,WAAW,CAAC,KAAK,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE;QACvI,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAA;IACnE,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAC7B,KAAK;QACH,CAAC,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,KAAK,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,KAAK,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE;QACtO,CAAC,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CACrF,CAAA;IACD,OAAO;QACL,MAAM,KAAK,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,YAAY,CAAC,CAAC,EAAE;QACrF,MAAM;QACN,GAAG,KAAK;QACR,EAAE;KACH,CAAA;AACH,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,QAAwB,EAAU,EAAE;IAC/D,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,EAAE,CAAA;IACpC,MAAM,SAAS,GACb,QAAQ,CAAC,MAAM,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;IAC/E,MAAM,OAAO,GACX,QAAQ,CAAC,OAAO,KAAK,CAAC;QACpB,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,OAAO,8CAA8C,EAAE,EAAE,CAAC,CAAA;IAC9E,OAAO;QACL,oBAAoB,QAAQ,CAAC,KAAK,EAAE;QACpC,EAAE;QACF,GAAG,OAAO,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QAChF,GAAG,OAAO,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QACnF,GAAG,OAAO,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QACvF,GAAG,OAAO,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QAC7E,GAAG,OAAO,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QACjF,GAAG,OAAO,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QACvF,GAAG,OAAO,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QACjF,GAAG,OAAO,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QAC7D,GAAG,OAAO;KACX,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACd,CAAC,CAAA"}
@@ -0,0 +1,36 @@
1
+ import type { ProfilerHandle } from './recorder.js';
2
+ export type ChromeTraceEvent = {
3
+ readonly ph: 'X';
4
+ readonly name: string;
5
+ readonly cat: string;
6
+ readonly ts: number;
7
+ readonly dur: number;
8
+ readonly pid: number;
9
+ readonly tid: number;
10
+ readonly args: Readonly<Record<string, string>>;
11
+ } | {
12
+ readonly ph: 'i';
13
+ readonly name: string;
14
+ readonly cat: string;
15
+ readonly ts: number;
16
+ readonly pid: number;
17
+ readonly tid: number;
18
+ readonly s: 't';
19
+ readonly args: Readonly<Record<string, string>>;
20
+ } | {
21
+ readonly ph: 'M';
22
+ readonly name: 'process_name' | 'thread_name';
23
+ readonly ts: 0;
24
+ readonly pid: number;
25
+ readonly tid: number;
26
+ readonly args: {
27
+ readonly name: string;
28
+ };
29
+ };
30
+ export interface ChromeTrace {
31
+ readonly traceEvents: ReadonlyArray<ChromeTraceEvent>;
32
+ readonly displayTimeUnit: 'ms';
33
+ }
34
+ export declare const toChromeTrace: (profiler: ProfilerHandle) => ChromeTrace;
35
+ export declare const traceJson: (profiler: ProfilerHandle) => string;
36
+ //# 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,cAAc,EAAE,MAAM,YAAY,CAAA;AAE7D,MAAM,MAAM,gBAAgB,GACxB;IACE,QAAQ,CAAC,EAAE,EAAE,GAAG,CAAA;IAChB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CAChD,GACD;IACE,QAAQ,CAAC,EAAE,EAAE,GAAG,CAAA;IAChB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAA;IACf,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CAChD,GACD;IACE,QAAQ,CAAC,EAAE,EAAE,GAAG,CAAA;IAChB,QAAQ,CAAC,IAAI,EAAE,cAAc,GAAG,aAAa,CAAA;IAC7C,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAA;IACd,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CACzC,CAAA;AAEL,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAA;IACrD,QAAQ,CAAC,eAAe,EAAE,IAAI,CAAA;CAC/B;AAkBD,eAAO,MAAM,aAAa,GAAI,UAAU,cAAc,KAAG,WA0CxD,CAAA;AAED,eAAO,MAAM,SAAS,GAAI,UAAU,cAAc,KAAG,MAEZ,CAAA"}
package/dist/trace.js ADDED
@@ -0,0 +1,58 @@
1
+ const lanes = {
2
+ frame: { tid: 1, title: 'frames' },
3
+ event: { tid: 2, title: 'events' },
4
+ reducer: { tid: 3, title: 'reducers' },
5
+ state: { tid: 4, title: 'state updates' },
6
+ calc: { tid: 5, title: 'calcs' },
7
+ query: { tid: 6, title: 'queries' },
8
+ procedure: { tid: 7, title: 'procedures' },
9
+ render: { tid: 8, title: 'renders' },
10
+ };
11
+ const instantKinds = new Set(['event', 'state']);
12
+ const pid = 1;
13
+ const US_PER_MS = 1000;
14
+ export const toChromeTrace = (profiler) => {
15
+ const { records } = profiler.snapshot();
16
+ const meta = [
17
+ {
18
+ ph: 'M',
19
+ name: 'process_name',
20
+ ts: 0,
21
+ pid,
22
+ tid: 0,
23
+ args: { name: `reform: ${profiler.label}` },
24
+ },
25
+ ...Object.values(lanes).map((lane) => ({
26
+ ph: 'M',
27
+ name: 'thread_name',
28
+ ts: 0,
29
+ pid,
30
+ tid: lane.tid,
31
+ args: { name: lane.title },
32
+ })),
33
+ ];
34
+ const body = records.map((record) => {
35
+ const startUs = record.start * US_PER_MS;
36
+ const cat = record.kind;
37
+ const tid = lanes[record.kind].tid;
38
+ const args = record.detail === '' ? {} : { detail: record.detail };
39
+ const complete = {
40
+ ph: 'X',
41
+ name: record.name,
42
+ cat,
43
+ ts: startUs,
44
+ dur: record.dur * US_PER_MS,
45
+ pid,
46
+ tid,
47
+ args,
48
+ };
49
+ return instantKinds.has(record.kind)
50
+ ? { ph: 'i', name: record.name, cat, ts: startUs, pid, tid, s: 't', args }
51
+ : complete;
52
+ });
53
+ return { traceEvents: [...meta, ...body], displayTimeUnit: 'ms' };
54
+ };
55
+ export const traceJson = (profiler) =>
56
+ // oxlint-disable-next-line reform-rules/no-json-parse-stringify -- Trace Event Format is a foreign wire format consumed by Chrome DevTools/Perfetto, not Schema-typed data
57
+ JSON.stringify(toChromeTrace(profiler));
58
+ //# sourceMappingURL=trace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace.js","sourceRoot":"","sources":["../src/trace.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,55 +1,75 @@
1
1
  {
2
2
  "name": "@playfast/reform-profiler",
3
- "playbook": "./playbook",
4
- "version": "1.0.2",
5
- "type": "module",
3
+ "version": "1.2.0",
6
4
  "description": "Performance profiler for reform scenes — a recording Instrumentation, Chrome DevTools trace export, terminal reports, and a React overlay. Dev builds get the real recorder; prod builds resolve to no-op stubs.",
7
5
  "keywords": [
8
- "reform",
6
+ "devtools",
9
7
  "effect",
10
- "profiler",
11
8
  "performance",
12
- "devtools"
9
+ "profiler",
10
+ "reform"
13
11
  ],
12
+ "bugs": {
13
+ "url": "https://github.com/playfast/reform/issues"
14
+ },
14
15
  "license": "MIT",
15
16
  "repository": {
16
17
  "type": "git",
17
18
  "url": "https://github.com/playfast/reform.git",
18
19
  "directory": "packages/reform-profiler"
19
20
  },
20
- "bugs": {
21
- "url": "https://github.com/playfast/reform/issues"
22
- },
21
+ "files": [
22
+ "dist",
23
+ "src",
24
+ "README.md"
25
+ ],
26
+ "type": "module",
23
27
  "sideEffects": false,
28
+ "main": "./dist/noop.js",
29
+ "types": "./dist/noop.d.ts",
24
30
  "exports": {
25
31
  "./package.json": "./package.json",
26
32
  ".": {
27
- "development": "./src/index.ts",
28
- "default": "./src/noop.ts"
33
+ "playfast-src": {
34
+ "development": "./src/index.ts",
35
+ "default": "./src/noop.ts"
36
+ },
37
+ "development": "./dist/index.js",
38
+ "types": "./dist/noop.d.ts",
39
+ "default": "./dist/noop.js"
29
40
  },
30
41
  "./react": {
31
- "development": "./src/react/index.ts",
32
- "default": "./src/react/noop.ts"
42
+ "playfast-src": {
43
+ "development": "./src/react/index.ts",
44
+ "default": "./src/react/noop.ts"
45
+ },
46
+ "development": "./dist/react/index.js",
47
+ "types": "./dist/react/noop.d.ts",
48
+ "default": "./dist/react/noop.js"
33
49
  },
34
- "./recorder": "./src/index.ts"
50
+ "./recorder": {
51
+ "playfast-src": "./src/index.ts",
52
+ "types": "./dist/index.d.ts",
53
+ "default": "./dist/index.js"
54
+ }
55
+ },
56
+ "publishConfig": {
57
+ "access": "public"
35
58
  },
36
- "files": [
37
- "src",
38
- "README.md"
39
- ],
40
59
  "scripts": {
41
60
  "clean": "rm -rf dist .tsbuildinfo",
42
61
  "check": "tsc --noEmit",
43
- "build": "tsc -p tsconfig.build.json",
62
+ "build": "rm -rf dist .tsbuildinfo && tsc -p tsconfig.build.json && bun ../../scripts/fix-esm-extensions.ts dist",
44
63
  "test": "vitest run",
45
64
  "test:watch": "vitest",
46
65
  "coverage": "vitest run --coverage",
47
66
  "lint": "oxlint src",
48
- "lint:fix": "oxlint --fix src"
67
+ "lint:fix": "oxlint --fix src",
68
+ "prepack": "bun run build"
49
69
  },
50
70
  "peerDependencies": {
51
- "effect": "*",
52
71
  "@playfast/reform": "*",
72
+ "effect": "*",
53
73
  "react": "^19.0.0"
54
74
  },
55
75
  "peerDependenciesMeta": {
@@ -57,7 +77,5 @@
57
77
  "optional": true
58
78
  }
59
79
  },
60
- "publishConfig": {
61
- "access": "public"
62
- }
80
+ "playbook": "./playbook"
63
81
  }
package/src/index.ts CHANGED
@@ -7,7 +7,7 @@ export {
7
7
  type ProfileSnapshot,
8
8
  type ProfileStat,
9
9
  } from './recorder'
10
- export { type ProfiledScene, profileScene } from './profileScene'
10
+ export { type CapturedProfiledScene, type ProfiledScene, profileScene } from './profileScene'
11
11
  export { type ChromeTrace, type ChromeTraceEvent, toChromeTrace, traceJson } from './trace'
12
12
  export { formatReport } from './report'
13
13
  export {
package/src/noop.ts CHANGED
@@ -1,10 +1,7 @@
1
- import { noopInstrumentation, type Scene, type UiContract } from '@playfast/reform'
2
- import type { ProfiledScene } from './profileScene'
3
- import type {
4
- ProfilerHandle,
5
- ProfilerOptionsExternalApi,
6
- ProfileSnapshot,
7
- } from './recorder'
1
+ import { type CapturedScene, type Scene, type UiContract } from '@playfast/reform'
2
+ import { noopInstrumentation } from '@playfast/reform/internal'
3
+ import type { CapturedProfiledScene, ProfiledScene } from './profileScene'
4
+ import type { ProfilerHandle, ProfilerOptionsExternalApi, ProfileSnapshot } from './recorder'
8
5
  import type { ProfilerRegistration } from './registry'
9
6
  import type { ChromeTrace } from './trace'
10
7
 
@@ -18,7 +15,7 @@ export type {
18
15
  ProfileSnapshot,
19
16
  ProfileStat,
20
17
  } from './recorder'
21
- export type { ProfiledScene } from './profileScene'
18
+ export type { CapturedProfiledScene, ProfiledScene } from './profileScene'
22
19
  export type { ChromeTrace, ChromeTraceEvent } from './trace'
23
20
  export type { ProfilerRegistration } from './registry'
24
21
 
@@ -47,10 +44,45 @@ export const noopProfiler: ProfilerHandle = {
47
44
  export const makeProfiler = (_options: ProfilerOptionsExternalApi = {}): ProfilerHandle =>
48
45
  noopProfiler
49
46
 
50
- export const profileScene = <C extends UiContract, S extends ReadonlyArray<unknown>>(
47
+ const profileCapturedScene = <
48
+ C extends UiContract,
49
+ S extends ReadonlyArray<unknown>,
50
+ Services,
51
+ P,
52
+ N extends string,
53
+ Identity,
54
+ >(
55
+ base: CapturedScene<C, S, Services, P, N, Identity>,
56
+ ): CapturedProfiledScene<C, S, Services, P, N, Identity> => ({
57
+ scene: base,
58
+ profiler: noopProfiler,
59
+ })
60
+
61
+ export function profileScene<
62
+ C extends UiContract,
63
+ S extends ReadonlyArray<unknown>,
64
+ Services,
65
+ P,
66
+ N extends string,
67
+ Identity,
68
+ >(
69
+ base: CapturedScene<C, S, Services, P, N, Identity>,
70
+ _options?: ProfilerOptionsExternalApi,
71
+ ): CapturedProfiledScene<C, S, Services, P, N, Identity>
72
+ export function profileScene<C extends UiContract, S extends ReadonlyArray<unknown>>(
73
+ base: Scene<C, S>,
74
+ _options?: ProfilerOptionsExternalApi,
75
+ ): ProfiledScene<C, S>
76
+ export function profileScene<C extends UiContract, S extends ReadonlyArray<unknown>>(
51
77
  base: Scene<C, S>,
52
78
  _options: ProfilerOptionsExternalApi = {},
53
- ): ProfiledScene<C, S> => ({ scene: base, profiler: noopProfiler })
79
+ ): ProfiledScene<C, S> {
80
+ return base.capture<ProfiledScene<C, S>>(
81
+ <Services, P, N extends string, Identity>(
82
+ exactScene: CapturedScene<C, S, Services, P, N, Identity>,
83
+ ): ProfiledScene<C, S> => profileCapturedScene(exactScene),
84
+ )
85
+ }
54
86
 
55
87
  const emptyTrace: ChromeTrace = { traceEvents: [], displayTimeUnit: 'ms' }
56
88
 
@@ -61,11 +93,15 @@ export const traceJson = (_profiler: ProfilerHandle): string =>
61
93
 
62
94
  export const formatReport = (_profiler: ProfilerHandle): string => ''
63
95
 
64
- export const registerProfiler = (_registration: ProfilerRegistration): (() => void) => () => {}
96
+ export const registerProfiler =
97
+ (_registration: ProfilerRegistration): (() => void) =>
98
+ () => {}
65
99
 
66
100
  export const listProfilers = (): ReadonlyArray<ProfilerRegistration> => []
67
101
 
68
- export const subscribeProfilers = (_listener: () => void): (() => void) => () => {}
102
+ export const subscribeProfilers =
103
+ (_listener: () => void): (() => void) =>
104
+ () => {}
69
105
 
70
106
  export const profilersVersion = (): number => 0
71
107
 
@@ -1,5 +1,6 @@
1
1
  import { Effect, Layer } from 'effect'
2
- import { profiledScene, type Scene, type UiContract } from '@playfast/reform'
2
+ import { type CapturedScene, profiledScene, type Scene, type UiContract } from '@playfast/reform'
3
+ import { type AnySceneCapture, type SceneCapture } from '@playfast/reform/internal'
3
4
  import { makeProfiler, type ProfilerHandle, type ProfilerOptionsExternalApi } from './recorder'
4
5
  import { registerProfiler } from './registry'
5
6
 
@@ -11,29 +12,94 @@ export interface ProfiledScene<
11
12
  readonly profiler: ProfilerHandle
12
13
  }
13
14
 
14
- export const profileScene = <C extends UiContract, S extends ReadonlyArray<unknown>>(
15
+ export interface CapturedProfiledScene<
16
+ C extends UiContract,
17
+ S extends ReadonlyArray<unknown>,
18
+ Services,
19
+ P,
20
+ N extends string,
21
+ Identity,
22
+ > extends ProfiledScene<C, S> {
23
+ readonly scene: CapturedScene<C, S, Services, P, N, Identity>
24
+ }
25
+
26
+ const makeLifecycle = (profiler: ProfilerHandle) =>
27
+ Layer.scopedDiscard(
28
+ Effect.acquireRelease(
29
+ Effect.sync(() => registerProfiler({ label: profiler.label, handle: profiler })),
30
+ (unregister) => Effect.sync(unregister),
31
+ ),
32
+ )
33
+
34
+ const withLifecycle = <
35
+ C extends UiContract,
36
+ S extends ReadonlyArray<unknown>,
37
+ Services,
38
+ P,
39
+ N extends string,
40
+ Identity,
41
+ >(
42
+ base: CapturedScene<C, S, Services, P, N, Identity>,
43
+ lifecycle: ReturnType<typeof makeLifecycle>,
44
+ ): CapturedScene<C, S, Services, P, N, Identity> => {
45
+ const captured: CapturedScene<C, S, Services, P, N, Identity> = {
46
+ kind: 'Scene',
47
+ composition: base.composition,
48
+ // Lifecycle on the first provide layer tracks the runtime's build/dispose scope.
49
+ provide: base.provide.map((layer, index) =>
50
+ index === 0 ? Layer.merge(layer, lifecycle) : layer,
51
+ ),
52
+ ...(base.boot === undefined ? {} : { boot: base.boot }),
53
+ ...(base.instrumentation === undefined ? {} : { instrumentation: base.instrumentation }),
54
+ capture: <Result>(capture: SceneCapture<C, S, Result>): Result => capture(captured),
55
+ captureAny: <Result>(capture: AnySceneCapture<Result>): Result => capture(captured),
56
+ }
57
+ return captured
58
+ }
59
+
60
+ const profileCapturedScene = <
61
+ C extends UiContract,
62
+ S extends ReadonlyArray<unknown>,
63
+ Services,
64
+ P,
65
+ N extends string,
66
+ Identity,
67
+ >(
68
+ base: CapturedScene<C, S, Services, P, N, Identity>,
69
+ profiler: ProfilerHandle,
70
+ lifecycle: ReturnType<typeof makeLifecycle>,
71
+ ): CapturedProfiledScene<C, S, Services, P, N, Identity> => ({
72
+ scene: withLifecycle(profiledScene(base, profiler), lifecycle),
73
+ profiler,
74
+ })
75
+
76
+ export function profileScene<
77
+ C extends UiContract,
78
+ S extends ReadonlyArray<unknown>,
79
+ Services,
80
+ P,
81
+ N extends string,
82
+ Identity,
83
+ >(
84
+ base: CapturedScene<C, S, Services, P, N, Identity>,
85
+ options?: ProfilerOptionsExternalApi,
86
+ ): CapturedProfiledScene<C, S, Services, P, N, Identity>
87
+ export function profileScene<C extends UiContract, S extends ReadonlyArray<unknown>>(
88
+ base: Scene<C, S>,
89
+ options?: ProfilerOptionsExternalApi,
90
+ ): ProfiledScene<C, S>
91
+ export function profileScene<C extends UiContract, S extends ReadonlyArray<unknown>>(
15
92
  base: Scene<C, S>,
16
93
  options: ProfilerOptionsExternalApi = {},
17
- ): ProfiledScene<C, S> => {
94
+ ): ProfiledScene<C, S> {
18
95
  const profiler = makeProfiler({
19
96
  label: options.label ?? base.composition.manifest.title,
20
97
  ...(options.capacity !== undefined ? { capacity: options.capacity } : {}),
21
98
  })
22
- const lifecycle = Layer.scopedDiscard(
23
- Effect.acquireRelease(
24
- Effect.sync(() => registerProfiler({ label: profiler.label, handle: profiler })),
25
- (unregister) => Effect.sync(unregister),
26
- ),
99
+ const lifecycle = makeLifecycle(profiler)
100
+ return base.capture<ProfiledScene<C, S>>(
101
+ <Services, P, N extends string, Identity>(
102
+ exactScene: CapturedScene<C, S, Services, P, N, Identity>,
103
+ ): ProfiledScene<C, S> => profileCapturedScene(exactScene, profiler, lifecycle),
27
104
  )
28
- const wrapped = profiledScene(base, profiler)
29
- return {
30
- // Lifecycle on first provide layer so register/unregister track runtime build/dispose.
31
- scene: {
32
- ...wrapped,
33
- provide: wrapped.provide.map((layer, index) =>
34
- index === 0 ? Layer.merge(layer, lifecycle) : layer,
35
- ),
36
- },
37
- profiler,
38
- }
39
105
  }