stitchkit 0.59.1 → 0.59.2
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/README.md +44 -4
- package/dist/application/activity.d.ts +70 -0
- package/dist/application/activity.d.ts.map +1 -0
- package/dist/application/events.d.ts +65 -0
- package/dist/application/events.d.ts.map +1 -0
- package/dist/application/grammy.d.ts +35 -0
- package/dist/application/grammy.d.ts.map +1 -0
- package/dist/application/graph.d.ts +11 -0
- package/dist/application/graph.d.ts.map +1 -0
- package/dist/application/health.d.ts +13 -0
- package/dist/application/health.d.ts.map +1 -0
- package/dist/application/kernel.d.ts +31 -0
- package/dist/application/kernel.d.ts.map +1 -0
- package/dist/application/latest-sink.d.ts +39 -0
- package/dist/application/latest-sink.d.ts.map +1 -0
- package/dist/application/resource.d.ts +30 -0
- package/dist/application/resource.d.ts.map +1 -0
- package/dist/application/schedule.d.ts +112 -0
- package/dist/application/schedule.d.ts.map +1 -0
- package/dist/application/schemas.d.ts +160 -0
- package/dist/application/schemas.d.ts.map +1 -0
- package/dist/application/server-resource.d.ts +12 -0
- package/dist/application/server-resource.d.ts.map +1 -0
- package/dist/application-grammy.d.ts +2 -0
- package/dist/application-grammy.d.ts.map +1 -0
- package/dist/application-grammy.js +165 -0
- package/dist/application.d.ts +10 -0
- package/dist/application.d.ts.map +1 -0
- package/dist/application.js +1370 -0
- package/dist/index-dk6e56g0.js +211 -0
- package/dist/{index-9zn9fb4e.js → index-he4psyve.js} +6 -213
- package/dist/index-yr276yz0.js +6 -0
- package/dist/internal/fetch-port.d.ts +2 -0
- package/dist/internal/fetch-port.d.ts.map +1 -0
- package/dist/node.js +115 -13
- package/dist/server/index.js +8 -6
- package/dist/server/node.d.ts.map +1 -1
- package/dist/server/process-signals.d.ts +10 -8
- package/dist/server/process-signals.d.ts.map +1 -1
- package/llms-full.txt +379 -0
- package/llms.txt +1 -0
- package/package.json +15 -2
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* throws, and a third signal on a process whose default disposition the first
|
|
10
10
|
* `process.on` already suppressed. → ADR 0076
|
|
11
11
|
*/
|
|
12
|
-
import type {
|
|
12
|
+
import type { ShutdownOptions, ShutdownResult } from './shutdown';
|
|
13
13
|
/**
|
|
14
14
|
* Signal names this binding can listen for.
|
|
15
15
|
*
|
|
@@ -21,8 +21,10 @@ import type { ManagedServerHandle, ShutdownOptions, ShutdownResult } from './shu
|
|
|
21
21
|
* cannot listen for `SIGTERM` at all.
|
|
22
22
|
*/
|
|
23
23
|
export type ProcessSignalName = 'SIGINT' | 'SIGTERM' | 'SIGHUP' | 'SIGQUIT' | 'SIGUSR2' | 'SIGBREAK';
|
|
24
|
-
/** The part of
|
|
25
|
-
export
|
|
24
|
+
/** The part of any managed handle this binding uses. */
|
|
25
|
+
export interface ShutdownTarget<TResult = ShutdownResult> {
|
|
26
|
+
shutdown(options?: ShutdownOptions): Promise<TResult>;
|
|
27
|
+
}
|
|
26
28
|
/** Which phase failed, so one `onError` can tell them apart. */
|
|
27
29
|
export type ProcessSignalsErrorPhase = 'prepare' | 'shutdown' | 'complete';
|
|
28
30
|
/**
|
|
@@ -43,7 +45,7 @@ export interface SignalSource {
|
|
|
43
45
|
*/
|
|
44
46
|
raiseDefault(signal: ProcessSignalName): boolean;
|
|
45
47
|
}
|
|
46
|
-
export interface ProcessSignalsOptions {
|
|
48
|
+
export interface ProcessSignalsOptions<TResult = ShutdownResult> {
|
|
47
49
|
/**
|
|
48
50
|
* Defaults to `['SIGINT', 'SIGTERM']`. Duplicates are ignored. Node on Windows
|
|
49
51
|
* cannot listen for `SIGTERM`.
|
|
@@ -72,7 +74,7 @@ export interface ProcessSignalsOptions {
|
|
|
72
74
|
* here. If it throws, `promise` stays resolved — the transport really did shut
|
|
73
75
|
* down — and the failure is reported through `onError('complete', …)`.
|
|
74
76
|
*/
|
|
75
|
-
onComplete?: (result:
|
|
77
|
+
onComplete?: (result: TResult) => void | Promise<void>;
|
|
76
78
|
/** Reports any phase failure, with the phase that produced it. */
|
|
77
79
|
onError?: (phase: ProcessSignalsErrorPhase, error: unknown) => void;
|
|
78
80
|
/** Runs for every counted signal after the first: one forces, later ones escalate. */
|
|
@@ -85,14 +87,14 @@ export interface ProcessSignalsOptions {
|
|
|
85
87
|
*/
|
|
86
88
|
onEscalationBlocked?: (signal: ProcessSignalName) => void;
|
|
87
89
|
}
|
|
88
|
-
export interface ProcessSignalsBinding {
|
|
90
|
+
export interface ProcessSignalsBinding<TResult = ShutdownResult> {
|
|
89
91
|
/**
|
|
90
92
|
* Resolves with the shutdown result, or with `undefined` when the binding was
|
|
91
93
|
* closed before any signal arrived. Rejects only when the shutdown itself
|
|
92
94
|
* failed. Already handled internally, so ignoring it never raises
|
|
93
95
|
* `unhandledRejection`.
|
|
94
96
|
*/
|
|
95
|
-
readonly promise: Promise<
|
|
97
|
+
readonly promise: Promise<TResult | undefined>;
|
|
96
98
|
/** Remove the listeners. Idempotent. */
|
|
97
99
|
close(): void;
|
|
98
100
|
}
|
|
@@ -113,5 +115,5 @@ export interface ProcessSignalsBinding {
|
|
|
113
115
|
* is supervisor policy (→ ADR 0074). Set `process.exitCode` in `onComplete` /
|
|
114
116
|
* `onError`.
|
|
115
117
|
*/
|
|
116
|
-
export declare function bindProcessSignals(handle: ShutdownTarget
|
|
118
|
+
export declare function bindProcessSignals<TResult = ShutdownResult>(handle: ShutdownTarget<TResult>, options?: ProcessSignalsOptions<TResult>): ProcessSignalsBinding<TResult>;
|
|
117
119
|
//# sourceMappingURL=process-signals.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-signals.d.ts","sourceRoot":"","sources":["../../src/server/process-signals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"process-signals.d.ts","sourceRoot":"","sources":["../../src/server/process-signals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGlE;;;;;;;;;GASG;AACH,MAAM,MAAM,iBAAiB,GACzB,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,SAAS,GACT,SAAS,GACT,UAAU,CAAC;AAEf,wDAAwD;AACxD,MAAM,WAAW,cAAc,CAAC,OAAO,GAAG,cAAc;IACtD,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACvD;AAED,gEAAgE;AAChE,MAAM,MAAM,wBAAwB,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,CAAC;AAE3E;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,EAAE,CAAC,MAAM,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IACzD,GAAG,CAAC,MAAM,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAC1D;;;;;;;;OAQG;IACH,YAAY,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC;CAClD;AAED,MAAM,WAAW,qBAAqB,CAAC,OAAO,GAAG,cAAc;IAC7D;;;OAGG;IACH,OAAO,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAC;IAC3C,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B;;;;;;;;;OASG;IACH,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,kEAAkE;IAClE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,wBAAwB,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACpE,sFAAsF;IACtF,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,EAAE,KAAK,EAAE,OAAO,GAAG,UAAU,KAAK,IAAI,CAAC;IACpF;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;CAC3D;AAED,MAAM,WAAW,qBAAqB,CAAC,OAAO,GAAG,cAAc;IAC7D;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IAC/C,wCAAwC;IACxC,KAAK,IAAI,IAAI,CAAC;CACf;AAUD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAG,cAAc,EACzD,MAAM,EAAE,cAAc,CAAC,OAAO,CAAC,EAC/B,OAAO,GAAE,qBAAqB,CAAC,OAAO,CAAM,GAC3C,qBAAqB,CAAC,OAAO,CAAC,CA0IhC"}
|
package/llms-full.txt
CHANGED
|
@@ -3744,6 +3744,299 @@ coalescing, collision, stale checkpoint, replay safety, terminal race, compactio
|
|
|
3744
3744
|
invariants against any fresh durable adapter.
|
|
3745
3745
|
|
|
3746
3746
|
|
|
3747
|
+
==============================================================================
|
|
3748
|
+
# Guide: Managed application kernel (docs/guide/application-kernel.md)
|
|
3749
|
+
==============================================================================
|
|
3750
|
+
|
|
3751
|
+
---
|
|
3752
|
+
title: Managed application kernel
|
|
3753
|
+
description: Compose process-local resources, schedules, readiness, drain and optional provider adapters without building a second job platform.
|
|
3754
|
+
type: architecture
|
|
3755
|
+
status: active
|
|
3756
|
+
created: 2026-08-23
|
|
3757
|
+
updated: 2026-08-23
|
|
3758
|
+
---
|
|
3759
|
+
|
|
3760
|
+
# Managed application kernel
|
|
3761
|
+
|
|
3762
|
+
Use `stitchkit/application` when several process-local resources must become
|
|
3763
|
+
ready and shut down as one application. Keep using the lower-level server and
|
|
3764
|
+
signal APIs when one managed server is already the complete lifecycle boundary.
|
|
3765
|
+
|
|
3766
|
+
The neutral entrypoint is server-only and works on Bun and Node ≥ 22. It does
|
|
3767
|
+
not import provider SDKs.
|
|
3768
|
+
|
|
3769
|
+
## Minimal composition
|
|
3770
|
+
|
|
3771
|
+
```ts
|
|
3772
|
+
import {
|
|
3773
|
+
createApplication,
|
|
3774
|
+
createManagedSchedule,
|
|
3775
|
+
defineManagedResource,
|
|
3776
|
+
managedServerResource,
|
|
3777
|
+
} from 'stitchkit/application'
|
|
3778
|
+
import { bindProcessSignals } from 'stitchkit/server'
|
|
3779
|
+
|
|
3780
|
+
const database = defineManagedResource({
|
|
3781
|
+
id: 'database',
|
|
3782
|
+
required: true,
|
|
3783
|
+
start: async ({ signal, reportHealth }) => {
|
|
3784
|
+
await db.connect({ signal })
|
|
3785
|
+
reportHealth('healthy')
|
|
3786
|
+
},
|
|
3787
|
+
close: () => db.disconnect(),
|
|
3788
|
+
})
|
|
3789
|
+
|
|
3790
|
+
const cleanup = createManagedSchedule({
|
|
3791
|
+
id: 'cleanup',
|
|
3792
|
+
dependsOn: ['database'],
|
|
3793
|
+
everyMs: 60_000,
|
|
3794
|
+
startAfterMs: 60_000,
|
|
3795
|
+
overlap: { mode: 'skip' },
|
|
3796
|
+
errorPolicy: 'continue',
|
|
3797
|
+
run: ({ signal }) => removeExpiredRecords(signal),
|
|
3798
|
+
})
|
|
3799
|
+
|
|
3800
|
+
const app = createApplication({
|
|
3801
|
+
id: 'service',
|
|
3802
|
+
resources: [
|
|
3803
|
+
database,
|
|
3804
|
+
managedServerResource({ id: 'http', server, dependsOn: ['database'] }),
|
|
3805
|
+
cleanup,
|
|
3806
|
+
],
|
|
3807
|
+
})
|
|
3808
|
+
|
|
3809
|
+
const signals = bindProcessSignals(app, {
|
|
3810
|
+
shutdown: { gracePeriodMs: 30_000, forceTimeoutMs: 5_000 },
|
|
3811
|
+
onComplete: (result) => {
|
|
3812
|
+
process.exitCode = result.outcome === 'clean' ? 0 : 1
|
|
3813
|
+
},
|
|
3814
|
+
})
|
|
3815
|
+
|
|
3816
|
+
await app.start()
|
|
3817
|
+
```
|
|
3818
|
+
|
|
3819
|
+
The exact resource callbacks are typed by their public configuration. The
|
|
3820
|
+
important ownership rule is stable: the application creates and configures the
|
|
3821
|
+
database/server/provider objects; Stitchkit orders their process-local
|
|
3822
|
+
lifecycle.
|
|
3823
|
+
|
|
3824
|
+
## Resource authoring
|
|
3825
|
+
|
|
3826
|
+
Give every resource a stable, bounded ID and list its dependencies explicitly.
|
|
3827
|
+
The graph is validated before any start. Required resources may depend only on
|
|
3828
|
+
required resources; an optional integration cannot silently become a required
|
|
3829
|
+
dependency.
|
|
3830
|
+
|
|
3831
|
+
Start and readiness are different. A connection can finish setup and return a
|
|
3832
|
+
ready handle. A poller can return a handle immediately, settle `ready` later,
|
|
3833
|
+
and keep a long-lived `completion` promise. Stitchkit observes both.
|
|
3834
|
+
|
|
3835
|
+
Once Stitchkit invokes start/activation, cleanup is guaranteed to be attempted.
|
|
3836
|
+
Write `close` so it is safe when setup was partial:
|
|
3837
|
+
|
|
3838
|
+
```ts
|
|
3839
|
+
let cacheClient: CacheClient | undefined
|
|
3840
|
+
|
|
3841
|
+
const cache = defineManagedResource({
|
|
3842
|
+
id: 'cache',
|
|
3843
|
+
required: false,
|
|
3844
|
+
start: async ({ signal, reportHealth }) => {
|
|
3845
|
+
cacheClient = new CacheClient()
|
|
3846
|
+
await cacheClient.connect(signal)
|
|
3847
|
+
reportHealth('healthy')
|
|
3848
|
+
},
|
|
3849
|
+
close: async () => {
|
|
3850
|
+
await cacheClient?.close()
|
|
3851
|
+
cacheClient = undefined
|
|
3852
|
+
},
|
|
3853
|
+
})
|
|
3854
|
+
```
|
|
3855
|
+
|
|
3856
|
+
Invoking `start` makes the descriptor rollback-eligible immediately. Its
|
|
3857
|
+
`close` callback must therefore clean side effects even when `start` rejects
|
|
3858
|
+
before returning a runtime handle. The kernel calls cleanup once and continues
|
|
3859
|
+
cleaning other attempted resources even if one close fails.
|
|
3860
|
+
|
|
3861
|
+
## Readiness and health
|
|
3862
|
+
|
|
3863
|
+
`await app.start()` resolves only after every required resource is ready and
|
|
3864
|
+
every required post-ready activation has succeeded. Lifecycle `ready` is
|
|
3865
|
+
published before activation so schedules never arm during `starting`;
|
|
3866
|
+
application admission opens only after successful activation. Optional failure
|
|
3867
|
+
may retain lifecycle `ready`, but snapshot health is `degraded`, never
|
|
3868
|
+
`healthy`.
|
|
3869
|
+
|
|
3870
|
+
Readiness is not hidden polling. A resource reports health changes through its
|
|
3871
|
+
lifecycle context; the application decides when a database/provider probe runs.
|
|
3872
|
+
A required long-lived completion that rejects after startup makes readiness
|
|
3873
|
+
false and health unhealthy. Stitchkit records the failure but does not restart
|
|
3874
|
+
the resource or process.
|
|
3875
|
+
|
|
3876
|
+
`createApplicationHealthHandler` exposes the neutral lifecycle/health snapshot
|
|
3877
|
+
through a Fetch-compatible handler, suitable for a raw route on Bun or Node.
|
|
3878
|
+
Product-specific probes may be composed beside it; do not put secrets or raw
|
|
3879
|
+
provider failures in the response.
|
|
3880
|
+
|
|
3881
|
+
## Admission and graceful shutdown
|
|
3882
|
+
|
|
3883
|
+
Use the application operation lease for work that is not already counted by a
|
|
3884
|
+
managed resource:
|
|
3885
|
+
|
|
3886
|
+
```ts
|
|
3887
|
+
const operation = app.admission.acquire()
|
|
3888
|
+
if (!operation) return Response.json({ error: 'unavailable' }, { status: 503 })
|
|
3889
|
+
|
|
3890
|
+
try {
|
|
3891
|
+
await performAcceptedWork()
|
|
3892
|
+
} finally {
|
|
3893
|
+
operation.release()
|
|
3894
|
+
}
|
|
3895
|
+
```
|
|
3896
|
+
|
|
3897
|
+
`release()` is idempotent. Admission and counter increment are atomic, so work
|
|
3898
|
+
cannot slip between the shutdown check and drain accounting.
|
|
3899
|
+
|
|
3900
|
+
Shutdown performs one phase barrier at a time: stop admission everywhere,
|
|
3901
|
+
cancel future schedules, drain admitted work, then close in reverse stable
|
|
3902
|
+
topological order. Every hook shares the same grace deadline. Forced cleanup
|
|
3903
|
+
shares one force deadline. Repeated `shutdown()` calls return the cached promise;
|
|
3904
|
+
a repeated process signal forces that same chain.
|
|
3905
|
+
|
|
3906
|
+
Do not add a second `process.on('SIGTERM')` handler around the application.
|
|
3907
|
+
`bindProcessSignals(app)` is the force/escalation owner. Exit code and hard-exit
|
|
3908
|
+
policy remain application/supervisor choices.
|
|
3909
|
+
|
|
3910
|
+
## Managed schedules
|
|
3911
|
+
|
|
3912
|
+
Schedules activate only after top-level readiness:
|
|
3913
|
+
|
|
3914
|
+
```ts
|
|
3915
|
+
createManagedSchedule({
|
|
3916
|
+
id: 'reconcile',
|
|
3917
|
+
everyMs: 5_000,
|
|
3918
|
+
startAfterMs: 0,
|
|
3919
|
+
overlap: { mode: 'queue-one' },
|
|
3920
|
+
errorPolicy: 'continue',
|
|
3921
|
+
onError: (error) => internalLogger.error(error),
|
|
3922
|
+
run: ({ signal }) => reconcile(signal),
|
|
3923
|
+
})
|
|
3924
|
+
```
|
|
3925
|
+
|
|
3926
|
+
- `skip` ignores a due tick while one execution is active.
|
|
3927
|
+
- `queue-one` retains one successor; additional due ticks coalesce.
|
|
3928
|
+
- `parallel` requires `maxConcurrent`; overflow skips rather than creating a
|
|
3929
|
+
hidden queue.
|
|
3930
|
+
|
|
3931
|
+
The callback receives the application shutdown signal. Normal shutdown waits
|
|
3932
|
+
for admitted executions; force aborts the shared signal. There are no retries,
|
|
3933
|
+
cron/timezone rules, persisted cursors, backfill or cross-process locks. Put
|
|
3934
|
+
those semantics in a durable application scheduler when they are required.
|
|
3935
|
+
Schedule cadence stays monotonic, while status snapshots expose ISO wall-clock
|
|
3936
|
+
`capturedAt`, `changedAt` and next/last-run timestamps for portable observation.
|
|
3937
|
+
|
|
3938
|
+
## Operational projection
|
|
3939
|
+
|
|
3940
|
+
Declare aggregate activity stages, then update them with anonymous handles:
|
|
3941
|
+
|
|
3942
|
+
```ts
|
|
3943
|
+
const generations = createActivityProjection({
|
|
3944
|
+
id: 'generation',
|
|
3945
|
+
stages: ['queued', 'running', 'finalizing'],
|
|
3946
|
+
})
|
|
3947
|
+
|
|
3948
|
+
const activity = generations.open('queued', 'queued')
|
|
3949
|
+
generations.transition(activity, { stage: 'running', state: 'active' })
|
|
3950
|
+
generations.transition(activity, { stage: 'finalizing', state: 'active' })
|
|
3951
|
+
generations.complete(activity)
|
|
3952
|
+
```
|
|
3953
|
+
|
|
3954
|
+
Item identity is deliberately absent from the snapshot. Observers receive
|
|
3955
|
+
aggregate counts with application identity, process epoch, revision and
|
|
3956
|
+
timestamps. Calling a terminal method twice does not double-count.
|
|
3957
|
+
|
|
3958
|
+
```ts
|
|
3959
|
+
const sink = createApplicationSnapshotSink({
|
|
3960
|
+
write: (snapshot) => publishOperationalSnapshot(snapshot),
|
|
3961
|
+
})
|
|
3962
|
+
|
|
3963
|
+
const unsubscribe = generations.subscribe((snapshot) => {
|
|
3964
|
+
sink.publish(snapshot)
|
|
3965
|
+
})
|
|
3966
|
+
|
|
3967
|
+
// During application cleanup:
|
|
3968
|
+
unsubscribe()
|
|
3969
|
+
await sink.close()
|
|
3970
|
+
```
|
|
3971
|
+
|
|
3972
|
+
The sink replays the current absolute value and coalesces obsolete pending
|
|
3973
|
+
revisions behind a slow write. Sink failure is observable but cannot fail the
|
|
3974
|
+
application operation that changed the projection.
|
|
3975
|
+
|
|
3976
|
+
## Optional grammY adapter
|
|
3977
|
+
|
|
3978
|
+
Install grammY only in an application that imports the adapter:
|
|
3979
|
+
|
|
3980
|
+
```ts
|
|
3981
|
+
import { Bot } from 'grammy'
|
|
3982
|
+
import {
|
|
3983
|
+
createGrammyWebhookResource,
|
|
3984
|
+
grammyPollingResource,
|
|
3985
|
+
} from 'stitchkit/application/grammy'
|
|
3986
|
+
```
|
|
3987
|
+
|
|
3988
|
+
For simple long polling, pass an already-configured bot:
|
|
3989
|
+
|
|
3990
|
+
```ts
|
|
3991
|
+
const bot = new Bot(env.BOT_TOKEN)
|
|
3992
|
+
bot.command('start', (ctx) => ctx.reply('Hello'))
|
|
3993
|
+
bot.catch(({ error }) => internalLogger.error(error))
|
|
3994
|
+
|
|
3995
|
+
const telegram = grammyPollingResource({
|
|
3996
|
+
id: 'telegram',
|
|
3997
|
+
bot,
|
|
3998
|
+
required: true,
|
|
3999
|
+
})
|
|
4000
|
+
```
|
|
4001
|
+
|
|
4002
|
+
Readiness follows grammY's `onStart`. Shutdown calls `bot.stop()` once and then
|
|
4003
|
+
awaits the retained `bot.start()` promise, because grammY documents that
|
|
4004
|
+
`stop()` alone does not wait for middleware completion.
|
|
4005
|
+
|
|
4006
|
+
For webhook ingress, `createGrammyWebhookResource` wraps provider-owned update
|
|
4007
|
+
handling with admission and drain. The application still mounts the HTTP route,
|
|
4008
|
+
verifies/configures its webhook and chooses the grammY framework adapter. An
|
|
4009
|
+
update accepted before shutdown may finish; a later update is rejected.
|
|
4010
|
+
|
|
4011
|
+
The adapter never reads the token/env, installs commands, changes `bot.catch`,
|
|
4012
|
+
chooses retry plugins, drops pending updates, sends outbound messages or stores
|
|
4013
|
+
updates. A durable inbox/retry worker remains an application component, not a
|
|
4014
|
+
mode of this adapter.
|
|
4015
|
+
|
|
4016
|
+
## What disappears from an application
|
|
4017
|
+
|
|
4018
|
+
A typical small service can replace roughly 120–220 lines of generic signal,
|
|
4019
|
+
timer, admission and close bookkeeping with resource declarations. A larger
|
|
4020
|
+
multi-resource service commonly carries 250–450 generic lifecycle lines plus
|
|
4021
|
+
15–30 lines for each periodic timer. These are directional review estimates,
|
|
4022
|
+
not an API guarantee.
|
|
4023
|
+
|
|
4024
|
+
The responsibility change is more important than line count:
|
|
4025
|
+
|
|
4026
|
+
| Before | After |
|
|
4027
|
+
|---|---|
|
|
4028
|
+
| hand-written phase enum and readiness waiters | `createApplication` snapshot |
|
|
4029
|
+
| `process.on` signal loop and escalation | `bindProcessSignals(app)` |
|
|
4030
|
+
| interval/timeout handles and overlap flag | `createManagedSchedule` |
|
|
4031
|
+
| global in-flight counter and drain waiter set | application operation leases |
|
|
4032
|
+
| manual stop/close fan-out | resource graph + bounded shutdown |
|
|
4033
|
+
| progress delta events | absolute activity projection |
|
|
4034
|
+
|
|
4035
|
+
Durable job tables, lifecycle journals/outboxes, provider inboxes and business
|
|
4036
|
+
retry rules do **not** disappear. They were never process-local glue and remain
|
|
4037
|
+
application-owned.
|
|
4038
|
+
|
|
4039
|
+
|
|
3747
4040
|
==============================================================================
|
|
3748
4041
|
# Guide: CLI (docs/guide/cli.md)
|
|
3749
4042
|
==============================================================================
|
|
@@ -8129,6 +8422,92 @@ Also re-exports the error helpers from `stitchkit/contract`.
|
|
|
8129
8422
|
|
|
8130
8423
|
---
|
|
8131
8424
|
|
|
8425
|
+
## `stitchkit/application`
|
|
8426
|
+
|
|
8427
|
+
Server-only process-local application composition. See the
|
|
8428
|
+
[application kernel guide](../guide/application-kernel.md) and
|
|
8429
|
+
[architecture](../architecture/application-kernel.md).
|
|
8430
|
+
|
|
8431
|
+
### Kernel and resources
|
|
8432
|
+
|
|
8433
|
+
| Export | Kind | Summary |
|
|
8434
|
+
|--------|------|---------|
|
|
8435
|
+
| `createApplication` | function | compose a validated resource DAG into one non-restartable startup, readiness, admission and shutdown state machine |
|
|
8436
|
+
| `defineManagedResource` | function | retain the exact typed resource declaration; every invoked start is rollback-eligible |
|
|
8437
|
+
| `managedServerResource` | function | adapt an existing managed server without copying its HTTP/WebSocket shutdown machine |
|
|
8438
|
+
| `createApplicationHealthHandler` | function | build a Fetch-clean liveness or readiness response from the sanitized application snapshot |
|
|
8439
|
+
| `ApplicationAdmissionError` | class | stable `APPLICATION_NOT_ACCEPTING` rejection from `admission.run(...)` |
|
|
8440
|
+
| `ApplicationConfig` / `ApplicationHandle` | _type_ | application declaration and its start/snapshot/subscription/admission/shutdown handle |
|
|
8441
|
+
| `ApplicationAdmission` / `ApplicationOperationLease` | _type_ | atomic process-local admission and idempotent release primitive |
|
|
8442
|
+
| `ManagedResource` / `ManagedResourceContext` / `ManagedResourceStartResult` | _type_ | resource lifecycle callbacks, shared deadlines, health reporting and separate readiness/completion promises |
|
|
8443
|
+
| `ManagedServerResourceConfig` | _type_ | existing managed server, stable ID, dependencies and policy for `managedServerResource` |
|
|
8444
|
+
| `ApplicationHealthHandlerOptions` / `ApplicationHealthHandlerOptionsSchema` | _type_ / schema | liveness/readiness selection and sanitized `Retry-After` policy |
|
|
8445
|
+
|
|
8446
|
+
### Managed schedules
|
|
8447
|
+
|
|
8448
|
+
| Export | Kind | Summary |
|
|
8449
|
+
|--------|------|---------|
|
|
8450
|
+
| `createManagedSchedule` | function | fixed-rate process-local timer that activates after top-level readiness and participates in drain |
|
|
8451
|
+
| `ManagedScheduleOverlapSchema` / `ManagedScheduleOverlap` | schema / _type_ | `skip`, `queue-one` or bounded `parallel` overlap policy |
|
|
8452
|
+
| `ManagedScheduleErrorPolicySchema` / `ManagedScheduleErrorPolicy` | schema / _type_ | `continue` or `stop-schedule` after an observed callback failure |
|
|
8453
|
+
| `ManagedScheduleDescriptorSchema` / `ManagedScheduleDescriptor` | schema / _type_ | immutable public schedule identity and cadence policy |
|
|
8454
|
+
| `ManagedScheduleStatusSchema` / `ManagedScheduleStatus` | schema / _type_ | absolute schedule state, revision, counters and timestamps |
|
|
8455
|
+
|
|
8456
|
+
Schedule authoring additionally exports `ManagedSchedule`, `ManagedScheduleConfig`,
|
|
8457
|
+
`ManagedScheduleRunContext`, `ManagedScheduleClock` and `ManagedScheduleTimer`.
|
|
8458
|
+
The clock exposes monotonic cadence/deadline arithmetic and a wall-clock projection for portable
|
|
8459
|
+
status timestamps; it is a deterministic test boundary, not a durable scheduler.
|
|
8460
|
+
|
|
8461
|
+
### Application and activity projection
|
|
8462
|
+
|
|
8463
|
+
| Export | Kind | Summary |
|
|
8464
|
+
|--------|------|---------|
|
|
8465
|
+
| `createActivityProjection` | function | aggregate anonymous process-local activity into declared bounded stages and absolute snapshots |
|
|
8466
|
+
| `createApplicationSnapshotSink` | function | latest-value delivery with one write in flight and one replaceable pending revision |
|
|
8467
|
+
| `applicationLifecycleEvent` | function | project a sanitized lifecycle fact from a canonical application snapshot |
|
|
8468
|
+
| `createApplicationEventSink` | function | bounded failure-isolated lifecycle-event delivery; events are not canonical state |
|
|
8469
|
+
| `ActivitySnapshotSchema` / `ActivitySnapshot` | schema / _type_ | epoch, monotonic revision, timestamps, declared stages and aggregate counts |
|
|
8470
|
+
| `ApplicationSnapshotSinkStatusSchema` / `ApplicationSnapshotSinkStatus` | schema / _type_ | immutable latest-value sink delivery and coalescing counters |
|
|
8471
|
+
| `ApplicationLifecycleEventSchema` / `ApplicationLifecycleEvent` | schema / _type_ | sanitized operator-facing application lifecycle fact |
|
|
8472
|
+
|
|
8473
|
+
Activity authoring additionally exports `ActivityId`, `ActivityIdSchema`,
|
|
8474
|
+
`ActivityStageId`, `ActivityStageIdSchema`, `ActivityStageSnapshot`,
|
|
8475
|
+
`ActivityStageSnapshotSchema`, `ActivityLiveState`, `ActivityLiveStateSchema`,
|
|
8476
|
+
`ActivityProjection`, `ActivityProjectionConfig`,
|
|
8477
|
+
`ActivityProjectionSubscriberError`, `ActivityToken` and `ActivityTransition`.
|
|
8478
|
+
Latest-value delivery exports `ApplicationSnapshotSink`,
|
|
8479
|
+
`ApplicationSnapshotSinkConfig`, `ApplicationSnapshotSinkError` and
|
|
8480
|
+
`RevisionedApplicationSnapshot`; event delivery exports `ApplicationEventSink`
|
|
8481
|
+
and `ApplicationEventSinkConfig`.
|
|
8482
|
+
|
|
8483
|
+
### Canonical application records
|
|
8484
|
+
|
|
8485
|
+
The entrypoint exports each Zod schema beside its inferred type:
|
|
8486
|
+
`ApplicationIdSchema` / `ApplicationId`, `ApplicationLifecycleSchema` /
|
|
8487
|
+
`ApplicationLifecycle`, `ApplicationHealthSchema` / `ApplicationHealth`,
|
|
8488
|
+
`ManagedResourceStateSchema` / `ManagedResourceState`,
|
|
8489
|
+
`ApplicationAdmissionSnapshotSchema` / `ApplicationAdmissionSnapshot`,
|
|
8490
|
+
`ManagedResourceSnapshotSchema` / `ManagedResourceSnapshot`,
|
|
8491
|
+
`ApplicationSnapshotSchema` / `ApplicationSnapshot`,
|
|
8492
|
+
`ApplicationResourceShutdownSchema` / `ApplicationResourceShutdown`, and
|
|
8493
|
+
`ApplicationShutdownResultSchema` / `ApplicationShutdownResult`.
|
|
8494
|
+
|
|
8495
|
+
## `stitchkit/application/grammy`
|
|
8496
|
+
|
|
8497
|
+
Isolated optional-peer lifecycle adapters. Importing `stitchkit/application`
|
|
8498
|
+
does not resolve grammY.
|
|
8499
|
+
|
|
8500
|
+
| Export | Kind | Summary |
|
|
8501
|
+
|--------|------|---------|
|
|
8502
|
+
| `grammyPollingResource` | function | adapt an injected bot's long polling with distinct `onStart` readiness, observed completion and one stop chain |
|
|
8503
|
+
| `createGrammyWebhookResource` | function | return a managed resource plus admission-guarded `handleUpdate`; HTTP hosting and webhook registration stay application-owned |
|
|
8504
|
+
| `GrammyWebhookUnavailableError` | class | stable `GRAMMY_WEBHOOK_NOT_ACCEPTING` rejection after webhook admission closes |
|
|
8505
|
+
| `GrammyPollingResourceConfig` | _type_ | injected bot, polling options, resource graph policy and isolated error observer |
|
|
8506
|
+
| `GrammyWebhookResourceConfig` / `GrammyWebhookResource` | _type_ | injected webhook bot declaration and `{ resource, handleUpdate }` handle |
|
|
8507
|
+
| `GrammyUpdate` | _type_ | exact update input inferred from the injected grammY bot context |
|
|
8508
|
+
|
|
8509
|
+
---
|
|
8510
|
+
|
|
8132
8511
|
## `stitchkit/agent-runtime`
|
|
8133
8512
|
|
|
8134
8513
|
Server-only optional application runtime. See the
|
package/llms.txt
CHANGED
|
@@ -11,6 +11,7 @@ Build with stitchkit: define a contract once, then `implement` it and serve it (
|
|
|
11
11
|
- [Typed client](https://github.com/max-listov/stitchkit/blob/master/docs/guide/client.md): createClient/createHttpClient, the typed call surface, scoped clients, SSE
|
|
12
12
|
- [MCP & agents](https://github.com/max-listov/stitchkit/blob/master/docs/guide/mcp-and-agents.md): contracts as MCP tools (createMcpHandler) and AI-agent tools (mountAgent); tool lifecycle, extend, identity
|
|
13
13
|
- [Agent application runtime](https://github.com/max-listov/stitchkit/blob/master/docs/guide/agent-runtime.md): optional durable history, prompt/model composition, stream loop, coordination, fencing and events
|
|
14
|
+
- [Managed application kernel](https://github.com/max-listov/stitchkit/blob/master/docs/guide/application-kernel.md): process-local resources, readiness, admission, schedules, projections and optional provider adapters
|
|
14
15
|
- [CLI](https://github.com/max-listov/stitchkit/blob/master/docs/guide/cli.md): contracts as a command-line program
|
|
15
16
|
- [Realtime](https://github.com/max-listov/stitchkit/blob/master/docs/guide/realtime.md): Socket.IO server/client wrappers, handshake auth, the cache bridge, a raw WebSocket lane
|
|
16
17
|
- [Auth & errors](https://github.com/max-listov/stitchkit/blob/master/docs/guide/auth-and-errors.md): scopes, createAuthHook, JWT/cookies, the AppError model, the stitch error-code registry
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stitchkit",
|
|
3
|
-
"version": "0.59.
|
|
3
|
+
"version": "0.59.2",
|
|
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",
|
|
@@ -84,6 +84,14 @@
|
|
|
84
84
|
"types": "./dist/agent-runtime-openrouter.d.ts",
|
|
85
85
|
"import": "./dist/agent-runtime-openrouter.js"
|
|
86
86
|
},
|
|
87
|
+
"./application": {
|
|
88
|
+
"types": "./dist/application.d.ts",
|
|
89
|
+
"import": "./dist/application.js"
|
|
90
|
+
},
|
|
91
|
+
"./application/grammy": {
|
|
92
|
+
"types": "./dist/application-grammy.d.ts",
|
|
93
|
+
"import": "./dist/application-grammy.js"
|
|
94
|
+
},
|
|
87
95
|
"./testing": {
|
|
88
96
|
"types": "./dist/testing.d.ts",
|
|
89
97
|
"import": "./dist/testing.js"
|
|
@@ -103,7 +111,7 @@
|
|
|
103
111
|
"scripts": {
|
|
104
112
|
"check": "bun x tsc --noEmit",
|
|
105
113
|
"build:browser": "bun build src/index.ts src/react.ts src/contract/index.ts --outdir dist --target node --packages external --splitting --root src",
|
|
106
|
-
"build:server": "bun build src/server/index.ts src/node.ts src/tools.ts src/cli.ts src/remote.ts src/files.ts src/testing.ts src/observability/index.ts src/agent-runtime.ts src/agent-runtime-openrouter.ts --outdir dist --target node --packages external --splitting --root src",
|
|
114
|
+
"build:server": "bun build src/server/index.ts src/node.ts src/tools.ts src/cli.ts src/remote.ts src/files.ts src/testing.ts src/observability/index.ts src/agent-runtime.ts src/agent-runtime-openrouter.ts src/application.ts src/application-grammy.ts --outdir dist --target node --packages external --splitting --root src",
|
|
107
115
|
"build:js": "bun run build:browser && bun run build:server",
|
|
108
116
|
"build:types": "bun x tsc -p tsconfig.build.json --emitDeclarationOnly",
|
|
109
117
|
"build": "rm -rf dist && bun run build:js && bun run build:types && bun scripts/check-browser-clean.mjs && bun scripts/check-env-live.mjs && bun scripts/check-public-types.mjs",
|
|
@@ -124,6 +132,7 @@
|
|
|
124
132
|
"@socket.io/component-emitter": "^3.1.2",
|
|
125
133
|
"@tanstack/react-query": ">=5",
|
|
126
134
|
"ai": "^7.0.0",
|
|
135
|
+
"grammy": "^1.45.1",
|
|
127
136
|
"react": ">=18",
|
|
128
137
|
"react-query-kit": "^3.3.3",
|
|
129
138
|
"socket.io": "^4.8.3",
|
|
@@ -156,6 +165,9 @@
|
|
|
156
165
|
"ai": {
|
|
157
166
|
"optional": true
|
|
158
167
|
},
|
|
168
|
+
"grammy": {
|
|
169
|
+
"optional": true
|
|
170
|
+
},
|
|
159
171
|
"react": {
|
|
160
172
|
"optional": true
|
|
161
173
|
},
|
|
@@ -188,6 +200,7 @@
|
|
|
188
200
|
"@types/react": "^19.2.18",
|
|
189
201
|
"@typescript/typescript6": "^6.0.2",
|
|
190
202
|
"ai": "^7.0.65",
|
|
203
|
+
"grammy": "^1.45.1",
|
|
191
204
|
"react": "^19.2.8",
|
|
192
205
|
"react-query-kit": "^3.3.4",
|
|
193
206
|
"socket.io": "^4.8.3",
|