okengine 0.19.9 → 0.21.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.
- package/AGENTS.md +1 -1
- package/package.json +2 -1
- package/site/content/docs/client/auth.mdx +1 -2
- package/site/content/docs/client/calling.mdx +125 -37
- package/site/content/docs/client/index.mdx +7 -7
- package/site/content/docs/client/react.mdx +5 -0
- package/site/content/docs/elements/channel/email.mdx +25 -36
- package/site/content/docs/elements/channel/index.mdx +88 -46
- package/site/content/docs/elements/channel/push.mdx +7 -9
- package/site/content/docs/elements/channel/sms.mdx +6 -4
- package/site/content/docs/elements/channel/whatsapp.mdx +11 -9
- package/site/content/docs/elements/clock/index.mdx +14 -25
- package/site/content/docs/elements/flow/http.mdx +42 -23
- package/site/content/docs/elements/flow/index.mdx +41 -30
- package/site/content/docs/elements/flow/routing.mdx +166 -122
- package/site/content/docs/elements/gate/rls.mdx +2 -2
- package/site/content/docs/elements/gate/tenancy.mdx +1 -1
- package/site/content/docs/elements/store/files.mdx +11 -5
- package/site/content/docs/elements/store/index.mdx +13 -6
- package/site/content/docs/elements/store/kv.mdx +8 -2
- package/site/content/docs/elements/store/search.mdx +5 -5
- package/site/content/docs/elements/store/sql.mdx +100 -39
- package/site/content/docs/elements/vault/index.mdx +14 -13
- package/site/content/docs/elements/vault/secrets.mdx +6 -3
- package/site/content/docs/index.mdx +1 -1
- package/site/content/docs/plugins/magic-link.mdx +4 -3
- package/site/content/docs/plugins/otp.mdx +4 -3
- package/site/content/docs/plugins/two-factor.mdx +4 -0
- package/site/content/docs/providers/index.mdx +1 -1
- package/site/content/docs/recipes/index.mdx +1 -1
- package/site/content/docs/recipes/rustfs.mdx +1 -1
- package/site/content/docs/reference/cli.mdx +7 -4
- package/site/content/docs/reference/configuration.mdx +8 -8
- package/site/content/docs/reference/errors.mdx +229 -55
- package/site/content/docs/reference/fx.mdx +22 -9
- package/site/content/docs/reference/i18n.mdx +4 -4
- package/site/content/docs/reference/plugins.mdx +4 -4
- package/site/content/docs/understand/the-architecture.mdx +2 -2
- package/site/content/docs/understand/try-it.mdx +759 -24
- package/src/cli/ai-setup/ai-setup.test.ts +40 -0
- package/src/cli/ai-setup/apply.ts +28 -46
- package/src/cli/build.test.ts +3 -3
- package/src/cli/build.ts +5 -5
- package/src/cli/db-auto-push.test.ts +11 -0
- package/src/cli/db-auto-push.ts +6 -2
- package/src/cli/db.test.ts +1 -1
- package/src/cli/db.ts +6 -6
- package/src/cli/dev-app-runner.ts +2 -1
- package/src/cli/dev-db-push.test.ts +6 -2
- package/src/cli/dev-schema-sync.ts +1 -1
- package/src/cli/dev.test.ts +10 -7
- package/src/cli/dev.ts +13 -11
- package/src/cli/ensure-drizzle-config.ts +4 -3
- package/src/cli/start.ts +2 -1
- package/src/client/create.ts +3 -3
- package/src/client/explain.test.ts +252 -0
- package/src/client/explain.ts +272 -0
- package/src/client/live.test.ts +44 -0
- package/src/client/notes-contract.test.ts +10 -0
- package/src/client/sse.ts +6 -2
- package/src/client/transport.test.ts +67 -0
- package/src/client/transport.ts +24 -40
- package/src/client/types.ts +17 -6
- package/src/client-react/browser.test.ts +23 -0
- package/src/client-react/live-resource.ts +6 -2
- package/src/client-react/use-live-query.ts +1 -1
- package/src/compiler/flow-path.test.ts +1 -0
- package/src/compiler/flow-path.ts +1 -1
- package/src/compiler/generate-adopt.test.ts +55 -1
- package/src/compiler/generate-adopt.ts +111 -21
- package/src/compiler/response.ts +17 -27
- package/src/config/index.ts +6 -4
- package/src/console/server/invoke-user-flow.test.ts +8 -2
- package/src/console/server/invoke-user-flow.ts +12 -18
- package/src/console/server/security.gate.test.ts +1 -1
- package/src/console/ui-next/dist/assets/{access-page-BoC83Ubl.js → access-page-tIbsiphz.js} +1 -1
- package/src/console/ui-next/dist/assets/{agent-disclosure-CKjAEOqA.js → agent-disclosure-CSKumwS2.js} +1 -1
- package/src/console/ui-next/dist/assets/{cache-glyph-Ceaq9pYh.js → cache-glyph-BCC-DxKT.js} +1 -1
- package/src/console/ui-next/dist/assets/{call-pii-button-C4lmY7ck.js → call-pii-button-jOmYISdc.js} +1 -1
- package/src/console/ui-next/dist/assets/{collapsible-82y257sL.js → collapsible-DC2xNaAb.js} +1 -1
- package/src/console/ui-next/dist/assets/{duration-tone-W63jaMZ8.js → duration-tone-CwoV56jn.js} +1 -1
- package/src/console/ui-next/dist/assets/{flows-page-KFTFj2rK.js → flows-page-gT1lsWQK.js} +1 -1
- package/src/console/ui-next/dist/assets/{highlighted-json-yE9zNTqC.js → highlighted-json-C2GZEJNI.js} +1 -1
- package/src/console/ui-next/dist/assets/{http-method-CwFeFroN.js → http-method-BdYjcIrD.js} +1 -1
- package/src/console/ui-next/dist/assets/{index-r7xXt_VV.js → index-Cul17AcV.js} +3 -3
- package/src/console/ui-next/dist/assets/{observability-page-BDiliMiC.js → observability-page-oY9vdYBk.js} +1 -1
- package/src/console/ui-next/dist/assets/{replica-lag-DYDzWUFT.js → replica-lag-C4QdAF7J.js} +1 -1
- package/src/console/ui-next/dist/assets/{request-meta-CzrOfgiz.js → request-meta-C43DHyld.js} +1 -1
- package/src/console/ui-next/dist/assets/{store-page-CZC2cwaw.js → store-page-CL0D9dOq.js} +1 -1
- package/src/console/ui-next/dist/assets/{trace-detail-sheet-DgeeejW7.js → trace-detail-sheet-Dcpo4Us_.js} +1 -1
- package/src/console/ui-next/dist/assets/{tree-expand-toggle-CzGIyOPY.js → tree-expand-toggle-CKJTuv43.js} +1 -1
- package/src/console/ui-next/dist/assets/{units-page-DBiDCLIB.js → units-page-1PlT19ft.js} +1 -1
- package/src/console/ui-next/dist/assets/{vault-page-CcHsthPe.js → vault-page-BwZ9YjTW.js} +1 -1
- package/src/console/ui-next/dist/index.html +1 -1
- package/src/docker/docker.test.ts +3 -3
- package/src/docker/images-config.test.ts +4 -4
- package/src/docker/stack-id.test.ts +1 -1
- package/src/drivers/clock-postgres.test.ts +10 -2
- package/src/drivers/clock-postgres.ts +18 -2
- package/src/drivers/vault-driver-removal.test.ts +2 -2
- package/src/elements/channel/declare.ts +66 -3
- package/src/elements/channel/runtime.ts +9 -11
- package/src/elements/channel.test.ts +42 -0
- package/src/elements/channel.ts +4 -2
- package/src/elements/clock/reconcile.ts +45 -24
- package/src/elements/clock.test.ts +33 -0
- package/src/elements/store/emit-drizzle.ts +285 -65
- package/src/elements/store/files-errors.test.ts +149 -0
- package/src/elements/store/files-errors.ts +189 -0
- package/src/elements/store/kv-errors.test.ts +98 -0
- package/src/elements/store/kv-errors.ts +139 -0
- package/src/elements/store/load-plugin-tables.ts +1 -1
- package/src/elements/store/prepare-row.test.ts +57 -4
- package/src/elements/store/resource.ts +11 -7
- package/src/elements/store/runtime.ts +18 -13
- package/src/elements/store/schema-decl.test.ts +178 -0
- package/src/elements/store/sql-errors.test.ts +197 -0
- package/src/elements/store/sql-errors.ts +294 -0
- package/src/elements/store/sql-session.test.ts +52 -0
- package/src/elements/store/sql-session.ts +50 -2
- package/src/elements/store/store-errors.ts +47 -0
- package/src/elements/store/table.ts +8 -6
- package/src/http.ts +9 -1
- package/src/i18n/catalogs/ar.ts +18 -0
- package/src/i18n/catalogs/en.ts +18 -0
- package/src/index.ts +9 -1
- package/src/kernel/adopt-barrel-fresh.test.ts +1 -1
- package/src/kernel/app.ts +40 -34
- package/src/kernel/auto-registry.test.ts +26 -1
- package/src/kernel/boot.ts +2 -2
- package/src/kernel/boundary-contract.ts +6 -1
- package/src/kernel/builtin-errors.test.ts +117 -0
- package/src/kernel/builtin-errors.ts +129 -0
- package/src/kernel/call.test.ts +182 -0
- package/src/kernel/errors-vault.ts +16 -0
- package/src/kernel/errors.registry.test.ts +7 -0
- package/src/kernel/errors.ts +97 -24
- package/src/kernel/fail-helpers.ts +34 -0
- package/src/kernel/flow-units.ts +3 -3
- package/src/kernel/fx.test.ts +8 -0
- package/src/kernel/fx.ts +24 -8
- package/src/kernel/index.ts +12 -1
- package/src/kernel/mutation-id.ts +8 -0
- package/src/kernel/plugin.ts +4 -3
- package/src/kernel/project-out.test.ts +176 -0
- package/src/kernel/project-out.ts +91 -0
- package/src/kernel/realtime-bind.ts +2 -3
- package/src/kernel/router/linear.ts +12 -6
- package/src/kernel/router.test.ts +13 -0
- package/src/plugins/magic-link.ts +25 -24
- package/src/plugins/otp.ts +35 -24
- package/src/plugins/two-factor.ts +15 -0
- package/src/runs/duckdb.test.ts +2 -2
- package/src/runtime/dev-request-log.ts +29 -11
- package/src/term.test.ts +76 -0
- package/src/term.ts +166 -3
package/src/term.test.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
formatOkeWordmark,
|
|
17
17
|
formatServiceLine,
|
|
18
18
|
formatSurfaceBox,
|
|
19
|
+
formatFatalError,
|
|
19
20
|
formatRequestLine,
|
|
20
21
|
formatStackSummary,
|
|
21
22
|
countTermLines,
|
|
@@ -296,6 +297,81 @@ describe("term", () => {
|
|
|
296
297
|
expect(out).not.toContain("↳");
|
|
297
298
|
});
|
|
298
299
|
|
|
300
|
+
test("formatRequestLine title chip includes error.code", () => {
|
|
301
|
+
const out = formatRequestLine({
|
|
302
|
+
surface: "Backend",
|
|
303
|
+
method: "POST",
|
|
304
|
+
path: "/links",
|
|
305
|
+
flow: "links.create",
|
|
306
|
+
status: 409,
|
|
307
|
+
ms: 3,
|
|
308
|
+
detail: "That value is already in use.",
|
|
309
|
+
errorCode: "Conflict",
|
|
310
|
+
color: false,
|
|
311
|
+
});
|
|
312
|
+
expect(out).toMatch(/◇ {2}409 Conflict\n/);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
test("formatFatalError prints VaultBootError as OKE1510 with cause and fix", () => {
|
|
316
|
+
const err = Object.assign(new Error("vault boot failed"), {
|
|
317
|
+
name: "VaultBootError",
|
|
318
|
+
gaps: [{ name: "STRIPE_SECRET_KEY" }, { name: "DATABASE_URL" }],
|
|
319
|
+
});
|
|
320
|
+
const out = formatFatalError(err, false);
|
|
321
|
+
expect(out).toContain("OKE1510");
|
|
322
|
+
expect(out).toContain("2 secrets have no value in any resolution layer.");
|
|
323
|
+
expect(out).toContain("- STRIPE_SECRET_KEY");
|
|
324
|
+
expect(out).toContain("oke vault set");
|
|
325
|
+
expect(out).toContain("https://oke.omqkhafi.dev/e/1510");
|
|
326
|
+
expect(out).not.toContain("Error:");
|
|
327
|
+
expect(out).not.toContain(" at ");
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
test("formatFatalError prints GateBootError with posture gaps", () => {
|
|
331
|
+
const err = Object.assign(new Error("gate boot failed"), {
|
|
332
|
+
name: "GateBootError",
|
|
333
|
+
gaps: [{ flowId: "notes.get", method: "GET", path: "/notes/:id" }],
|
|
334
|
+
});
|
|
335
|
+
const out = formatFatalError(err, false);
|
|
336
|
+
expect(out).toContain("Gate");
|
|
337
|
+
expect(out).toContain("1 trigger(s) missing auth posture.");
|
|
338
|
+
expect(out).toContain("- notes.get GET /notes/:id");
|
|
339
|
+
expect(out).toContain("Attach a gate or `.public()`.");
|
|
340
|
+
expect(out).not.toContain(" at ");
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test("formatFatalError prints PluginNeedsError with unmet needs", () => {
|
|
344
|
+
const err = Object.assign(new Error("plugin needs failed"), {
|
|
345
|
+
name: "PluginNeedsError",
|
|
346
|
+
gaps: [{ plugin: "auth", need: "store.sql" }],
|
|
347
|
+
});
|
|
348
|
+
const out = formatFatalError(err, false);
|
|
349
|
+
expect(out).toContain("Plugin");
|
|
350
|
+
expect(out).toContain("1 unmet .needs() dependency.");
|
|
351
|
+
expect(out).toContain('- auth needs "store.sql"');
|
|
352
|
+
expect(out).toContain("Install the missing plugin");
|
|
353
|
+
expect(out).not.toContain(" at ");
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
test("formatFatalError hides stack unless OKE_DEBUG=1", () => {
|
|
357
|
+
const prev = process.env["OKE_DEBUG"];
|
|
358
|
+
const boom = new Error("plain boom");
|
|
359
|
+
try {
|
|
360
|
+
delete process.env["OKE_DEBUG"];
|
|
361
|
+
const hidden = formatFatalError(boom, false);
|
|
362
|
+
expect(hidden).toContain("Error");
|
|
363
|
+
expect(hidden).toContain("plain boom");
|
|
364
|
+
expect(hidden).not.toContain(" at ");
|
|
365
|
+
|
|
366
|
+
process.env["OKE_DEBUG"] = "1";
|
|
367
|
+
const shown = formatFatalError(boom, false);
|
|
368
|
+
expect(shown).toContain("Error");
|
|
369
|
+
} finally {
|
|
370
|
+
if (prev === undefined) delete process.env["OKE_DEBUG"];
|
|
371
|
+
else process.env["OKE_DEBUG"] = prev;
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
|
|
299
375
|
test("formatStackSummary is scannable", () => {
|
|
300
376
|
const out = formatStackSummary({
|
|
301
377
|
project: "oke-dev-a3f791",
|
package/src/term.ts
CHANGED
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
* Flip to monochrome with `NO_COLOR=1` or non-TTY stdout.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { docsUrl } from "./docs-origin.ts";
|
|
9
|
+
import { lookupOkeError } from "./kernel/errors.ts";
|
|
10
|
+
|
|
8
11
|
/** Whether ANSI color should be applied. */
|
|
9
12
|
export function termColorEnabled(stream: { readonly isTTY?: boolean } = process.stdout): boolean {
|
|
10
13
|
if (process.env["NO_COLOR"] !== undefined) return false;
|
|
@@ -701,15 +704,171 @@ export function formatDevNote(options: {
|
|
|
701
704
|
const ink = options.tone === "error" ? s.red : s.yellow;
|
|
702
705
|
const bar = `${s.dim}│${s.reset}`;
|
|
703
706
|
const width = Math.max(40, Math.min(72, (process.stdout.columns ?? 80) - 6));
|
|
704
|
-
const wrapped =
|
|
707
|
+
const wrapped = wrapBodyLines(options.body.trimEnd(), width);
|
|
705
708
|
const lines = [`${ink}◇${s.reset} ${s.bold}${ink}${options.title}${s.reset}`, bar];
|
|
706
709
|
for (const line of wrapped) {
|
|
707
|
-
lines.push(`${bar} ${ink}${line}${s.reset}`);
|
|
710
|
+
lines.push(line.length === 0 ? bar : `${bar} ${ink}${line}${s.reset}`);
|
|
708
711
|
}
|
|
709
712
|
lines.push(bar);
|
|
710
713
|
return `${lines.join("\n")}\n`;
|
|
711
714
|
}
|
|
712
715
|
|
|
716
|
+
function wrapBodyLines(body: string, width: number): string[] {
|
|
717
|
+
const out: string[] = [];
|
|
718
|
+
for (const raw of body.split("\n")) {
|
|
719
|
+
const line = raw.trimEnd();
|
|
720
|
+
if (line.length === 0) {
|
|
721
|
+
out.push("");
|
|
722
|
+
continue;
|
|
723
|
+
}
|
|
724
|
+
out.push(...wrapWords(line, width));
|
|
725
|
+
}
|
|
726
|
+
return out;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Boot / start fatal — cause, optional list, fix, docs URL. No stack unless
|
|
731
|
+
* `OKE_DEBUG=1`.
|
|
732
|
+
*
|
|
733
|
+
* @param err - Thrown boot value
|
|
734
|
+
* @param color - Color on/off
|
|
735
|
+
*/
|
|
736
|
+
export function formatFatalError(err: unknown, color: boolean = termColorEnabled()): string {
|
|
737
|
+
const debug = process.env["OKE_DEBUG"] === "1";
|
|
738
|
+
const formatted = describeFatalError(err, debug);
|
|
739
|
+
return formatDevNote({
|
|
740
|
+
title: formatted.title,
|
|
741
|
+
body: formatted.body,
|
|
742
|
+
tone: "error",
|
|
743
|
+
color,
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function describeFatalError(
|
|
748
|
+
err: unknown,
|
|
749
|
+
debug: boolean,
|
|
750
|
+
): { readonly title: string; readonly body: string } {
|
|
751
|
+
const vault = vaultBootGaps(err);
|
|
752
|
+
if (vault) {
|
|
753
|
+
const def = lookupOkeError(1510);
|
|
754
|
+
const count = String(vault.length);
|
|
755
|
+
const cause = def
|
|
756
|
+
? interpolateTemplate(def.cause, { count })
|
|
757
|
+
: `${count} secrets have no value in any resolution layer.`;
|
|
758
|
+
const fix = def
|
|
759
|
+
? interpolateTemplate(def.fix, { count })
|
|
760
|
+
: "Set each name (`oke vault set <name>`, or `.env.local`).";
|
|
761
|
+
const url = def ? docsUrl(`/e/${def.code}`) : docsUrl("/e/1510");
|
|
762
|
+
const names = vault.map((g) =>
|
|
763
|
+
g.description ? `- ${g.name}: ${g.description}` : `- ${g.name}`,
|
|
764
|
+
);
|
|
765
|
+
return {
|
|
766
|
+
title: "OKE1510",
|
|
767
|
+
body: [cause, ...names, `→ ${fix}`, url].join("\n"),
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
const gate = namedGaps(err, "GateBootError");
|
|
772
|
+
if (gate) {
|
|
773
|
+
const lines = gate.map((g) => {
|
|
774
|
+
const rec = g as Record<string, unknown>;
|
|
775
|
+
const flow = typeof rec.flowId === "string" ? rec.flowId : "flow";
|
|
776
|
+
const method = typeof rec.method === "string" ? rec.method : "";
|
|
777
|
+
const path = typeof rec.path === "string" ? rec.path : "";
|
|
778
|
+
return `- ${flow} ${method} ${path}`.trim();
|
|
779
|
+
});
|
|
780
|
+
return {
|
|
781
|
+
title: "Gate",
|
|
782
|
+
body: [
|
|
783
|
+
`${gate.length} trigger(s) missing auth posture.`,
|
|
784
|
+
...lines,
|
|
785
|
+
"→ Attach a gate or `.public()`.",
|
|
786
|
+
].join("\n"),
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
const plugin = namedGaps(err, "PluginNeedsError");
|
|
791
|
+
if (plugin) {
|
|
792
|
+
const lines = plugin.map((g) => {
|
|
793
|
+
const rec = g as Record<string, unknown>;
|
|
794
|
+
const name = typeof rec.plugin === "string" ? rec.plugin : "plugin";
|
|
795
|
+
const need = typeof rec.need === "string" ? rec.need : "";
|
|
796
|
+
return `- ${name} needs "${need}"`;
|
|
797
|
+
});
|
|
798
|
+
return {
|
|
799
|
+
title: "Plugin",
|
|
800
|
+
body: [
|
|
801
|
+
`${plugin.length} unmet .needs() dependenc${plugin.length === 1 ? "y" : "ies"}.`,
|
|
802
|
+
...lines,
|
|
803
|
+
"→ Install the missing plugin or bind the element / driver.",
|
|
804
|
+
].join("\n"),
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
if (isOkeErrorShape(err)) {
|
|
809
|
+
return {
|
|
810
|
+
title: `OKE${err.code}`,
|
|
811
|
+
body: [err.causeText, `→ ${err.fix}`, err.docsUrl].join("\n"),
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
816
|
+
const stack = debug && err instanceof Error && err.stack ? `\n${err.stack}` : "";
|
|
817
|
+
return { title: "Error", body: `${message}${stack}` };
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
function interpolateTemplate(template: string, params: Readonly<Record<string, string>>): string {
|
|
821
|
+
return template.replace(/\{([a-zA-Z0-9_]+)\}/g, (_, key: string) => params[key] ?? `{${key}}`);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
function vaultBootGaps(
|
|
825
|
+
err: unknown,
|
|
826
|
+
): readonly { readonly name: string; readonly description?: string }[] | undefined {
|
|
827
|
+
if (!err || typeof err !== "object") return undefined;
|
|
828
|
+
if ((err as { name?: unknown }).name !== "VaultBootError") return undefined;
|
|
829
|
+
const gaps = (err as { gaps?: unknown }).gaps;
|
|
830
|
+
if (!Array.isArray(gaps)) return undefined;
|
|
831
|
+
const out: { name: string; description?: string }[] = [];
|
|
832
|
+
for (const g of gaps) {
|
|
833
|
+
if (!g || typeof g !== "object") continue;
|
|
834
|
+
const name = (g as { name?: unknown }).name;
|
|
835
|
+
if (typeof name !== "string") continue;
|
|
836
|
+
const description = (g as { description?: unknown }).description;
|
|
837
|
+
out.push({
|
|
838
|
+
name,
|
|
839
|
+
...(typeof description === "string" ? { description } : {}),
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
return out;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
function namedGaps(err: unknown, name: string): readonly unknown[] | undefined {
|
|
846
|
+
if (!err || typeof err !== "object") return undefined;
|
|
847
|
+
if ((err as { name?: unknown }).name !== name) return undefined;
|
|
848
|
+
const gaps = (err as { gaps?: unknown }).gaps;
|
|
849
|
+
return Array.isArray(gaps) ? gaps : undefined;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
function isOkeErrorShape(
|
|
853
|
+
err: unknown,
|
|
854
|
+
): err is { code: number; causeText: string; fix: string; docsUrl: string } {
|
|
855
|
+
if (!err || typeof err !== "object") return false;
|
|
856
|
+
const rec = err as {
|
|
857
|
+
name?: unknown;
|
|
858
|
+
code?: unknown;
|
|
859
|
+
causeText?: unknown;
|
|
860
|
+
fix?: unknown;
|
|
861
|
+
docsUrl?: unknown;
|
|
862
|
+
};
|
|
863
|
+
return (
|
|
864
|
+
rec.name === "OkeError" &&
|
|
865
|
+
typeof rec.code === "number" &&
|
|
866
|
+
typeof rec.causeText === "string" &&
|
|
867
|
+
typeof rec.fix === "string" &&
|
|
868
|
+
typeof rec.docsUrl === "string"
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
|
|
713
872
|
/**
|
|
714
873
|
* Boot honesty notice — Clack-like column under the `oke dev` hero.
|
|
715
874
|
*
|
|
@@ -808,6 +967,8 @@ export function formatRequestLine(options: {
|
|
|
808
967
|
readonly color?: boolean;
|
|
809
968
|
/** Failure detail printed on a follow-up line (4xx/5xx). */
|
|
810
969
|
readonly detail?: string;
|
|
970
|
+
/** Failure `error.code` for the title chip (`409 Conflict`). */
|
|
971
|
+
readonly errorCode?: string;
|
|
811
972
|
/** WideEvent / run id when the request executed a flow. */
|
|
812
973
|
readonly runId?: string;
|
|
813
974
|
}): string {
|
|
@@ -849,8 +1010,10 @@ export function formatRequestLine(options: {
|
|
|
849
1010
|
const detail = options.detail?.trim();
|
|
850
1011
|
if (!detail || options.status < 400) return main;
|
|
851
1012
|
const tone: DevNoteTone = options.status >= 500 ? "error" : "warn";
|
|
1013
|
+
const code = options.errorCode?.trim();
|
|
1014
|
+
const title = code && code.length > 0 ? `${options.status} ${code}` : String(options.status);
|
|
852
1015
|
const note = formatDevNote({
|
|
853
|
-
title
|
|
1016
|
+
title,
|
|
854
1017
|
body: detail,
|
|
855
1018
|
tone,
|
|
856
1019
|
color: options.color ?? termColorEnabled(),
|