@nzila/sdk 0.1.0 → 0.1.3

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 (38) hide show
  1. package/README.md +27 -1
  2. package/dist/cli/constants.d.ts +13 -0
  3. package/dist/cli/constants.d.ts.map +1 -0
  4. package/dist/cli/constants.js +20 -0
  5. package/dist/cli/main.d.ts +3 -0
  6. package/dist/cli/main.d.ts.map +1 -0
  7. package/dist/cli/main.js +223 -0
  8. package/dist/cli/open-browser.d.ts +2 -0
  9. package/dist/cli/open-browser.d.ts.map +1 -0
  10. package/dist/cli/open-browser.js +26 -0
  11. package/examples/COMPLEX-USAGE.md +113 -0
  12. package/examples/INDEX.md +27 -0
  13. package/examples/README.md +94 -0
  14. package/examples/RUNTIME-TRACE.md +222 -0
  15. package/examples/TRACING-FAILURES.md +186 -0
  16. package/examples/backend/error-tracing.test.ts +44 -0
  17. package/examples/backend/order-pricing.test.ts +54 -0
  18. package/examples/backend/order-pricing.ts +27 -0
  19. package/examples/backend/runtime-trace.example.ts +31 -0
  20. package/examples/backend/sample-payload.json +62 -0
  21. package/examples/backend/sample-tracing-payload.json +90 -0
  22. package/examples/complex/api-services.demo.ts +72 -0
  23. package/examples/complex/full-platform.demo.test.ts +46 -0
  24. package/examples/frontend/checkout-form.test.ts +54 -0
  25. package/examples/frontend/checkout-form.ts +29 -0
  26. package/examples/frontend/error-tracing.test.ts +59 -0
  27. package/examples/frontend/sample-payload.json +62 -0
  28. package/examples/frontend/sample-tracing-payload.json +73 -0
  29. package/examples/frontend-webhook-client.ts +32 -0
  30. package/examples/jest.config.example.cjs +18 -0
  31. package/examples/mocharc.nzila.example.cjs +21 -0
  32. package/examples/react/checkout-feature.example.tsx +41 -0
  33. package/examples/send-run-manual.mjs +16 -0
  34. package/examples/shared/feature-map.ts +24 -0
  35. package/examples/shared/load-env.mjs +24 -0
  36. package/examples/shared/send-webhook.mjs +43 -0
  37. package/examples/vitest.config.example.ts +20 -0
  38. package/package.json +9 -4
@@ -0,0 +1,222 @@
1
+ # Runtime feature tracing (`traceNzilaFeature` / `useNzilaFeature`)
2
+
3
+ Pair **Vitest** failures (webhook) with **live** signals on the same **feature** name (e.g. `Checkout`). Call one function at the top of the UI component or backend handler that owns that feature; use **`captureError`** when you already handle errors.
4
+
5
+ ## SDK package exports
6
+
7
+ | Import path | Use for |
8
+ |-------------|---------|
9
+ | `@nzila/sdk` | `initNzilaRuntime`, `traceNzilaFeature`, `reportFeatureError`, Vitest/Jest reporters |
10
+ | `@nzila/sdk/react` | `useNzilaFeature`, `NzilaFeatureBoundary`, `reportFeatureError` |
11
+ | `@nzila/sdk/runtime` | Low-level runtime client only |
12
+ | `@nzila/sdk/vitest` | `NzilaVitestReporter` |
13
+
14
+ ---
15
+
16
+ ## Setup (once per app)
17
+
18
+ ```ts
19
+ import { initNzilaRuntime } from "@nzila/sdk/runtime";
20
+ // or: import { initNzilaRuntime } from "@nzila/sdk";
21
+
22
+ initNzilaRuntime({
23
+ signalsUrl: process.env.NZILA_SIGNALS_URL!, // https://your-app.vercel.app/api/signals/runtime
24
+ apiKey: process.env.NZILA_API_KEY!,
25
+ locale: "en",
26
+ renderLoopThreshold: 40, // optional — React render-loop detection
27
+ });
28
+ ```
29
+
30
+ | Variable | Where |
31
+ |----------|--------|
32
+ | `NZILA_SIGNALS_URL` | Node / Route Handlers |
33
+ | `NEXT_PUBLIC_NZILA_SIGNALS_URL` | Browser (Next.js) |
34
+ | `NZILA_API_KEY` / `NEXT_PUBLIC_NZILA_API_KEY` | Same project key as test webhooks |
35
+
36
+ Requires DB migration `0003_feature_intelligence.sql` (`npm run db:migrate`).
37
+
38
+ ---
39
+
40
+ ## API reference
41
+
42
+ | Function | Signature | When to use |
43
+ |----------|-----------|-------------|
44
+ | `initNzilaRuntime` | `(config)` once | Before any trace calls |
45
+ | `useNzilaFeature` | `(feature, options?)` | **Top of React component** |
46
+ | `traceNzilaFeature` | `(feature)` | **Top of backend handler** |
47
+ | `handle.captureError` | `(error, detail?)` | Your `try/catch`, toasts, form errors |
48
+ | `handle.captureApiError` | `(error, label, detail?)` | Caught fetch/HTTP without `runApi` |
49
+ | `handle.runApi` | `(label, fn)` | Wrap async API calls |
50
+ | `handle.reportError` | same as `captureError` | Alias |
51
+ | `reportFeatureError` | `(feature, error, detail?)` | Shared util; pass feature + error |
52
+ | `captureFeatureError` | alias of `reportFeatureError` | |
53
+ | `extractErrorFields` | `(error)` | `message`, `name`, `stack` normalization |
54
+
55
+ Returned **handle** (from hook or `traceNzilaFeature`): `{ feature, runApi, captureError, captureApiError, reportError, reportUi }`.
56
+
57
+ ---
58
+
59
+ ## React — `useNzilaFeature` at the top of the component
60
+
61
+ ```tsx
62
+ "use client";
63
+
64
+ import { useNzilaFeature } from "@nzila/sdk/react";
65
+ import { initNzilaRuntime } from "@nzila/sdk/react";
66
+
67
+ // e.g. app/layout.tsx or instrumentation.ts (once)
68
+ initNzilaRuntime({
69
+ signalsUrl: process.env.NEXT_PUBLIC_NZILA_SIGNALS_URL!,
70
+ apiKey: process.env.NEXT_PUBLIC_NZILA_API_KEY!,
71
+ });
72
+
73
+ export function CheckoutPage() {
74
+ const nzila = useNzilaFeature("Checkout"); // same feature as Vitest mapFeature
75
+
76
+ async function pay() {
77
+ await nzila.runApi("POST /api/checkout", () =>
78
+ fetch("/api/checkout", { method: "POST", body: "{}" }),
79
+ );
80
+ }
81
+
82
+ return <button onClick={() => void pay()}>Pay</button>;
83
+ }
84
+ ```
85
+
86
+ ### Manual error handling (your `try/catch`)
87
+
88
+ Use the handle from the hook (feature is already bound):
89
+
90
+ ```tsx
91
+ export function CheckoutPage() {
92
+ const nzila = useNzilaFeature("Checkout");
93
+
94
+ async function pay() {
95
+ try {
96
+ await nzila.runApi("POST /api/checkout", () =>
97
+ fetch("/api/checkout", { method: "POST" }),
98
+ );
99
+ } catch (err) {
100
+ nzila.captureError(err, { step: "pay", handled: true });
101
+ // your toast / form error state
102
+ }
103
+ }
104
+ }
105
+ ```
106
+
107
+ Or pass **feature + error** from anywhere (e.g. shared error util):
108
+
109
+ ```ts
110
+ import { reportFeatureError } from "@nzila/sdk/react";
111
+
112
+ reportFeatureError("Checkout", error, { step: "validation" });
113
+ ```
114
+
115
+ `captureError` uses `extractErrorFields` — works with `Error`, strings, and unknown values.
116
+
117
+ ### What is reported automatically
118
+
119
+ | Issue | Signal `type` | `payload` notes |
120
+ |-------|---------------|-----------------|
121
+ | Too many re-renders in ~1s | `ui` | `kind: "render_loop"`, `renderCount` |
122
+ | `window.onerror` | `error` | while component mounted |
123
+ | Unhandled promise rejection | `error` | while mounted |
124
+ | `runApi` failure | `api` | `success: false`, message |
125
+ | `runApi` success | `api` | `success: true`, `label` |
126
+
127
+ Optional error boundary:
128
+
129
+ ```tsx
130
+ import { NzilaFeatureBoundary } from "@nzila/sdk/react";
131
+
132
+ <NzilaFeatureBoundary feature="Checkout" nzila={nzila}>
133
+ <CheckoutForm />
134
+ </NzilaFeatureBoundary>
135
+ ```
136
+
137
+ ---
138
+
139
+ ## Backend — `traceNzilaFeature` at the top of the handler
140
+
141
+ ```ts
142
+ import { initNzilaRuntime, traceNzilaFeature } from "@nzila/sdk";
143
+
144
+ initNzilaRuntime({
145
+ signalsUrl: process.env.NZILA_SIGNALS_URL!,
146
+ apiKey: process.env.NZILA_API_KEY!,
147
+ });
148
+
149
+ export async function postCheckout(req: Request) {
150
+ const nzila = traceNzilaFeature("Checkout");
151
+
152
+ try {
153
+ const upstream = await nzila.runApi("payment-gateway", () =>
154
+ fetch("https://api.example.com/charge", { method: "POST" }),
155
+ );
156
+ if (!upstream.ok) throw new Error(`charge failed: ${upstream.status}`);
157
+ return Response.json({ ok: true });
158
+ } catch (err) {
159
+ nzila.captureError(err, { route: "POST /checkout", handled: true });
160
+ return Response.json({ error: "payment_failed" }, { status: 502 });
161
+ }
162
+ }
163
+ ```
164
+
165
+ Caught an API error without `runApi`?
166
+
167
+ ```ts
168
+ nzila.captureApiError(err, "payment-gateway", { status: 502 });
169
+ ```
170
+
171
+ From a nested helper without the handle:
172
+
173
+ ```ts
174
+ import { reportFeatureError } from "@nzila/sdk";
175
+
176
+ reportFeatureError("Checkout", err, { layer: "inventory-service" });
177
+ ```
178
+
179
+ Call **`traceNzilaFeature("Checkout")` as the first line** of the route/service that matches your tested feature.
180
+
181
+ Repo example: [backend/runtime-trace.example.ts](./backend/runtime-trace.example.ts).
182
+
183
+ ---
184
+
185
+ ## Dashboard
186
+
187
+ Open **Feature health** for the project — compare **tests** (from webhooks) with **runtime** (`api` / `ui` / `error` signals).
188
+
189
+ Use the **same feature string** everywhere:
190
+
191
+ | Source | Example |
192
+ |--------|---------|
193
+ | Vitest `describe` / `mapFeature` | `"Checkout"` |
194
+ | `useNzilaFeature("…")` | `"Checkout"` |
195
+ | `traceNzilaFeature("…")` | `"Checkout"` |
196
+ | `reportFeatureError("…", err)` | `"Checkout"` |
197
+ | Manual HTTP (advanced) | `POST /api/signals/runtime` |
198
+
199
+ Test-side tracing: [TRACING-FAILURES.md](./TRACING-FAILURES.md).
200
+ Deploy (Vercel + Render): [../docs/DEPLOY-VERCEL-RENDER.md](../docs/DEPLOY-VERCEL-RENDER.md).
201
+
202
+ ---
203
+
204
+ ## Manual HTTP (optional)
205
+
206
+ Same payload the SDK sends:
207
+
208
+ ```json
209
+ {
210
+ "locale": "en",
211
+ "signals": [
212
+ {
213
+ "feature": "Checkout",
214
+ "type": "error",
215
+ "success": false,
216
+ "payload": { "message": "card declined", "handled": true }
217
+ }
218
+ ]
219
+ }
220
+ ```
221
+
222
+ Types: `navigation`, `api`, `ui`, `error`, `custom`.
@@ -0,0 +1,186 @@
1
+ # Tracing test failures in Nzila
2
+
3
+ How failures move from **backend** and **frontend** test runs into the dashboard, how to **attach tests to a feature**, and how to trace an error even when the test was already in your suite.
4
+
5
+ ## Mental model
6
+
7
+ ```text
8
+ describe() tree → describePath + fingerprint
9
+ mapFeature / feature field → dashboard "Feature" column
10
+ test name → human label
11
+ errors[] → processed into Failure analysis (not raw stacks in UI)
12
+ worker → summaries + feature health
13
+ ```
14
+
15
+ 1. Vitest finishes one run → reporter sends **one** webhook (`runId` is idempotent).
16
+ 2. Each test becomes a row with `describePath`, `feature`, `testName`, `status`, `errors`.
17
+ 3. The worker enriches failures and updates **Stability**, **Failures**, and **Feature health**.
18
+
19
+ ## Developer setup (one-time)
20
+
21
+ ### 1. Run Nzila locally
22
+
23
+ From the repo root (see [README](../README.md)):
24
+
25
+ ```bash
26
+ npm install
27
+ cp .env.example .env.local # fill DATABASE_URL, AUTH_SECRET, etc.
28
+ npm run db:migrate
29
+ npm run dev # http://localhost:3000
30
+ npm run worker # second terminal — processes webhooks
31
+ ```
32
+
33
+ ### 2. Create two projects (API + web)
34
+
35
+ In the dashboard, create **two** projects so backend and frontend keys do not mix:
36
+
37
+ | Project display name | **App name** (must match Vitest `appName`) | Example tests |
38
+ |----------------------|------------------------------------------|---------------|
39
+ | Checkout API | `checkout-api` | `examples/backend/` |
40
+ | Checkout Web | `checkout-web` | `examples/frontend/` |
41
+
42
+ For each project: **Settings → API keys** → create a key. You can use one key for both demos if both projects belong to your org and you switch `NZILA_API_KEY` when running each suite — separate keys are clearer.
43
+
44
+ ### 3. Configure `.env.local`
45
+
46
+ ```bash
47
+ NZILA_WEBHOOK_URL=http://localhost:3000/api/webhooks/tests
48
+ NZILA_API_KEY=nzl_your_key_for_checkout_api
49
+ ```
50
+
51
+ For frontend tracing, use the **checkout-web** key (or change the key before `example:test:tracing:frontend`).
52
+
53
+ ### 4. Run tracing demos
54
+
55
+ ```bash
56
+ npm run example:test:tracing:backend # failures → checkout-api
57
+ npm run example:test:tracing:frontend # failures → checkout-web
58
+ npm run example:test:tracing:all # both
59
+ ```
60
+
61
+ Tracing-only Vitest configs (reporter + `mapFeature`):
62
+
63
+ - `examples/vitest.tracing.backend.config.ts`
64
+ - `examples/vitest.tracing.frontend.config.ts`
65
+
66
+ ## Attach tests to a feature
67
+
68
+ Nzila groups failures and health by **feature** string. You have three ways to set it:
69
+
70
+ ### A. Top-level `describe` name (default)
71
+
72
+ The Vitest reporter uses the **first** `describe()` segment as `feature` unless you override with `mapFeature`.
73
+
74
+ ```ts
75
+ describe("Checkout", () => {
76
+ it("should reject expired cards", () => { /* ... */ });
77
+ });
78
+ ```
79
+
80
+ → `feature: "Checkout"`, fingerprint includes full `describePath`.
81
+
82
+ ### B. `mapFeature` in the reporter (recommended for UI vs product names)
83
+
84
+ When suite titles are long (e.g. `"Checkout UI"`) but the product feature is `"Checkout"`, map them in config:
85
+
86
+ ```ts
87
+ // examples/shared/feature-map.ts
88
+ export function resolveFrontendFeature(describePath: string[], _testName: string) {
89
+ if (describePath[0] === "Checkout UI") return "Checkout";
90
+ return describePath[0] ?? "general";
91
+ }
92
+ ```
93
+
94
+ Wire it in `NzilaVitestReporter({ mapFeature: resolveFrontendFeature, ... })`.
95
+
96
+ ### C. Explicit `feature` in webhook JSON
97
+
98
+ For manual/CI payloads, set `feature` on each test (see `examples/backend/sample-tracing-payload.json` and `examples/frontend/sample-tracing-payload.json`):
99
+
100
+ ```json
101
+ {
102
+ "describePath": ["Checkout UI", "email field"],
103
+ "feature": "Checkout",
104
+ "testName": "should reject malformed email addresses",
105
+ "status": "failed",
106
+ "errors": [{ "message": "...", "stack": "..." }]
107
+ }
108
+ ```
109
+
110
+ Server fallback: `feature ?? describePath[0] ?? "general"` (`ensureTestCase`).
111
+
112
+ **Runtime signals** should use the **same** feature string so **Feature health** lines up tests and production.
113
+
114
+ **Recommended (live app):** use the SDK so production matches tests:
115
+
116
+ | Layer | Call at top of unit | Manual `catch` |
117
+ |-------|---------------------|----------------|
118
+ | React | `const nzila = useNzilaFeature("Checkout")` | `nzila.captureError(err, { … })` |
119
+ | Backend | `const nzila = traceNzilaFeature("Checkout")` | `nzila.captureError(err, { … })` |
120
+ | Either | — | `reportFeatureError("Checkout", err, { … })` |
121
+
122
+ Auto-reported: render loops (`ui`), `runApi` failures (`api`), unhandled window errors. Full API: **[RUNTIME-TRACE.md](./RUNTIME-TRACE.md)**.
123
+
124
+ ## Example suites
125
+
126
+ | Layer | File | App name | Script |
127
+ |-------|------|----------|--------|
128
+ | Backend | `examples/backend/error-tracing.test.ts` | `checkout-api` | `npm run example:test:tracing:backend` |
129
+ | Frontend | `examples/frontend/error-tracing.test.ts` | `checkout-web` | `npm run example:test:tracing:frontend` |
130
+
131
+ Failures are intentional so you can practice tracing without breaking production code paths.
132
+
133
+ ## Trace a failure after tests ran (even with tests in CI)
134
+
135
+ Tests passing or failing does not block tracing: each **run** is stored with per-test `errors`. When a test **fails** (or starts failing in a new run):
136
+
137
+ 1. **Test runs** (`/en/runs`) — open the latest run for `checkout-api` or `checkout-web`.
138
+ 2. **Failure analysis** (`/en/failures`) — read processed summaries (from `errors[].message`, not raw stacks).
139
+ 3. **Feature insights** / **Feature health** — filter by the same `feature` you attached in the suite or payload.
140
+ 4. **Code** — match **fingerprint**: `describePath[0] › … › testName` (same `describe` + `it` names as in repo).
141
+
142
+ Optional: send runtime signals for the same feature and compare on **Feature health**.
143
+
144
+ ## Manual webhook (no Vitest)
145
+
146
+ ```bash
147
+ npm run example:webhook:tracing # backend sample
148
+ npm run example:webhook:tracing:frontend # frontend sample
149
+ ```
150
+
151
+ Requires `NZILA_API_KEY` and `NZILA_WEBHOOK_URL` in `.env.local` (script loads env via `send-webhook.mjs`).
152
+
153
+ ## Webhook error fields
154
+
155
+ Per test in `tests[]`:
156
+
157
+ ```json
158
+ {
159
+ "describePath": ["Checkout", "Payment"],
160
+ "feature": "Checkout",
161
+ "testName": "should reject expired cards",
162
+ "status": "failed",
163
+ "duration": 42,
164
+ "errors": [
165
+ { "message": "expected false to be true", "stack": "AssertionError: ..." }
166
+ ]
167
+ }
168
+ ```
169
+
170
+ - **message** — drives operator-facing copy after processing.
171
+ - **stack** — optional; used server-side, not shown as-is on **Failures**.
172
+
173
+ ## Related files
174
+
175
+ | File | Role |
176
+ |------|------|
177
+ | `examples/shared/feature-map.ts` | Backend/frontend feature mapping |
178
+ | `@nzila/sdk/vitest-reporter.ts` | Vitest → webhook + `mapFeature` |
179
+ | `src/lib/webhook-schema.ts` | Payload validation |
180
+ | `src/server/test-cases.ts` | `feature` resolution |
181
+ | `src/server/process-run.ts` | Worker enrichment |
182
+ | `examples/TRACING-FAILURES.md` | This guide (tests) |
183
+ | `examples/RUNTIME-TRACE.md` | Live `useNzilaFeature`, `captureError` |
184
+ | `examples/backend/runtime-trace.example.ts` | Backend handler sample |
185
+ | `@nzila/sdk/runtime/` | Runtime client |
186
+ | `@nzila/sdk/react/` | React hook + boundary |
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Demo suite for tracing failures in Nzila.
3
+ * Top-level describe() names become "feature" in the webhook and dashboard.
4
+ *
5
+ * Run: npm run example:test:tracing
6
+ */
7
+ import { describe, it, expect } from "vitest";
8
+
9
+ describe("Checkout", () => {
10
+ describe("Payment", () => {
11
+ it("should accept valid card payloads", () => {
12
+ expect({ status: "authorized" }).toEqual({ status: "authorized" });
13
+ });
14
+
15
+ it("should reject expired cards", () => {
16
+ const cardExpired = false;
17
+ expect(cardExpired).toBe(true);
18
+ });
19
+ });
20
+
21
+ describe("Coupons", () => {
22
+ it("should apply percentage discounts", () => {
23
+ const total = 100;
24
+ const discounted = total * 0.9;
25
+ expect(discounted).toBe(80);
26
+ });
27
+ });
28
+ });
29
+
30
+ describe("Inventory", () => {
31
+ it("should block checkout when stock is zero", () => {
32
+ const available = 0;
33
+ const canCheckout = available > 0;
34
+ expect(canCheckout).toBe(true);
35
+ });
36
+ });
37
+
38
+ describe("Auth API", () => {
39
+ it("should rate-limit repeated login failures", () => {
40
+ const attempts = 3;
41
+ const locked = attempts >= 5;
42
+ expect(locked).toBe(true);
43
+ });
44
+ });
@@ -0,0 +1,54 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { applyCoupon, lineTotal, orderTotal, reserveInventory } from "./order-pricing";
3
+
4
+ describe("Payment API", () => {
5
+ describe("order pricing", () => {
6
+ it("should compute line totals from quantity and unit price", () => {
7
+ expect(lineTotal({ sku: "SKU-1", qty: 2, unitCents: 1500 })).toBe(3000);
8
+ });
9
+
10
+ it("should apply SAVE10 coupon to subtotal", () => {
11
+ expect(applyCoupon(10_000, "save10")).toBe(9000);
12
+ });
13
+
14
+ it("should reject negative quantities", () => {
15
+ expect(() => lineTotal({ sku: "SKU-2", qty: -1, unitCents: 100 })).toThrow();
16
+ });
17
+ });
18
+
19
+ describe("checkout total", () => {
20
+ it("should sum multiple line items before coupon", () => {
21
+ const total = orderTotal(
22
+ [
23
+ { sku: "A", qty: 1, unitCents: 2500 },
24
+ { sku: "B", qty: 2, unitCents: 500 },
25
+ ],
26
+ "FREESHIP",
27
+ );
28
+ expect(total).toBe(3500);
29
+ });
30
+ });
31
+ });
32
+
33
+ describe("Inventory service", () => {
34
+ it("should reserve stock when quantity is available", () => {
35
+ expect(reserveInventory("widget", 3, 10)).toBe(true);
36
+ });
37
+
38
+ it("should fail reservation when stock is insufficient", () => {
39
+ expect(reserveInventory("widget", 50, 10)).toBe(false);
40
+ });
41
+ });
42
+
43
+ describe("Auth API", () => {
44
+ it("should reject invalid password attempts", () => {
45
+ const attempts = 6;
46
+ const maxAttempts = 5;
47
+ expect(attempts > maxAttempts).toBe(true);
48
+ });
49
+
50
+ it("should lock account after 5 failures", () => {
51
+ const accountLocked = false;
52
+ expect(accountLocked).toBe(true);
53
+ });
54
+ });
@@ -0,0 +1,27 @@
1
+ export type LineItem = { sku: string; qty: number; unitCents: number };
2
+
3
+ export function lineTotal(item: LineItem): number {
4
+ if (item.qty < 0 || item.unitCents < 0) {
5
+ throw new Error("Invalid line item");
6
+ }
7
+ return item.qty * item.unitCents;
8
+ }
9
+
10
+ export function applyCoupon(subtotalCents: number, code: string): number {
11
+ if (subtotalCents < 0) throw new Error("Invalid subtotal");
12
+ const normalized = code.trim().toUpperCase();
13
+ if (normalized === "SAVE10") return Math.round(subtotalCents * 0.9);
14
+ if (normalized === "FREESHIP") return subtotalCents;
15
+ return subtotalCents;
16
+ }
17
+
18
+ export function orderTotal(items: LineItem[], coupon?: string): number {
19
+ const subtotal = items.reduce((sum, item) => sum + lineTotal(item), 0);
20
+ return coupon ? applyCoupon(subtotal, coupon) : subtotal;
21
+ }
22
+
23
+ export function reserveInventory(sku: string, qty: number, onHand: number): boolean {
24
+ if (!sku.trim()) return false;
25
+ if (qty <= 0) return false;
26
+ return onHand >= qty;
27
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Backend pattern: call traceNzilaFeature at the top of the handler
3
+ * that owns the same feature name as your Vitest suite.
4
+ *
5
+ * Env: NZILA_SIGNALS_URL, NZILA_API_KEY
6
+ */
7
+ import { initNzilaRuntime, traceNzilaFeature } from "@nzila/sdk/runtime";
8
+
9
+ initNzilaRuntime({
10
+ signalsUrl:
11
+ process.env.NZILA_SIGNALS_URL ??
12
+ "http://localhost:3000/api/signals/runtime",
13
+ apiKey: process.env.NZILA_API_KEY ?? "",
14
+ locale: "en",
15
+ });
16
+
17
+ export async function exampleCheckoutHandler(): Promise<{ ok: boolean }> {
18
+ const nzila = traceNzilaFeature("Checkout");
19
+
20
+ try {
21
+ await nzila.runApi("inventory-reserve", async () => {
22
+ const res = await fetch("https://httpbin.org/status/500");
23
+ if (!res.ok) throw new Error(`inventory ${res.status}`);
24
+ return res;
25
+ });
26
+ return { ok: true };
27
+ } catch (error) {
28
+ nzila.captureError(error, { handler: "exampleCheckoutHandler", handled: true });
29
+ return { ok: false };
30
+ }
31
+ }
@@ -0,0 +1,62 @@
1
+ {
2
+ "runId": "backend-run-placeholder",
3
+ "appName": "checkout-api",
4
+ "framework": "vitest",
5
+ "startedAt": "2026-05-16T14:00:00.000Z",
6
+ "finishedAt": "2026-05-16T14:00:04.200Z",
7
+ "tests": [
8
+ {
9
+ "appName": "checkout-api",
10
+ "framework": "vitest",
11
+ "startedAt": "2026-05-16T14:00:00.000Z",
12
+ "finishedAt": "2026-05-16T14:00:00.800Z",
13
+ "describePath": ["Payment API", "order pricing"],
14
+ "feature": "Payment API",
15
+ "testName": "should compute line totals from quantity and unit price",
16
+ "status": "passed",
17
+ "duration": 12,
18
+ "errors": []
19
+ },
20
+ {
21
+ "appName": "checkout-api",
22
+ "framework": "vitest",
23
+ "startedAt": "2026-05-16T14:00:00.800Z",
24
+ "finishedAt": "2026-05-16T14:00:01.400Z",
25
+ "describePath": ["Payment API", "order pricing"],
26
+ "feature": "Payment API",
27
+ "testName": "should apply SAVE10 coupon to subtotal",
28
+ "status": "passed",
29
+ "duration": 8,
30
+ "errors": []
31
+ },
32
+ {
33
+ "appName": "checkout-api",
34
+ "framework": "vitest",
35
+ "startedAt": "2026-05-16T14:00:01.400Z",
36
+ "finishedAt": "2026-05-16T14:00:02.000Z",
37
+ "describePath": ["Inventory service"],
38
+ "feature": "Inventory",
39
+ "testName": "should reserve stock when quantity is available",
40
+ "status": "passed",
41
+ "duration": 15,
42
+ "errors": []
43
+ },
44
+ {
45
+ "appName": "checkout-api",
46
+ "framework": "vitest",
47
+ "startedAt": "2026-05-16T14:00:02.000Z",
48
+ "finishedAt": "2026-05-16T14:00:03.200Z",
49
+ "describePath": ["Auth API"],
50
+ "feature": "Auth API",
51
+ "testName": "should lock account after 5 failures",
52
+ "status": "failed",
53
+ "duration": 95,
54
+ "errors": [
55
+ {
56
+ "message": "Expected accountLocked to be true, received false",
57
+ "stack": "AssertionError: expected false to be true\n at examples/backend/order-pricing.test.ts"
58
+ }
59
+ ]
60
+ }
61
+ ]
62
+ }