stitchkit 0.73.0 → 0.74.1
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/dist/application/diagnostic-journal-lock.d.ts +14 -0
- package/dist/application/diagnostic-journal-lock.d.ts.map +1 -1
- package/dist/application.js +20 -3
- package/dist/browser/client-multipart.d.ts.map +1 -1
- package/dist/browser/client-url.d.ts.map +1 -1
- package/dist/browser/client.d.ts.map +1 -1
- package/dist/browser/http.d.ts +15 -0
- package/dist/browser/http.d.ts.map +1 -1
- package/dist/{index-0tfm7beg.js → index-58jzmnn4.js} +79 -67
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +572 -550
- package/dist/remote.js +1 -1
- package/dist/testing.js +1 -1
- package/llms-full.txt +150 -14
- package/package.json +1 -1
package/dist/remote.js
CHANGED
package/dist/testing.js
CHANGED
package/llms-full.txt
CHANGED
|
@@ -61,7 +61,7 @@ own, recorded as an ADR.
|
|
|
61
61
|
| `stitchkit/testing` | tests on Bun or Node | stable | in-process generated clients over a real Fetch handler, plus the store and managed-resource conformance kits |
|
|
62
62
|
| `stitchkit/declaration` | build and deployment tooling (Bun or Node) | evolving | `ProjectDeclarationSchema` — the one machine-readable statement a repository makes about itself |
|
|
63
63
|
| `stitchkit/react` | browser | stable | `createCursorQuery`, `createCacheBridge` |
|
|
64
|
-
| `stitchkit/agent-runtime` | server | evolving<br>_redefined in 10 of the
|
|
64
|
+
| `stitchkit/agent-runtime` | server | evolving<br>_redefined in 10 of the 19 minors since 0.56.2, most recently 0.69.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
|
|
65
65
|
| `stitchkit/agent-runtime/harness` | server | evolving | resource-aware process-local facade over the canonical Agent runtime; supervision stays outside |
|
|
66
66
|
| `stitchkit/agent-runtime/coding-tools` | server (Bun or Node) | evolving | bounded host-authorized direct file and shell tools; a root boundary, not an OS sandbox |
|
|
67
67
|
| `stitchkit/agent-runtime/openrouter` | server | evolving | isolated OpenRouter language-model adapter |
|
|
@@ -69,7 +69,7 @@ own, recorded as an ADR.
|
|
|
69
69
|
| `stitchkit/agent-runtime/sqlite/bun` | server (Bun) | evolving | durable built-in SQLite store for the agent runtime |
|
|
70
70
|
| `stitchkit/agent-runtime/sqlite/node` | server (Node ≥ 22.5) | evolving | durable built-in SQLite store for the agent runtime |
|
|
71
71
|
| `stitchkit-tui` | terminal (Bun) | evolving | optional official OpenTUI host over a caller-composed headless runtime |
|
|
72
|
-
| `stitchkit/application` | server | evolving<br>_redefined in 4 of the
|
|
72
|
+
| `stitchkit/application` | server | evolving<br>_redefined in 4 of the 19 minors since 0.56.2, most recently 0.72.0_ | managed resource graph, readiness, admission, schedules and bounded shutdown |
|
|
73
73
|
| `stitchkit/application/grammy` | server | evolving | isolated grammY polling and webhook lifecycle adapters |
|
|
74
74
|
| `stitchkit/application/opentelemetry` | server | evolving | maps application snapshots onto an injected OpenTelemetry `Meter` |
|
|
75
75
|
|
|
@@ -2098,19 +2098,55 @@ naming every offending field in the `message` and again in `details.issues` as
|
|
|
2098
2098
|
`{ path, code, message }`. That holds for a JSON body, a query string and a path
|
|
2099
2099
|
parameter alike, so a rejected argument always costs a round trip.
|
|
2100
2100
|
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2101
|
+
It is tempting to close that gap locally: the schemas are on the contract object,
|
|
2102
|
+
and `input` is the very Zod object the server validates with, so parsing it before
|
|
2103
|
+
the call looks free. **It is not, and it fails in the direction that breaks
|
|
2104
|
+
working code.** A call's arguments and the server's `input` are not the same
|
|
2105
|
+
value, for two independent reasons.
|
|
2106
|
+
|
|
2107
|
+
An argument object is typed as the intersection of `params`, `input` and any
|
|
2108
|
+
multipart fields, so `input` covers one factor of it. Parsed over the whole
|
|
2109
|
+
object it silently strips the keys it does not own — or, being `.strict()`,
|
|
2110
|
+
refuses them:
|
|
2111
|
+
|
|
2112
|
+
```ts
|
|
2113
|
+
// params: { id }, input: z.object({ text }).strict()
|
|
2114
|
+
contract.endpoints.update.input.safeParse({ id: 'item-1', text: 'hello' })
|
|
2115
|
+
// → refused: unrecognized_keys: ["id"] the server accepts this call
|
|
2116
|
+
```
|
|
2117
|
+
|
|
2118
|
+
And on a query string or a multipart body the wire carries strings while the
|
|
2119
|
+
caller holds live values, so a coercing schema answers differently on each side:
|
|
2104
2120
|
|
|
2105
2121
|
```ts
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2122
|
+
// input: z.object({ flag: z.coerce.boolean() })
|
|
2123
|
+
api.list({ flag: false })
|
|
2124
|
+
// local safeParse({ flag: false }) → false
|
|
2125
|
+
// server safeParse({ flag: 'false' }) → true z.coerce.boolean()('false') is true
|
|
2109
2126
|
```
|
|
2110
2127
|
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2128
|
+
And a plain JSON body is not the safe exception it looks like, because it still
|
|
2129
|
+
crosses `JSON.stringify`. A schema holding any type that does not survive that
|
|
2130
|
+
round trip — `z.date()`, `z.map()`, `z.set()`, `z.bigint()`, `z.instanceof(...)`,
|
|
2131
|
+
or `NaN` / `Infinity` inside a `z.number()` — agrees locally and refuses on the
|
|
2132
|
+
server:
|
|
2133
|
+
|
|
2134
|
+
```ts
|
|
2135
|
+
// input: z.object({ when: z.date() }), POST, no path params
|
|
2136
|
+
check({ when: new Date() }) // accepted: a live Date
|
|
2137
|
+
// server // refused: expected date, received string
|
|
2138
|
+
```
|
|
2139
|
+
|
|
2140
|
+
This one fails in the safe direction — nothing valid is blocked — but "safe" is
|
|
2141
|
+
about traffic, not about diagnostics. The check stays silent exactly where it
|
|
2142
|
+
promised to speak: the refusal then arrives from the server, through a different
|
|
2143
|
+
path and without the field names the local gate would have given. A gate that
|
|
2144
|
+
lets through what it was added to catch is not a cheap gate, it is a quiet one.
|
|
2145
|
+
|
|
2146
|
+
One schema, one call, opposite answers — inside a single release, with no version
|
|
2147
|
+
skew involved. Argument validation therefore stays on the server, which is the
|
|
2148
|
+
only side that sees what was actually sent. A rejected argument costs a round
|
|
2149
|
+
trip, and the refusal names every offending field.
|
|
2114
2150
|
|
|
2115
2151
|
An explicit contract `HEAD` operation is exposed like any other typed method.
|
|
2116
2152
|
Because HEAD endpoints are `rawResponse`, it resolves to the untouched
|
|
@@ -2184,10 +2220,20 @@ the same client-only errors:
|
|
|
2184
2220
|
|
|
2185
2221
|
| Failure | `ApiError.code` | `status` |
|
|
2186
2222
|
|---------|-----------------|----------|
|
|
2223
|
+
| arguments refused before sending | `VALIDATION_ERROR` | `0` |
|
|
2187
2224
|
| caller `AbortSignal` | `REQUEST_ABORTED` | `0` |
|
|
2188
2225
|
| endpoint/client timeout | `REQUEST_TIMEOUT` | `0` |
|
|
2189
2226
|
| other transport failure | `UNKNOWN_ERROR` | `0` |
|
|
2190
2227
|
|
|
2228
|
+
`status: 0` is what separates a refusal made here from one made by the server:
|
|
2229
|
+
the same `VALIDATION_ERROR` arrives with `400` when the server refused, and
|
|
2230
|
+
`details.issues` is the same `{ path, code, message }` array either way, so one
|
|
2231
|
+
rendering serves both. A refusal raised here is always a **rejection** — on both
|
|
2232
|
+
transports, for every call shape — and it never reaches the server. The client
|
|
2233
|
+
refuses this way for a missing path param, a missing scoped prefix key, a
|
|
2234
|
+
non-flat field in a `GET` input, and a missing or invalid multipart file. → ADR
|
|
2235
|
+
0148.
|
|
2236
|
+
|
|
2191
2237
|
Abort and timeout do not emit `network_error` and are not retried. The same
|
|
2192
2238
|
options work for query, JSON, multipart and raw-response calls. Stitchkit does
|
|
2193
2239
|
not expose upload progress: Fetch has no portable upload-progress primitive.
|
|
@@ -2231,9 +2277,16 @@ const work = createClient(requestWorkContract, {
|
|
|
2231
2277
|
|
|
2232
2278
|
The same adapter accepts unrelated contract shapes without learning their DTOs
|
|
2233
2279
|
or operation inventory. `createClient` chooses method/path from the contract,
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2280
|
+
forwards `.withOptions(..., { signal })` and validates the response through the
|
|
2281
|
+
endpoint's output schema. A caller payload cannot replace the configured base URL
|
|
2282
|
+
or reserved operation path.
|
|
2283
|
+
|
|
2284
|
+
It does **not** filter your arguments down to the declared ones. Keys the contract
|
|
2285
|
+
does not declare are consumed for the path where they belong there and otherwise
|
|
2286
|
+
sent as they are — `api.list({ q: 'hello', note: 'internal' })` puts
|
|
2287
|
+
`?q=hello¬e=internal` on the wire, and the server drops `note` only because its
|
|
2288
|
+
schema is not `.strict()`. Nothing was validated away on this side: pass the
|
|
2289
|
+
object the contract declares, not a wider one it happens to contain.
|
|
2237
2290
|
|
|
2238
2291
|
Contract metadata describes an effect; it does not authorize one. Authentication,
|
|
2239
2292
|
scope and destination policy remain in the application/server boundary. A schema
|
|
@@ -9541,6 +9594,67 @@ makes one thing your job rather than the resolver's:
|
|
|
9541
9594
|
The mechanical part is identical either way. Only the *noticing* differs, and an
|
|
9542
9595
|
exact pin moves it onto you.
|
|
9543
9596
|
|
|
9597
|
+
## Released migration: 0.74.0
|
|
9598
|
+
|
|
9599
|
+
One change, and it only reaches you if you catch a refusal the **client** raised — one it made while
|
|
9600
|
+
planning the request, before anything was sent. A server refusal is unchanged.
|
|
9601
|
+
|
|
9602
|
+
### If you match on the text of a client-side refusal
|
|
9603
|
+
|
|
9604
|
+
A missing path param, a missing scoped prefix key, a non-flat field in a `GET` input, a missing or
|
|
9605
|
+
invalid multipart file: each used to arrive as a plain `Error` carrying only a sentence. They now
|
|
9606
|
+
arrive as an `ApiError` in the same shape a server validation failure uses.
|
|
9607
|
+
|
|
9608
|
+
```ts
|
|
9609
|
+
// before
|
|
9610
|
+
catch (e) {
|
|
9611
|
+
if (/Missing path param/.test(e.message)) …
|
|
9612
|
+
}
|
|
9613
|
+
|
|
9614
|
+
// after
|
|
9615
|
+
catch (e) {
|
|
9616
|
+
if (e.code === 'VALIDATION_ERROR' && e.status === 0) … // refused here, nothing was sent
|
|
9617
|
+
}
|
|
9618
|
+
// e.details.issues[0].path === 'id'
|
|
9619
|
+
```
|
|
9620
|
+
|
|
9621
|
+
`status` is what separates the two worlds, and it is not new — `0` has always meant *this never
|
|
9622
|
+
reached the server*, as `REQUEST_ABORTED` and `REQUEST_TIMEOUT` already used it:
|
|
9623
|
+
|
|
9624
|
+
| | `code` | `status` |
|
|
9625
|
+
|---|---|---|
|
|
9626
|
+
| the client refused your arguments | `VALIDATION_ERROR` | `0` |
|
|
9627
|
+
| the server refused your arguments | `VALIDATION_ERROR` | `400` |
|
|
9628
|
+
|
|
9629
|
+
`details.issues` is the same `{ path, code, message }` array on both, so one rendering serves both —
|
|
9630
|
+
and `zodIssues` / `ZodIssueSummary` are now importable from `stitchkit` itself, not only from
|
|
9631
|
+
`stitchkit/server`.
|
|
9632
|
+
|
|
9633
|
+
### If you are on `createHttpClient` and your call site relied on a synchronous throw
|
|
9634
|
+
|
|
9635
|
+
This is the sharp one, because it is invisible in a diff. Those refusals used to be thrown
|
|
9636
|
+
**synchronously** on the Ky-backed client — before the call returned a promise — so
|
|
9637
|
+
`api.upload({}).catch(handler)` never reached the handler, while the same mistake on the bare-fetch
|
|
9638
|
+
client rejected normally. They now reject on both.
|
|
9639
|
+
|
|
9640
|
+
```ts
|
|
9641
|
+
// before, on createHttpClient only
|
|
9642
|
+
try {
|
|
9643
|
+
api.upload({}) // threw here
|
|
9644
|
+
} catch (e) { … }
|
|
9645
|
+
|
|
9646
|
+
// after, on both transports
|
|
9647
|
+
await api.upload({}).catch(handler)
|
|
9648
|
+
```
|
|
9649
|
+
|
|
9650
|
+
A `try/catch` around an `await`ed call keeps working. A `try/catch` around an un-awaited call no
|
|
9651
|
+
longer catches anything — which was already true on the other transport.
|
|
9652
|
+
|
|
9653
|
+
Not a migration, but worth knowing: a missing multipart file used to be reported as
|
|
9654
|
+
`UNKNOWN_ERROR`, the code that means *this client cannot tell you what happened*, on the one refusal
|
|
9655
|
+
where it is certain nothing was sent. → ADR 0148 carries the reasoning, including why argument
|
|
9656
|
+
validation stays on the server.
|
|
9657
|
+
|
|
9544
9658
|
## Released migration: 0.73.0
|
|
9545
9659
|
|
|
9546
9660
|
Two breaking changes. Nothing you send to a server changed, and only one of these
|
|
@@ -9588,6 +9702,25 @@ Match `err.code` instead: it is the contract, and it did not change. An error
|
|
|
9588
9702
|
carrying a real message is untouched. An empty message now counts as no message,
|
|
9589
9703
|
for the same reason — an empty string is not an explanation either.
|
|
9590
9704
|
|
|
9705
|
+
### If a diagnostic journal lock file is already on disk
|
|
9706
|
+
|
|
9707
|
+
One operator step, and only on a host that has been **renamed** since its lock was written.
|
|
9708
|
+
|
|
9709
|
+
A lock written before this version carries no machine identity, so there is nothing to compare and
|
|
9710
|
+
the host name is all that is left. If the name still matches, the lock reclaims exactly as it always
|
|
9711
|
+
did and there is nothing to do. If the name has changed, the lock is `unattributable` and is refused
|
|
9712
|
+
— the same refusal this release exists to end, except that this particular file predates the fix and
|
|
9713
|
+
cannot be repaired from the inside. Delete it once:
|
|
9714
|
+
|
|
9715
|
+
```sh
|
|
9716
|
+
rm <journal path>.lock # only when the host was renamed since the file was written
|
|
9717
|
+
```
|
|
9718
|
+
|
|
9719
|
+
Locks written from this version on carry the machine identity, so the situation cannot recur. To see
|
|
9720
|
+
which case you are in without guessing, read the refusal instead of the file:
|
|
9721
|
+
`readDiagnosticJournalLockDiagnosis(err)` returns `attribution: 'unattributable'` for exactly this
|
|
9722
|
+
one, and `'another-machine'` for a lock that genuinely belongs elsewhere and must **not** be deleted.
|
|
9723
|
+
|
|
9591
9724
|
Two related fixes need no migration, but change what you will see. An error the
|
|
9592
9725
|
transport raised through `createHttpClient` now carries the transport's own text
|
|
9593
9726
|
in `message` and the original error as `cause`, where before it carried neither
|
|
@@ -12338,6 +12471,9 @@ The browser-and-server entrypoint. Re-exports everything from
|
|
|
12338
12471
|
| `PathPrefixArgs` | _type_ | required string-valued keys exposed to a typed dynamic `pathPrefix` callback |
|
|
12339
12472
|
| `createHttpClient` | function | the Ky-based HTTP transport; on Next.js SSR its first attempt stays request-memoizable while every retry is a distinct transport attempt — [guide](../guide/client.md#createhttpclient) |
|
|
12340
12473
|
| `ApiError` | class | a non-2xx or client failure, with `code` / `status` / `details` / `hint`, optional readonly `traceId` from `x-request-id`, and standard `cause` preserving an injected transport failure |
|
|
12474
|
+
| `zodIssues` | function | a `ZodError` → structured `{ path, code, message }[]` — the exact projection a `VALIDATION_ERROR` carries in `ApiError.details.issues`, so a caller renders one shape. Also on `stitchkit/server`; same name, one definition |
|
|
12475
|
+
| `ZodIssueSummary` | _type_ | one structured validation issue (`{ path, code, message }`) — the element type of `details.issues`. Also on `stitchkit/server` |
|
|
12476
|
+
| `formatZodError` | function | a `ZodError` → a readable, field-summarised string. Also on `stitchkit/server` |
|
|
12341
12477
|
| `HttpClient` | _type_ | the transport interface `createClient` builds on |
|
|
12342
12478
|
| `ConfiguredHttpClient` | _type_ | a framework-created `HttpClient` carrying its readonly `baseUrl` for URL builders |
|
|
12343
12479
|
| `HttpClientConfig` | _type_ | config for `createHttpClient`; retry `limit` counts retries after the initial attempt (default 2 = at most 3 GET attempts), with `statusCodes: []` by default; `fetch` installs an explicit transport and is mutually exclusive with the legacy Bun-only `unix` option — [details](../guide/client.md#createhttpclient) |
|
package/package.json
CHANGED