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.
Files changed (42) hide show
  1. package/README.md +44 -4
  2. package/dist/application/activity.d.ts +70 -0
  3. package/dist/application/activity.d.ts.map +1 -0
  4. package/dist/application/events.d.ts +65 -0
  5. package/dist/application/events.d.ts.map +1 -0
  6. package/dist/application/grammy.d.ts +35 -0
  7. package/dist/application/grammy.d.ts.map +1 -0
  8. package/dist/application/graph.d.ts +11 -0
  9. package/dist/application/graph.d.ts.map +1 -0
  10. package/dist/application/health.d.ts +13 -0
  11. package/dist/application/health.d.ts.map +1 -0
  12. package/dist/application/kernel.d.ts +31 -0
  13. package/dist/application/kernel.d.ts.map +1 -0
  14. package/dist/application/latest-sink.d.ts +39 -0
  15. package/dist/application/latest-sink.d.ts.map +1 -0
  16. package/dist/application/resource.d.ts +30 -0
  17. package/dist/application/resource.d.ts.map +1 -0
  18. package/dist/application/schedule.d.ts +112 -0
  19. package/dist/application/schedule.d.ts.map +1 -0
  20. package/dist/application/schemas.d.ts +160 -0
  21. package/dist/application/schemas.d.ts.map +1 -0
  22. package/dist/application/server-resource.d.ts +12 -0
  23. package/dist/application/server-resource.d.ts.map +1 -0
  24. package/dist/application-grammy.d.ts +2 -0
  25. package/dist/application-grammy.d.ts.map +1 -0
  26. package/dist/application-grammy.js +165 -0
  27. package/dist/application.d.ts +10 -0
  28. package/dist/application.d.ts.map +1 -0
  29. package/dist/application.js +1370 -0
  30. package/dist/index-dk6e56g0.js +211 -0
  31. package/dist/{index-9zn9fb4e.js → index-he4psyve.js} +6 -213
  32. package/dist/index-yr276yz0.js +6 -0
  33. package/dist/internal/fetch-port.d.ts +2 -0
  34. package/dist/internal/fetch-port.d.ts.map +1 -0
  35. package/dist/node.js +115 -13
  36. package/dist/server/index.js +8 -6
  37. package/dist/server/node.d.ts.map +1 -1
  38. package/dist/server/process-signals.d.ts +10 -8
  39. package/dist/server/process-signals.d.ts.map +1 -1
  40. package/llms-full.txt +379 -0
  41. package/llms.txt +1 -0
  42. 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 { ManagedServerHandle, ShutdownOptions, ShutdownResult } from './shutdown';
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 a managed handle this binding uses. */
25
- export type ShutdownTarget = Pick<ManagedServerHandle<unknown>, 'shutdown'>;
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: ShutdownResult) => void | Promise<void>;
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<ShutdownResult | undefined>;
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, options?: ProcessSignalsOptions): ProcessSignalsBinding;
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,mBAAmB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGvF;;;;;;;;;GASG;AACH,MAAM,MAAM,iBAAiB,GACzB,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,SAAS,GACT,SAAS,GACT,UAAU,CAAC;AAEf,sDAAsD;AACtD,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC,CAAC;AAE5E,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;IACpC;;;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,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,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;IACpC;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;IACtD,wCAAwC;IACxC,KAAK,IAAI,IAAI,CAAC;CACf;AAUD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,cAAc,EACtB,OAAO,GAAE,qBAA0B,GAClC,qBAAqB,CA0IvB"}
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.1",
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",