stitchkit 0.49.1 → 0.50.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/dist/contract/errors-factory.d.ts +27 -6
- package/dist/contract/errors-factory.d.ts.map +1 -1
- package/dist/contract/factory.d.ts +15 -2
- package/dist/contract/factory.d.ts.map +1 -1
- package/dist/contract/index.d.ts +1 -1
- package/dist/contract/index.d.ts.map +1 -1
- package/dist/contract/index.js +1 -1
- package/dist/{index-z992nfbx.js → index-a4qmxybq.js} +157 -4
- package/dist/{index-45dz4m51.js → index-trz4ate5.js} +6 -2
- package/dist/index.js +1 -1
- package/dist/node.d.ts +3 -2
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +9 -1
- package/dist/server/implement.d.ts +72 -6
- package/dist/server/implement.d.ts.map +1 -1
- package/dist/server/index.d.ts +3 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +9 -1
- package/dist/server/process-signals.d.ts +117 -0
- package/dist/server/process-signals.d.ts.map +1 -0
- package/dist/server/types.d.ts +38 -0
- package/dist/server/types.d.ts.map +1 -1
- package/llms-full.txt +266 -41
- package/package.json +1 -1
package/llms-full.txt
CHANGED
|
@@ -617,6 +617,21 @@ the exact config without `NonNullable` or another wrapper. Plain `ContractDef`
|
|
|
617
617
|
keeps optional scope because ordinary `defineContract` still supports its
|
|
618
618
|
default-public overload.
|
|
619
619
|
|
|
620
|
+
The union covers **per-endpoint overrides too**, not just the contract's scope —
|
|
621
|
+
that is where typos are densest, and one there used to compile and then fail
|
|
622
|
+
closed at request time (`[stitchkit] auth: no rule for scope "…"`):
|
|
623
|
+
|
|
624
|
+
```ts
|
|
625
|
+
defineContract({ prefix: 'posts', scope: 'user' }, {
|
|
626
|
+
drop: { method: 'DELETE', path: '/:id', desc: 'Drop', scope: 'admn', … },
|
|
627
|
+
// ^ compile error, and TypeScript
|
|
628
|
+
// suggests 'admin'
|
|
629
|
+
})
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
Contracts built with plain `defineContract` are unaffected — their endpoint
|
|
633
|
+
`scope` stays a free string.
|
|
634
|
+
|
|
620
635
|
## One source of truth
|
|
621
636
|
|
|
622
637
|
A contract is plain data — no classes, no decorators, no codegen. It is imported
|
|
@@ -686,6 +701,74 @@ export const implement = createImplement<AppContext>()
|
|
|
686
701
|
// every implement() call now has ctx.user typed
|
|
687
702
|
```
|
|
688
703
|
|
|
704
|
+
### Per-scope handler context — `createScopedImplement`
|
|
705
|
+
|
|
706
|
+
`createImplement<Ctx>()` gives every handler the **same** context type. In an app
|
|
707
|
+
where each scope guarantees different injected fields, that single type is a
|
|
708
|
+
superset: a `public` handler is told it has a `userId` the runtime never injects
|
|
709
|
+
there. `createScopedImplement` types each handler by its endpoint's **effective**
|
|
710
|
+
scope instead — the endpoint's own `scope`, else the contract's:
|
|
711
|
+
|
|
712
|
+
```ts
|
|
713
|
+
import { createScopedImplement } from 'stitchkit/server'
|
|
714
|
+
|
|
715
|
+
export const implementFor = createScopedImplement<{
|
|
716
|
+
public: object // no extra fields
|
|
717
|
+
user: { userId: string }
|
|
718
|
+
admin: { userId: string; isAdmin: boolean }
|
|
719
|
+
}>()
|
|
720
|
+
|
|
721
|
+
implementFor(postsContract, {
|
|
722
|
+
list: (ctx) => listPosts(ctx.userId), // contract scope 'user'
|
|
723
|
+
purge: (ctx) => (ctx.isAdmin ? purge() : []), // endpoint scope 'admin'
|
|
724
|
+
ping: () => ({ ok: true }), // endpoint scope 'public'
|
|
725
|
+
})
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
The map is **type-only** — scope fields are types, so there is no runtime
|
|
729
|
+
argument and no `{} as UserFields` at the call site. `'public'` must be a key:
|
|
730
|
+
a contract without a `scope` is `'public'`. A scope outside the map is a compile
|
|
731
|
+
error on that endpoint, naming the scope.
|
|
732
|
+
|
|
733
|
+
Three boundaries worth knowing:
|
|
734
|
+
|
|
735
|
+
- A field of another scope is not a compile error on access — `RuntimeContext`
|
|
736
|
+
keeps its `[key: string]: unknown` index signature (transports write through
|
|
737
|
+
it). It degrades to `unknown`, so it can no longer pose as a `string`; using it
|
|
738
|
+
in a typed position fails.
|
|
739
|
+
- The map states what **your** `beforeHandle` / `createAuthHook.inject` puts on
|
|
740
|
+
the context. The framework does not verify that claim (→ ADR 0075).
|
|
741
|
+
- An endpoint hoisted out of the contract literal widens its `scope` to `string`,
|
|
742
|
+
which is no key of any map, and lands in the error branch. An endpoint whose
|
|
743
|
+
`scope` is added by a conditional spread is optional, so it resolves to **both**
|
|
744
|
+
scopes and is typed against what they guarantee in common. Write endpoints
|
|
745
|
+
inline for the precise type.
|
|
746
|
+
- Scope keys are string literals. A map declared as an index signature
|
|
747
|
+
(`Record<string, …>`) makes every scope valid and disables the check.
|
|
748
|
+
|
|
749
|
+
The same map has a registry form and a streaming form:
|
|
750
|
+
|
|
751
|
+
```ts
|
|
752
|
+
// one contract registry, handlers still typed per endpoint scope
|
|
753
|
+
const implementAll = createScopedImplementRegistry<Scopes>()
|
|
754
|
+
export const services = implementAll(apiContractRegistry, { posts, users })
|
|
755
|
+
|
|
756
|
+
// a streaming multipart handler that reads its scope's fields. The endpoint
|
|
757
|
+
// must declare its own `scope`, and only that literal is accepted: an endpoint
|
|
758
|
+
// inheriting the contract's scope cannot be verified from here, and guessing it
|
|
759
|
+
// would rebuild the superset this factory removes.
|
|
760
|
+
implementFor(mediaContract, {
|
|
761
|
+
upload: implementFor.stream('admin', mediaContract.endpoints.upload, {
|
|
762
|
+
files: { file: receiveFile },
|
|
763
|
+
handler: ({ files, userId }) => store(files.file, userId),
|
|
764
|
+
}),
|
|
765
|
+
})
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
Outside a scoped app, `createMultipartStream<Ctx>()` does for streaming handlers
|
|
769
|
+
what `createImplement<Ctx>()` does for ordinary ones. Plain
|
|
770
|
+
`defineMultipartStream` still gives the loose `RuntimeContext`.
|
|
771
|
+
|
|
689
772
|
### `implementRegistry` — one backend registry
|
|
690
773
|
|
|
691
774
|
When contracts already live in one literal registry, bind the backend from that
|
|
@@ -974,9 +1057,11 @@ createServer({
|
|
|
974
1057
|
|
|
975
1058
|
A prefix may carry `:param` segments (they land on the context exactly as above).
|
|
976
1059
|
Services listed under explicit `groups` are unaffected — the group prefix wins.
|
|
977
|
-
Scope stays a free string
|
|
978
|
-
(
|
|
979
|
-
|
|
1060
|
+
Scope stays a free string (unless the contract is authored through
|
|
1061
|
+
`createContractFactory<Scope>()`); the core attaches no meaning beyond this lookup
|
|
1062
|
+
(→ ADR 0024). When each scope needs a different handler-context shape, use
|
|
1063
|
+
[`createScopedImplement`](#per-scope-handler-context--createscopedimplement)
|
|
1064
|
+
rather than a single superset context.
|
|
980
1065
|
|
|
981
1066
|
## Lifecycle hooks
|
|
982
1067
|
|
|
@@ -3578,7 +3663,9 @@ the scope vocabulary are yours.
|
|
|
3578
3663
|
## Scopes
|
|
3579
3664
|
|
|
3580
3665
|
An endpoint declares a `scope` — a free string. The contract `meta.scope` is the
|
|
3581
|
-
default for endpoints that declare none.
|
|
3666
|
+
default for endpoints that declare none. (Authored through
|
|
3667
|
+
[`createContractFactory<Scope>()`](./contracts.md#scope), both the contract's
|
|
3668
|
+
scope and any endpoint override are held to your own union instead.)
|
|
3582
3669
|
|
|
3583
3670
|
```ts
|
|
3584
3671
|
export const posts = defineContract({ prefix: 'posts', scope: 'user' }, {
|
|
@@ -3870,36 +3957,57 @@ onError: (ctx, err) => {
|
|
|
3870
3957
|
|
|
3871
3958
|
## Domain errors — `defineErrors`
|
|
3872
3959
|
|
|
3873
|
-
Declare each domain code, HTTP status and optional
|
|
3874
|
-
one immutable registry. The generated functions
|
|
3875
|
-
`AppError` instances; ordinary `throw` remains explicit
|
|
3960
|
+
Declare each domain code, HTTP status, default message and optional
|
|
3961
|
+
structured-details schema in one immutable registry. The generated functions
|
|
3962
|
+
construct typed branded `AppError` instances; ordinary `throw` remains explicit
|
|
3963
|
+
at the call site:
|
|
3876
3964
|
|
|
3877
3965
|
```ts
|
|
3878
3966
|
import { z } from 'zod'
|
|
3879
3967
|
|
|
3880
3968
|
export const { errors, codes, definitions, isCode } = defineErrors({
|
|
3881
|
-
SESSION_NOT_FOUND: { status: 404 },
|
|
3969
|
+
SESSION_NOT_FOUND: { status: 404, message: 'No such session' },
|
|
3882
3970
|
QUOTA_EXCEEDED: {
|
|
3883
3971
|
status: 429,
|
|
3972
|
+
message: 'Monthly quota exhausted',
|
|
3884
3973
|
details: z.object({ retryAfterSeconds: z.number().int().positive() }),
|
|
3885
3974
|
},
|
|
3886
3975
|
})
|
|
3887
3976
|
|
|
3888
|
-
throw errors.SESSION_NOT_FOUND(
|
|
3977
|
+
throw errors.SESSION_NOT_FOUND() // uses the declared message
|
|
3889
3978
|
throw errors.QUOTA_EXCEEDED({
|
|
3890
|
-
message: 'Try later',
|
|
3979
|
+
message: 'Try later', // overrides it here only
|
|
3891
3980
|
details: { retryAfterSeconds: 30 },
|
|
3892
3981
|
hint: 'Wait for the current window to expire',
|
|
3893
3982
|
})
|
|
3894
3983
|
|
|
3895
3984
|
// Construction without throwing is useful for composition or inspection.
|
|
3896
3985
|
const error = errors.QUOTA_EXCEEDED({ details: { retryAfterSeconds: 30 } })
|
|
3897
|
-
definitions.QUOTA_EXCEEDED.status
|
|
3986
|
+
definitions.QUOTA_EXCEEDED.status // 429 — same source, no copied status map
|
|
3987
|
+
definitions[code].message // the declared text, by a `code` variable
|
|
3898
3988
|
|
|
3899
3989
|
// client — match the code, never a magic string
|
|
3900
3990
|
if (err instanceof ApiError && err.code === codes.SESSION_NOT_FOUND) { … }
|
|
3901
3991
|
```
|
|
3902
3992
|
|
|
3993
|
+
`message` is optional: declare it once instead of repeating the sentence at every
|
|
3994
|
+
`throw`, and a per-call `message` still wins. A code without one keeps the
|
|
3995
|
+
existing fallback — `AppError.message` is the code itself. An empty declared
|
|
3996
|
+
message is rejected when the registry is declared, not when the error is thrown.
|
|
3997
|
+
|
|
3998
|
+
**The tool envelope has no `message` field**, and what the model sees depends on
|
|
3999
|
+
whether the code declares `details`:
|
|
4000
|
+
|
|
4001
|
+
| | HTTP / typed client | MCP / agent / CLI |
|
|
4002
|
+
|---|---|---|
|
|
4003
|
+
| code **with** a `details` schema | the declared message | no text at all — only `details` and `hint` |
|
|
4004
|
+
| code **without** one | the declared message | the same text, delivered as `details.message` |
|
|
4005
|
+
|
|
4006
|
+
The model-facing envelope is `{ error, details?, _hint? }`; for a code with no
|
|
4007
|
+
details schema the framework fills `details` with `{ message }`, so declaring a
|
|
4008
|
+
message changes what the model reads there — it used to be the code itself. Put
|
|
4009
|
+
anything the model must reliably read in `details` or `hint`, not in `message`.
|
|
4010
|
+
|
|
3903
4011
|
With no `details` schema, the options object forbids `details`. A required
|
|
3904
4012
|
`z.object` makes `details` required; `z.object(...).optional()` makes it
|
|
3905
4013
|
optional. Supplied details are parsed when the error is constructed, before any
|
|
@@ -4612,8 +4720,9 @@ expect(res.status).toBe(404)
|
|
|
4612
4720
|
expect(await res.json()).toEqual({ error: { code: 'NOT_FOUND', message: 'Note not found' } })
|
|
4613
4721
|
```
|
|
4614
4722
|
|
|
4615
|
-
|
|
4616
|
-
|
|
4723
|
+
The suite in `packages/core/tests` is the working reference for testing each
|
|
4724
|
+
piece. Its size changes with the public surface, so the guide does not pin a
|
|
4725
|
+
count that can drift independently from the test runner.
|
|
4617
4726
|
|
|
4618
4727
|
## Deployment
|
|
4619
4728
|
|
|
@@ -4636,34 +4745,58 @@ createServer({
|
|
|
4636
4745
|
})
|
|
4637
4746
|
```
|
|
4638
4747
|
|
|
4639
|
-
|
|
4748
|
+
### Process signals — `bindProcessSignals`
|
|
4749
|
+
|
|
4750
|
+
Keep the managed handle and wire process policy explicitly. The framework
|
|
4751
|
+
registers no signal listener on its own; `bindProcessSignals` is that explicit
|
|
4752
|
+
step, and it owns the state machine:
|
|
4640
4753
|
|
|
4641
4754
|
```ts
|
|
4755
|
+
import { bindProcessSignals } from 'stitchkit/server'
|
|
4756
|
+
|
|
4642
4757
|
const server = createServer({ services, socket })
|
|
4643
|
-
const force = new AbortController()
|
|
4644
|
-
let closing: Promise<void> | undefined
|
|
4645
4758
|
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
}
|
|
4651
|
-
closing = server.shutdown({
|
|
4652
|
-
gracePeriodMs: 30_000,
|
|
4653
|
-
forceTimeoutMs: 5_000,
|
|
4654
|
-
signal: force.signal,
|
|
4655
|
-
}).then(async result => {
|
|
4759
|
+
bindProcessSignals(server, {
|
|
4760
|
+
shutdown: { gracePeriodMs: 30_000, forceTimeoutMs: 5_000 },
|
|
4761
|
+
onShutdown: () => stopSchedulers(), // before the drain starts
|
|
4762
|
+
onComplete: async (result) => { // after it finishes
|
|
4656
4763
|
await mcp.close()
|
|
4657
4764
|
await prisma.$disconnect()
|
|
4658
|
-
|
|
4659
|
-
}
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4765
|
+
process.exitCode = result.outcome === 'clean' ? 0 : 1
|
|
4766
|
+
},
|
|
4767
|
+
onError: (error) => {
|
|
4768
|
+
console.error(error)
|
|
4769
|
+
process.exitCode = 1
|
|
4770
|
+
},
|
|
4771
|
+
})
|
|
4665
4772
|
```
|
|
4666
4773
|
|
|
4774
|
+
- The first signal runs `onShutdown`, then one `shutdown()`.
|
|
4775
|
+
- A later signal **forces that same chain** — never a second shutdown. Signals
|
|
4776
|
+
delivered in the same turn as the first (a supervisor sending `SIGINT` and
|
|
4777
|
+
`SIGTERM` together) are not counted, so the grace period is not collapsed to
|
|
4778
|
+
zero.
|
|
4779
|
+
- The signal after the force — or one arriving while `onComplete` still runs —
|
|
4780
|
+
re-delivers the signal so its default disposition applies, letting an operator
|
|
4781
|
+
kill a process stuck on some other resource. This works only while nothing else
|
|
4782
|
+
in the process listens for that signal; if something does, the signal is
|
|
4783
|
+
swallowed and `onEscalationBlocked` fires instead. The framework will not call
|
|
4784
|
+
`process.exit` on your behalf.
|
|
4785
|
+
- `shutdown` budgets are validated when you bind, not when the signal arrives.
|
|
4786
|
+
`signal` is not accepted there — the binding owns it, and that is what makes a
|
|
4787
|
+
later signal force the first chain.
|
|
4788
|
+
- `onError(phase, error)` separates the phases. A failing `onShutdown`
|
|
4789
|
+
(`'prepare'`) is reported and the shutdown **still runs** — a failed
|
|
4790
|
+
preparation must not leave the server listening. A failing `onComplete`
|
|
4791
|
+
(`'complete'`) leaves `promise` resolved, because the transport did shut down.
|
|
4792
|
+
Only a failing `shutdown()` (`'shutdown'`) rejects it.
|
|
4793
|
+
|
|
4794
|
+
`close()` removes the listeners. Closing a binding that never received a signal
|
|
4795
|
+
resolves `promise` with `undefined`, so an awaiting application does not hang;
|
|
4796
|
+
closing one whose chain is already running leaves that chain to finish and keeps
|
|
4797
|
+
the handle claimed, since a second binding could not force it. `promise` is
|
|
4798
|
+
already observed internally, so ignoring it never raises an `unhandledRejection`.
|
|
4799
|
+
|
|
4667
4800
|
The server owns HTTP/Socket.IO transport resources. MCP, databases, queues and
|
|
4668
4801
|
domain run-state remain application resources and close explicitly after server
|
|
4669
4802
|
drain. Do not call `runtime.stop()` or `socket.io.close()` in parallel with
|
|
@@ -4796,10 +4929,20 @@ createServer({
|
|
|
4796
4929
|
Handlers read `ctx.tenantId` (a raw `string` — narrow it). To get it typed inside
|
|
4797
4930
|
`ctx.params`, add `tenantId` to the endpoint's `params` schema (a `z.strictObject`
|
|
4798
4931
|
that omits it will reject the request). When each scope guarantees different
|
|
4799
|
-
injected fields,
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4932
|
+
injected fields, declare the scope map once with
|
|
4933
|
+
[`createScopedImplement`](./server.md#per-scope-handler-context--createscopedimplement):
|
|
4934
|
+
|
|
4935
|
+
```ts
|
|
4936
|
+
export const implementFor = createScopedImplement<{
|
|
4937
|
+
public: object
|
|
4938
|
+
tenant: { tenantId: string; userId: string }
|
|
4939
|
+
project: { projectId: string; userId: string }
|
|
4940
|
+
}>()
|
|
4941
|
+
```
|
|
4942
|
+
|
|
4943
|
+
Each handler is then typed by its endpoint's effective scope — no superset
|
|
4944
|
+
context that promises a `tenantId` a `public` handler never receives. A contract
|
|
4945
|
+
that mixes scopes across its endpoints needs no splitting.
|
|
4803
4946
|
|
|
4804
4947
|
## 3. Auth — gate the tenant in the path
|
|
4805
4948
|
|
|
@@ -4989,6 +5132,69 @@ current one *up to* your target, and apply each snippet.
|
|
|
4989
5132
|
runtime): bootstrap the server, one HTTP request, and any feature you rely on
|
|
4990
5133
|
(Socket.IO connect, an MCP tool call, a multipart upload, …).
|
|
4991
5134
|
|
|
5135
|
+
## Released migration: 0.50.0
|
|
5136
|
+
|
|
5137
|
+
### The factory's scope union now covers per-endpoint overrides
|
|
5138
|
+
|
|
5139
|
+
`createContractFactory<Scope>()` already required a typed `scope` on the
|
|
5140
|
+
contract. It now holds a per-endpoint `scope` override to the same union. Nothing
|
|
5141
|
+
to migrate unless an override is outside the union — which was always a bug: the
|
|
5142
|
+
scope reached no auth rule, and `createAuthHook` threw
|
|
5143
|
+
`[stitchkit] auth: no rule for scope "…"` on the first request to it.
|
|
5144
|
+
|
|
5145
|
+
```ts
|
|
5146
|
+
const { defineContract } = createContractFactory<'public' | 'user' | 'admin'>()
|
|
5147
|
+
|
|
5148
|
+
defineContract({ prefix: 'posts', scope: 'user' }, {
|
|
5149
|
+
// before: compiled; failed at request time
|
|
5150
|
+
// after: compile error naming the scope (TypeScript even suggests 'admin')
|
|
5151
|
+
purge: { method: 'DELETE', path: '/all', desc: 'Purge', scope: 'admn', output },
|
|
5152
|
+
})
|
|
5153
|
+
```
|
|
5154
|
+
|
|
5155
|
+
Fix the typo, or widen the factory union if the scope is real. Contracts built
|
|
5156
|
+
with plain `defineContract` are unaffected.
|
|
5157
|
+
|
|
5158
|
+
### A declared `defineErrors` message is now used
|
|
5159
|
+
|
|
5160
|
+
`ErrorDefinition` accepts `message`. Nothing to migrate unless a registry already
|
|
5161
|
+
wrote that key: it type-checked before (excess-property checking does not fire
|
|
5162
|
+
through a `const` generic) and was ignored, so the code itself went on the wire.
|
|
5163
|
+
|
|
5164
|
+
```ts
|
|
5165
|
+
const { errors } = defineErrors({ GONE: { status: 410, message: 'Long gone' } })
|
|
5166
|
+
// before: errors.GONE().message === 'GONE'
|
|
5167
|
+
// after: errors.GONE().message === 'Long gone'
|
|
5168
|
+
```
|
|
5169
|
+
|
|
5170
|
+
If a registry carried `message` as a note to the reader rather than as
|
|
5171
|
+
user-facing text, either fix the text or drop the key. A code with no `details`
|
|
5172
|
+
schema also shows that text to a model (its tool `details` is `{ message }`).
|
|
5173
|
+
|
|
5174
|
+
### Optional: adopt `createScopedImplement`
|
|
5175
|
+
|
|
5176
|
+
If the app declares one superset handler context because different scopes inject
|
|
5177
|
+
different fields, replace it with a scope map. This is additive — `createImplement`
|
|
5178
|
+
still works.
|
|
5179
|
+
|
|
5180
|
+
```ts
|
|
5181
|
+
// before — one context for every scope; `ctx.userId` is typed even in a
|
|
5182
|
+
// `public` handler, where the runtime never injects it
|
|
5183
|
+
interface AppContext extends RuntimeContext { userId: string; isAdmin: boolean }
|
|
5184
|
+
export const implement = createImplement<AppContext>()
|
|
5185
|
+
|
|
5186
|
+
// after — each handler typed by its endpoint's effective scope
|
|
5187
|
+
export const implementFor = createScopedImplement<{
|
|
5188
|
+
public: object
|
|
5189
|
+
user: { userId: string }
|
|
5190
|
+
admin: { userId: string; isAdmin: boolean }
|
|
5191
|
+
}>()
|
|
5192
|
+
```
|
|
5193
|
+
|
|
5194
|
+
`'public'` must be a key (a contract with no `scope` is `'public'`). Write
|
|
5195
|
+
endpoints inline in the contract literal: an endpoint hoisted into a variable
|
|
5196
|
+
widens its `scope` to `string` and is reported as undeclared.
|
|
5197
|
+
|
|
4992
5198
|
## Released migration: 0.48.0
|
|
4993
5199
|
|
|
4994
5200
|
### Typed-client request options move to `.withOptions`
|
|
@@ -5918,13 +6124,14 @@ from the root `stitchkit`.
|
|
|
5918
6124
|
| Export | Kind | Summary |
|
|
5919
6125
|
|--------|------|---------|
|
|
5920
6126
|
| `defineContract` | function | declare a contract — [guide](../guide/contracts.md#definecontract) |
|
|
5921
|
-
| `createContractFactory` | function | a `defineContract`
|
|
6127
|
+
| `createContractFactory` | function | a `defineContract` whose scope — on the contract **and** on any endpoint override — is required and held to your union, retaining each concrete literal — [guide](../guide/contracts.md#scope) |
|
|
5922
6128
|
| `ContractFactoryConfig` | _type_ | optional scoped-factory policy, including explicit tool exposure |
|
|
5923
6129
|
| `ContractFactoryToolExposure` | _type_ | `'explicit'` — omitted endpoint exposure materializes as HTTP-only |
|
|
5924
6130
|
| `ExplicitScopedDefineContract` | _type_ | scoped factory authoring with explicit tool opt-in |
|
|
5925
6131
|
| `ExplicitToolExposureEndpoints` | _type_ | endpoint map after missing exposure is materialized as `['HTTP']` |
|
|
6132
|
+
| `FactoryScopedEndpoint` | _type_ | an endpoint authored through the factory — its own `scope` override is held to the same union |
|
|
5926
6133
|
| `ScopedContractDef` | _type_ | a factory-defined contract whose `meta.scope` is the required concrete literal |
|
|
5927
|
-
| `ScopedDefineContract` | _type_ | the `defineContract` `createContractFactory` returns |
|
|
6134
|
+
| `ScopedDefineContract` | _type_ | the `defineContract` `createContractFactory` returns — endpoint `scope` overrides join the union |
|
|
5928
6135
|
| `ALL_TRANSPORTS` | constant | `['HTTP', 'MCP', 'AGENT', 'CLI']` |
|
|
5929
6136
|
| `ContractDef` | _type_ | a defined contract |
|
|
5930
6137
|
| `ContractMeta` | _type_ | a contract's `prefix` + optional `scope` and `meta` (a default every endpoint shallow-merges over) |
|
|
@@ -5974,7 +6181,7 @@ from the root `stitchkit`.
|
|
|
5974
6181
|
| `defineErrors` | function | declare immutable domain error definitions → typed `AppError` constructors, codes and schemas — [guide](../guide/auth-and-errors.md#domain-errors--defineerrors) |
|
|
5975
6182
|
| `DefinedErrors` | _type_ | the `{ errors, codes, definitions, isCode }` handle `defineErrors` returns |
|
|
5976
6183
|
| `DefinedAppError` | _type_ | literal-code error instance with schema-refined details |
|
|
5977
|
-
| `ErrorDefinition` | _type_ | `{ status, details? }` definition for one domain code |
|
|
6184
|
+
| `ErrorDefinition` | _type_ | `{ status, message?, details? }` definition for one domain code |
|
|
5978
6185
|
| `ErrorDefinitions` | _type_ | string-keyed domain error definition registry |
|
|
5979
6186
|
| `ErrorDetailsSchema` | _type_ | required or optional Zod object accepted for structured details |
|
|
5980
6187
|
| `ErrorDetailsOutput` | _type_ | parsed details inferred from one definition |
|
|
@@ -6010,6 +6217,15 @@ Also re-exports the error helpers from `stitchkit/contract`.
|
|
|
6010
6217
|
| `createHandler` | function | the router as a bare `(req) => Response` — [guide](../guide/server.md#createserver) |
|
|
6011
6218
|
| `implement` | function | bind a contract to typed handlers — [guide](../guide/server.md#implement) |
|
|
6012
6219
|
| `createImplement` | function | fix the handler context type once |
|
|
6220
|
+
| `createScopedImplement` | function | fix one scope→context map, then type every handler by its endpoint's effective scope — [guide](../guide/server.md#per-scope-handler-context--createscopedimplement) |
|
|
6221
|
+
| `ScopedHandlers` | _type_ | handler map typed per endpoint by its effective scope |
|
|
6222
|
+
| `ScopeContexts` | _type_ | scope → extra context fields map accepted by `createScopedImplement` |
|
|
6223
|
+
| `EffectiveScope` | _type_ | an endpoint's own `scope`, else its contract's group scope |
|
|
6224
|
+
| `createScopedImplementRegistry` | function | the registry form of `createScopedImplement` — one contract registry, handlers still typed per endpoint scope |
|
|
6225
|
+
| `ScopedRegistryHandlers` | _type_ | scoped handler registry inferred from a contract registry |
|
|
6226
|
+
| `ScopedImplementationRegistry` | _type_ | contract registry whose every group scope is a key of the scope map |
|
|
6227
|
+
| `StreamScope` | _type_ | the scope `createScopedImplement(...).stream` accepts for one endpoint |
|
|
6228
|
+
| `ExactScopedRegistryHandlers` | _type_ | fail-first scoped registry shape that rejects extra registry and endpoint keys |
|
|
6013
6229
|
| `implementRegistry` | function | bind one exact contract registry to one exact backend handler registry |
|
|
6014
6230
|
| `createImplementRegistry` | function | context-typed factory for `implementRegistry` |
|
|
6015
6231
|
| `ImplementationRegistry` | _type_ | flat literal registry of concrete contracts accepted by `implementRegistry` |
|
|
@@ -6124,10 +6340,19 @@ Also re-exports the error helpers from `stitchkit/contract`.
|
|
|
6124
6340
|
| `MultipartResult` | _type_ | what `parseMultipart` returns |
|
|
6125
6341
|
| `parseMultipart` | function | parse a typed buffered/streaming multipart descriptor — [guide](../guide/server.md#multipart) |
|
|
6126
6342
|
| `defineMultipartStream` | function | bind typed streaming file receivers and a final endpoint handler |
|
|
6343
|
+
| `createMultipartStream` | function | fix one handler context type for streaming multipart endpoints |
|
|
6344
|
+
| `MultipartStreamConfig` | _type_ | receivers plus the context-typed handler accepted by the streaming builders |
|
|
6127
6345
|
| `MultipartFileMetadata` | _type_ | field, filename, declared media type and optional declared size |
|
|
6128
6346
|
| `MultipartReceiver` | _type_ | consumer-owned Web-stream storage receiver |
|
|
6129
6347
|
| `MultipartReceiverResult` | _type_ | receiver value plus rollback cleanup |
|
|
6130
6348
|
| `StreamingMultipartImplementation` | _type_ | receiver registry and handler shape inferred by `defineMultipartStream` |
|
|
6349
|
+
| `bindProcessSignals` | function | bind `SIGINT` / `SIGTERM` to one managed `shutdown()` — one chain, force on a later signal, default disposition on the one after — [guide](../guide/testing-and-deployment.md#process-signals--bindprocesssignals) |
|
|
6350
|
+
| `ProcessSignalsOptions` | _type_ | config for `bindProcessSignals` |
|
|
6351
|
+
| `ProcessSignalsBinding` | _type_ | the `{ promise, close }` handle `bindProcessSignals` returns |
|
|
6352
|
+
| `ProcessSignalsErrorPhase` | _type_ | `'prepare' \| 'shutdown' \| 'complete'` — which phase an `onError` report came from |
|
|
6353
|
+
| `SignalSource` | _type_ | injectable signal source — `process` by default |
|
|
6354
|
+
| `ProcessSignalName` | _type_ | signal names the binding accepts (owned, so the published types need no `@types/node`) |
|
|
6355
|
+
| `ShutdownTarget` | _type_ | the `shutdown`-only slice of a managed handle a binding needs |
|
|
6131
6356
|
| `createRateLimiter` | function | token-bucket rate limiting — [guide](../guide/server.md#rate-limiting) |
|
|
6132
6357
|
| `createCache` | function | an in-memory TTL cache |
|
|
6133
6358
|
| `CacheOptions` | _type_ | bounded-cache options, including the maximum retained entry count |
|
|
@@ -6433,7 +6658,7 @@ runtime-agnostic pieces of `stitchkit/server` and the error helpers.
|
|
|
6433
6658
|
| `serveNode` | function | build the router and start a Node HTTP server (via `srvx`) |
|
|
6434
6659
|
| `createHandler` | function | the router as a bare `(req) => Response` (same as `/server`) |
|
|
6435
6660
|
| `createSocketIOServer` | function | the typed Node Socket.IO server (`io` + `attach`; no Bun engine declarations) |
|
|
6436
|
-
| `implement` / `createImplement` | function | bind a contract to typed handlers (same as `/server`) |
|
|
6661
|
+
| `implement` / `createImplement` / `createScopedImplement` / `createScopedImplementRegistry` / `createMultipartStream` | function | bind a contract to typed handlers, optionally typed per endpoint scope (same as `/server`) |
|
|
6437
6662
|
| `NodeServerConfig` | _type_ | config for `serveNode` |
|
|
6438
6663
|
| `NodeServerHandle` | _type_ | managed Node handle (`url`, `port`, `runtime`, `status`, `shutdown`) |
|
|
6439
6664
|
| `NodeRuntimeServer` | _type_ | concrete `srvx/node` runtime escape hatch |
|
package/package.json
CHANGED