@ultimat3/action 19.1.2 → 19.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/action",
3
- "version": "19.1.2",
3
+ "version": "19.2.0",
4
4
  "description": "The action primitive: one declaration projected to route, OpenAPI, client, MCP tool, job handle, tests",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -34,10 +34,10 @@
34
34
  "test": "bun test"
35
35
  },
36
36
  "dependencies": {
37
- "@ultimat3/cache": "19.1.2",
38
- "@ultimat3/core": "19.1.2",
39
- "@ultimat3/http": "19.1.2",
40
- "@ultimat3/policy": "19.1.2",
41
- "@ultimat3/schema": "19.1.2"
37
+ "@ultimat3/cache": "19.2.0",
38
+ "@ultimat3/core": "19.2.0",
39
+ "@ultimat3/http": "19.2.0",
40
+ "@ultimat3/policy": "19.2.0",
41
+ "@ultimat3/schema": "19.2.0"
42
42
  }
43
43
  }
package/src/client.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  * and the way that is guaranteed is that `keyFor` is never called here.
11
11
  */
12
12
  import type { ClientFlight, ClientRetry, UltimateError, WireAnswer } from '@ultimat3/core';
13
- import { FRAMEWORK_CODE, problemOf, traceHeaders } from '@ultimat3/core';
13
+ import { FRAMEWORK_CODE, isJsonObject, problemOf, traceHeaders } from '@ultimat3/core';
14
14
  import type { InferInput, InferOutput, StandardSchemaV1 } from '@ultimat3/schema';
15
15
  import type { Action } from './action';
16
16
  import { ContractDriftError, RemoteActionError, RpcFailedError } from './errors';
@@ -216,6 +216,7 @@ function toUltimateError(text: string, status: number, name: string): UltimateEr
216
216
  // Parsed, never taken: `body` is whatever answered the request. A list this build cannot read
217
217
  // is dropped rather than repaired, and `cause` below still carries every rejection in it.
218
218
  issues: issuesFromWire(body['issues']),
219
+ meta: metaFromWire(body['meta']),
219
220
  cause: stringOr(body['cause'] ?? body['detail'], `${name} failed with ${status}`),
220
221
  fix: stringOr(body['fix'], `x actions describe ${name} --json`),
221
222
  // RFC-9457's `type` IS a documentation URI, so a server that sends no `docs` extension has
@@ -226,6 +227,31 @@ function toUltimateError(text: string, status: number, name: string): UltimateEr
226
227
  });
227
228
  }
228
229
 
230
+ /**
231
+ * The document's `meta` member, or nothing. Parsed JSON is JSON-safe by construction, so the only
232
+ * work is shape: an object, copied own key by own key onto a fresh record. `issues` is skipped
233
+ * because it has its own reader and its own bound one line up, and `__proto__` because
234
+ * `JSON.parse` mints it as a real own key — copied by assignment it would replace the prototype
235
+ * of the error's `meta`, which the reporter and the overlay walk. EMPTY is `undefined`: an
236
+ * `{}` under `meta` would say the server declared keys and sent none.
237
+ */
238
+ function metaFromWire(value: unknown): Readonly<Record<string, unknown>> | undefined {
239
+ if (!isJsonObject(value)) return undefined;
240
+ const out: Record<string, unknown> = {};
241
+ let carried = 0;
242
+ for (const [key, member] of Object.entries(value)) {
243
+ if (key === 'issues' || key === '__proto__') continue;
244
+ Object.defineProperty(out, key, {
245
+ value: member,
246
+ writable: true,
247
+ enumerable: true,
248
+ configurable: true,
249
+ });
250
+ carried += 1;
251
+ }
252
+ return carried === 0 ? undefined : out;
253
+ }
254
+
229
255
  function nonEmpty(value: unknown): string | undefined {
230
256
  return typeof value === 'string' && value.length > 0 ? value : undefined;
231
257
  }
package/src/errors.ts CHANGED
@@ -260,6 +260,12 @@ export interface RemoteFailure {
260
260
  * refuses to read, and in both cases `cause` still holds every rejection.
261
261
  */
262
262
  readonly issues?: readonly ValidationIssue[] | undefined;
263
+ /**
264
+ * The document's `meta` member — the keys the server's `registerProblemMeta` declared for the
265
+ * code, already read as a plain JSON object by `client.ts`. Carried onto the error's own `meta`
266
+ * so an island reads `error.meta.sessionId` where it used to run a regex over `cause`.
267
+ */
268
+ readonly meta?: Readonly<Record<string, unknown>> | undefined;
263
269
  }
264
270
 
265
271
  /** A link, not a string the server happened to put in a field the overlay renders as an href. */
@@ -314,6 +320,10 @@ export class RemoteActionError extends UltimateError {
314
320
  // `terminal` on the one field the framework promises a client never has to infer.
315
321
  retry: retryForStatus(failure.code, failure.status),
316
322
  meta: {
323
+ // The server's declared keys FIRST, so the four this class owns win a collision: a server
324
+ // meta naming `status` would otherwise overwrite the one a report reads the HTTP status
325
+ // from, and `origin: 'remote'` is what marks the code as one this bundle never declared.
326
+ ...failure.meta,
317
327
  origin: 'remote',
318
328
  action: failure.action,
319
329
  status: failure.status,