stitchkit 0.52.0 → 0.53.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.
Files changed (42) hide show
  1. package/README.md +1 -1
  2. package/dist/browser/http.d.ts +7 -0
  3. package/dist/browser/http.d.ts.map +1 -1
  4. package/dist/browser/socket-io.d.ts +38 -2
  5. package/dist/browser/socket-io.d.ts.map +1 -1
  6. package/dist/cli.js +2 -2
  7. package/dist/contract/errors.d.ts +13 -1
  8. package/dist/contract/errors.d.ts.map +1 -1
  9. package/dist/contract/index.js +1 -1
  10. package/dist/{index-8qghvg18.js → index-173tvqcp.js} +3 -1
  11. package/dist/{index-h55e1wyq.js → index-6j4j5wf9.js} +1 -1
  12. package/dist/{index-gff2mxzk.js → index-e1es808r.js} +4 -1
  13. package/dist/{index-psrxjvbw.js → index-gvha32jh.js} +1 -1
  14. package/dist/{index-d6jj3cp5.js → index-pdv1mjjr.js} +44 -4
  15. package/dist/{index-trz4ate5.js → index-pfqjb5xy.js} +3 -1
  16. package/dist/{index-tw7mhqgy.js → index-yez419px.js} +1 -1
  17. package/dist/{index-drsnz8gw.js → index-yydqk4fh.js} +1 -1
  18. package/dist/index.js +28 -10
  19. package/dist/node.d.ts +1 -1
  20. package/dist/node.d.ts.map +1 -1
  21. package/dist/node.js +3 -3
  22. package/dist/observability/index.js +2 -2
  23. package/dist/realtime/socket.d.ts +8 -1
  24. package/dist/realtime/socket.d.ts.map +1 -1
  25. package/dist/server/bun.d.ts +19 -0
  26. package/dist/server/bun.d.ts.map +1 -1
  27. package/dist/server/index.d.ts +2 -2
  28. package/dist/server/index.d.ts.map +1 -1
  29. package/dist/server/index.js +84 -19
  30. package/dist/server/realtime.d.ts +14 -10
  31. package/dist/server/realtime.d.ts.map +1 -1
  32. package/dist/server/socket-io-config.d.ts +45 -2
  33. package/dist/server/socket-io-config.d.ts.map +1 -1
  34. package/dist/server/socket-io-node.d.ts +5 -5
  35. package/dist/server/socket-io-node.d.ts.map +1 -1
  36. package/dist/server/socket-io.d.ts +5 -5
  37. package/dist/server/socket-io.d.ts.map +1 -1
  38. package/dist/testing.js +1 -1
  39. package/dist/tools/remote.d.ts.map +1 -1
  40. package/dist/tools.js +8 -8
  41. package/llms-full.txt +184 -28
  42. package/package.json +1 -1
package/llms-full.txt CHANGED
@@ -851,6 +851,7 @@ server. See [Testing & deployment](./testing-and-deployment.md).
851
851
  | `rawRoutes` | non-contract routes (see below) |
852
852
  | `maxJsonBodyBytes` | optional JSON body cap (bytes); per-route value overrides; unset preserves existing behaviour |
853
853
  | `port` / `hostname` | listen address — port defaults to `3000` |
854
+ | `unix` | listen on a unix domain socket instead of TCP — `'/run/app.sock'` or `{ path, mode }` (see below) |
854
855
  | `cors` | CORS policy — `{ origin, credentials, methods, headers, exposeHeaders }`. `origin` is **required** when `cors` is present: pass an explicit origin (or list), or `'*'` to deliberately allow every origin — an origin-less config is a construction error, never a silent wildcard. Omit `cors` entirely to emit no CORS headers. |
855
856
  | `hooks` | lifecycle hooks (see below) |
856
857
  | `logging` | `true` for built-in request logs, or a `LoggingConfig` (see below) |
@@ -864,6 +865,41 @@ Native Bun `routes` are intentionally not accepted: Bun matches them before
864
865
  `fetch`, so they could bypass shutdown admission. Use `rawRoutes`; they retain
865
866
  the Fetch `Request → Response` model and participate in lifecycle tracking.
866
867
 
868
+ ### Local daemon over a unix socket
869
+
870
+ A local daemon whose door is a socket file needs no TCP port at all:
871
+
872
+ ```ts
873
+ const server = createServer({
874
+ services,
875
+ unix: { path: '/run/my-daemon.sock', mode: 0o600 },
876
+ })
877
+ ```
878
+
879
+ `unix` is mutually exclusive with `port`/`hostname` (construction error). The
880
+ handle stays honest: `server.url` is `unix:///run/my-daemon.sock` — an
881
+ identifier, **not** a fetchable address (dial the path with
882
+ `createHttpClient({ unix })`, [client guide](client.md#unix-domain-sockets)) —
883
+ and `server.port` is `0`.
884
+
885
+ Socket-file hygiene is built in. A stale file left by a killed process is
886
+ reclaimed automatically: the path is probed, and only a socket that is owned by
887
+ the current user **and** answers no live listener is removed before binding
888
+ (best-effort — two processes racing the same path resolve through the loud
889
+ bind error of the loser). A regular file at the path, another user's socket, or
890
+ a live listener each fail with a specific error instead of being unlinked. A
891
+ clean `server.shutdown()` removes the file.
892
+
893
+ `mode` tightens the socket file permissions after listen. Bun creates the file
894
+ `0755` under a normal umask — since `connect(2)` requires *write* permission
895
+ that is already owner-only in practice — but when access to the socket **is**
896
+ the credential, set `mode: 0o600` explicitly and let the filesystem be the
897
+ policy. The Socket.IO lifecycle (`socket`) cannot mount on a unix listener
898
+ (socket.io clients dial TCP only — construction error); Bun's own WebSocket
899
+ client cannot dial a unix path either. Unix listeners are Bun-only: `srvx`
900
+ resolves a numeric port unconditionally, so `stitchkit/node` does not offer
901
+ `unix` (no half-support).
902
+
867
903
  ### Managed shutdown
868
904
 
869
905
  ```ts
@@ -1678,6 +1714,7 @@ without repeating transport configuration.
1678
1714
  | `suppressUnauthorizedFor` | `[]` | exact contract-derived operation matchers whose expected 401 does not emit `unauthorized` |
1679
1715
  | `parseError` | built-in | map an error body to `{ code, message, details, hint }` |
1680
1716
  | `trace` | `false` | emit a W3C `traceparent` header on every request |
1717
+ | `unix` | — | dial a unix domain socket instead of TCP (Bun only, see below) |
1681
1718
 
1682
1719
  `headers` as a function is the hook for runtime tokens — a bearer token or any
1683
1720
  short-lived credential — re-evaluated on every request.
@@ -1691,6 +1728,26 @@ compile the prefix structure without a concrete tenant id. Matching is exact by
1691
1728
  path segments, including params and trailing wildcards — a shared prefix never
1692
1729
  suppresses a neighbouring protected endpoint.
1693
1730
 
1731
+ ### Unix domain sockets
1732
+
1733
+ The same typed client dials a local daemon's socket file
1734
+ ([server side](server.md#local-daemon-over-a-unix-socket)):
1735
+
1736
+ ```ts
1737
+ const http = createHttpClient({
1738
+ baseUrl: 'http://localhost', // required prefix source; its host is ignored
1739
+ unix: '/run/my-daemon.sock',
1740
+ })
1741
+ const daemon = createClient(daemonContract, http)
1742
+ ```
1743
+
1744
+ `baseUrl` stays required — it supplies the path prefix and the `Host` header,
1745
+ while the connection itself goes through the socket file. Bun runtime only:
1746
+ other runtimes ignore the option and dial `baseUrl` over TCP (Node's fetch
1747
+ would need an undici dispatcher — out of scope). A missing socket file
1748
+ surfaces as a normal `ApiError` and is not retried (transport retry stays
1749
+ connection-refused-only).
1750
+
1694
1751
  `trace: true` mints a fresh root trace per request. The stitchkit server
1695
1752
  [continues an inbound `traceparent`](./observability.md#trace-context), so the
1696
1753
  browser call, the HTTP handler and every nested tool call share one trace id
@@ -3286,8 +3343,11 @@ export function publishExampleNote(realtime: ExampleRealtimePublisher): void {
3286
3343
  | `beginShutdown()` / `connections()` | lifecycle surface consumed by the managed server |
3287
3344
 
3288
3345
  `SocketIOServerConfig` also takes `path`, `transports`, `pingTimeout`,
3289
- `pingInterval` and a runtime-neutral `allowRequest(Request)` handshake policy.
3290
- The policy is composed with managed-shutdown admission on both Bun and Node.
3346
+ `pingInterval`, a runtime-neutral `allowRequest(Request)` handshake policy and
3347
+ the typed **`handshake`** identity gate (schema + verify
3348
+ [typed `socket.data`](#handshake-auth--cookie-or-token)). `allowRequest` is the
3349
+ transport gate (composed with managed-shutdown admission on both Bun and Node);
3350
+ `handshake` is the identity gate that runs after it.
3291
3351
  For anything else socket.io's `ServerOptions` exposes, use the
3292
3352
  typed **`serverOptions`** passthrough — most often `maxHttpBufferSize` to lift the
3293
3353
  1 MB default for large emits:
@@ -3337,7 +3397,40 @@ subscribe once; reconnection is the wrapper's problem, not yours.
3337
3397
 
3338
3398
  `SocketIOClientConfig` takes `url`, `path`, `withCredentials` (cookies on the
3339
3399
  handshake — default `true`), `auth`, `query`, `extraHeaders`, `transports`,
3340
- `reconnectionAttempts`, `reconnectionDelay` and `retain` (below).
3400
+ `reconnectionAttempts`, `reconnectionDelay`, `retain` (below), `onConnectError`
3401
+ (handshake/connection failures — see the auth section) and `onDroppedEmit`
3402
+ (observability for emits dropped while disconnected — see below).
3403
+
3404
+ ### Honest emit — what happens while disconnected
3405
+
3406
+ `emit` has exactly three outcomes, in order:
3407
+
3408
+ 1. **A local contract violation throws** (validated `RealtimeClient` only) —
3409
+ validation runs before the connection guard, even while disconnected.
3410
+ 2. **Disconnected → the emit is dropped**: `emit` returns `false` and the
3411
+ `onDroppedEmit` hook fires with the event name and wire arguments. This
3412
+ includes the short window right after `connect()` while the socket.io peer
3413
+ is still loading.
3414
+ 3. **Otherwise `emit` returns `true`** — handed to the transport, which is
3415
+ *not* a delivery guarantee.
3416
+
3417
+ The default is deliberately a drop, not a buffer: after a reconnect, durable
3418
+ subscriptions and [sticky events](#sticky-events) replay current state
3419
+ deterministically, instead of the server receiving an unordered backlog of
3420
+ stale emits. What changed is that the drop is now observable — assert it
3421
+ per-call (`if (!client.emit(...)) …`) or centrally:
3422
+
3423
+ ```ts
3424
+ const client = createSocketIOClient({
3425
+ url,
3426
+ onDroppedEmit: ({ event }) => metrics.count('realtime.dropped_emit', { event }),
3427
+ })
3428
+ ```
3429
+
3430
+ Server-side emits (`realtime.emit`, `to(room).emit`, `connection.events.emit`)
3431
+ always return `true` — a broadcast into an empty room is not a drop, and the
3432
+ server has no disconnected state of this kind. For state that must survive
3433
+ gaps, see [durability](#durability--idempotent--replay).
3341
3434
 
3342
3435
  ### Sticky events
3343
3436
 
@@ -3392,32 +3485,71 @@ const socket = createRealtimeClient(realtimeContract, {
3392
3485
  ```
3393
3486
 
3394
3487
  ```ts
3395
- // server — the gate is your logic, on socket.handshake.auth
3488
+ // server — the typed identity gate: Zod-validate handshake.auth, verify,
3489
+ // and the result lands in socket.data — typed all the way to onConnection.
3396
3490
  import { verifyJwt } from 'stitchkit/server'
3397
3491
 
3398
- socket.io.use(async (s, next) => {
3399
- const token = s.handshake.auth.token
3400
- if (typeof token !== 'string') return next(new Error('unauthorized'))
3401
- try {
3402
- s.data.user = await verifyJwt(token, secret)
3403
- next()
3404
- } catch {
3405
- next(new Error('unauthorized'))
3406
- }
3492
+ const socket = await createSocketIOServer({
3493
+ cors: { origin: 'https://app.example.com' },
3494
+ handshake: {
3495
+ schema: z.object({ token: z.string() }),
3496
+ verify: async ({ token }) => ({ user: await verifyJwt(token, secret) }),
3497
+ },
3498
+ })
3499
+
3500
+ const realtime = bindRealtimeServer(realtimeContract, socket)
3501
+ realtime.onConnection(({ raw }) => {
3502
+ raw.data.user // typed — no String(...) coercions, no casts
3407
3503
  })
3408
3504
  ```
3409
3505
 
3506
+ `verify` may be async (the wrapper runs it inside a settled promise chain —
3507
+ raw async `io.use` middleware would leak an unhandled rejection); throwing or
3508
+ returning `null` rejects the handshake **before** the connection handler and
3509
+ before any event validation. A thrown error's raw message is logged
3510
+ server-side and never reaches the unauthenticated peer — the wire always
3511
+ carries the generic `handshake rejected` (the same never-leak policy as the
3512
+ HTTP error normalizer). The schema itself must be synchronous (no async
3513
+ refine/transform). Omit `verify` and the schema output itself is the
3514
+ identity. The gate registers as the **first** middleware, so `socket.io.use(…)`
3515
+ middlewares you add afterwards see the typed `socket.data` already in place.
3516
+ Raw `io.use` remains available for anything beyond identity.
3517
+
3518
+ Type inference works on calls without explicit event generics (the
3519
+ `bindRealtimeServer` lane above). With explicit generics TypeScript cannot
3520
+ partially infer — pass the identity types too:
3521
+ `createSocketIOServer<S, C, z.infer<typeof schema>, Identity>`.
3522
+
3410
3523
  A static object (`auth: { token }`) works too, but only the function form
3411
3524
  re-reads on reconnect — prefer it for rotating tokens. `query` adds handshake
3412
- URL params (`socket.handshake.query`); `extraHeaders` adds handshake headers,
3413
- but in a browser those apply to the **polling** transport only (a WebSocket
3414
- upgrade cannot set request headers) for browser WebSocket auth use `auth`.
3415
- If an auth producer throws or rejects, the wrapper sends an empty auth object so
3416
- the server gate can reject it instead of leaving the handshake waiting forever.
3525
+ URL params (`socket.handshake.query`) note its values are **strings** on the
3526
+ wire, which is why the schema gate reads `auth`; `extraHeaders` adds handshake
3527
+ headers, but in a browser those apply to the **polling** transport only (a
3528
+ WebSocket upgrade cannot set request headers) for browser WebSocket auth use
3529
+ `auth`.
3530
+
3531
+ **A gate rejection is terminal for the client.** Unlike a transport-level
3532
+ failure (which socket.io retries indefinitely), a middleware/handshake-gate
3533
+ rejection stops the client: socket.io destroys its retry path and will not
3534
+ reconnect on its own. The stitchkit client surfaces this through
3535
+ **`onConnectError`** with `terminal: true` (and `data.code ===
3536
+ 'handshake_rejected'` for the built-in gate) and resets its connection intent —
3537
+ so recovery is explicit: rotate the credential, call `connect()`, and the
3538
+ function-form `auth` is re-read:
3539
+
3540
+ ```ts
3541
+ const client = createRealtimeClient(realtimeContract, {
3542
+ url: 'https://api.example.com',
3543
+ auth: () => ({ token: getAccessToken() }),
3544
+ onConnectError: ({ terminal }) => {
3545
+ if (terminal) refreshTokenThen(() => client.connect())
3546
+ },
3547
+ })
3548
+ ```
3417
3549
 
3418
- On a hard auth failure Socket.IO emits `connect_error` and keeps retrying
3419
- (`reconnectionAttempts` defaults to `Infinity`) set a finite value if a
3420
- rejected token should stop hammering the server.
3550
+ If an auth producer throws or rejects, the wrapper sends an empty auth object —
3551
+ the server gate then rejects it visibly (via `onConnectError`) instead of
3552
+ leaving the handshake waiting forever.
3421
3553
 
3422
3554
  ## Cache bridge
3423
3555
 
@@ -5277,6 +5409,28 @@ current one *up to* your target, and apply each snippet.
5277
5409
  runtime): bootstrap the server, one HTTP request, and any feature you rely on
5278
5410
  (Socket.IO connect, an MCP tool call, a multipart upload, …).
5279
5411
 
5412
+ ## Released migration: 0.53.0
5413
+
5414
+ ### Realtime `emit` returns `boolean` instead of `void`
5415
+
5416
+ Every **call site** compiles and behaves exactly as before — the return value
5417
+ is new information (`true` = accepted by the transport, `false` = dropped
5418
+ while the browser client was disconnected), not a behavior change. What breaks
5419
+ is **implementing** the interfaces: a test mock or app-side adapter of
5420
+ `SocketIOClient` / `RealtimeClient` / `ValidatedRealtimeSocket` /
5421
+ `RealtimeServer` whose `emit` returns `void` no longer typechecks.
5422
+
5423
+ ```ts
5424
+ // before — a void mock satisfied the interface
5425
+ const mock: Pick<RealtimeClient<S, C>, 'emit'> = { emit: () => {} }
5426
+ // after — report acceptance (true is what a live server-side emit reports)
5427
+ const mock: Pick<RealtimeClient<S, C>, 'emit'> = { emit: () => true }
5428
+ ```
5429
+
5430
+ While migrating, consider replacing hand-rolled `if (client.connected)` guards
5431
+ with the new honest surface: check `client.emit(...) === false`, or observe
5432
+ drops centrally with `onDroppedEmit`.
5433
+
5280
5434
  ## Released migration: 0.50.0
5281
5435
 
5282
5436
  ### The factory's scope union now covers per-endpoint overrides
@@ -6208,7 +6362,7 @@ The browser-and-server entrypoint. Re-exports everything from
6208
6362
  | `ApiError` | class | a non-2xx response, with `code` / `status` / `details` / `hint` and optional readonly `traceId` from `x-request-id` |
6209
6363
  | `HttpClient` | _type_ | the transport interface `createClient` builds on |
6210
6364
  | `ConfiguredHttpClient` | _type_ | a framework-created `HttpClient` carrying its readonly `baseUrl` for URL builders |
6211
- | `HttpClientConfig` | _type_ | config for `createHttpClient`; retry `limit` counts retries after the initial attempt (default 2 = at most 3 GET attempts), with `statusCodes: []` by default — [details](../guide/client.md#createhttpclient) |
6365
+ | `HttpClientConfig` | _type_ | config for `createHttpClient`; retry `limit` counts retries after the initial attempt (default 2 = at most 3 GET attempts), with `statusCodes: []` by default; `unix` dials a unix domain socket (Bun only) — [details](../guide/client.md#createhttpclient) |
6212
6366
  | `UnauthorizedMatcher` | _type_ | exact `(pathname) => boolean` policy accepted by `suppressUnauthorizedFor` |
6213
6367
  | `RequestOptions` | _type_ | per-call options — params, timeout, response type |
6214
6368
  | `HeaderProvider` | _type_ | static or per-request headers |
@@ -6224,8 +6378,8 @@ The browser-and-server entrypoint. Re-exports everything from
6224
6378
  | `createRealtimeClient` | function | inferred, runtime-validated Socket.IO client — [guide](../guide/realtime.md#client--createrealtimeclient) |
6225
6379
  | `createRetainedTopics` | function | retained last-value store for sticky events — [guide](../guide/realtime.md#sticky-events) |
6226
6380
  | `parseSSE` | function | parse an SSE `Response` into an async generator — [guide](../guide/client.md#sse) |
6227
- | `SocketIOClient` | _type_ | the client handle |
6228
- | `SocketIOClientConfig` | _type_ | config for `createSocketIOClient` (incl. `retain`) |
6381
+ | `SocketIOClient` | _type_ | the client handle; `emit` returns `false` for an emit dropped while disconnected |
6382
+ | `SocketIOClientConfig` | _type_ | config for `createSocketIOClient` (incl. `retain`, `onConnectError`, `onDroppedEmit`) |
6229
6383
  | `SocketEventMap` | _type_ | the shape of an event map |
6230
6384
  | `RealtimeClient` | _type_ | validated client inferred from a realtime contract |
6231
6385
  | `RealtimeClientOptions` | _type_ | transport options and the rejected-event hook for `createRealtimeClient` |
@@ -6239,7 +6393,7 @@ The browser-and-server entrypoint. Re-exports everything from
6239
6393
  | `RealtimeRejectDirection` | _type_ | server/client inbound/outbound rejection direction |
6240
6394
  | `RealtimeRejectedEvent` | _type_ | structured rejected event with event, direction, phase, reason and fault |
6241
6395
  | `RealtimeRejectedEventHook` | _type_ | sync/async observer for structured realtime rejections |
6242
- | `ValidatedRealtimeSocket` | _type_ | runtime-validating `on`/`emit` surface inferred from registries |
6396
+ | `ValidatedRealtimeSocket` | _type_ | runtime-validating `on`/`emit` surface inferred from registries; `emit` returns "accepted by the transport" (`false` only for a client-side disconnected drop) |
6243
6397
  | `RetainedTopics` | _type_ | the `createRetainedTopics` handle |
6244
6398
  | `ParseSSEOptions` | _type_ | options for `parseSSE` |
6245
6399
 
@@ -6393,7 +6547,8 @@ Also re-exports the error helpers from `stitchkit/contract`.
6393
6547
  | `ZodIssueSummary` | _type_ | one structured validation issue (`{ path, code, message }`) |
6394
6548
  | `parseBody` | function | parse + Zod-validate a JSON body → `data` or `null` (no throw) |
6395
6549
  | `HandlerConfig` | _type_ | config for `createHandler`, including optional `maxJsonBodyBytes`; bound to `BunServer` on this entrypoint |
6396
- | `BunServerConfig` | _type_ | config for `createServer` (Bun) |
6550
+ | `BunServerConfig` | _type_ | config for `createServer` (Bun); `unix` listens on a unix domain socket (mutually exclusive with `port`/`hostname`) — [details](../guide/server.md#local-daemon-over-a-unix-socket) |
6551
+ | `UnixListenConfig` | _type_ | unix listener — a socket path, or `{ path, mode }` to tighten the file mode after listen |
6397
6552
  | `BunServerHandle` | _type_ | managed Bun handle (`url`, `port`, `runtime`, `status`, `shutdown`) |
6398
6553
  | `ManagedServerHandle` | _type_ | shared lifecycle shape generic over the runtime escape hatch |
6399
6554
  | `ShutdownOptionsSchema` / `ShutdownOptions` | schema / _type_ | one graceful budget, bounded forced-completion timeout, retry hint and optional external abort signal |
@@ -6469,9 +6624,10 @@ Also re-exports the error helpers from `stitchkit/contract`.
6469
6624
  | `bindRealtimeServer` | function | inferred, runtime-validated connection and broadcast boundary |
6470
6625
  | `RealtimeServer` | _type_ | validated broadcast and connection API inferred from a realtime contract |
6471
6626
  | `RealtimeServerConnection` | _type_ | one validated connection with raw socket access for auth and rooms |
6472
- | `RealtimeServerHandle` | _type_ | minimal Socket.IO server handle accepted by `bindRealtimeServer` |
6627
+ | `RealtimeServerHandle` | _type_ | minimal Socket.IO server handle accepted by `bindRealtimeServer`; carries the handshake identity type through to `connection.raw.data` |
6473
6628
  | `SocketIORequestPolicy` | _type_ | runtime-neutral async-capable Web `Request` handshake admission policy |
6474
- | `SocketIOServerConfig` | _type_ | config for `createSocketIOServer` |
6629
+ | `SocketIOServerConfig` | _type_ | config for `createSocketIOServer`; `handshake` is the typed identity gate — [guide](../guide/realtime.md#handshake-auth--cookie-or-token) |
6630
+ | `SocketIOHandshakeConfig` | _type_ | the `handshake` gate — Zod `schema` over `handshake.auth` plus optional async `verify`; the result lands typed in `socket.data` |
6475
6631
  | `SocketIOServerHandle` | _type_ | typed Socket.IO server plus Bun mount fields and idempotent lifecycle |
6476
6632
  | `SocketIOServerLifecycle` | _type_ | non-generic Bun mount/shutdown portion accepted by `createServer` |
6477
6633
  | `composeWebSocketHandlers` | function | compose one Bun `websocket` from N lanes — a raw binary lane beside Socket.IO ([guide](../guide/realtime.md#raw-binary-lane-bun)) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.52.0",
3
+ "version": "0.53.1",
4
4
  "description": "Contract-first backend framework — one defineContract() into an HTTP API, MCP tools, AI-agent tools and a typed client. Bun and Node.",
5
5
  "keywords": [
6
6
  "bun",