jev-devtools 0.5.4 → 0.5.5

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,45 @@
1
+ export const record = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2
+ export const text = (value) => typeof value === "string";
3
+ export const id = (value) => text(value) && value.length > 0;
4
+ export const finite = (value) => typeof value === "number" && Number.isFinite(value);
5
+ export const nonnegative = (value) => finite(value) && value >= 0;
6
+ export const integer = (value) => nonnegative(value) && Number.isSafeInteger(value);
7
+ export const boolean = (value) => typeof value === "boolean";
8
+ export const literal = (...values) => (value) => values.some((candidate) => candidate === value);
9
+ export const optional = (check) => (value) => value === undefined || check(value);
10
+ export const array = (check, max = Infinity) => (value) => Array.isArray(value) && value.length <= max && value.every(check);
11
+ // Every protocol property, including optional ones, needs a compatible check.
12
+ // Extra wire fields are retained for version-1 forward compatibility.
13
+ export function shape(fields) {
14
+ const checks = fields;
15
+ return (value) => record(value) &&
16
+ Object.entries(checks).every(([key, check]) => check(value[key])) &&
17
+ // Unknown metadata remains visible in Raw, so it must also be safe JSON.
18
+ Object.entries(value).every(([key, extra]) => Object.hasOwn(checks, key) || json(extra));
19
+ }
20
+ export const strings = array(text);
21
+ export const ids = (value) => array(id, 200)(value) && new Set(value).size === value.length;
22
+ export const status = literal("pending", "success", "error", "cancelled");
23
+ // capture() traverses through depth 12 and emits scalar markers at depth 13.
24
+ // Image previews can exceed the ordinary 16,000-character string limit, so
25
+ // validation does not impose that limit on all strings or invent a byte budget.
26
+ export const json = (value) => {
27
+ const visit = (input, depth) => {
28
+ if (input === null || text(input) || boolean(input) || finite(input))
29
+ return true;
30
+ if (depth > 12)
31
+ return false;
32
+ if (Array.isArray(input))
33
+ return (input.length <= 150 &&
34
+ input.every((child) => visit(child, depth + 1)));
35
+ return (record(input) &&
36
+ Object.keys(input).length <= 256 &&
37
+ Object.values(input).every((child) => visit(child, depth + 1)));
38
+ };
39
+ return visit(value, 0);
40
+ };
41
+ export function parse(check, value, name) {
42
+ if (!check(value))
43
+ throw new Error(`Invalid ${name}`);
44
+ return value;
45
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jev-devtools",
3
- "version": "0.5.4",
3
+ "version": "0.5.5",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22.12"
@@ -19,7 +19,7 @@ Inspect the app's package manager/lockfile, React and Node versions, root compon
19
19
 
20
20
  Install `jev-devtools` using the app's existing package manager (npm: `npm install jev-devtools @typesafe-ai/sdk`; pnpm: `pnpm add jev-devtools @typesafe-ai/sdk`; Yarn: `yarn add jev-devtools @typesafe-ai/sdk`; Bun: `bun add jev-devtools @typesafe-ai/sdk`). `@typesafe-ai/sdk` is a peer dependency: keep the app's existing SDK version when it satisfies the peer range. Install in the workspace(s) that actually import it. It is a regular dependency when application modules import it, even though capture/display are development-only. Do not replace the app's framework or upgrade unrelated dependencies to force compatibility.
21
21
 
22
- ### Consumer compatibility (0.5.4)
22
+ ### Consumer compatibility (0.5.5)
23
23
 
24
24
  | Requirement | Contract |
25
25
  | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
@@ -35,7 +35,7 @@ Import application APIs from `jev-devtools` (React and browser-safe capture help
35
35
 
36
36
  ## Optional connected flow API
37
37
 
38
- When asked to build or inspect connected calls, read [references/flows.md](references/flows.md) after the framework recipe. For 0.5.4 it covers requests, server function tasks, runtime request factories, parallel checks, terminal branches, returning conditionals, bounded collections, stateful loops, captured-answer replay, and fixture comparison. It includes sample verification, scope/execution rules, and privacy limits. For an older installation, use its bundled recipe and declarations rather than assuming these operations exist. Preserve existing application policies; do not convert unrelated calls just to install the drawer. Flow execution is server-only and continues even with the inspector disabled.
38
+ When asked to build or inspect connected calls, read [references/flows.md](references/flows.md) after the framework recipe. For 0.5.5 it covers requests, server function tasks, runtime request factories, parallel checks, terminal branches, returning conditionals, bounded collections, stateful loops, captured-answer replay, and fixture comparison. It includes sample verification, scope/execution rules, and privacy limits. For an older installation, use its bundled recipe and declarations rather than assuming these operations exist. Preserve existing application policies; do not convert unrelated calls just to install the drawer. Flow execution is server-only and continues even with the inspector disabled.
39
39
 
40
40
  ## Integration invariants
41
41
 
@@ -1,6 +1,6 @@
1
1
  # Build and inspect a Jev flow
2
2
 
3
- For `jev-devtools` **0.5.4**. Check the [release notes](https://github.com/diegoag64/jev-devtools/releases/tag/v0.5.4) for verified publication status when installing from source. Read the installed package version and `lib/flow/index.d.ts` first; online `main` may describe newer APIs. Use this recipe when the user asks to connect multiple Jev requests, inspect branches, run server function steps, or visualize parallel checks, collections, and loops. Adding the inspector alone does not require converting existing calls to flows.
3
+ For `jev-devtools` **0.5.5**. Check the [release notes](https://github.com/diegoag64/jev-devtools/releases/tag/v0.5.5) for verified publication status when installing from source. Read the installed package version and `lib/flow/index.d.ts` first; online `main` may describe newer APIs. Use this recipe when the user asks to connect multiple Jev requests, inspect branches, run server function steps, or visualize parallel checks, collections, and loops. Adding the inspector alone does not require converting existing calls to flows.
4
4
 
5
5
  ## Plan the integration
6
6
 
@@ -9,7 +9,7 @@ For `jev-devtools` **0.5.4**. Check the [release notes](https://github.com/diego
9
9
  3. Import `defineJevFlow` (and `all`, `any`, `not` when needed) from **server-only** `jev-devtools/flow`. Define the graph once at module scope and call `run()` from the existing Node handler/action. In Next.js, mark the module `server-only` and keep the executing route/segment on the Node runtime. No new client provider or root component is needed.
10
10
  4. A named output returns data and does not execute an action. Existing business actions can remain in the application after the returned outcome; use `task()` when the user wants application-owned server work included in the flow. Preserve authorization, idempotency, input validation, and error handling. Cancellation cannot undo side effects.
11
11
 
12
- If importing question helpers as below, install `@typesafe-ai/sdk` as a direct dependency in the server workspace using its package manager. Version 0.5.4 requires SDK `>=0.7.0 <0.8.0` and is verified with 0.7.0; preserve a compatible existing version rather than silently changing SDK semantics. The flow also accepts ordinary SDK question objects.
12
+ If importing question helpers as below, install `@typesafe-ai/sdk` as a direct dependency in the server workspace using its package manager. Version 0.5.5 requires SDK `>=0.7.0 <0.8.0` and is verified with 0.7.0; preserve a compatible existing version rather than silently changing SDK semantics. The flow also accepts ordinary SDK question objects.
13
13
 
14
14
  ## Complete example: parallel checks, one decision, two outcomes
15
15
 
@@ -148,6 +148,8 @@ For custom server-derived sessions, use `await inspector.run(request, () => revi
148
148
  | `flow.output(name, value?, display?)` | Terminal named result, optionally containing nested resolved expressions. Output names are globally unique node IDs. For metadata without a value, pass `undefined` second. |
149
149
  | `definition.run(input, { client, devtools?, signal? })` | Returns `{ runId, outcome: string, value?: unknown }`. Narrow/validate values in application code; outcomes are not inferred as a discriminated union. A normal SDK client works without recording. |
150
150
 
151
+ The bundled declarations reject array methods such as `.get("map")` and non-scalar `.eq()` receivers or operands. Equality supports strings, finite numbers, booleans, and null, including literal and branded types. Scalar unions still need matching resolved types. Array bounds and absent optional fields remain execution checks. Preserve generic field helpers by deriving their key constraint as `K extends keyof T & Parameters<FlowExpression<T>["get"]>[0]`; unrestricted `keyof T` also includes array methods. Older package declarations may accept these invalid operations, so check the installed version.
152
+
151
153
  Use the callback's current builder (`lane` or `path`) to add steps; do not call the outer builder inside it. References must belong to the same definition and be available on the current path. Reusing an earlier parent reference is allowed. IDs must be unique across all paths/lanes: 1–80 letters, digits, `_`, or `-`, starting with a letter or digit. Maximum 256 nodes including collection/iteration templates, 16 nested blocks, and 1,024 expanded steps per run. Version 0.4.0 includes tasks, factories, maps, returning conditionals and loops as described below. Plain templates are copied when defining; treat SDK assets as immutable. Define once to avoid a new definition identity on every request; hot reload can still create a new identity.
152
154
 
153
155
  **0.5.2:** expressions are bounded during construction to depth 80 (leaf depth 0), 4,096 expanded nodes, and 256 operands per operation. Reusing a child counts its complete size again: eleven `all(e, e)` doublings of Boolean `flow.input` fit (4,095 nodes), but twelve do not. Split larger logic into tasks or smaller steps. Legacy condition descriptions are capped at 4,096 UTF-16 code units including `[condition truncated]`; structured rules keep their 128-node projection budget. Display truncation never changes accepted expression evaluation.
@@ -7,30 +7,34 @@ Use the host app's typecheck/build commands. Do not run the Jev DevTools monorep
7
7
  Use this server-only fixture when live inference is not authorized or no key is configured. It exercises the real SDK and recorder with fixed sample data and no external inference call. Create it from the **same inspector** that serves the endpoints. Keep it visibly sample-only; never substitute it for the user's production inference path.
8
8
 
9
9
  ```ts
10
- const sampleJev = inspector.createClient({
11
- apiKey: 'sample-only',
12
- logLevel: 'off',
13
- fetch: async () => Response.json({
14
- model: 'jev-sample',
15
- answers: {
16
- category: {
17
- type: 'choice',
18
- choice: 'billing',
19
- probabilities: { billing: 0.8, other: 0.2 },
20
- confidence: 0.7,
21
- },
22
- },
23
- }),
24
- }, { label: 'Setup verification', source: 'sample' });
10
+ const sampleJev = inspector.createClient(
11
+ {
12
+ apiKey: "sample-only",
13
+ logLevel: "off",
14
+ fetch: async () =>
15
+ Response.json({
16
+ model: "jev-sample",
17
+ answers: {
18
+ category: {
19
+ type: "choice",
20
+ choice: "billing",
21
+ probabilities: { billing: 0.8, other: 0.2 },
22
+ confidence: 0.7,
23
+ },
24
+ },
25
+ }),
26
+ },
27
+ { label: "Setup verification", source: "sample" },
28
+ );
25
29
 
26
30
  async function runSample() {
27
31
  return sampleJev.systemOne({
28
- state: { message: 'I was charged twice.' },
32
+ state: { message: "I was charged twice." },
29
33
  questions: {
30
34
  category: {
31
- type: 'choice',
32
- instructions: 'Which team should handle this message?',
33
- criteria: { billing: 'Payment issues', other: 'Everything else' },
35
+ type: "choice",
36
+ instructions: "Which team should handle this message?",
37
+ criteria: { billing: "Payment issues", other: "Everything else" },
34
38
  },
35
39
  },
36
40
  });
@@ -41,12 +45,14 @@ For a Vite/Connect dev plugin, register a temporary local handler after mounting
41
45
 
42
46
  ```ts
43
47
  server.middlewares.use(async (req, res, next) => {
44
- if (req.url !== '/__jev-setup-sample' || req.method !== 'POST') return next();
48
+ if (req.url !== "/__jev-setup-sample" || req.method !== "POST") return next();
45
49
  try {
46
50
  await runSample();
47
- res.writeHead(200, { 'content-type': 'application/json' }).end('{"ok":true}');
51
+ res
52
+ .writeHead(200, { "content-type": "application/json" })
53
+ .end('{"ok":true}');
48
54
  } catch {
49
- res.writeHead(500).end('Sample failed');
55
+ res.writeHead(500).end("Sample failed");
50
56
  }
51
57
  });
52
58
  ```
@@ -66,16 +72,19 @@ Report HTTP/capture checks separately from visual browser checks, and sample ver
66
72
 
67
73
  ## Troubleshooting
68
74
 
69
- | Symptom | Check |
70
- | --- | --- |
71
- | No launcher | Mount once and explicitly enable in development. Styles load automatically; no CSS import is needed. It intentionally renders nothing on the server. |
72
- | Disconnected or HTML instead of config | Ensure base path **and** subpaths reach the adapter before the SPA fallback; check proxy and Next optional catch-all. |
73
- | 403 | Match scheme/hostname/port and Host exactly; preserve proxy Host, verify Origin and authorization. Don't disable guards. |
74
- | Connected but empty | Use the adapter's instrumented client, same adapter/key/process and session. Direct SDK clients are not intercepted. |
75
- | Clear fails | Browser/server endpoint agreement, same-origin DELETE, exact Origin, authorization and session. |
76
- | Missing key | SDK needs a server key even with recording off; use the labeled sample transport for offline verification. |
77
- | Next reload loses or ignores settings | Use a stable `getJevDevtools` key; restart after option changes. Don't dispose during hot reload. |
78
- | Server import/build errors | Keep `/server` outside client modules. In Next externalize the SDK only. Use a supported Node runtime. |
79
- | Production 404 | Expected when disabled; app inference remains independent. |
75
+ | Symptom | Check |
76
+ | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
77
+ | No launcher | Mount once and explicitly enable in development. Styles load automatically; no CSS import is needed. It intentionally renders nothing on the server. |
78
+ | Disconnected or HTML instead of config | Ensure base path **and** subpaths reach the adapter before the SPA fallback; check proxy and Next optional catch-all. |
79
+ | 403 | Match scheme/hostname/port and Host exactly; preserve proxy Host, verify Origin and authorization. Don't disable guards. |
80
+ | Repeated resynchronization | Inspect SSE records against the [wire contract](https://github.com/diegoag64/jev-devtools/blob/main/docs/stream-protocol.md#record-validation-and-compatibility). Malformed structural metadata rejects the whole event. Redacted content remains inspectable; use capture warnings to identify unavailable structured views. Restart both server and browser after upgrading. |
81
+ | Connected but empty | Use the adapter's instrumented client, same adapter/key/process and session. Direct SDK clients are not intercepted. |
82
+ | Clear fails | Browser/server endpoint agreement, same-origin DELETE, exact Origin, authorization and session. |
83
+ | Missing key | SDK needs a server key even with recording off; use the labeled sample transport for offline verification. |
84
+ | Next reload loses or ignores settings | Use a stable `getJevDevtools` key; restart after option changes. Don't dispose during hot reload. |
85
+ | Server import/build errors | Keep `/server` outside client modules. In Next externalize the SDK only. Use a supported Node runtime. |
86
+ | Production 404 | Expected when disabled; app inference remains independent. |
80
87
 
81
88
  Capture is bounded to 50 calls in memory by default. Known credential fields are always redacted, including common API-key, client-secret and session-token aliases (case, hyphens and underscores are ignored). HTTP headers/options are not captured. The `redactAdditionalSensitiveData` component prop defaults to `true` and masks supported email, phone, postal address, SSN and card-number fields **only in DevTools views**. Set it to `false` to inspect those fields; changes apply immediately to existing captures, independently in each viewer, without server requests. These personal values remain in server/stream/browser memory even while masked. Custom server redaction always remains active, and neither credentials nor custom-redacted values can be recovered through the prop. See the [exact policy](https://github.com/diegoag64/jev-devtools/blob/main/docs/integration.md#redaction-policy). Arbitrary secrets in prose and unusual field names still need custom redaction. Verify both modes with synthetic sentinels and confirm SDK/runtime values remain unchanged. Diagrams show captured inputs/outputs, not hidden model reasoning. Routing decisions are recorded only for explicitly defined flows; see [flows.md](flows.md) for flow verification.
89
+
90
+ When configuring a custom redactor, verify replacements that change a field type or the whole request root with synthetic values. The drawer must retain usable siblings beside field-level unavailable placeholders and warnings, retain bounded redacted JSON in `Trace.capture.request` or `Trace.capture.result`, and leave SDK results unchanged. Presence includes `null`; strings such as `[redacted]` remain ordinary content. On the wire, an incompatible request has an empty structured view, and an incompatible response omits `Trace.result`. The drawer can display usable fields from `Trace.capture`; it must not invent values or box overlays from redacted coordinates. Fixture export reports that response as missing and keeps its warnings.