@forgeax/engine-profiler 0.1.3 → 0.1.6

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 (40) hide show
  1. package/README.md +1 -1
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/browser-user-timing.d.ts.map +1 -1
  4. package/dist/browser-user-timing.mjs +1 -6
  5. package/dist/browser-user-timing.mjs.map +1 -1
  6. package/dist/cli.d.ts +2 -14
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.mjs +1 -14
  9. package/dist/cli.mjs.map +1 -1
  10. package/dist/compare.d.ts.map +1 -1
  11. package/dist/index.d.ts +0 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.mjs +14 -51
  14. package/dist/index.mjs.map +1 -1
  15. package/dist/recorder.d.ts.map +1 -1
  16. package/package.json +1 -1
  17. package/scripts/generate-profile-types.mjs +3 -12
  18. package/src/__tests__/cli-input-errors.test.ts +0 -39
  19. package/src/__tests__/cli-process.integration.test.ts +1 -28
  20. package/src/browser-user-timing.ts +3 -11
  21. package/src/cli.ts +6 -21
  22. package/src/clock.ts +1 -1
  23. package/src/compare.ts +1 -4
  24. package/src/index.ts +0 -6
  25. package/src/recorder.ts +15 -16
  26. package/dist/__tests__/browser-profile-detail-owner.test-d.d.ts +0 -2
  27. package/dist/__tests__/browser-profile-detail-owner.test-d.d.ts.map +0 -1
  28. package/dist/__tests__/browser-user-timing.test.d.ts +0 -2
  29. package/dist/__tests__/browser-user-timing.test.d.ts.map +0 -1
  30. package/dist/__tests__/comparison-side-owner.test-d.d.ts +0 -2
  31. package/dist/__tests__/comparison-side-owner.test-d.d.ts.map +0 -1
  32. package/dist/__tests__/schema-derived-union-owner.test.d.ts +0 -2
  33. package/dist/__tests__/schema-derived-union-owner.test.d.ts.map +0 -1
  34. package/dist/tool-preview-profile.d.ts +0 -33
  35. package/dist/tool-preview-profile.d.ts.map +0 -1
  36. package/src/__tests__/browser-profile-detail-owner.test-d.ts +0 -26
  37. package/src/__tests__/browser-user-timing.test.ts +0 -127
  38. package/src/__tests__/comparison-side-owner.test-d.ts +0 -28
  39. package/src/__tests__/schema-derived-union-owner.test.ts +0 -57
  40. package/src/tool-preview-profile.ts +0 -67
@@ -1 +1 @@
1
- {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../src/recorder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAiB,KAAK,cAAc,EAAc,MAAM,aAAa,CAAC;AAC7E,OAAO,KAAK,EACV,cAAc,EACd,iBAAiB,EAEjB,gBAAgB,EAChB,aAAa,EACd,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;CACjC;AAED,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE1D,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC,MAAM,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;AAEtF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IAClD,UAAU,CAAC,KAAK,EAAE,iBAAiB,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IAC3D,UAAU,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACvE,QAAQ,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC;IACjC,UAAU,CAAC,KAAK,EAAE,gBAAgB,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IAC1D,QAAQ,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC;IACjC,MAAM,IAAI,cAAc,CAAC,cAAc,CAAC,CAAC;CAC1C;AAgDD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,cAAc,GAAG,cAAc,CAAC,IAAI,CAAC,CAKnF;AAgLD,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,YAAY,EACnB,YAAY,EAAE,oBAAoB,EAClC,gBAAgB,CAAC,EAAE;IAAE,8BAA8B,EAAE,MAAM,CAAA;CAAE,GAC5D,cAAc,CAAC,eAAe,CAAC,CAuJjC"}
1
+ {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../src/recorder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAiB,KAAK,cAAc,EAAc,MAAM,aAAa,CAAC;AAC7E,OAAO,KAAK,EACV,cAAc,EACd,iBAAiB,EAEjB,gBAAgB,EAChB,aAAa,EACd,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;CACjC;AAED,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE1D,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC,MAAM,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;AAEtF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IAClD,UAAU,CAAC,KAAK,EAAE,iBAAiB,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IAC3D,UAAU,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACvE,QAAQ,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC;IACjC,UAAU,CAAC,KAAK,EAAE,gBAAgB,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IAC1D,QAAQ,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC;IACjC,MAAM,IAAI,cAAc,CAAC,cAAc,CAAC,CAAC;CAC1C;AAgDD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,cAAc,GAAG,cAAc,CAAC,IAAI,CAAC,CAKnF;AAgLD,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,YAAY,EACnB,YAAY,EAAE,oBAAoB,EAClC,gBAAgB,CAAC,EAAE;IAAE,8BAA8B,EAAE,MAAM,CAAA;CAAE,GAC5D,cAAc,CAAC,eAAe,CAAC,CAsJjC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgeax/engine-profiler",
3
- "version": "0.1.3",
3
+ "version": "0.1.6",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -4,22 +4,13 @@ const packageRoot = new URL('../', import.meta.url);
4
4
  const schemaPath = new URL('./schema/profile-capture.schema.json', packageRoot);
5
5
  const outputPath = new URL('./src/generated/profile-capture.ts', packageRoot);
6
6
  const schema = JSON.parse(readFileSync(schemaPath, 'utf8'));
7
- const literalUnion = (values) => values.map((value) => `'${value}'`).join(' | ');
8
- const resolveDefinition = (reference) => schema.$defs[reference.slice('#/$defs/'.length)];
9
- const profileSource = literalUnion(schema.$defs.source.enum);
10
- const profileRecordKind = literalUnion(
11
- schema.$defs.record.oneOf.map(
12
- ({ $ref }) => resolveDefinition($ref).properties.kind.const,
13
- ),
14
- );
15
- const profileCompletenessStatus = literalUnion(schema.$defs.completeness.properties.status.enum);
16
7
 
17
8
  const output = `// Generated by scripts/generate-profile-types.mjs. Do not edit.
18
9
  // Schema version: ${schema.properties.schemaVersion.const}
19
10
 
20
- export type ProfileSource = ${profileSource};
21
- export type ProfileRecordKind = ${profileRecordKind};
22
- export type ProfileCompletenessStatus = ${profileCompletenessStatus};
11
+ export type ProfileSource = 'app' | 'render';
12
+ export type ProfileRecordKind = 'phase' | 'skip';
13
+ export type ProfileCompletenessStatus = 'complete' | 'partial' | 'overflow';
23
14
 
24
15
  export interface ProfilePhaseRecord {
25
16
  readonly kind: 'phase';
@@ -1,4 +1,3 @@
1
- import { readFileSync } from 'node:fs';
2
1
  import { fileURLToPath } from 'node:url';
3
2
  import { describe, expect, it } from 'vitest';
4
3
  import { readCliInput, runProfilerCli } from '../cli.js';
@@ -56,44 +55,6 @@ describe('profiler CLI input error contract', () => {
56
55
  expect(readCliInput(['summary'], () => 'capture')).toBe('capture');
57
56
  });
58
57
 
59
- it('returns a structured reader failure and retries the same reader deterministically', () => {
60
- const input = readFileSync(
61
- new URL('./fixtures/cli/valid-capture.json', import.meta.url),
62
- 'utf8',
63
- );
64
- let refuse = true;
65
- const readStdin = (): string => {
66
- if (refuse) {
67
- refuse = false;
68
- throw new Error('EISDIR: illegal operation on a directory, read');
69
- }
70
- return input;
71
- };
72
-
73
- const first = runProfilerCli(['summary'], readCliInput(['summary'], readStdin));
74
- expectStructuredError(first, 'cli-input-stdin-read-failed');
75
- expect(JSON.parse(first.stderr)).toEqual({
76
- error: {
77
- code: 'cli-input-stdin-read-failed',
78
- expected: expect.any(String),
79
- hint: expect.any(String),
80
- detail: {
81
- message: 'EISDIR: illegal operation on a directory, read',
82
- },
83
- },
84
- });
85
-
86
- const second = runProfilerCli(['summary'], readCliInput(['summary'], readStdin));
87
- const repeat = runProfilerCli(['summary'], readCliInput(['summary'], readStdin));
88
- expect(second.exitCode).toBe(0);
89
- expect(second.stderr).toBe('');
90
- expect(second.stdout).toBe(repeat.stdout);
91
- expect(JSON.parse(second.stdout)).toMatchObject({
92
- query: 'summary',
93
- captureId: 'capture-0042',
94
- });
95
- });
96
-
97
58
  it('rejects empty stdin without producing a summary', () => {
98
59
  expectStructuredError(runProfilerCli(['summary'], ''), 'cli-input-empty');
99
60
  });
@@ -1,5 +1,5 @@
1
1
  import { spawnSync } from 'node:child_process';
2
- import { closeSync, openSync, readFileSync } from 'node:fs';
2
+ import { readFileSync } from 'node:fs';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import { describe, expect, it } from 'vitest';
5
5
 
@@ -23,18 +23,6 @@ function runCli(args: readonly string[], input: string) {
23
23
  });
24
24
  }
25
25
 
26
- function runCliWithDirectoryStdin(args: readonly string[]) {
27
- const stdin = openSync(fileURLToPath(new URL('./', import.meta.url)), 'r');
28
- try {
29
- return spawnSync(process.execPath, [cliPath, ...args], {
30
- stdio: [stdin, 'pipe', 'pipe'],
31
- encoding: 'utf8',
32
- });
33
- } finally {
34
- closeSync(stdin);
35
- }
36
- }
37
-
38
26
  function parseObject(output: string): Record<string, unknown> {
39
27
  return JSON.parse(output) as Record<string, unknown>;
40
28
  }
@@ -84,21 +72,6 @@ describe('profiler CLI public process contract', () => {
84
72
  });
85
73
  });
86
74
 
87
- it('returns one structured stdin reader error through the built entry', () => {
88
- const refusal = runCliWithDirectoryStdin(['summary']);
89
-
90
- expect(refusal.status).toBe(2);
91
- expect(refusal.stdout).toBe('');
92
- expect(parseObject(refusal.stderr)).toEqual({
93
- error: {
94
- code: 'cli-input-stdin-read-failed',
95
- expected: expect.any(String),
96
- hint: expect.any(String),
97
- detail: { message: expect.stringContaining('EISDIR') },
98
- },
99
- });
100
- });
101
-
102
75
  it('compares two file artifacts deterministically through the built entry', () => {
103
76
  const args = ['compare', '--left-file', fixturePath, '--right-file', partialFixturePath];
104
77
  const first = runCli(args, 'stdin must not be read');
@@ -1,5 +1,5 @@
1
1
  import type { Profiler } from './profiler.js';
2
- import type { ProfileDetail, RecorderSession } from './recorder.js';
2
+ import type { RecorderSession } from './recorder.js';
3
3
  import type { ProfileCapture, ProfilePhaseStart, ProfileSource } from './types.js';
4
4
 
5
5
  /** The small browser API surface used by the opt-in User Timing adapter. */
@@ -17,15 +17,7 @@ export interface UserTimingProfilerOptions {
17
17
 
18
18
  type DiagnosticGlobal = Record<string, unknown>;
19
19
  type PhaseCatalog = ProfileCapture['phaseCatalog'];
20
- type BrowserProfileDetail = ProfileDetail;
21
- const DEFAULT_CAPTURE_ID = 'capture-0001';
22
- const CAPTURE_ID_PATTERN = /^capture-[0-9]{4,}$/;
23
-
24
- function resolveCaptureId(captureId: string | undefined): string {
25
- return captureId === undefined || CAPTURE_ID_PATTERN.test(captureId)
26
- ? (captureId ?? DEFAULT_CAPTURE_ID)
27
- : DEFAULT_CAPTURE_ID;
28
- }
20
+ type BrowserProfileDetail = 'owner' | 'passes' | 'nested';
29
21
 
30
22
  function partialCapture(captureId: string, phaseCatalog: PhaseCatalog): ProfileCapture {
31
23
  return {
@@ -73,7 +65,7 @@ export function createUserTimingProfiler(
73
65
  options: UserTimingProfilerOptions = {},
74
66
  ): Profiler | undefined {
75
67
  const diagnosticsKey = options.diagnosticsKey ?? '__forgeaxFramePhaseDiagnostics';
76
- const captureId = resolveCaptureId(options.captureId);
68
+ const captureId = options.captureId ?? 'forgeax-user-timing';
77
69
  const timingEnabled = diagnosticsEnabled(diagnosticsKey);
78
70
  const detail = diagnosticsDetail(diagnosticsKey);
79
71
  const performanceApi = timingEnabled
package/src/cli.ts CHANGED
@@ -31,7 +31,6 @@ type CliError = {
31
31
  | 'cli-input-empty'
32
32
  | 'cli-input-file-read-failed'
33
33
  | 'cli-input-invalid-json'
34
- | 'cli-input-stdin-read-failed'
35
34
  | 'cli-query-invalid';
36
35
  readonly expected: string;
37
36
  readonly hint: string;
@@ -250,7 +249,7 @@ function parseArguments(args: readonly string[]): CliArguments | CliError {
250
249
  return validateCommand(state) ?? toCliArguments(state);
251
250
  }
252
251
 
253
- function readArtifact(filePath: string | undefined, stdin: string | CliError): string | CliError {
252
+ function readArtifact(filePath: string | undefined, stdin: string): string | CliError {
254
253
  if (filePath !== undefined) {
255
254
  try {
256
255
  return readFileSync(filePath, 'utf8');
@@ -266,7 +265,6 @@ function readArtifact(filePath: string | undefined, stdin: string | CliError): s
266
265
  };
267
266
  }
268
267
  }
269
- if (isCliError(stdin)) return stdin;
270
268
  if (stdin.trim().length === 0) {
271
269
  return {
272
270
  code: 'cli-input-empty',
@@ -352,10 +350,7 @@ function projectComparison(value: ProfileComparisonProjection): Record<string, u
352
350
  return { query: 'compare', ...value };
353
351
  }
354
352
 
355
- export function runProfilerCli(
356
- args: readonly string[],
357
- stdin: string | CliError,
358
- ): ProfilerCliResult {
353
+ export function runProfilerCli(args: readonly string[], stdin: string): ProfilerCliResult {
359
354
  const parsedArguments = parseArguments(args);
360
355
  if ('code' in parsedArguments) {
361
356
  return { stdout: '', stderr: output({ error: parsedArguments }), exitCode: 2 };
@@ -390,20 +385,10 @@ export function runProfilerCli(
390
385
  };
391
386
  }
392
387
 
393
- export function readCliInput(args: readonly string[], readStdin: () => string): string | CliError {
394
- if (args.some((argument) => ['--file', '--left-file', '--right-file'].includes(argument))) {
395
- return '';
396
- }
397
- try {
398
- return readStdin();
399
- } catch (error) {
400
- return {
401
- code: 'cli-input-stdin-read-failed',
402
- expected: 'stdin can be read as one ProfileCapture JSON object',
403
- hint: 'Provide readable ProfileCapture JSON on stdin or pass --file <path>.',
404
- detail: { message: error instanceof Error ? error.message : String(error) },
405
- };
406
- }
388
+ export function readCliInput(args: readonly string[], readStdin: () => string): string {
389
+ return args.some((argument) => ['--file', '--left-file', '--right-file'].includes(argument))
390
+ ? ''
391
+ : readStdin();
407
392
  }
408
393
 
409
394
  export function main(): void {
package/src/clock.ts CHANGED
@@ -7,7 +7,7 @@ export function createProfileClock(readMicros: () => number = defaultReadMicros)
7
7
  return {
8
8
  nowMicros() {
9
9
  const currentMicros = readMicros();
10
- if (currentMicros > lastMicros) lastMicros = currentMicros;
10
+ lastMicros = Math.max(lastMicros, currentMicros);
11
11
  return lastMicros;
12
12
  },
13
13
  };
package/src/compare.ts CHANGED
@@ -50,10 +50,7 @@ export type ProfileComparisonError = ProfileArtifactError & {
50
50
  };
51
51
  };
52
52
 
53
- function withSide(
54
- side: ProfileComparisonError['detail']['side'],
55
- error: ProfileArtifactError,
56
- ): ProfileComparisonError {
53
+ function withSide(side: 'left' | 'right', error: ProfileArtifactError): ProfileComparisonError {
57
54
  return {
58
55
  ...error,
59
56
  detail: { ...error.detail, side },
package/src/index.ts CHANGED
@@ -34,12 +34,6 @@ export { createProfiler } from './profiler.js';
34
34
  export type { ProfileDetail, RecorderSession } from './recorder.js';
35
35
  /** Validates a persisted capture before model building or CLI-style analysis. */
36
36
  export { validateProfileCapture } from './schema.js';
37
- export {
38
- consumeToolPreviewProfile,
39
- type ToolPreviewProfileManifest,
40
- type ToolPreviewProfileRef,
41
- type ToolPreviewProfileResult,
42
- } from './tool-preview-profile.js';
43
37
  export type {
44
38
  ProfileAllocationReport,
45
39
  ProfileCapture,
package/src/recorder.ts CHANGED
@@ -34,14 +34,14 @@ const PHASE_RECORD_KIND = 1;
34
34
  const SKIP_RECORD_KIND = 2;
35
35
 
36
36
  interface RetainedRecordStore {
37
- readonly kinds: Uint8Array;
37
+ readonly kinds: number[];
38
38
  readonly sources: ProfileSource[];
39
39
  readonly phases: string[];
40
- readonly frameIds: Float64Array;
40
+ readonly frameIds: number[];
41
41
  readonly parentSources: Array<ProfileSource | undefined>;
42
42
  readonly parentPhases: Array<string | undefined>;
43
- readonly startMicros: Float64Array;
44
- readonly endMicros: Float64Array;
43
+ readonly startMicros: number[];
44
+ readonly endMicros: number[];
45
45
  readonly reasons: Array<string | undefined>;
46
46
  count: number;
47
47
  }
@@ -266,7 +266,6 @@ export function createRecorder(
266
266
  ): ProfilerResult<RecorderSession> {
267
267
  const validLimits = validateRecorderLimits(limits);
268
268
  if (!validLimits.ok) return validLimits;
269
- const nowMicros = clock.nowMicros;
270
269
  const state: RecorderState = {
271
270
  limits,
272
271
  phaseCatalog,
@@ -279,15 +278,15 @@ export function createRecorder(
279
278
  render: new Set(phaseCatalog.render),
280
279
  },
281
280
  recordStore: {
282
- kinds: new Uint8Array(limits.eventLimit),
283
- sources: new Array<ProfileSource>(limits.eventLimit),
284
- phases: new Array<string>(limits.eventLimit),
285
- frameIds: new Float64Array(limits.eventLimit),
286
- parentSources: new Array<ProfileSource | undefined>(limits.eventLimit),
287
- parentPhases: new Array<string | undefined>(limits.eventLimit),
288
- startMicros: new Float64Array(limits.eventLimit),
289
- endMicros: new Float64Array(limits.eventLimit),
290
- reasons: new Array<string | undefined>(limits.eventLimit),
281
+ kinds: [],
282
+ sources: [],
283
+ phases: [],
284
+ frameIds: [],
285
+ parentSources: [],
286
+ parentPhases: [],
287
+ startMicros: [],
288
+ endMicros: [],
289
+ reasons: [],
291
290
  count: 0,
292
291
  },
293
292
  captureId,
@@ -341,7 +340,7 @@ export function createRecorder(
341
340
  state.openSources[index] = source;
342
341
  state.openPhaseNames[index] = phase;
343
342
  state.openFrameIds[index] = frameId;
344
- state.openStartMicros[index] = nowMicros();
343
+ state.openStartMicros[index] = clock.nowMicros();
345
344
  state.openDepth += 1;
346
345
  return OK_VOID;
347
346
  },
@@ -367,7 +366,7 @@ export function createRecorder(
367
366
  const parentSource = parentIndex >= 0 ? state.openSources[parentIndex] : undefined;
368
367
  const parentPhase = parentIndex >= 0 ? state.openPhaseNames[parentIndex] : undefined;
369
368
  if (!state.overflow) {
370
- const endMicros = Math.max(startMicros, nowMicros());
369
+ const endMicros = Math.max(startMicros, clock.nowMicros());
371
370
  retainPhaseRecord(
372
371
  state,
373
372
  source,
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=browser-profile-detail-owner.test-d.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"browser-profile-detail-owner.test-d.d.ts","sourceRoot":"","sources":["../../src/__tests__/browser-profile-detail-owner.test-d.ts"],"names":[],"mappings":""}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=browser-user-timing.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"browser-user-timing.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/browser-user-timing.test.ts"],"names":[],"mappings":""}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=comparison-side-owner.test-d.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"comparison-side-owner.test-d.d.ts","sourceRoot":"","sources":["../../src/__tests__/comparison-side-owner.test-d.ts"],"names":[],"mappings":""}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=schema-derived-union-owner.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"schema-derived-union-owner.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/schema-derived-union-owner.test.ts"],"names":[],"mappings":""}
@@ -1,33 +0,0 @@
1
- import { type ProfileCapture } from './index.js';
2
- export interface ToolPreviewProfileRef {
3
- readonly kind: 'profile-capture';
4
- readonly digest: string;
5
- readonly uri: string;
6
- }
7
- export interface ToolPreviewProfileManifest {
8
- readonly runId: string;
9
- readonly captureId: string;
10
- readonly profileDigest: string;
11
- }
12
- export type ToolPreviewProfileResult = {
13
- readonly ok: true;
14
- readonly value: {
15
- readonly runId: string;
16
- readonly captureId: string;
17
- readonly profile: ProfileCapture;
18
- };
19
- } | {
20
- readonly ok: false;
21
- readonly error: {
22
- readonly code: string;
23
- readonly expected: string;
24
- readonly hint: string;
25
- readonly detail: unknown;
26
- };
27
- };
28
- export declare function consumeToolPreviewProfile(input: {
29
- readonly manifest: ToolPreviewProfileManifest;
30
- readonly profile: unknown;
31
- readonly ref: ToolPreviewProfileRef;
32
- }): ToolPreviewProfileResult;
33
- //# sourceMappingURL=tool-preview-profile.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"tool-preview-profile.d.ts","sourceRoot":"","sources":["../src/tool-preview-profile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAA0B,MAAM,YAAY,CAAC;AAEzE,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,MAAM,wBAAwB,GAChC;IACE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,KAAK,EAAE;QACd,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;QAC3B,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;KAClC,CAAC;CACH,GACD;IACE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,KAAK,EAAE;QACd,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;KAC1B,CAAC;CACH,CAAC;AAEN,wBAAgB,yBAAyB,CAAC,KAAK,EAAE;IAC/C,QAAQ,CAAC,QAAQ,EAAE,0BAA0B,CAAC;IAC9C,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,GAAG,EAAE,qBAAqB,CAAC;CACrC,GAAG,wBAAwB,CAiB3B"}
@@ -1,26 +0,0 @@
1
- import { readFileSync } from 'node:fs';
2
- import { describe, expect, expectTypeOf, it } from 'vitest';
3
- import type { ProfileDetail, RecorderSession } from '../index.js';
4
-
5
- type ExpectedProfileDetail = 'owner' | 'passes' | 'nested';
6
- const browserSource = readFileSync(new URL('../browser-user-timing.ts', import.meta.url), 'utf8');
7
- const recorderSource = readFileSync(new URL('../recorder.ts', import.meta.url), 'utf8');
8
-
9
- describe('browser ProfileDetail owner', () => {
10
- it('preserves the public vocabulary and session detail projection', () => {
11
- expectTypeOf<ProfileDetail>().toEqualTypeOf<ExpectedProfileDetail>();
12
- expectTypeOf<RecorderSession['detail']>().toEqualTypeOf<ProfileDetail>();
13
- expectTypeOf<ProfileDetail>().toEqualTypeOf<RecorderSession['detail']>();
14
- });
15
-
16
- it('derives the private browser view from the recorder owner', () => {
17
- expect(browserSource).toContain(
18
- "import type { ProfileDetail, RecorderSession } from './recorder.js';",
19
- );
20
- expect(browserSource).toContain('type BrowserProfileDetail = ProfileDetail;');
21
- expect(browserSource).not.toContain(
22
- "type BrowserProfileDetail = 'owner' | 'passes' | 'nested';",
23
- );
24
- expect(recorderSource).toContain("export type ProfileDetail = 'owner' | 'passes' | 'nested';");
25
- });
26
- });
@@ -1,127 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from 'vitest';
2
- import { createUserTimingProfiler } from '../browser-user-timing.js';
3
- import { runProfilerCli } from '../cli.js';
4
- import { buildProfileModel } from '../model.js';
5
- import { validateProfileCapture } from '../schema.js';
6
- import type { ProfileCapture, ProfileResult } from '../types.js';
7
-
8
- const DIAGNOSTICS_KEY = '__forgeaxFramePhaseDiagnostics';
9
- const APP_PHASES = ['frame-total', 'world-update-primary'] as const;
10
- const RENDER_PHASES = ['extract', 'record'] as const;
11
-
12
- function expectOk<T, E>(result: ProfileResult<T, E>): T {
13
- expect(result.ok).toBe(true);
14
- if (!result.ok) throw new Error('expected a successful profiler result');
15
- return result.value;
16
- }
17
-
18
- function enableDiagnostics(detail: 'owner' | 'passes' | 'nested' = 'nested'): void {
19
- (globalThis as Record<string, unknown>)[DIAGNOSTICS_KEY] = { enabled: true, detail };
20
- }
21
-
22
- function registerCatalogs(
23
- profiler: NonNullable<ReturnType<typeof createUserTimingProfiler>>,
24
- ): void {
25
- expectOk(profiler.registerPhaseCatalog('app', APP_PHASES));
26
- expectOk(profiler.registerPhaseCatalog('render', RENDER_PHASES));
27
- }
28
-
29
- function finishOneFrame(
30
- profiler: NonNullable<ReturnType<typeof createUserTimingProfiler>>,
31
- ): ProfileCapture {
32
- const session = expectOk(profiler.startCapture({ frameLimit: 2, eventLimit: 8 }));
33
- expectOk(session.beginFrame(1));
34
- expectOk(session.beginPhase({ source: 'app', phase: 'frame-total' }));
35
- expectOk(session.endPhase());
36
- expectOk(session.beginPhase({ source: 'render', phase: 'record' }));
37
- expectOk(session.endPhase());
38
- expectOk(session.endFrame());
39
- return expectOk(session.finish());
40
- }
41
-
42
- afterEach(() => {
43
- Reflect.deleteProperty(globalThis, DIAGNOSTICS_KEY);
44
- vi.unstubAllGlobals();
45
- });
46
-
47
- describe('browser User Timing profiler', () => {
48
- it('emits a schema-valid default capture through model and CLI consumers', () => {
49
- enableDiagnostics();
50
- const profiler = createUserTimingProfiler();
51
- expect(profiler).toBeDefined();
52
- if (profiler === undefined) return;
53
- registerCatalogs(profiler);
54
-
55
- const capture = finishOneFrame(profiler);
56
-
57
- expect(capture.captureId).toBe('capture-0001');
58
- expect(capture.completeness).toEqual({
59
- status: 'partial',
60
- retainedEventCount: 0,
61
- droppedEventCount: 0,
62
- incompleteReason: 'user-timing-transport',
63
- });
64
- expect(validateProfileCapture(capture).ok).toBe(true);
65
-
66
- const model = buildProfileModel(capture);
67
- expect(model.ok).toBe(true);
68
- if (!model.ok) return;
69
- expect(model.value.summary.captureId).toBe('capture-0001');
70
-
71
- const cli = runProfilerCli(['summary'], JSON.stringify(capture));
72
- expect(cli.exitCode).toBe(0);
73
- expect(JSON.parse(cli.stdout)).toMatchObject({ captureId: 'capture-0001' });
74
- });
75
-
76
- it('preserves valid caller IDs and derives the default for invalid IDs', () => {
77
- enableDiagnostics('owner');
78
- const valid = createUserTimingProfiler({ captureId: 'capture-1234' });
79
- expect(valid).toBeDefined();
80
- if (valid === undefined) return;
81
- expect(finishOneFrame(valid).captureId).toBe('capture-1234');
82
-
83
- const invalid = createUserTimingProfiler({ captureId: 'host-session' });
84
- expect(invalid).toBeDefined();
85
- if (invalid === undefined) return;
86
- const capture = finishOneFrame(invalid);
87
- expect(capture.captureId).toBe('capture-0001');
88
- expect(validateProfileCapture(capture).ok).toBe(true);
89
- });
90
-
91
- it('keeps disabled creation inert and retains onPhaseEnd-only callbacks', () => {
92
- const mark = vi.fn();
93
- vi.stubGlobal('performance', { mark });
94
-
95
- expect(createUserTimingProfiler()).toBeUndefined();
96
- expect(mark).not.toHaveBeenCalled();
97
-
98
- const onPhaseEnd = vi.fn();
99
- const profiler = createUserTimingProfiler({ onPhaseEnd });
100
- expect(profiler).toBeDefined();
101
- if (profiler === undefined) return;
102
- const capture = finishOneFrame(profiler);
103
-
104
- expect(onPhaseEnd).toHaveBeenCalledWith({ source: 'app', phase: 'frame-total' });
105
- expect(onPhaseEnd).toHaveBeenCalledWith({ source: 'render', phase: 'record' });
106
- expect(mark).not.toHaveBeenCalled();
107
- expect(validateProfileCapture(capture).ok).toBe(true);
108
- });
109
-
110
- it('supports repeated start and finish with one partial artifact per session', () => {
111
- enableDiagnostics('passes');
112
- const profiler = createUserTimingProfiler();
113
- expect(profiler).toBeDefined();
114
- if (profiler === undefined) return;
115
-
116
- const first = expectOk(profiler.startCapture({ frameLimit: 1, eventLimit: 1 }));
117
- const firstCapture = expectOk(first.finish());
118
- const second = expectOk(profiler.startCapture({ frameLimit: 1, eventLimit: 1 }));
119
- const secondCapture = expectOk(second.finish());
120
-
121
- expect(firstCapture.captureId).toBe('capture-0001');
122
- expect(secondCapture.captureId).toBe('capture-0001');
123
- expect(profiler.latestCapture()).toBe(secondCapture);
124
- expect(validateProfileCapture(firstCapture).ok).toBe(true);
125
- expect(validateProfileCapture(secondCapture).ok).toBe(true);
126
- });
127
- });
@@ -1,28 +0,0 @@
1
- import { readFileSync } from 'node:fs';
2
- import { describe, expect, expectTypeOf, it } from 'vitest';
3
- import type { ProfileComparisonError } from '../index.js';
4
-
5
- type ComparisonErrorSide = ProfileComparisonError['detail']['side'];
6
- type ExpectedComparisonErrorSide = 'left' | 'right';
7
-
8
- const compareSource = readFileSync(new URL('../compare.ts', import.meta.url), 'utf8');
9
-
10
- describe('profile comparison side owner', () => {
11
- it('keeps the public side vocabulary bilaterally exact', () => {
12
- expectTypeOf<ComparisonErrorSide>().toEqualTypeOf<ExpectedComparisonErrorSide>();
13
- expectTypeOf<ExpectedComparisonErrorSide>().toEqualTypeOf<ComparisonErrorSide>();
14
-
15
- const acceptsSide = (side: ComparisonErrorSide): void => void side;
16
- acceptsSide('left');
17
- acceptsSide('right');
18
- // @ts-expect-error Unknown sides remain outside the public comparison vocabulary.
19
- acceptsSide('comparison-side-not-real');
20
- });
21
-
22
- it('derives withSide from the public error detail owner', () => {
23
- expect(compareSource).toContain("side: ProfileComparisonError['detail']['side'],");
24
- expect(compareSource).not.toContain("side: 'left' | 'right'");
25
- expect(compareSource).toContain("withSide('left', leftModel.error)");
26
- expect(compareSource).toContain("withSide('right', rightModel.error)");
27
- });
28
- });
@@ -1,57 +0,0 @@
1
- import { readFileSync } from 'node:fs';
2
- import { describe, expect, it } from 'vitest';
3
-
4
- type Schema = { $defs: Record<string, unknown> };
5
-
6
- function readSchema(): Schema {
7
- return JSON.parse(
8
- readFileSync(new URL('../../schema/profile-capture.schema.json', import.meta.url), 'utf8'),
9
- ) as Schema;
10
- }
11
-
12
- function literalUnion(values: readonly string[]): string {
13
- return values.map((value) => `'${value}'`).join(' | ');
14
- }
15
-
16
- function readStringEnum(definition: unknown): readonly string[] {
17
- return (definition as { enum: readonly string[] }).enum;
18
- }
19
-
20
- function readRecordKinds(schema: Schema): readonly string[] {
21
- const record = schema.$defs.record as { oneOf: readonly { $ref: string }[] };
22
- return record.oneOf.map(({ $ref }) => {
23
- const definitionName = $ref.slice('#/$defs/'.length);
24
- const definition = schema.$defs[definitionName] as {
25
- properties: { kind: { const: string } };
26
- };
27
- return definition.properties.kind.const;
28
- });
29
- }
30
-
31
- describe('schema-derived profiler union owners', () => {
32
- it('projects the three closed unions from schema memberships', () => {
33
- const schema = readSchema();
34
- const source = readFileSync(
35
- new URL('../../scripts/generate-profile-types.mjs', import.meta.url),
36
- 'utf8',
37
- );
38
- const generated = readFileSync(
39
- new URL('../generated/profile-capture.ts', import.meta.url),
40
- 'utf8',
41
- );
42
-
43
- const sourceUnion = literalUnion(readStringEnum(schema.$defs.source));
44
- const recordKindUnion = literalUnion(readRecordKinds(schema));
45
- const completenessStatus = schema.$defs.completeness as {
46
- properties: { status: { enum: readonly string[] } };
47
- };
48
- const completenessUnion = literalUnion(readStringEnum(completenessStatus.properties.status));
49
-
50
- expect(generated).toContain(`export type ProfileSource = ${sourceUnion};`);
51
- expect(generated).toContain(`export type ProfileRecordKind = ${recordKindUnion};`);
52
- expect(generated).toContain(`export type ProfileCompletenessStatus = ${completenessUnion};`);
53
- expect(source).toContain('schema.$defs.source.enum');
54
- expect(source).toContain('schema.$defs.record.oneOf');
55
- expect(source).toContain('schema.$defs.completeness.properties.status.enum');
56
- });
57
- });