@danypops/tickets 0.8.5 → 0.8.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/tickets",
3
- "version": "0.8.5",
3
+ "version": "0.8.7",
4
4
  "description": "Unified CLI, daemon, and TypeScript library for issue tracking across GitHub, GitLab, and Jira.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -24,8 +24,8 @@
24
24
  "typecheck": "tsc --noEmit"
25
25
  },
26
26
  "dependencies": {
27
- "@danypops/vehicle-core": "^0.11.0",
28
- "@danypops/vehicle-server": "^0.16.0",
27
+ "@danypops/vehicle-core": "^0.12.3",
28
+ "@danypops/vehicle-server": "^0.17.1",
29
29
  "@danypops/vehicle-client": "^0.5.0",
30
30
  "@danypops/enigma-client": "^0.3.0",
31
31
  "@gitbeaker/rest": "^43.8.0",
@@ -16,6 +16,7 @@
16
16
  */
17
17
  import {
18
18
  bindVehicleOperation,
19
+ defineErrorMapping,
19
20
  defineLooseObjectSchema,
20
21
  defineVehicleOperation,
21
22
  type LooseObjectProperty,
@@ -24,11 +25,21 @@ import {
24
25
  } from "@danypops/vehicle-core";
25
26
  import { VehicleRegistry } from "@danypops/vehicle-server";
26
27
  import type { BackendCapabilities, TicketService } from "../issue/service.js";
28
+ import { statusForKnownTicketError } from "../rpc/error-status.js";
27
29
  import type { TicketOperation } from "../rpc/ops.js";
28
30
  import { TICKET_OP_HANDLERS, type TicketsAppDeps } from "../rpc/server.js";
29
31
 
30
32
  const OWNER = "tickets";
31
33
 
34
+ const withTicketsErrorParity = defineErrorMapping(
35
+ [
36
+ { matches: (error) => statusForKnownTicketError(error) === 404, category: "not_found" },
37
+ { matches: (error) => statusForKnownTicketError(error) === 400, category: "validation" },
38
+ { matches: (error) => statusForKnownTicketError(error) === 422, category: "authorization" },
39
+ ],
40
+ { fallbackCategory: "internal", fallbackCode: "handler-failed", fallbackMessage: "Tickets operation failed" },
41
+ );
42
+
32
43
  const LIMITS = { defaultTimeoutMs: 10_000, maxTimeoutMs: 30_000, maxRequestBytes: 65_536, maxResponseBytes: 262_144 };
33
44
 
34
45
  const stringProp: LooseObjectProperty = { type: "string" };
@@ -288,10 +299,8 @@ export function createTicketsVehicleRegistry(deps: Omit<TicketsAppDeps, "vehicle
288
299
  version: deps.version,
289
300
  description: "Unified issue tracking across GitHub, GitLab, and Jira.",
290
301
  });
291
- // setExposeHandlerFailureDetails left off: ApiError (issue/errors.ts) embeds the raw HTTP
292
- // response body, untrusted external content from GitHub/GitLab/Jira. No error-parity wrapper
293
- // here either -- TICKET_OP_HANDLERS' real errors reach the registry's own catch-all as-is.
294
-
302
+ // Every handler passes through the reviewed mapper above; unmatched failures stay redacted.
303
+ registry.setExposeHandlerFailureDetails(true);
295
304
  for (const spec of OPERATIONS) {
296
305
  const operation = defineVehicleOperation({
297
306
  name: spec.action,
@@ -308,7 +317,11 @@ export function createTicketsVehicleRegistry(deps: Omit<TicketsAppDeps, "vehicle
308
317
  const mapInput = spec.mapInput ?? ((input: Record<string, unknown>) => input);
309
318
  registry.register(
310
319
  OWNER,
311
- bindVehicleOperation(operation, () => async (context) => handler(deps, mapInput(context.input as Record<string, unknown>) as never)),
320
+ bindVehicleOperation(
321
+ operation,
322
+ () => async (context) =>
323
+ withTicketsErrorParity<unknown>(() => handler(deps, mapInput(context.input as Record<string, unknown>) as never)),
324
+ ),
312
325
  );
313
326
  }
314
327
 
@@ -0,0 +1,12 @@
1
+ import { AuthRequiredError, IssueNotFoundError } from "../issue/errors.js";
2
+ import { NotSupportedError, UnknownBackendError } from "../issue/service.js";
3
+ import { FocusError } from "../sqlite/focus.js";
4
+ import { SavedQueryNotFoundError } from "../sqlite/saved-queries.js";
5
+
6
+ /** Returns the legacy HTTP status only for reviewed business errors; unknown failures stay unclassified. */
7
+ export function statusForKnownTicketError(error: unknown): number | undefined {
8
+ if (error instanceof IssueNotFoundError || error instanceof SavedQueryNotFoundError) return 404;
9
+ if (error instanceof UnknownBackendError || error instanceof NotSupportedError || error instanceof FocusError) return 400;
10
+ if (error instanceof AuthRequiredError) return 422;
11
+ return undefined;
12
+ }
package/src/rpc/server.ts CHANGED
@@ -11,12 +11,12 @@ import type { VehicleRegistry } from "@danypops/vehicle-server";
11
11
  import { createVehicleHttpApp } from "@danypops/vehicle-server/http";
12
12
  import type { Logger } from "@danypops/vehicle-server/logging";
13
13
  import { errorResponse, healthResponse, jsonResponse, readyResponse, requireBearerToken } from "@danypops/vehicle-server/rpc-http";
14
- import { AuthRequiredError, IssueNotFoundError } from "../issue/errors.js";
15
14
  import { parseRef } from "../issue/issue.js";
16
- import { NotSupportedError, type TicketService, UnknownBackendError } from "../issue/service.js";
15
+ import type { TicketService } from "../issue/service.js";
17
16
  import { FocusError, type FocusStore } from "../sqlite/focus.js";
18
17
  import type { Ledger } from "../sqlite/ledger.js";
19
18
  import { SavedQueryNotFoundError, type SavedQueryStore } from "../sqlite/saved-queries.js";
19
+ import { statusForKnownTicketError } from "./error-status.js";
20
20
  import { TICKET_OPERATIONS, type TicketOperation, type TicketOpInputs, type TicketOpOutputs } from "./ops.js";
21
21
 
22
22
  export interface TicketsAppDeps {
@@ -117,10 +117,7 @@ function isTicketOperation(value: unknown): value is TicketOperation {
117
117
  }
118
118
 
119
119
  function statusFor(error: unknown): number {
120
- if (error instanceof IssueNotFoundError || error instanceof SavedQueryNotFoundError) return 404;
121
- if (error instanceof UnknownBackendError || error instanceof NotSupportedError || error instanceof FocusError) return 400;
122
- if (error instanceof AuthRequiredError) return 422;
123
- return 500;
120
+ return statusForKnownTicketError(error) ?? 500;
124
121
  }
125
122
 
126
123
  export function buildApp(deps: TicketsAppDeps): { fetch(request: Request): Promise<Response> } {