stitchkit 0.60.0 → 0.61.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.
Files changed (63) hide show
  1. package/dist/agent-runtime/coordinator.d.ts +10 -0
  2. package/dist/agent-runtime/coordinator.d.ts.map +1 -1
  3. package/dist/agent-runtime/run-execution.d.ts.map +1 -1
  4. package/dist/agent-runtime/runtime.d.ts.map +1 -1
  5. package/dist/agent-runtime/schemas.d.ts.map +1 -1
  6. package/dist/agent-runtime/store-driver.d.ts.map +1 -1
  7. package/dist/agent-runtime.js +97 -12
  8. package/dist/application/grammy.d.ts +12 -6
  9. package/dist/application/grammy.d.ts.map +1 -1
  10. package/dist/application/kernel.d.ts +35 -2
  11. package/dist/application/kernel.d.ts.map +1 -1
  12. package/dist/application/server-resource.d.ts.map +1 -1
  13. package/dist/application-grammy.js +5 -3
  14. package/dist/application.d.ts +1 -1
  15. package/dist/application.d.ts.map +1 -1
  16. package/dist/application.js +12 -6
  17. package/dist/browser/socket-io.d.ts +47 -0
  18. package/dist/browser/socket-io.d.ts.map +1 -1
  19. package/dist/browser/stream.d.ts +23 -0
  20. package/dist/browser/stream.d.ts.map +1 -1
  21. package/dist/cli.js +5 -4
  22. package/dist/contract/errors.d.ts +1 -0
  23. package/dist/contract/errors.d.ts.map +1 -1
  24. package/dist/contract/index.js +1 -1
  25. package/dist/{index-8pc5s3pz.js → index-1pgeyyee.js} +3 -3
  26. package/dist/{index-9h1vba8n.js → index-413xk7ga.js} +2 -1
  27. package/dist/{index-8es1hrv4.js → index-6taryy00.js} +1 -1
  28. package/dist/{index-1zencgcb.js → index-eabpd4tb.js} +61 -10
  29. package/dist/{index-3w74r08v.js → index-mfw1pec7.js} +7 -5
  30. package/dist/{index-f7521k0x.js → index-s1tywej8.js} +95 -24
  31. package/dist/{index-6fbp58g8.js → index-tvwcrx2d.js} +4 -2
  32. package/dist/{index-v6e8hnkx.js → index-wd8g8z6e.js} +1 -84
  33. package/dist/{index-2akar0rq.js → index-wrhf06ak.js} +7 -5
  34. package/dist/index-xxye8j3k.js +85 -0
  35. package/dist/{index-3hn10n4c.js → index-y91zd0ch.js} +1 -1
  36. package/dist/index.d.ts +2 -2
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +213 -17
  39. package/dist/internal/optional-peer.d.ts +14 -0
  40. package/dist/internal/optional-peer.d.ts.map +1 -0
  41. package/dist/node.js +8 -7
  42. package/dist/observability/index.js +4 -3
  43. package/dist/realtime/contract.d.ts +7 -1
  44. package/dist/realtime/contract.d.ts.map +1 -1
  45. package/dist/realtime/index.d.ts +2 -1
  46. package/dist/realtime/index.d.ts.map +1 -1
  47. package/dist/realtime/rejected-frame.d.ts +86 -0
  48. package/dist/realtime/rejected-frame.d.ts.map +1 -0
  49. package/dist/realtime/request.d.ts +24 -0
  50. package/dist/realtime/request.d.ts.map +1 -1
  51. package/dist/realtime/socket.d.ts.map +1 -1
  52. package/dist/remote.js +4 -2
  53. package/dist/server/index.d.ts +1 -0
  54. package/dist/server/index.d.ts.map +1 -1
  55. package/dist/server/index.js +209 -15
  56. package/dist/server/socket-io.d.ts.map +1 -1
  57. package/dist/server/stream.d.ts.map +1 -1
  58. package/dist/server/streaming-route.d.ts +130 -0
  59. package/dist/server/streaming-route.d.ts.map +1 -0
  60. package/dist/testing.js +2 -1
  61. package/dist/tools.js +11 -9
  62. package/llms-full.txt +422 -79
  63. package/package.json +5 -5
@@ -1,2 +1,25 @@
1
1
  export { type ParseSSEOptions, parseSSE } from '../server/stream';
2
+ /** Options for `parseNDJSON`. */
3
+ export interface ParseNDJSONOptions {
4
+ /** Called for a line that is not valid JSON — the alternative to throwing. */
5
+ onParseError?: (raw: string, error: Error) => void;
6
+ }
7
+ /**
8
+ * Parse a newline-delimited JSON response body — the client half of
9
+ * `ndjsonRoute`.
10
+ *
11
+ * **Blank lines are skipped**, and that is the contract rather than a
12
+ * convenience. A long-lived NDJSON stream has to send something while it is
13
+ * idle or intermediaries drop it, and the natural pulse for this framing is an
14
+ * empty line. Writing the skip down here is what stops it being a verbal
15
+ * agreement between the two halves of one project: the server's keep-alive and
16
+ * the reader's rule are one decision with two implementations.
17
+ *
18
+ * **To end a subscription, abort the request.** Leaving the loop cancels the
19
+ * body — which is right, and this does it — but a client-side cancel is not a
20
+ * reliable way to reach the server: measured against Bun today, the source on
21
+ * the other end stayed alive for seconds afterwards. An aborted request reaches
22
+ * the route's `context.signal` at once.
23
+ */
24
+ export declare function parseNDJSON<T>(response: Response, options?: ParseNDJSONOptions): AsyncGenerator<T>;
2
25
  //# sourceMappingURL=stream.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../src/browser/stream.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,eAAe,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../src/browser/stream.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,eAAe,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAElE,iCAAiC;AACjC,MAAM,WAAW,kBAAkB;IACjC,8EAA8E;IAC9E,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CACpD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAuB,WAAW,CAAC,CAAC,EAClC,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE,kBAAkB,GAC3B,cAAc,CAAC,CAAC,CAAC,CAmDnB"}
package/dist/cli.js CHANGED
@@ -5,17 +5,18 @@ import {
5
5
  emitResult,
6
6
  parseCliArgs,
7
7
  pollUntilDone
8
- } from "./index-8pc5s3pz.js";
9
- import"./index-2akar0rq.js";
8
+ } from "./index-1pgeyyee.js";
9
+ import"./index-wrhf06ak.js";
10
10
  import"./index-sbdmyz75.js";
11
11
  import"./index-vkk06pv1.js";
12
12
  import"./index-6djpbnda.js";
13
13
  import"./index-cby4ar3v.js";
14
- import"./index-6fbp58g8.js";
15
- import"./index-v6e8hnkx.js";
14
+ import"./index-tvwcrx2d.js";
15
+ import"./index-wd8g8z6e.js";
16
16
  import"./index-6k1937bx.js";
17
17
  import"./index-pzyt11ch.js";
18
18
  import"./index-smpbdg6k.js";
19
+ import"./index-xxye8j3k.js";
19
20
  export {
20
21
  DEFAULT_EXIT_CODES,
21
22
  createCli,
@@ -82,6 +82,7 @@ export declare const STITCH_ERROR_STATUS: {
82
82
  FILE_EXISTS: number;
83
83
  REALTIME_CONTRACT_VIOLATION: number;
84
84
  APPLICATION_NOT_ACCEPTING: number;
85
+ GRAMMY_WEBHOOK_NOT_ACCEPTING: number;
85
86
  WAIT_TIMEOUT: number;
86
87
  WAIT_FAILED: number;
87
88
  DOWNLOAD_NOT_FOUND: number;
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/contract/errors.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE;QACL,2CAA2C;QAC3C,IAAI,EAAE,MAAM,CAAC;QACb,8BAA8B;QAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,gCAAgC;QAChC,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,0BAA0B;QAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;CACH;AAcD;;;;GAIG;AACH,qBAAa,QAAQ,CACnB,KAAK,SAAS,MAAM,GAAG,MAAM,EAC7B,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAC1F,SAAQ,KAAK;aAEK,IAAI,EAAE,KAAK;aAEX,MAAM,EAAE,MAAM;aACd,OAAO,CAAC,EAAE,QAAQ;aAClB,IAAI,CAAC,EAAE,MAAM;IAC7B;;;;OAIG;aACa,OAAO,CAAC,EAAE,MAAM;IAXlC,YACkB,IAAI,EAAE,KAAK,EAC3B,OAAO,CAAC,EAAE,MAAM,EACA,MAAM,GAAE,MAAY,EACpB,OAAO,CAAC,EAAE,QAAQ,YAAA,EAClB,IAAI,CAAC,EAAE,MAAM,YAAA;IAC7B;;;;OAIG;IACa,OAAO,CAAC,EAAE,MAAM,YAAA,EAQjC;IAED,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,QAAQ,CAEvC;IAED,MAAM,IAAI,aAAa,CAStB;CACF;AAED,qCAAqC;AACrC,wBAAgB,QAAQ,CAAC,OAAO,SAAc,GAAG,KAAK,CAErD;AAED,6EAA6E;AAC7E,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,KAAK,CAEpF;AAED,wCAAwC;AACxC,wBAAgB,YAAY,CAAC,OAAO,SAAiB,GAAG,KAAK,CAE5D;AAED,qCAAqC;AACrC,wBAAgB,SAAS,CAAC,OAAO,SAAc,GAAG,KAAK,CAEtD;AAED,0EAA0E;AAC1E,wBAAgB,QAAQ,CAAC,OAAO,SAAa,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,KAAK,CAEvF;AAED,wCAAwC;AACxC,wBAAgB,WAAW,CAAC,OAAO,SAAsB,GAAG,KAAK,CAEhE;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;CA8BE,CAAC;AAEnC,mFAAmF;AACnF,MAAM,MAAM,eAAe,GAAG,MAAM,OAAO,mBAAmB,CAAC;AAE/D,iEAAiE;AACjE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,IAAI,eAAe,CAEvE;AAED,+FAA+F;AAC/F,wBAAgB,QAAQ,CACtB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,KAAK,CAOP"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/contract/errors.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE;QACL,2CAA2C;QAC3C,IAAI,EAAE,MAAM,CAAC;QACb,8BAA8B;QAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,gCAAgC;QAChC,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,0BAA0B;QAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;CACH;AAcD;;;;GAIG;AACH,qBAAa,QAAQ,CACnB,KAAK,SAAS,MAAM,GAAG,MAAM,EAC7B,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAC1F,SAAQ,KAAK;aAEK,IAAI,EAAE,KAAK;aAEX,MAAM,EAAE,MAAM;aACd,OAAO,CAAC,EAAE,QAAQ;aAClB,IAAI,CAAC,EAAE,MAAM;IAC7B;;;;OAIG;aACa,OAAO,CAAC,EAAE,MAAM;IAXlC,YACkB,IAAI,EAAE,KAAK,EAC3B,OAAO,CAAC,EAAE,MAAM,EACA,MAAM,GAAE,MAAY,EACpB,OAAO,CAAC,EAAE,QAAQ,YAAA,EAClB,IAAI,CAAC,EAAE,MAAM,YAAA;IAC7B;;;;OAIG;IACa,OAAO,CAAC,EAAE,MAAM,YAAA,EAQjC;IAED,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,QAAQ,CAEvC;IAED,MAAM,IAAI,aAAa,CAStB;CACF;AAED,qCAAqC;AACrC,wBAAgB,QAAQ,CAAC,OAAO,SAAc,GAAG,KAAK,CAErD;AAED,6EAA6E;AAC7E,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,KAAK,CAEpF;AAED,wCAAwC;AACxC,wBAAgB,YAAY,CAAC,OAAO,SAAiB,GAAG,KAAK,CAE5D;AAED,qCAAqC;AACrC,wBAAgB,SAAS,CAAC,OAAO,SAAc,GAAG,KAAK,CAEtD;AAED,0EAA0E;AAC1E,wBAAgB,QAAQ,CAAC,OAAO,SAAa,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,KAAK,CAEvF;AAED,wCAAwC;AACxC,wBAAgB,WAAW,CAAC,OAAO,SAAsB,GAAG,KAAK,CAEhE;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;CA+BE,CAAC;AAEnC,mFAAmF;AACnF,MAAM,MAAM,eAAe,GAAG,MAAM,OAAO,mBAAmB,CAAC;AAE/D,iEAAiE;AACjE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,IAAI,eAAe,CAEvE;AAED,+FAA+F;AAC/F,wBAAgB,QAAQ,CACtB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,KAAK,CAOP"}
@@ -18,7 +18,7 @@ import {
18
18
  paginatedSchema,
19
19
  rateLimited,
20
20
  unauthorized
21
- } from "../index-9h1vba8n.js";
21
+ } from "../index-413xk7ga.js";
22
22
  import"../index-tss6bk5c.js";
23
23
  export {
24
24
  ALL_TRANSPORTS,
@@ -2,7 +2,7 @@ import {
2
2
  coerceJsonArgs,
3
3
  executeToolMethod,
4
4
  toolResultFromError
5
- } from "./index-2akar0rq.js";
5
+ } from "./index-wrhf06ak.js";
6
6
  import {
7
7
  isWithinDir
8
8
  } from "./index-sbdmyz75.js";
@@ -24,11 +24,11 @@ import {
24
24
  import {
25
25
  formatZodError,
26
26
  validateDeclaredOutput
27
- } from "./index-6fbp58g8.js";
27
+ } from "./index-tvwcrx2d.js";
28
28
  import {
29
29
  isUnsafeKey,
30
30
  safeJsonParse
31
- } from "./index-v6e8hnkx.js";
31
+ } from "./index-wd8g8z6e.js";
32
32
  import {
33
33
  isRecord
34
34
  } from "./index-smpbdg6k.js";
@@ -285,6 +285,7 @@ var STITCH_ERROR_STATUS = {
285
285
  FILE_EXISTS: 409,
286
286
  REALTIME_CONTRACT_VIOLATION: 500,
287
287
  APPLICATION_NOT_ACCEPTING: 503,
288
+ GRAMMY_WEBHOOK_NOT_ACCEPTING: 503,
288
289
  WAIT_TIMEOUT: 408,
289
290
  WAIT_FAILED: 409,
290
291
  DOWNLOAD_NOT_FOUND: 404,
@@ -426,4 +427,4 @@ function decodeCursor(cursor, schema) {
426
427
  return null;
427
428
  }
428
429
  }
429
- export { parseTrailingWildcard, AppError, notFound, badRequest, unauthorized, forbidden, conflict, rateLimited, STITCH_ERROR_STATUS, isStitchErrorCode, appError, ALL_TRANSPORTS, defineContract, defineErrors, createContractFactory, ManagedFilePathSchema, ManagedFileRefSchema, paginatedSchema, encodeCursor, decodeCursor };
430
+ export { parseTrailingWildcard, ALL_TRANSPORTS, defineContract, AppError, notFound, badRequest, unauthorized, forbidden, conflict, rateLimited, STITCH_ERROR_STATUS, isStitchErrorCode, appError, defineErrors, createContractFactory, ManagedFilePathSchema, ManagedFileRefSchema, paginatedSchema, encodeCursor, decodeCursor };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  isUnsafeKey
3
- } from "./index-v6e8hnkx.js";
3
+ } from "./index-wd8g8z6e.js";
4
4
  import {
5
5
  isRecord
6
6
  } from "./index-smpbdg6k.js";
@@ -6,6 +6,9 @@ import {
6
6
  import {
7
7
  ShutdownOptionsSchema
8
8
  } from "./index-dk6e56g0.js";
9
+ import {
10
+ AppError
11
+ } from "./index-xxye8j3k.js";
9
12
 
10
13
  // src/application/graph.ts
11
14
  function resolveResourceGraph(resources) {
@@ -72,11 +75,14 @@ var ApplicationShutdownOptionsSchema = ShutdownOptionsSchema.pick({
72
75
  forceTimeoutMs: true,
73
76
  signal: true
74
77
  });
78
+ var ApplicationShutdownBudgetSchema = ShutdownOptionsSchema.pick({
79
+ gracePeriodMs: true,
80
+ forceTimeoutMs: true
81
+ });
75
82
 
76
- class ApplicationAdmissionError extends Error {
77
- code = "APPLICATION_NOT_ACCEPTING";
83
+ class ApplicationAdmissionError extends AppError {
78
84
  constructor() {
79
- super("Application is not accepting new operations");
85
+ super("APPLICATION_NOT_ACCEPTING", "Application is not accepting new operations", 503);
80
86
  this.name = "ApplicationAdmissionError";
81
87
  }
82
88
  }
@@ -98,6 +104,7 @@ async function untilDeadline(work, signal) {
98
104
  function createApplication(config) {
99
105
  const id = ApplicationIdSchema.parse(config.id);
100
106
  const ordered = resolveResourceGraph(config.resources ?? []);
107
+ const shutdownBudget = ApplicationShutdownBudgetSchema.parse(config.shutdown ?? {});
101
108
  const reverse = [...ordered].reverse();
102
109
  const reportFailure = (resourceId, phase, error) => {
103
110
  if (!config.onResourceFailure)
@@ -114,6 +121,8 @@ function createApplication(config) {
114
121
  entry,
115
122
  state: "registered",
116
123
  health: "unknown",
124
+ healthReported: false,
125
+ everHealthy: false,
117
126
  attempted: false,
118
127
  activated: false,
119
128
  closeInvoked: false,
@@ -201,6 +210,9 @@ function createApplication(config) {
201
210
  if (record.state === "stopped")
202
211
  return;
203
212
  record.health = health;
213
+ record.healthReported = true;
214
+ if (health === "healthy")
215
+ record.everHealthy = true;
204
216
  accepting = activationComplete && !shutdownRequested && isReady();
205
217
  publish();
206
218
  }
@@ -219,6 +231,22 @@ function createApplication(config) {
219
231
  };
220
232
  const closeAttempted = async () => {
221
233
  const errors = [];
234
+ const rollbackStartedAt = performance.now();
235
+ const rollbackDeadlineAt = rollbackStartedAt + shutdownBudget.gracePeriodMs;
236
+ const rollbackForceDeadlineAt = rollbackDeadlineAt + shutdownBudget.forceTimeoutMs;
237
+ const rollbackAbort = new AbortController;
238
+ const rollbackTimer = setTimeout(() => rollbackAbort.abort(), Math.max(0, rollbackForceDeadlineAt - performance.now()));
239
+ try {
240
+ await closeEachAttempted(rollbackAbort.signal, errors, {
241
+ deadlineAt: rollbackDeadlineAt,
242
+ forceDeadlineAt: rollbackForceDeadlineAt
243
+ });
244
+ } finally {
245
+ clearTimeout(rollbackTimer);
246
+ }
247
+ return errors;
248
+ };
249
+ const closeEachAttempted = async (bound, errors, deadlines) => {
222
250
  for (const entry of reverse) {
223
251
  const record = records.get(entry.id);
224
252
  if (!record?.attempted || record.closed)
@@ -226,7 +254,18 @@ function createApplication(config) {
226
254
  record.state = "stopping";
227
255
  try {
228
256
  record.closeInvoked = true;
229
- await entry.resource.close?.(contextFor(record, { signal: startupAbort.signal }));
257
+ const settled = await untilDeadline(Promise.resolve(entry.resource.close?.(contextFor(record, { signal: startupAbort.signal, ...deadlines }))), bound);
258
+ if (!settled.settled) {
259
+ record.failures.push("close");
260
+ record.state = "failed";
261
+ const timedOut = new Error(`[stitchkit] resource "${entry.id}" did not finish closing during rollback`);
262
+ reportFailure(entry.id, "close", timedOut);
263
+ errors.push(timedOut);
264
+ publish();
265
+ break;
266
+ }
267
+ if (settled.error !== undefined)
268
+ throw settled.error;
230
269
  record.closed = true;
231
270
  record.state = "stopped";
232
271
  } catch (error) {
@@ -237,7 +276,6 @@ function createApplication(config) {
237
276
  }
238
277
  publish();
239
278
  }
240
- return errors;
241
279
  };
242
280
  const runStart = async () => {
243
281
  lifecycle = "starting";
@@ -305,7 +343,10 @@ function createApplication(config) {
305
343
  throw new ApplicationStartupInterruptedError;
306
344
  }
307
345
  record.state = "ready";
308
- record.health = "healthy";
346
+ if (!record.healthReported) {
347
+ record.health = "healthy";
348
+ record.everHealthy = true;
349
+ }
309
350
  publish();
310
351
  } catch (error) {
311
352
  const interrupted = shutdownRequested || startupAbort.signal.aborted;
@@ -350,7 +391,8 @@ function createApplication(config) {
350
391
  }
351
392
  record.activated = true;
352
393
  if (entry.required && (record.state !== "ready" || record.health !== "healthy")) {
353
- throw new Error(`[stitchkit] required resource "${entry.id}" lost readiness during activation`);
394
+ const observed = `${record.state}/${record.health}`;
395
+ throw new Error(record.everHealthy ? `[stitchkit] required resource "${entry.id}" lost readiness during activation (${observed})` : `[stitchkit] required resource "${entry.id}" is not healthy (${observed}). A required resource must be healthy for the application to be ready; a resource that is expected to start degraded belongs behind \`required: false\`.`);
354
396
  }
355
397
  } catch (error) {
356
398
  const interrupted = shutdownRequested || startupAbort.signal.aborted;
@@ -369,7 +411,11 @@ function createApplication(config) {
369
411
  throw new ApplicationStartupInterruptedError;
370
412
  }
371
413
  if (!isReady()) {
372
- throw new Error("[stitchkit] a required resource lost readiness during startup");
414
+ const blocking = [...records.values()].filter((record) => record.entry.required && (record.state !== "ready" || record.health !== "healthy")).map((record) => `${record.entry.id} (${record.state}/${record.health})`);
415
+ if (blocking.length === 0)
416
+ blocking.push("(none identified)");
417
+ const chosen = [...records.values()].some((record) => record.entry.required && record.health !== "healthy" && !record.everHealthy);
418
+ throw new Error(`[stitchkit] the application is not ready after startup — required ${blocking.length === 1 ? "resource" : "resources"} ${blocking.join(", ")}. A required resource must be healthy for the application to be ready; ${chosen ? "a resource that is expected to start degraded belongs behind `required: false`." : "this one was healthy and stopped being so — read `onResourceFailure` for the cause."}`);
373
419
  }
374
420
  activationComplete = true;
375
421
  accepting = isReady();
@@ -445,7 +491,12 @@ function createApplication(config) {
445
491
  const shutdown = (options) => {
446
492
  if (shutdownPromise)
447
493
  return shutdownPromise;
448
- const parsed = ApplicationShutdownOptionsSchema.parse(options ?? {});
494
+ const requested = options ?? {};
495
+ const parsed = ApplicationShutdownOptionsSchema.parse({
496
+ gracePeriodMs: requested.gracePeriodMs ?? shutdownBudget.gracePeriodMs,
497
+ forceTimeoutMs: requested.forceTimeoutMs ?? shutdownBudget.forceTimeoutMs,
498
+ ...requested.signal !== undefined && { signal: requested.signal }
499
+ });
449
500
  const startedAt = performance.now();
450
501
  const graceDeadlineAt = startedAt + parsed.gracePeriodMs;
451
502
  const forceDeadlineAt = graceDeadlineAt + parsed.forceTimeoutMs;
@@ -648,4 +699,4 @@ function createApplication(config) {
648
699
  };
649
700
  }
650
701
 
651
- export { ApplicationShutdownOptionsSchema, ApplicationAdmissionError, createApplication };
702
+ export { ApplicationShutdownOptionsSchema, ApplicationShutdownBudgetSchema, ApplicationAdmissionError, createApplication };
@@ -2,18 +2,20 @@ import {
2
2
  contextContributionDelta,
3
3
  mergeContextContribution,
4
4
  prepareContextContribution
5
- } from "./index-3hn10n4c.js";
5
+ } from "./index-y91zd0ch.js";
6
6
  import {
7
7
  base64UrlToBytes,
8
8
  bytesToBase64Url,
9
- forbidden,
10
9
  isUnsafeKey,
11
- safeJsonParse,
12
- unauthorized
13
- } from "./index-v6e8hnkx.js";
10
+ safeJsonParse
11
+ } from "./index-wd8g8z6e.js";
14
12
  import {
15
13
  isRecord
16
14
  } from "./index-smpbdg6k.js";
15
+ import {
16
+ forbidden,
17
+ unauthorized
18
+ } from "./index-xxye8j3k.js";
17
19
 
18
20
  // src/server/middleware/cookies.ts
19
21
  function parseCookies(header) {
@@ -7,7 +7,7 @@ import {
7
7
  defaultSignalSource,
8
8
  guardSignalCallback,
9
9
  reportSignalError
10
- } from "./index-3hn10n4c.js";
10
+ } from "./index-y91zd0ch.js";
11
11
  import {
12
12
  ShutdownOptionsSchema
13
13
  } from "./index-dk6e56g0.js";
@@ -26,13 +26,11 @@ import {
26
26
  setRequestError,
27
27
  validateDeclaredOutput,
28
28
  zodIssues
29
- } from "./index-6fbp58g8.js";
29
+ } from "./index-tvwcrx2d.js";
30
30
  import {
31
- AppError,
32
- badRequest,
33
31
  isUnsafeKey,
34
32
  safeJsonParse
35
- } from "./index-v6e8hnkx.js";
33
+ } from "./index-wd8g8z6e.js";
36
34
  import {
37
35
  joinRoutePath,
38
36
  parseTrailingWildcard,
@@ -42,6 +40,10 @@ import {
42
40
  isRecord,
43
41
  transportResult
44
42
  } from "./index-smpbdg6k.js";
43
+ import {
44
+ AppError,
45
+ badRequest
46
+ } from "./index-xxye8j3k.js";
45
47
 
46
48
  // src/server/multipart.ts
47
49
  var DEFAULT_MAX_REQUEST_BYTES = 25 * 1024 * 1024;
@@ -1561,6 +1563,46 @@ function bindProcessSignals(handle, options = {}) {
1561
1563
  return { promise, close };
1562
1564
  }
1563
1565
 
1566
+ // src/realtime/rejected-frame.ts
1567
+ var REALTIME_REJECTION_KEY = "@stitchkit/realtime-rejected";
1568
+ var MAX_REJECTION_ISSUES = 20;
1569
+ function realtimeRejectionEnvelope(report) {
1570
+ return { [REALTIME_REJECTION_KEY]: report };
1571
+ }
1572
+ function asRealtimeRejection(value) {
1573
+ if (typeof value !== "object" || value === null)
1574
+ return null;
1575
+ const report = Reflect.get(value, REALTIME_REJECTION_KEY);
1576
+ if (typeof report !== "object" || report === null)
1577
+ return null;
1578
+ const event = Reflect.get(report, "event");
1579
+ const reason = Reflect.get(report, "reason");
1580
+ const message = Reflect.get(report, "message");
1581
+ if (typeof event !== "string" || typeof reason !== "string" || typeof message !== "string") {
1582
+ return null;
1583
+ }
1584
+ if (reason.length === 0)
1585
+ return null;
1586
+ const issues = parseIssues(Reflect.get(report, "issues"));
1587
+ return { event, reason, message, ...issues !== undefined && { issues } };
1588
+ }
1589
+ function parseIssues(value) {
1590
+ if (!Array.isArray(value))
1591
+ return;
1592
+ const issues = [];
1593
+ for (const entry of value.slice(0, MAX_REJECTION_ISSUES)) {
1594
+ if (typeof entry !== "object" || entry === null)
1595
+ continue;
1596
+ const path = Reflect.get(entry, "path");
1597
+ const code = Reflect.get(entry, "code");
1598
+ const message = Reflect.get(entry, "message");
1599
+ if (typeof path === "string" && typeof code === "string" && typeof message === "string") {
1600
+ issues.push({ path, code, message });
1601
+ }
1602
+ }
1603
+ return issues.length > 0 ? issues : undefined;
1604
+ }
1605
+
1564
1606
  // src/realtime/rejection.ts
1565
1607
  import { z } from "zod";
1566
1608
  function realtimeContractViolation(options) {
@@ -1588,15 +1630,11 @@ function method(target, name) {
1588
1630
  return (...args) => Reflect.apply(value, target, args);
1589
1631
  }
1590
1632
  function splitWireArguments(definition, args) {
1591
- if (!definition.ack)
1592
- return { values: args };
1593
- const acknowledgement = args.at(-1);
1594
- if (typeof acknowledgement !== "function")
1595
- return { values: args };
1596
- return {
1597
- values: args.slice(0, -1),
1598
- acknowledgement: (...callbackArgs) => Reflect.apply(acknowledgement, undefined, callbackArgs)
1599
- };
1633
+ const trailing = args.at(-1);
1634
+ const replyTo = typeof trailing === "function" ? (...callbackArgs) => Reflect.apply(trailing, undefined, callbackArgs) : undefined;
1635
+ if (!definition.ack || !replyTo)
1636
+ return { values: args, ...replyTo && { replyTo } };
1637
+ return { values: args.slice(0, -1), acknowledgement: replyTo, replyTo };
1600
1638
  }
1601
1639
  function eventDefinition(registry, event, direction) {
1602
1640
  const definition = registry[event];
@@ -1624,6 +1662,19 @@ function reportRejected(rejected, hook, logger) {
1624
1662
  console.error("[stitchkit] realtime rejection hook failed", error);
1625
1663
  });
1626
1664
  }
1665
+ function answerWithRejection(acknowledgement, report, logger) {
1666
+ if (!acknowledgement)
1667
+ return;
1668
+ try {
1669
+ acknowledgement(realtimeRejectionEnvelope(report));
1670
+ } catch (error) {
1671
+ const message = "[stitchkit] could not answer a rejected realtime frame";
1672
+ if (logger)
1673
+ logger.warn(message, { error });
1674
+ else
1675
+ console.warn(message, error);
1676
+ }
1677
+ }
1627
1678
  function createValidatedRealtimeSocket({
1628
1679
  target,
1629
1680
  inbound,
@@ -1639,17 +1690,24 @@ function createValidatedRealtimeSocket({
1639
1690
  on: (event, handler) => {
1640
1691
  const definition = eventDefinition(inbound, event, inboundDirection);
1641
1692
  const wrapped = (...wireArgs) => {
1642
- const { values, acknowledgement } = splitWireArguments(definition, wireArgs);
1693
+ const { values, acknowledgement, replyTo } = splitWireArguments(definition, wireArgs);
1643
1694
  const parsed = definition.args.safeParse(values);
1644
1695
  if (!parsed.success) {
1645
- reportRejected(realtimeContractViolation({
1696
+ const rejected = realtimeContractViolation({
1646
1697
  event,
1647
1698
  direction: inboundDirection,
1648
1699
  phase: "arguments",
1649
1700
  reason: "invalid-arguments",
1650
1701
  fault: "peer",
1651
1702
  cause: parsed.error
1652
- }), onRejected, logger);
1703
+ });
1704
+ reportRejected(rejected, onRejected, logger);
1705
+ answerWithRejection(replyTo, {
1706
+ event,
1707
+ reason: "invalid-arguments",
1708
+ message: rejected.error.message,
1709
+ issues: zodIssues(parsed.error).slice(0, MAX_REJECTION_ISSUES)
1710
+ }, logger);
1653
1711
  return;
1654
1712
  }
1655
1713
  if (definition.ack && !acknowledgement) {
@@ -1719,6 +1777,17 @@ function createValidatedRealtimeSocket({
1719
1777
  const wireArgs = [...parsed.data];
1720
1778
  if (definition.ack && acknowledgement) {
1721
1779
  wireArgs.push((value) => {
1780
+ const refusal = asRealtimeRejection(value);
1781
+ if (refusal) {
1782
+ reportRejected(realtimeContractViolation({
1783
+ event,
1784
+ direction: outboundDirection,
1785
+ phase: "arguments",
1786
+ reason: "rejected-by-peer",
1787
+ fault: "local"
1788
+ }), onRejected, logger);
1789
+ return;
1790
+ }
1722
1791
  const ack = definition.ack?.safeParse(value);
1723
1792
  if (!ack?.success) {
1724
1793
  if (ack) {
@@ -1839,17 +1908,19 @@ function composeWebSocketHandlers(lanes, config) {
1839
1908
  };
1840
1909
  }
1841
1910
 
1911
+ // src/internal/optional-peer.ts
1912
+ function isModuleNotFound(error) {
1913
+ if (typeof error !== "object" || error === null)
1914
+ return false;
1915
+ const code = "code" in error ? error.code : undefined;
1916
+ const message = "message" in error && typeof error.message === "string" ? error.message : "";
1917
+ return code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND" || message.includes("Cannot find module") || message.includes("Cannot find package");
1918
+ }
1919
+
1842
1920
  // src/server/socket-io.ts
1843
1921
  var SOCKET_IO_SERVER = "socket.io";
1844
1922
  var SOCKET_IO_BUN_ENGINE = "@socket.io/bun-engine";
1845
1923
  var noop = () => {};
1846
- function isModuleNotFound(err) {
1847
- if (typeof err !== "object" || err === null)
1848
- return false;
1849
- const code = "code" in err ? err.code : undefined;
1850
- const message = "message" in err && typeof err.message === "string" ? err.message : "";
1851
- return code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND" || message.includes("Cannot find module") || message.includes("Cannot find package");
1852
- }
1853
1924
  async function importPeer(load, pkg, injected) {
1854
1925
  try {
1855
1926
  return await load();
@@ -1,13 +1,15 @@
1
1
  import {
2
- AppError,
3
2
  isUnsafeKey
4
- } from "./index-v6e8hnkx.js";
3
+ } from "./index-wd8g8z6e.js";
5
4
  import {
6
5
  resolveTraceContext
7
6
  } from "./index-pzyt11ch.js";
8
7
  import {
9
8
  isRecord
10
9
  } from "./index-smpbdg6k.js";
10
+ import {
11
+ AppError
12
+ } from "./index-xxye8j3k.js";
11
13
 
12
14
  // src/internal/errors.ts
13
15
  import { z } from "zod";
@@ -2,89 +2,6 @@ import {
2
2
  parseTrailingWildcard
3
3
  } from "./index-pzyt11ch.js";
4
4
 
5
- // src/contract/errors.ts
6
- var APP_ERROR_BRAND = Symbol.for("stitchkit.AppError");
7
-
8
- class AppError extends Error {
9
- code;
10
- status;
11
- details;
12
- hint;
13
- traceId;
14
- constructor(code, message, status = 500, details, hint, traceId) {
15
- super(message ?? code);
16
- this.code = code;
17
- this.status = status;
18
- this.details = details;
19
- this.hint = hint;
20
- this.traceId = traceId;
21
- this.name = "AppError";
22
- Object.defineProperty(this, APP_ERROR_BRAND, { value: true });
23
- }
24
- static is(err) {
25
- return typeof err === "object" && err !== null && APP_ERROR_BRAND in err;
26
- }
27
- toJSON() {
28
- return {
29
- error: {
30
- code: this.code,
31
- message: this.message,
32
- ...this.details && { details: this.details },
33
- ...this.hint && { hint: this.hint }
34
- }
35
- };
36
- }
37
- }
38
- function notFound(message = "Not found") {
39
- throw new AppError("NOT_FOUND", message, 404);
40
- }
41
- function badRequest(message, details) {
42
- throw new AppError("BAD_REQUEST", message, 400, details);
43
- }
44
- function unauthorized(message = "Unauthorized") {
45
- throw new AppError("UNAUTHORIZED", message, 401);
46
- }
47
- function forbidden(message = "Forbidden") {
48
- throw new AppError("FORBIDDEN", message, 403);
49
- }
50
- function conflict(message = "Conflict", details) {
51
- throw new AppError("CONFLICT", message, 409, details);
52
- }
53
- function rateLimited(message = "Too many requests") {
54
- throw new AppError("RATE_LIMITED", message, 429);
55
- }
56
- var STITCH_ERROR_STATUS = {
57
- BAD_REQUEST: 400,
58
- UNAUTHORIZED: 401,
59
- FORBIDDEN: 403,
60
- NOT_FOUND: 404,
61
- METHOD_NOT_ALLOWED: 405,
62
- CONFLICT: 409,
63
- RATE_LIMITED: 429,
64
- VALIDATION_ERROR: 400,
65
- FILE_INVALID_PATH: 400,
66
- FILE_OUTSIDE_ROOT: 400,
67
- FILE_NOT_FOUND: 404,
68
- FILE_NOT_REGULAR: 422,
69
- FILE_INSPECTION_REJECTED: 422,
70
- FILE_TOO_LARGE: 413,
71
- FILE_EXISTS: 409,
72
- REALTIME_CONTRACT_VIOLATION: 500,
73
- APPLICATION_NOT_ACCEPTING: 503,
74
- WAIT_TIMEOUT: 408,
75
- WAIT_FAILED: 409,
76
- DOWNLOAD_NOT_FOUND: 404,
77
- VIEW_HTTP_ERROR: 502,
78
- OPERATION_NOT_SUCCEEDED: 409,
79
- INTERNAL_SERVER_ERROR: 500
80
- };
81
- function isStitchErrorCode(code) {
82
- return Object.hasOwn(STITCH_ERROR_STATUS, code);
83
- }
84
- function appError(code, message, details) {
85
- throw new AppError(code, message, isStitchErrorCode(code) ? STITCH_ERROR_STATUS[code] : 500, details);
86
- }
87
-
88
5
  // src/contract/define.ts
89
6
  import { z } from "zod";
90
7
 
@@ -286,4 +203,4 @@ function base64UrlToBytes(segment) {
286
203
  const padded = b64.padEnd(Math.ceil(b64.length / 4) * 4, "=");
287
204
  return Uint8Array.from(atob(padded), (c) => c.charCodeAt(0));
288
205
  }
289
- export { isUnsafeKey, safeJsonParse, defineContract, mergeMeta, AppError, notFound, badRequest, unauthorized, forbidden, conflict, rateLimited, STITCH_ERROR_STATUS, isStitchErrorCode, appError, bytesToBase64Url, base64UrlToBytes };
206
+ export { isUnsafeKey, safeJsonParse, defineContract, mergeMeta, bytesToBase64Url, base64UrlToBytes };
@@ -7,17 +7,19 @@ import {
7
7
  normalizeError,
8
8
  runWithRequestContext,
9
9
  validateDeclaredOutput
10
- } from "./index-6fbp58g8.js";
10
+ } from "./index-tvwcrx2d.js";
11
11
  import {
12
- AppError,
13
- STITCH_ERROR_STATUS,
14
- isStitchErrorCode,
15
12
  isUnsafeKey,
16
13
  safeJsonParse
17
- } from "./index-v6e8hnkx.js";
14
+ } from "./index-wd8g8z6e.js";
18
15
  import {
19
16
  isRecord
20
17
  } from "./index-smpbdg6k.js";
18
+ import {
19
+ AppError,
20
+ STITCH_ERROR_STATUS,
21
+ isStitchErrorCode
22
+ } from "./index-xxye8j3k.js";
21
23
 
22
24
  // src/tools/coerce.ts
23
25
  import { z } from "zod";