@voltro/protocol 0.81.0 → 0.82.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/inspect.d.ts CHANGED
@@ -2,9 +2,9 @@ import { ParseOptions } from 'effect/SchemaAST';
2
2
  import { Schema } from 'effect';
3
3
 
4
4
  export declare const decodeInspectBackfillInput: (u: unknown, overrideOptions?: ParseOptions) => {
5
- readonly from: string;
6
5
  readonly name: string;
7
6
  readonly limit: number;
7
+ readonly from: string;
8
8
  readonly to: string;
9
9
  readonly requestId: string;
10
10
  readonly confirm: boolean;
@@ -78,6 +78,72 @@ export declare type InspectReplicaSelection = {
78
78
  readonly replicaIds: ReadonlyArray<string>;
79
79
  };
80
80
 
81
+ export declare type InspectWorkflowExecutionObservation = typeof InspectWorkflowExecutionObservationSchema.Type;
82
+
83
+ /** A projection of engine evidence, not a run-row or recorder-entry claim.
84
+ * Deliberately excludes payloads, outputs, raw causes and journal identifiers. */
85
+ export declare const InspectWorkflowExecutionObservationSchema: Schema.Union<[Schema.Struct<{
86
+ kind: Schema.Literal<["not-observed"]>;
87
+ workflowName: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
88
+ executionId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
89
+ }>, Schema.Struct<{
90
+ kind: Schema.Literal<["pending"]>;
91
+ workflowName: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
92
+ executionId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
93
+ }>, Schema.Struct<{
94
+ kind: Schema.Literal<["delivery-failed"]>;
95
+ workflowName: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
96
+ executionId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
97
+ }>, Schema.Struct<{
98
+ kind: Schema.Literal<["suspended"]>;
99
+ workflowName: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
100
+ executionId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
101
+ }>, Schema.Struct<{
102
+ kind: Schema.Literal<["completed"]>;
103
+ outcome: Schema.Literal<["succeeded", "failed", "cancelled"]>;
104
+ workflowName: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
105
+ executionId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
106
+ }>]>;
107
+
108
+ export declare type InspectWorkflowExecutionTarget = typeof InspectWorkflowExecutionTargetSchema.Type;
109
+
110
+ /** Read an existing execution identity; this is never a start command. */
111
+ export declare const InspectWorkflowExecutionTargetSchema: Schema.Struct<{
112
+ workflowName: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
113
+ executionId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
114
+ }>;
115
+
116
+ export declare type InspectWorkflowQueuedStartObservation = typeof InspectWorkflowQueuedStartObservationSchema.Type;
117
+
118
+ /** Read-only admission evidence. Accepted means an execution exists, not that it succeeded. */
119
+ export declare const InspectWorkflowQueuedStartObservationSchema: Schema.Union<[Schema.Struct<{
120
+ kind: Schema.Literal<["pending"]>;
121
+ workflowName: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
122
+ receiptId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
123
+ tenantId: Schema.NullOr<Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>>;
124
+ }>, Schema.Struct<{
125
+ kind: Schema.Literal<["accepted"]>;
126
+ executionId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
127
+ workflowName: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
128
+ receiptId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
129
+ tenantId: Schema.NullOr<Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>>;
130
+ }>, Schema.Struct<{
131
+ kind: Schema.Literal<["not-executed"]>;
132
+ reason: Schema.Literal<["dropped", "skipped", "superseded", "cancelled", "abandoned", "rejected"]>;
133
+ workflowName: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
134
+ receiptId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
135
+ tenantId: Schema.NullOr<Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>>;
136
+ }>]>;
137
+
138
+ export declare type InspectWorkflowQueuedStartTarget = typeof InspectWorkflowQueuedStartTargetSchema.Type;
139
+
140
+ /** A caller receipt, never an execution or a reusable admission slot. */
141
+ export declare const InspectWorkflowQueuedStartTargetSchema: Schema.Struct<{
142
+ workflowName: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
143
+ receiptId: Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>;
144
+ tenantId: Schema.NullOr<Schema.filter<Schema.filter<typeof Schema.NonEmptyString>>>;
145
+ }>;
146
+
81
147
  export declare class InvalidReplicaSelection extends Error {
82
148
  readonly name = "InvalidReplicaSelection";
83
149
  }
package/dist/inspect.js CHANGED
@@ -1,85 +1,119 @@
1
1
  import { Schema as e } from "effect";
2
- //#region src/inspectBackfill.ts
3
- var t = e.String.pipe(e.filter((e) => Number.isFinite(Date.parse(e)))), n = e.String.pipe(e.minLength(1), e.maxLength(256)), r = e.Struct({
4
- id: n,
5
- name: n,
6
- from: t,
7
- to: t,
2
+ //#region src/inspectWorkflowQueuedStart.ts
3
+ var t = e.NonEmptyString.pipe(e.maxLength(1024), e.filter((e) => e.trim().length > 0 && !/[\u0000-\u001f\u007f]/.test(e))), n = e.Struct({
4
+ workflowName: t,
5
+ receiptId: t,
6
+ tenantId: e.NullOr(t)
7
+ }), r = n.fields, i = e.Union(e.Struct({
8
+ ...r,
9
+ kind: e.Literal("pending")
10
+ }), e.Struct({
11
+ ...r,
12
+ kind: e.Literal("accepted"),
13
+ executionId: t
14
+ }), e.Struct({
15
+ ...r,
16
+ kind: e.Literal("not-executed"),
17
+ reason: e.Literal("dropped", "skipped", "superseded", "cancelled", "abandoned", "rejected")
18
+ })), a = e.String.pipe(e.filter((e) => Number.isFinite(Date.parse(e)))), o = e.String.pipe(e.minLength(1), e.maxLength(256)), s = e.Struct({
19
+ id: o,
20
+ name: o,
21
+ from: a,
22
+ to: a,
8
23
  occurrences: e.Array(e.Number),
9
24
  status: e.Literal("queued", "running", "completed", "interrupted"),
10
25
  owner: e.NullOr(e.String),
11
- createdAt: t,
12
- updatedAt: t,
26
+ createdAt: a,
27
+ updatedAt: a,
13
28
  cursor: e.Number,
14
29
  activeIndex: e.NullOr(e.Number),
15
30
  results: e.Array(e.Struct({
16
- runId: n,
31
+ runId: o,
17
32
  status: e.Literal("succeeded", "failed", "skipped", "missed")
18
33
  })),
19
34
  error: e.NullOr(e.String)
20
- }), i = e.Struct({
21
- requestId: n,
22
- name: n,
23
- from: t,
24
- to: t,
35
+ }), c = e.Struct({
36
+ requestId: o,
37
+ name: o,
38
+ from: a,
39
+ to: a,
25
40
  confirm: e.Boolean,
26
41
  limit: e.Number.pipe(e.int(), e.between(1, 1e4))
27
- }), a = e.decodeUnknownSync(i), o = e.Union(e.Struct({
42
+ }), l = e.decodeUnknownSync(c), u = e.Union(e.Struct({
28
43
  kind: e.Literal("accepted"),
29
- request: r
44
+ request: s
30
45
  }), e.Struct({
31
46
  kind: e.Literal("refused"),
32
- name: n,
47
+ name: o,
33
48
  occurrences: e.Number,
34
49
  reason: e.String
35
- })), s = class extends Error {
50
+ })), d = class extends Error {
36
51
  name = "ScheduleBackfillRequestConflict";
37
- }, c = (e) => typeof e == "object" && e && !Array.isArray(e) ? e : void 0, l = (e) => Array.isArray(e) && e.every((e) => c(e) !== void 0) ? e : void 0, u = (e) => typeof e == "number" && Number.isFinite(e) ? e : 0, d = /* @__PURE__ */ new Set([
52
+ }, f = e.NonEmptyString.pipe(e.maxLength(1024), e.filter((e) => e.trim().length > 0 && !/[\u0000-\u001f\u007f]/.test(e))), p = e.Struct({
53
+ workflowName: f,
54
+ executionId: f
55
+ }), m = p.fields, h = e.Union(e.Struct({
56
+ ...m,
57
+ kind: e.Literal("not-observed")
58
+ }), e.Struct({
59
+ ...m,
60
+ kind: e.Literal("pending")
61
+ }), e.Struct({
62
+ ...m,
63
+ kind: e.Literal("delivery-failed")
64
+ }), e.Struct({
65
+ ...m,
66
+ kind: e.Literal("suspended")
67
+ }), e.Struct({
68
+ ...m,
69
+ kind: e.Literal("completed"),
70
+ outcome: e.Literal("succeeded", "failed", "cancelled")
71
+ })), g = (e) => typeof e == "object" && e && !Array.isArray(e) ? e : void 0, _ = (e) => Array.isArray(e) && e.every((e) => g(e) !== void 0) ? e : void 0, v = (e) => typeof e == "number" && Number.isFinite(e) ? e : 0, y = /* @__PURE__ */ new Set([
38
72
  "counter",
39
73
  "gauge",
40
74
  "histogram",
41
75
  "summary",
42
76
  "frequency"
43
- ]), f = (e) => {
44
- let t = l(e);
45
- return t?.every((e) => typeof e.name == "string" && d.has(String(e.type))) ? t : void 0;
46
- }, p = (e) => {
47
- let t = c(e), n = c(t?.data), r = l(n?.results);
77
+ ]), b = (e) => {
78
+ let t = _(e);
79
+ return t?.every((e) => typeof e.name == "string" && y.has(String(e.type))) ? t : void 0;
80
+ }, x = (e) => {
81
+ let t = g(e), n = g(t?.data), r = _(n?.results);
48
82
  if (!t || !n?.selection || !r) return e;
49
83
  let i = r.filter((e) => e.status === "answered").map((e) => ({
50
84
  replicaId: String(e.replicaId),
51
- observation: c(e.observation),
52
- data: c(e.observation)?.data
85
+ observation: g(e.observation),
86
+ data: g(e.observation)?.data
53
87
  }));
54
88
  if (i.length === 0) throw Error("None of the selected replicas answered. Check replica availability and retry.");
55
89
  let a = i.map((e) => ({
56
90
  ...e,
57
- object: c(e.data)
58
- })), o = (e) => a.reduce((t, n) => t + u(n.object?.[e]), 0), s = Math.max(...i.map((e) => u(e.observation?.capturedAt))), d = {
91
+ object: g(e.data)
92
+ })), o = (e) => a.reduce((t, n) => t + v(n.object?.[e]), 0), s = Math.max(...i.map((e) => v(e.observation?.capturedAt))), c = {
59
93
  capturedAt: s,
60
94
  bufferSize: o("bufferSize"),
61
95
  bufferCapacity: o("bufferCapacity")
62
- }, p;
63
- if (a.every((e) => l(e.object?.records))) {
64
- let e = a.flatMap((e) => l(e.object?.records).map((t) => ({
96
+ }, l;
97
+ if (a.every((e) => _(e.object?.records))) {
98
+ let e = a.flatMap((e) => _(e.object?.records).map((t) => ({
65
99
  ...t,
66
100
  fields: {
67
- ...c(t.fields),
101
+ ...g(t.fields),
68
102
  "voltro.replica": e.replicaId
69
103
  }
70
- }))).sort((e, t) => u(e.ts) - u(t.ts));
71
- p = {
72
- ...d,
104
+ }))).sort((e, t) => v(e.ts) - v(t.ts));
105
+ l = {
106
+ ...c,
73
107
  returned: e.length,
74
108
  records: e
75
109
  };
76
- } else if (a.every((e) => l(e.object?.traces))) {
110
+ } else if (a.every((e) => _(e.object?.traces))) {
77
111
  let e = /* @__PURE__ */ new Map();
78
- for (let t of a) for (let n of l(t.object?.traces)) {
79
- let r = String(n.traceId), i = (l(n.spans) ?? []).map((e) => ({
112
+ for (let t of a) for (let n of _(t.object?.traces)) {
113
+ let r = String(n.traceId), i = (_(n.spans) ?? []).map((e) => ({
80
114
  ...e,
81
115
  attributes: {
82
- ...c(e.attributes),
116
+ ...g(e.attributes),
83
117
  "voltro.replica": t.replicaId
84
118
  }
85
119
  })), a = e.get(r) ?? [];
@@ -89,7 +123,7 @@ var t = e.String.pipe(e.filter((e) => Number.isFinite(Date.parse(e)))), n = e.St
89
123
  }), e.set(r, a);
90
124
  }
91
125
  let t = [...e.values()].map((e) => {
92
- let t = e.flatMap((e) => l(e.spans) ?? []).sort((e, t) => u(e.startMs) - u(t.startMs)), n = [...e].sort((e, t) => u(e.startMs) - u(t.startMs))[0], r = Math.min(...e.map((e) => u(e.startMs))), i = Math.max(...e.map((e) => u(e.startMs) + u(e.durationMs)));
126
+ let t = e.flatMap((e) => _(e.spans) ?? []).sort((e, t) => v(e.startMs) - v(t.startMs)), n = [...e].sort((e, t) => v(e.startMs) - v(t.startMs))[0], r = Math.min(...e.map((e) => v(e.startMs))), i = Math.max(...e.map((e) => v(e.startMs) + v(e.durationMs)));
93
127
  return {
94
128
  ...n,
95
129
  startMs: r,
@@ -97,65 +131,65 @@ var t = e.String.pipe(e.filter((e) => Number.isFinite(Date.parse(e)))), n = e.St
97
131
  spanCount: t.length,
98
132
  hasError: e.some((e) => e.hasError === !0),
99
133
  spans: t,
100
- ...e.some((e) => e.deliveryCount !== void 0) ? { deliveryCount: e.reduce((e, t) => e + u(t.deliveryCount), 0) } : {},
101
- ...e.some((e) => e.lastDeliveryMs !== void 0) ? { lastDeliveryMs: Math.max(...e.map((e) => u(e.lastDeliveryMs))) } : {}
134
+ ...e.some((e) => e.deliveryCount !== void 0) ? { deliveryCount: e.reduce((e, t) => e + v(t.deliveryCount), 0) } : {},
135
+ ...e.some((e) => e.lastDeliveryMs !== void 0) ? { lastDeliveryMs: Math.max(...e.map((e) => v(e.lastDeliveryMs))) } : {}
102
136
  };
103
137
  }).sort((e, t) => t.startMs - e.startMs);
104
- p = {
105
- ...d,
138
+ l = {
139
+ ...c,
106
140
  returned: t.length,
107
141
  traces: t
108
142
  };
109
- } else if (a.every((e) => l(e.object?.buckets) && typeof e.object?.windowMs == "number")) p = {
143
+ } else if (a.every((e) => _(e.object?.buckets) && typeof e.object?.windowMs == "number")) l = {
110
144
  capturedAt: s,
111
- windowMs: Math.max(...a.map((e) => u(e.object?.windowMs))),
145
+ windowMs: Math.max(...a.map((e) => v(e.object?.windowMs))),
112
146
  totalSamples: o("totalSamples"),
113
- buckets: a.flatMap((e) => l(e.object?.buckets).map((t) => ({
147
+ buckets: a.flatMap((e) => _(e.object?.buckets).map((t) => ({
114
148
  ...t,
115
149
  tag: `${String(t.tag)} [${e.replicaId}]`
116
150
  })))
117
151
  };
118
- else if (i.every((e) => f(e.data))) p = i.flatMap((e) => f(e.data).map((t) => ({
152
+ else if (i.every((e) => b(e.data))) l = i.flatMap((e) => b(e.data).map((t) => ({
119
153
  ...t,
120
154
  labels: {
121
- ...c(t.labels),
155
+ ...g(t.labels),
122
156
  voltro_replica: e.replicaId
123
157
  }
124
158
  })));
125
- else if (a.every((e) => f(e.object?.samples))) p = { samples: a.flatMap((e) => f(e.object?.samples).map((t) => ({
159
+ else if (a.every((e) => b(e.object?.samples))) l = { samples: a.flatMap((e) => b(e.object?.samples).map((t) => ({
126
160
  ...t,
127
161
  labels: {
128
- ...c(t.labels),
162
+ ...g(t.labels),
129
163
  voltro_replica: e.replicaId
130
164
  }
131
165
  }))) };
132
- else if (r.length === 1 && i.length === 1) p = i[0].data;
166
+ else if (r.length === 1 && i.length === 1) l = i[0].data;
133
167
  else throw Error("This view must compare observations per replica; its values cannot be safely combined.");
134
168
  return {
135
169
  ...t,
136
- data: p
170
+ data: l
137
171
  };
138
- }, m = class extends Error {
172
+ }, S = class extends Error {
139
173
  name = "InvalidReplicaSelection";
140
- }, h = (e) => {
174
+ }, C = (e) => {
141
175
  let t = e.getAll("replicas"), n = e.getAll("replica");
142
176
  if (t.length !== 0 || n.length !== 0) {
143
- if (t.length > 1 || t.length === 1 && t[0] !== "all" || t.length > 0 && n.length > 0) throw new m("Choose all replicas or explicit replica IDs, not both.");
177
+ if (t.length > 1 || t.length === 1 && t[0] !== "all" || t.length > 0 && n.length > 0) throw new S("Choose all replicas or explicit replica IDs, not both.");
144
178
  if (t[0] === "all") return { mode: "all" };
145
- if (n.some((e) => e.trim().length === 0 || e.length > 512 || /[\u0000-\u001f\u007f]/.test(e))) throw new m("Replica IDs must be nonempty and contain no control characters.");
179
+ if (n.some((e) => e.trim().length === 0 || e.length > 512 || /[\u0000-\u001f\u007f]/.test(e))) throw new S("Replica IDs must be nonempty and contain no control characters.");
146
180
  return {
147
181
  mode: "selected",
148
182
  replicaIds: [...new Set(n)].sort()
149
183
  };
150
184
  }
151
- }, g = (e, t) => {
185
+ }, w = (e, t) => {
152
186
  let n = e.indexOf("#"), r = n < 0 ? "" : e.slice(n), i = n < 0 ? e : e.slice(0, n), a = i.indexOf("?"), o = a < 0 ? i : i.slice(0, a), s = new URLSearchParams(a < 0 ? "" : i.slice(a + 1));
153
187
  if (s.delete("replica"), s.delete("replicas"), t.mode === "all") s.set("replicas", "all");
154
188
  else {
155
- if (t.replicaIds.length === 0) throw new m("Select at least one replica.");
189
+ if (t.replicaIds.length === 0) throw new S("Select at least one replica.");
156
190
  for (let e of [...new Set(t.replicaIds)].sort()) s.append("replica", e);
157
191
  }
158
- return h(s), `${o}?${s}${r}`;
192
+ return C(s), `${o}?${s}${r}`;
159
193
  };
160
194
  //#endregion
161
- export { i as InspectBackfillInputSchema, r as InspectBackfillReceiptSchema, o as InspectBackfillResultSchema, m as InvalidReplicaSelection, s as ScheduleBackfillRequestConflict, a as decodeInspectBackfillInput, h as parseReplicaSelection, p as projectReplicaObservation, g as withReplicaSelection };
195
+ export { c as InspectBackfillInputSchema, s as InspectBackfillReceiptSchema, u as InspectBackfillResultSchema, h as InspectWorkflowExecutionObservationSchema, p as InspectWorkflowExecutionTargetSchema, i as InspectWorkflowQueuedStartObservationSchema, n as InspectWorkflowQueuedStartTargetSchema, S as InvalidReplicaSelection, d as ScheduleBackfillRequestConflict, l as decodeInspectBackfillInput, C as parseReplicaSelection, x as projectReplicaObservation, w as withReplicaSelection };
package/dist/jwt.d.ts CHANGED
@@ -26,7 +26,7 @@ declare interface AuthStrategy {
26
26
  readonly resolve: (input: AuthStrategyInput) => Promise<StrategyResolution> | StrategyResolution;
27
27
  /**
28
28
  * The bearer-token PREFIX this strategy claims, when it gates on one
29
- * (`'sk_'`, `'awb_'`). Declared so a collision is DETECTABLE.
29
+ * (`'sk_'`, `'acme_'`). Declared so a collision is DETECTABLE.
30
30
  *
31
31
  * Two strategies claiming the same prefix is not a harmless duplicate: the
32
32
  * chain is first-match-wins, so whichever runs first decides the Subject —
package/dist/rest.d.ts CHANGED
@@ -440,7 +440,7 @@ declare interface NestedTargetFields<Input = unknown> {
440
440
  /** Guard WHICH cached query entries this target patches: only entries whose
441
441
  * CURRENT value satisfies the predicate. Pure + browser-safe. Prevents a
442
442
  * patch from bleeding across sibling subscriptions that share a source table
443
- * (the guard AWB hand-writes as `roadmap.id === input.roadmapId`). Absent →
443
+ * (the guard an app would otherwise hand-write as `roadmap.id === input.roadmapId`). Absent →
444
444
  * every entry matching the target `table` is patched. */
445
445
  readonly match?: ((value: unknown, input: Input) => boolean) | undefined;
446
446
  }
@@ -799,7 +799,8 @@ export declare const problemTypeOf: (tag: string) => string;
799
799
  * and the status `errorStatus` assigns (`422` by default, the status the
800
800
  * OpenAPI projection already documents for a typed error); a plugin's
801
801
  * cross-cutting error answers under the status the plugin names. Anything
802
- * else is rethrown for the desugar's shared mapping (`ScopeError` → 403/401, a
802
+ * else is rethrown for the desugar's shared mapping (`ScopeError` → 403/401,
803
+ * a resource guard's `NoSuchResource` → 404, a
803
804
  * `status`-carrying error → itself, the rest → 500). The body is the tagged
804
805
  * `{ error, …fields }` form; `projectedFailureFor` speaks the profile.
805
806
  */
@@ -986,6 +987,13 @@ declare interface PublicApiSpec {
986
987
  * optional, the rest inherited. See `PublicApiProfileConfig`.
987
988
  */
988
989
  readonly format?: PublicApiProfileConfig;
990
+ /**
991
+ * GET only: how long a CDN may keep a successful answer, and the tags it can
992
+ * purge it by (`Cache-Tag`, `Surrogate-Key`). A request with a credential is
993
+ * always answered `private` — see `RestCache`. `tags` receives the answer as
994
+ * it goes on the wire.
995
+ */
996
+ readonly cache?: RestCache;
989
997
  }
990
998
 
991
999
  /**
@@ -1179,6 +1187,27 @@ export declare interface RestByteResponse {
1179
1187
  readonly headers?: Readonly<Record<string, string | ReadonlyArray<string>>>;
1180
1188
  }
1181
1189
 
1190
+ /**
1191
+ * Caching for a GET route's successful (200) answers.
1192
+ *
1193
+ * An anonymous request's answer is `public` for `maxAge` seconds (`max-age`
1194
+ * and `s-maxage`), `stale-while-revalidate` when set, and carries `tags` as
1195
+ * `Cache-Tag` (comma-separated) and `Surrogate-Key` (space-separated) — the
1196
+ * headers CDNs purge by. A request that carries a credential (`Authorization`
1197
+ * or a cookie) gets `private, no-cache` and no tags, whatever is declared:
1198
+ * what a key or a session may see must never land in a shared cache. Every
1199
+ * such answer says `Vary: Authorization, Cookie`. Errors are not affected.
1200
+ */
1201
+ export declare interface RestCache {
1202
+ /** Seconds a cache may serve the answer without asking again. */
1203
+ readonly maxAge: number;
1204
+ /** Seconds after that a stale answer may still be served while the cache refetches. */
1205
+ readonly staleWhileRevalidate?: number;
1206
+ /** Purge tags from the answer as it goes on the wire, and the input.
1207
+ * A tag with whitespace or a comma is dropped (no header can carry it). */
1208
+ readonly tags?: (output: unknown, input: unknown) => ReadonlyArray<string>;
1209
+ }
1210
+
1182
1211
  export declare type RestDates = 'iso' | 'epoch-ms';
1183
1212
 
1184
1213
  export declare type RestEnvelope = 'none' | 'data' | 'jsonapi';
@@ -1270,6 +1299,11 @@ export declare interface RestGuardRejection {
1270
1299
  readonly message: string;
1271
1300
  }
1272
1301
 
1302
+ /** What a REST handler may return: the decoded output, a `response(...)` with
1303
+ * its own status and headers, an `sse(...)` stream, or `bytes(...)` for a
1304
+ * non-JSON body (a file, an export, an XML document). */
1305
+ export declare type RestHandlerResult<O> = O | RestResponse<O> | RestStreamResponse | RestByteResponse;
1306
+
1273
1307
  export declare type RestMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
1274
1308
 
1275
1309
  export declare type RestNaming = 'camelCase' | 'snake_case';
@@ -1333,7 +1367,7 @@ export declare interface RestRouteDescriptor<I, O> {
1333
1367
  /** Output schema. The handler returns the decoded `Type`; the desugar
1334
1368
  * encodes it to JSON for the `200` response. */
1335
1369
  readonly output: Schema.Schema<O, any, never>;
1336
- readonly handler: (input: I, ctx: RestRouteContext) => Promise<O | RestResponse<O>> | O | RestResponse<O>;
1370
+ readonly handler: (input: I, ctx: RestRouteContext) => Promise<RestHandlerResult<O>> | RestHandlerResult<O>;
1337
1371
  readonly summary?: string;
1338
1372
  readonly description?: string;
1339
1373
  readonly example?: RestRouteExample;
@@ -1390,6 +1424,9 @@ export declare interface RestRouteDescriptor<I, O> {
1390
1424
  * of the JSON), so it is correct across content-encodings — the
1391
1425
  * transport's compression varies the bytes, not the representation. */
1392
1426
  readonly etag?: boolean;
1427
+ /** GET only: how long a shared cache (a CDN) may keep a successful answer,
1428
+ * and the tags it can be purged by. See {@link RestCache}. */
1429
+ readonly cache?: RestCache;
1393
1430
  }
1394
1431
 
1395
1432
  /** Intentional non-2xx REST control flow with an application-defined JSON