@graphorin/server 0.5.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 (204) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +116 -0
  4. package/dist/app.d.ts +174 -0
  5. package/dist/app.d.ts.map +1 -0
  6. package/dist/app.js +684 -0
  7. package/dist/app.js.map +1 -0
  8. package/dist/commentary/audit-bridge.d.ts +57 -0
  9. package/dist/commentary/audit-bridge.d.ts.map +1 -0
  10. package/dist/commentary/audit-bridge.js +92 -0
  11. package/dist/commentary/audit-bridge.js.map +1 -0
  12. package/dist/commentary/built-in-patterns.d.ts +24 -0
  13. package/dist/commentary/built-in-patterns.d.ts.map +1 -0
  14. package/dist/commentary/built-in-patterns.js +62 -0
  15. package/dist/commentary/built-in-patterns.js.map +1 -0
  16. package/dist/commentary/index.d.ts +5 -0
  17. package/dist/commentary/index.js +5 -0
  18. package/dist/commentary/sanitizer.d.ts +37 -0
  19. package/dist/commentary/sanitizer.d.ts.map +1 -0
  20. package/dist/commentary/sanitizer.js +182 -0
  21. package/dist/commentary/sanitizer.js.map +1 -0
  22. package/dist/commentary/types.d.ts +120 -0
  23. package/dist/commentary/types.d.ts.map +1 -0
  24. package/dist/config.d.ts +760 -0
  25. package/dist/config.d.ts.map +1 -0
  26. package/dist/config.js +217 -0
  27. package/dist/config.js.map +1 -0
  28. package/dist/consolidator/daemon.d.ts +88 -0
  29. package/dist/consolidator/daemon.d.ts.map +1 -0
  30. package/dist/consolidator/daemon.js +54 -0
  31. package/dist/consolidator/daemon.js.map +1 -0
  32. package/dist/consolidator/index.d.ts +2 -0
  33. package/dist/consolidator/index.js +3 -0
  34. package/dist/errors/index.d.ts +104 -0
  35. package/dist/errors/index.d.ts.map +1 -0
  36. package/dist/errors/index.js +131 -0
  37. package/dist/errors/index.js.map +1 -0
  38. package/dist/health/checks.d.ts +120 -0
  39. package/dist/health/checks.d.ts.map +1 -0
  40. package/dist/health/checks.js +127 -0
  41. package/dist/health/checks.js.map +1 -0
  42. package/dist/health/index.d.ts +3 -0
  43. package/dist/health/index.js +4 -0
  44. package/dist/health/routes.d.ts +66 -0
  45. package/dist/health/routes.d.ts.map +1 -0
  46. package/dist/health/routes.js +96 -0
  47. package/dist/health/routes.js.map +1 -0
  48. package/dist/index.d.ts +88 -0
  49. package/dist/index.d.ts.map +1 -0
  50. package/dist/index.js +86 -0
  51. package/dist/index.js.map +1 -0
  52. package/dist/internal/context.d.ts +66 -0
  53. package/dist/internal/context.d.ts.map +1 -0
  54. package/dist/internal/ids.js +21 -0
  55. package/dist/internal/ids.js.map +1 -0
  56. package/dist/internal/json.js +46 -0
  57. package/dist/internal/json.js.map +1 -0
  58. package/dist/lifecycle/hooks.d.ts +57 -0
  59. package/dist/lifecycle/hooks.d.ts.map +1 -0
  60. package/dist/lifecycle/index.d.ts +2 -0
  61. package/dist/lifecycle/index.js +3 -0
  62. package/dist/lifecycle/pre-bind.d.ts +38 -0
  63. package/dist/lifecycle/pre-bind.d.ts.map +1 -0
  64. package/dist/lifecycle/pre-bind.js +62 -0
  65. package/dist/lifecycle/pre-bind.js.map +1 -0
  66. package/dist/metrics/catalog.d.ts +34 -0
  67. package/dist/metrics/catalog.d.ts.map +1 -0
  68. package/dist/metrics/catalog.js +61 -0
  69. package/dist/metrics/catalog.js.map +1 -0
  70. package/dist/metrics/index.d.ts +3 -0
  71. package/dist/metrics/index.js +4 -0
  72. package/dist/metrics/registry.d.ts +63 -0
  73. package/dist/metrics/registry.d.ts.map +1 -0
  74. package/dist/metrics/registry.js +222 -0
  75. package/dist/metrics/registry.js.map +1 -0
  76. package/dist/middleware/audit.d.ts +47 -0
  77. package/dist/middleware/audit.d.ts.map +1 -0
  78. package/dist/middleware/audit.js +71 -0
  79. package/dist/middleware/audit.js.map +1 -0
  80. package/dist/middleware/auth.d.ts +50 -0
  81. package/dist/middleware/auth.d.ts.map +1 -0
  82. package/dist/middleware/auth.js +164 -0
  83. package/dist/middleware/auth.js.map +1 -0
  84. package/dist/middleware/cors.d.ts +15 -0
  85. package/dist/middleware/cors.d.ts.map +1 -0
  86. package/dist/middleware/cors.js +45 -0
  87. package/dist/middleware/cors.js.map +1 -0
  88. package/dist/middleware/csrf.d.ts +15 -0
  89. package/dist/middleware/csrf.d.ts.map +1 -0
  90. package/dist/middleware/csrf.js +77 -0
  91. package/dist/middleware/csrf.js.map +1 -0
  92. package/dist/middleware/idempotency.d.ts +41 -0
  93. package/dist/middleware/idempotency.d.ts.map +1 -0
  94. package/dist/middleware/idempotency.js +198 -0
  95. package/dist/middleware/idempotency.js.map +1 -0
  96. package/dist/middleware/index.d.ts +9 -0
  97. package/dist/middleware/index.js +10 -0
  98. package/dist/middleware/rate-limit.d.ts +17 -0
  99. package/dist/middleware/rate-limit.d.ts.map +1 -0
  100. package/dist/middleware/rate-limit.js +47 -0
  101. package/dist/middleware/rate-limit.js.map +1 -0
  102. package/dist/middleware/request-state.d.ts +27 -0
  103. package/dist/middleware/request-state.d.ts.map +1 -0
  104. package/dist/middleware/request-state.js +42 -0
  105. package/dist/middleware/request-state.js.map +1 -0
  106. package/dist/middleware/scope.d.ts +24 -0
  107. package/dist/middleware/scope.d.ts.map +1 -0
  108. package/dist/middleware/scope.js +50 -0
  109. package/dist/middleware/scope.js.map +1 -0
  110. package/dist/registry/index.d.ts +135 -0
  111. package/dist/registry/index.d.ts.map +1 -0
  112. package/dist/registry/index.js +96 -0
  113. package/dist/registry/index.js.map +1 -0
  114. package/dist/replay/index.d.ts +2 -0
  115. package/dist/replay/index.js +3 -0
  116. package/dist/replay/routes.d.ts +46 -0
  117. package/dist/replay/routes.d.ts.map +1 -0
  118. package/dist/replay/routes.js +189 -0
  119. package/dist/replay/routes.js.map +1 -0
  120. package/dist/routes/agents.d.ts +41 -0
  121. package/dist/routes/agents.d.ts.map +1 -0
  122. package/dist/routes/agents.js +213 -0
  123. package/dist/routes/agents.js.map +1 -0
  124. package/dist/routes/audit.d.ts +57 -0
  125. package/dist/routes/audit.d.ts.map +1 -0
  126. package/dist/routes/audit.js +116 -0
  127. package/dist/routes/audit.js.map +1 -0
  128. package/dist/routes/auth.d.ts +26 -0
  129. package/dist/routes/auth.d.ts.map +1 -0
  130. package/dist/routes/auth.js +54 -0
  131. package/dist/routes/auth.js.map +1 -0
  132. package/dist/routes/health.d.ts +22 -0
  133. package/dist/routes/health.d.ts.map +1 -0
  134. package/dist/routes/health.js +33 -0
  135. package/dist/routes/health.js.map +1 -0
  136. package/dist/routes/index.d.ts +10 -0
  137. package/dist/routes/index.js +12 -0
  138. package/dist/routes/mcp.d.ts +32 -0
  139. package/dist/routes/mcp.d.ts.map +1 -0
  140. package/dist/routes/mcp.js +61 -0
  141. package/dist/routes/mcp.js.map +1 -0
  142. package/dist/routes/memory.d.ts +141 -0
  143. package/dist/routes/memory.d.ts.map +1 -0
  144. package/dist/routes/memory.js +102 -0
  145. package/dist/routes/memory.js.map +1 -0
  146. package/dist/routes/sessions.d.ts +54 -0
  147. package/dist/routes/sessions.d.ts.map +1 -0
  148. package/dist/routes/sessions.js +135 -0
  149. package/dist/routes/sessions.js.map +1 -0
  150. package/dist/routes/skills.d.ts +34 -0
  151. package/dist/routes/skills.d.ts.map +1 -0
  152. package/dist/routes/skills.js +54 -0
  153. package/dist/routes/skills.js.map +1 -0
  154. package/dist/routes/tokens.d.ts +25 -0
  155. package/dist/routes/tokens.d.ts.map +1 -0
  156. package/dist/routes/tokens.js +87 -0
  157. package/dist/routes/tokens.js.map +1 -0
  158. package/dist/routes/workflows.d.ts +27 -0
  159. package/dist/routes/workflows.d.ts.map +1 -0
  160. package/dist/routes/workflows.js +220 -0
  161. package/dist/routes/workflows.js.map +1 -0
  162. package/dist/runtime/run-state.d.ts +158 -0
  163. package/dist/runtime/run-state.d.ts.map +1 -0
  164. package/dist/runtime/run-state.js +182 -0
  165. package/dist/runtime/run-state.js.map +1 -0
  166. package/dist/sse/events.d.ts +44 -0
  167. package/dist/sse/events.d.ts.map +1 -0
  168. package/dist/sse/events.js +200 -0
  169. package/dist/sse/events.js.map +1 -0
  170. package/dist/sse/index.d.ts +2 -0
  171. package/dist/sse/index.js +3 -0
  172. package/dist/triggers/daemon.d.ts +74 -0
  173. package/dist/triggers/daemon.d.ts.map +1 -0
  174. package/dist/triggers/daemon.js +114 -0
  175. package/dist/triggers/daemon.js.map +1 -0
  176. package/dist/triggers/index.d.ts +3 -0
  177. package/dist/triggers/index.js +4 -0
  178. package/dist/triggers/routes.d.ts +21 -0
  179. package/dist/triggers/routes.d.ts.map +1 -0
  180. package/dist/triggers/routes.js +117 -0
  181. package/dist/triggers/routes.js.map +1 -0
  182. package/dist/ws/dispatcher.d.ts +169 -0
  183. package/dist/ws/dispatcher.d.ts.map +1 -0
  184. package/dist/ws/dispatcher.js +303 -0
  185. package/dist/ws/dispatcher.js.map +1 -0
  186. package/dist/ws/index.d.ts +6 -0
  187. package/dist/ws/index.js +7 -0
  188. package/dist/ws/replay-buffer.d.ts +47 -0
  189. package/dist/ws/replay-buffer.d.ts.map +1 -0
  190. package/dist/ws/replay-buffer.js +88 -0
  191. package/dist/ws/replay-buffer.js.map +1 -0
  192. package/dist/ws/subjects.d.ts +71 -0
  193. package/dist/ws/subjects.d.ts.map +1 -0
  194. package/dist/ws/subjects.js +112 -0
  195. package/dist/ws/subjects.js.map +1 -0
  196. package/dist/ws/ticket.d.ts +74 -0
  197. package/dist/ws/ticket.d.ts.map +1 -0
  198. package/dist/ws/ticket.js +112 -0
  199. package/dist/ws/ticket.js.map +1 -0
  200. package/dist/ws/upgrade.d.ts +63 -0
  201. package/dist/ws/upgrade.d.ts.map +1 -0
  202. package/dist/ws/upgrade.js +324 -0
  203. package/dist/ws/upgrade.js.map +1 -0
  204. package/package.json +156 -0
@@ -0,0 +1,222 @@
1
+ //#region src/metrics/registry.ts
2
+ const SUMMARY_SAMPLE_CAP = 1024;
3
+ const SUMMARY_QUANTILES = Object.freeze([.5, .95]);
4
+ /**
5
+ * Lightweight Prometheus registry. Each instance owns its metric
6
+ * catalogue + per-label samples; `render()` emits the canonical text
7
+ * exposition block.
8
+ *
9
+ * @stable
10
+ */
11
+ var MetricRegistry = class {
12
+ #definitions = /* @__PURE__ */ new Map();
13
+ #counters = /* @__PURE__ */ new Map();
14
+ #gauges = /* @__PURE__ */ new Map();
15
+ #summaries = /* @__PURE__ */ new Map();
16
+ registerCounter(name, help, labelNames = []) {
17
+ this.#register(name, help, "counter", labelNames);
18
+ if (!this.#counters.has(name)) this.#counters.set(name, /* @__PURE__ */ new Map());
19
+ }
20
+ registerGauge(name, help, labelNames = []) {
21
+ this.#register(name, help, "gauge", labelNames);
22
+ if (!this.#gauges.has(name)) this.#gauges.set(name, /* @__PURE__ */ new Map());
23
+ }
24
+ registerSummary(name, help, labelNames = []) {
25
+ this.#register(name, help, "summary", labelNames);
26
+ if (!this.#summaries.has(name)) this.#summaries.set(name, /* @__PURE__ */ new Map());
27
+ }
28
+ inc(name, labels = {}, by = 1) {
29
+ this.#assertKind(name, "counter");
30
+ assertLabelNames(labels);
31
+ const bucket = this.#counters.get(name);
32
+ if (bucket === void 0) throw new Error(`MetricRegistry.inc: counter '${name}' is not registered`);
33
+ const key = serializeLabelKey(labels);
34
+ const entry = bucket.get(key) ?? {
35
+ labels: { ...labels },
36
+ value: 0
37
+ };
38
+ if (!Number.isFinite(by)) throw new Error(`MetricRegistry.inc(${name}): by must be finite`);
39
+ if (by < 0) throw new Error(`MetricRegistry.inc(${name}): counters never decrement`);
40
+ entry.value += by;
41
+ bucket.set(key, entry);
42
+ }
43
+ set(name, value, labels = {}) {
44
+ this.#assertKind(name, "gauge");
45
+ assertLabelNames(labels);
46
+ const bucket = this.#gauges.get(name);
47
+ if (bucket === void 0) throw new Error(`MetricRegistry.set: gauge '${name}' is not registered`);
48
+ const key = serializeLabelKey(labels);
49
+ bucket.set(key, {
50
+ labels: { ...labels },
51
+ value
52
+ });
53
+ }
54
+ observe(name, value, labels = {}) {
55
+ this.#assertKind(name, "summary");
56
+ assertLabelNames(labels);
57
+ const bucket = this.#summaries.get(name);
58
+ if (bucket === void 0) throw new Error(`MetricRegistry.observe: summary '${name}' is not registered`);
59
+ const key = serializeLabelKey(labels);
60
+ const entry = bucket.get(key) ?? {
61
+ labels: { ...labels },
62
+ count: 0,
63
+ sum: 0,
64
+ samples: []
65
+ };
66
+ entry.count += 1;
67
+ entry.sum += value;
68
+ entry.samples.push(value);
69
+ if (entry.samples.length > SUMMARY_SAMPLE_CAP) entry.samples.shift();
70
+ bucket.set(key, entry);
71
+ }
72
+ reset() {
73
+ this.#counters.clear();
74
+ this.#gauges.clear();
75
+ this.#summaries.clear();
76
+ for (const [name, def] of this.#definitions) switch (def.kind) {
77
+ case "counter":
78
+ this.#counters.set(name, /* @__PURE__ */ new Map());
79
+ break;
80
+ case "gauge":
81
+ this.#gauges.set(name, /* @__PURE__ */ new Map());
82
+ break;
83
+ case "summary":
84
+ this.#summaries.set(name, /* @__PURE__ */ new Map());
85
+ break;
86
+ }
87
+ }
88
+ /**
89
+ * Render the current snapshot in Prometheus text exposition
90
+ * format (v0.0.4). Never throws — incomplete sample buckets are
91
+ * skipped instead of failing the scrape.
92
+ */
93
+ render() {
94
+ const lines = [];
95
+ for (const def of this.#definitions.values()) {
96
+ lines.push(`# HELP ${def.name} ${escapeHelp(def.help)}`);
97
+ lines.push(`# TYPE ${def.name} ${def.kind}`);
98
+ switch (def.kind) {
99
+ case "counter": {
100
+ const bucket = this.#counters.get(def.name);
101
+ if (bucket !== void 0) for (const entry of bucket.values()) lines.push(formatSample(def.name, entry.labels, entry.value));
102
+ break;
103
+ }
104
+ case "gauge": {
105
+ const bucket = this.#gauges.get(def.name);
106
+ if (bucket !== void 0) for (const entry of bucket.values()) lines.push(formatSample(def.name, entry.labels, entry.value));
107
+ break;
108
+ }
109
+ case "summary": {
110
+ const bucket = this.#summaries.get(def.name);
111
+ if (bucket !== void 0) for (const entry of bucket.values()) {
112
+ for (const q of SUMMARY_QUANTILES) {
113
+ const value = computeQuantile(entry.samples, q);
114
+ if (value === void 0) continue;
115
+ lines.push(formatSample(def.name, {
116
+ ...entry.labels,
117
+ quantile: q.toString()
118
+ }, value));
119
+ }
120
+ lines.push(formatSample(`${def.name}_sum`, entry.labels, entry.sum));
121
+ lines.push(formatSample(`${def.name}_count`, entry.labels, entry.count));
122
+ }
123
+ break;
124
+ }
125
+ }
126
+ }
127
+ return `${lines.join("\n")}\n`;
128
+ }
129
+ contentType() {
130
+ return "text/plain; version=0.0.4; charset=utf-8";
131
+ }
132
+ /** Snapshot for tests / assertions. */
133
+ snapshot() {
134
+ const counters = {};
135
+ for (const [name, bucket] of this.#counters) counters[name] = [...bucket.values()].map((entry) => ({
136
+ labels: entry.labels,
137
+ value: entry.value
138
+ }));
139
+ const gauges = {};
140
+ for (const [name, bucket] of this.#gauges) gauges[name] = [...bucket.values()].map((entry) => ({
141
+ labels: entry.labels,
142
+ value: entry.value
143
+ }));
144
+ const summaries = {};
145
+ for (const [name, bucket] of this.#summaries) summaries[name] = [...bucket.values()].map((entry) => ({
146
+ labels: entry.labels,
147
+ count: entry.count,
148
+ sum: entry.sum,
149
+ samples: [...entry.samples]
150
+ }));
151
+ return {
152
+ counters,
153
+ gauges,
154
+ summaries
155
+ };
156
+ }
157
+ #register(name, help, kind, labelNames) {
158
+ if (!METRIC_NAME_RE.test(name)) throw new Error(`MetricRegistry: invalid metric name '${name}'`);
159
+ const existing = this.#definitions.get(name);
160
+ if (existing !== void 0) {
161
+ if (existing.kind !== kind) throw new Error(`MetricRegistry: metric '${name}' already registered as '${existing.kind}', cannot redefine as '${kind}'`);
162
+ return;
163
+ }
164
+ this.#definitions.set(name, {
165
+ name,
166
+ help,
167
+ kind,
168
+ labelNames: Object.freeze([...labelNames])
169
+ });
170
+ }
171
+ #assertKind(name, kind) {
172
+ const def = this.#definitions.get(name);
173
+ if (def === void 0) throw new Error(`MetricRegistry: unknown metric '${name}'`);
174
+ if (def.kind !== kind) throw new Error(`MetricRegistry: metric '${name}' is a ${def.kind}, not a ${kind}`);
175
+ }
176
+ };
177
+ const METRIC_NAME_RE = /^[a-zA-Z_:][a-zA-Z0-9_:]*$/;
178
+ const LABEL_NAME_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
179
+ function assertLabelNames(labels) {
180
+ for (const key of Object.keys(labels)) if (!LABEL_NAME_RE.test(key)) throw new Error(`MetricRegistry: invalid label name '${key}'`);
181
+ }
182
+ function serializeLabelKey(labels) {
183
+ const keys = Object.keys(labels).sort();
184
+ if (keys.length === 0) return "";
185
+ const parts = [];
186
+ for (const k of keys) parts.push(`${k}=${String(labels[k])}`);
187
+ return parts.join("|");
188
+ }
189
+ function formatSample(name, labels, value) {
190
+ return `${name}${formatLabels(labels)} ${formatNumber(value)}`;
191
+ }
192
+ function formatLabels(labels) {
193
+ const keys = Object.keys(labels).sort();
194
+ if (keys.length === 0) return "";
195
+ const parts = [];
196
+ for (const k of keys) {
197
+ if (!LABEL_NAME_RE.test(k)) throw new Error(`MetricRegistry: invalid label name '${k}'`);
198
+ parts.push(`${k}="${escapeLabelValue(String(labels[k]))}"`);
199
+ }
200
+ return `{${parts.join(",")}}`;
201
+ }
202
+ function escapeLabelValue(value) {
203
+ return value.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/"/g, "\\\"");
204
+ }
205
+ function escapeHelp(help) {
206
+ return help.replace(/\\/g, "\\\\").replace(/\n/g, "\\n");
207
+ }
208
+ function formatNumber(value) {
209
+ if (Number.isNaN(value)) return "NaN";
210
+ if (!Number.isFinite(value)) return value > 0 ? "+Inf" : "-Inf";
211
+ if (Number.isInteger(value)) return String(value);
212
+ return value.toString();
213
+ }
214
+ function computeQuantile(samples, q) {
215
+ if (samples.length === 0) return void 0;
216
+ const sorted = [...samples].sort((a, b) => a - b);
217
+ return sorted[Math.min(sorted.length - 1, Math.max(0, Math.ceil(q * sorted.length) - 1))];
218
+ }
219
+
220
+ //#endregion
221
+ export { MetricRegistry };
222
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","names":["#definitions","#counters","#gauges","#summaries","#register","#assertKind","lines: string[]","counters: Record<string, ReadonlyArray<{ labels: LabelSet; value: number }>>","gauges: Record<string, ReadonlyArray<{ labels: LabelSet; value: number }>>","summaries: Record<\n string,\n ReadonlyArray<{\n labels: LabelSet;\n count: number;\n sum: number;\n samples: ReadonlyArray<number>;\n }>\n >","parts: string[]"],"sources":["../../src/metrics/registry.ts"],"sourcesContent":["/**\n * Tiny Prometheus exposition layer. The framework intentionally does\n * not pull in `prom-client` so the bundle stays lean and so the\n * exposition is byte-stable across processes (`prom-client` adds\n * default counters that drift between Node releases).\n *\n * The supported metric kinds are the three the runtime spec calls\n * out: `counter`, `gauge`, and `summary` (with hard-coded p50/p95\n * quantiles). Sample rendering follows Prometheus text exposition\n * format v0.0.4 — every metric block is preceded by `# HELP` + `# TYPE`\n * and labels are quoted-escaped per the spec.\n *\n * @packageDocumentation\n */\n\n/** @stable */\nexport type MetricKind = 'counter' | 'gauge' | 'summary';\n\n/** @stable */\nexport type LabelSet = Readonly<Record<string, string | number | boolean>>;\n\ninterface MetricDefinition {\n readonly name: string;\n readonly help: string;\n readonly kind: MetricKind;\n readonly labelNames: ReadonlyArray<string>;\n}\n\ninterface CounterEntry {\n readonly labels: LabelSet;\n value: number;\n}\n\ninterface GaugeEntry {\n readonly labels: LabelSet;\n value: number;\n}\n\ninterface SummaryEntry {\n readonly labels: LabelSet;\n count: number;\n sum: number;\n /** Recent samples (capped) used to compute quantiles. */\n samples: number[];\n}\n\nconst SUMMARY_SAMPLE_CAP = 1024;\nconst SUMMARY_QUANTILES = Object.freeze([0.5, 0.95]);\n\n/**\n * Lightweight Prometheus registry. Each instance owns its metric\n * catalogue + per-label samples; `render()` emits the canonical text\n * exposition block.\n *\n * @stable\n */\nexport class MetricRegistry {\n readonly #definitions = new Map<string, MetricDefinition>();\n readonly #counters = new Map<string, Map<string, CounterEntry>>();\n readonly #gauges = new Map<string, Map<string, GaugeEntry>>();\n readonly #summaries = new Map<string, Map<string, SummaryEntry>>();\n\n registerCounter(name: string, help: string, labelNames: ReadonlyArray<string> = []): void {\n this.#register(name, help, 'counter', labelNames);\n if (!this.#counters.has(name)) this.#counters.set(name, new Map());\n }\n\n registerGauge(name: string, help: string, labelNames: ReadonlyArray<string> = []): void {\n this.#register(name, help, 'gauge', labelNames);\n if (!this.#gauges.has(name)) this.#gauges.set(name, new Map());\n }\n\n registerSummary(name: string, help: string, labelNames: ReadonlyArray<string> = []): void {\n this.#register(name, help, 'summary', labelNames);\n if (!this.#summaries.has(name)) this.#summaries.set(name, new Map());\n }\n\n inc(name: string, labels: LabelSet = {}, by = 1): void {\n this.#assertKind(name, 'counter');\n assertLabelNames(labels);\n const bucket = this.#counters.get(name);\n if (bucket === undefined) {\n throw new Error(`MetricRegistry.inc: counter '${name}' is not registered`);\n }\n const key = serializeLabelKey(labels);\n const entry = bucket.get(key) ?? { labels: { ...labels }, value: 0 };\n if (!Number.isFinite(by)) {\n throw new Error(`MetricRegistry.inc(${name}): by must be finite`);\n }\n if (by < 0) {\n throw new Error(`MetricRegistry.inc(${name}): counters never decrement`);\n }\n entry.value += by;\n bucket.set(key, entry);\n }\n\n set(name: string, value: number, labels: LabelSet = {}): void {\n this.#assertKind(name, 'gauge');\n assertLabelNames(labels);\n const bucket = this.#gauges.get(name);\n if (bucket === undefined) {\n throw new Error(`MetricRegistry.set: gauge '${name}' is not registered`);\n }\n const key = serializeLabelKey(labels);\n bucket.set(key, { labels: { ...labels }, value });\n }\n\n observe(name: string, value: number, labels: LabelSet = {}): void {\n this.#assertKind(name, 'summary');\n assertLabelNames(labels);\n const bucket = this.#summaries.get(name);\n if (bucket === undefined) {\n throw new Error(`MetricRegistry.observe: summary '${name}' is not registered`);\n }\n const key = serializeLabelKey(labels);\n const entry = bucket.get(key) ?? { labels: { ...labels }, count: 0, sum: 0, samples: [] };\n entry.count += 1;\n entry.sum += value;\n entry.samples.push(value);\n if (entry.samples.length > SUMMARY_SAMPLE_CAP) {\n entry.samples.shift();\n }\n bucket.set(key, entry);\n }\n\n reset(): void {\n this.#counters.clear();\n this.#gauges.clear();\n this.#summaries.clear();\n for (const [name, def] of this.#definitions) {\n switch (def.kind) {\n case 'counter':\n this.#counters.set(name, new Map());\n break;\n case 'gauge':\n this.#gauges.set(name, new Map());\n break;\n case 'summary':\n this.#summaries.set(name, new Map());\n break;\n }\n }\n }\n\n /**\n * Render the current snapshot in Prometheus text exposition\n * format (v0.0.4). Never throws — incomplete sample buckets are\n * skipped instead of failing the scrape.\n */\n render(): string {\n const lines: string[] = [];\n for (const def of this.#definitions.values()) {\n lines.push(`# HELP ${def.name} ${escapeHelp(def.help)}`);\n lines.push(`# TYPE ${def.name} ${def.kind}`);\n switch (def.kind) {\n case 'counter': {\n const bucket = this.#counters.get(def.name);\n if (bucket !== undefined) {\n for (const entry of bucket.values()) {\n lines.push(formatSample(def.name, entry.labels, entry.value));\n }\n }\n break;\n }\n case 'gauge': {\n const bucket = this.#gauges.get(def.name);\n if (bucket !== undefined) {\n for (const entry of bucket.values()) {\n lines.push(formatSample(def.name, entry.labels, entry.value));\n }\n }\n break;\n }\n case 'summary': {\n const bucket = this.#summaries.get(def.name);\n if (bucket !== undefined) {\n for (const entry of bucket.values()) {\n for (const q of SUMMARY_QUANTILES) {\n const value = computeQuantile(entry.samples, q);\n if (value === undefined) continue;\n lines.push(\n formatSample(def.name, { ...entry.labels, quantile: q.toString() }, value),\n );\n }\n lines.push(formatSample(`${def.name}_sum`, entry.labels, entry.sum));\n lines.push(formatSample(`${def.name}_count`, entry.labels, entry.count));\n }\n }\n break;\n }\n }\n }\n return `${lines.join('\\n')}\\n`;\n }\n\n contentType(): string {\n return 'text/plain; version=0.0.4; charset=utf-8';\n }\n\n /** Snapshot for tests / assertions. */\n snapshot(): {\n counters: Record<string, ReadonlyArray<{ labels: LabelSet; value: number }>>;\n gauges: Record<string, ReadonlyArray<{ labels: LabelSet; value: number }>>;\n summaries: Record<\n string,\n ReadonlyArray<{\n labels: LabelSet;\n count: number;\n sum: number;\n samples: ReadonlyArray<number>;\n }>\n >;\n } {\n const counters: Record<string, ReadonlyArray<{ labels: LabelSet; value: number }>> = {};\n for (const [name, bucket] of this.#counters) {\n counters[name] = [...bucket.values()].map((entry) => ({\n labels: entry.labels,\n value: entry.value,\n }));\n }\n const gauges: Record<string, ReadonlyArray<{ labels: LabelSet; value: number }>> = {};\n for (const [name, bucket] of this.#gauges) {\n gauges[name] = [...bucket.values()].map((entry) => ({\n labels: entry.labels,\n value: entry.value,\n }));\n }\n const summaries: Record<\n string,\n ReadonlyArray<{\n labels: LabelSet;\n count: number;\n sum: number;\n samples: ReadonlyArray<number>;\n }>\n > = {};\n for (const [name, bucket] of this.#summaries) {\n summaries[name] = [...bucket.values()].map((entry) => ({\n labels: entry.labels,\n count: entry.count,\n sum: entry.sum,\n samples: [...entry.samples],\n }));\n }\n return { counters, gauges, summaries };\n }\n\n #register(name: string, help: string, kind: MetricKind, labelNames: ReadonlyArray<string>): void {\n if (!METRIC_NAME_RE.test(name)) {\n throw new Error(`MetricRegistry: invalid metric name '${name}'`);\n }\n const existing = this.#definitions.get(name);\n if (existing !== undefined) {\n if (existing.kind !== kind) {\n throw new Error(\n `MetricRegistry: metric '${name}' already registered as '${existing.kind}', cannot redefine as '${kind}'`,\n );\n }\n return;\n }\n this.#definitions.set(name, {\n name,\n help,\n kind,\n labelNames: Object.freeze([...labelNames]),\n });\n }\n\n #assertKind(name: string, kind: MetricKind): void {\n const def = this.#definitions.get(name);\n if (def === undefined) {\n throw new Error(`MetricRegistry: unknown metric '${name}'`);\n }\n if (def.kind !== kind) {\n throw new Error(`MetricRegistry: metric '${name}' is a ${def.kind}, not a ${kind}`);\n }\n }\n}\n\nconst METRIC_NAME_RE = /^[a-zA-Z_:][a-zA-Z0-9_:]*$/;\nconst LABEL_NAME_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\n\nfunction assertLabelNames(labels: LabelSet): void {\n for (const key of Object.keys(labels)) {\n if (!LABEL_NAME_RE.test(key)) {\n throw new Error(`MetricRegistry: invalid label name '${key}'`);\n }\n }\n}\n\nfunction serializeLabelKey(labels: LabelSet): string {\n const keys = Object.keys(labels).sort();\n if (keys.length === 0) return '';\n const parts: string[] = [];\n for (const k of keys) parts.push(`${k}=${String(labels[k])}`);\n return parts.join('|');\n}\n\nfunction formatSample(name: string, labels: LabelSet, value: number): string {\n const labelStr = formatLabels(labels);\n return `${name}${labelStr} ${formatNumber(value)}`;\n}\n\nfunction formatLabels(labels: LabelSet): string {\n const keys = Object.keys(labels).sort();\n if (keys.length === 0) return '';\n const parts: string[] = [];\n for (const k of keys) {\n if (!LABEL_NAME_RE.test(k)) {\n throw new Error(`MetricRegistry: invalid label name '${k}'`);\n }\n parts.push(`${k}=\"${escapeLabelValue(String(labels[k]))}\"`);\n }\n return `{${parts.join(',')}}`;\n}\n\nfunction escapeLabelValue(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/\\n/g, '\\\\n').replace(/\"/g, '\\\\\"');\n}\n\nfunction escapeHelp(help: string): string {\n return help.replace(/\\\\/g, '\\\\\\\\').replace(/\\n/g, '\\\\n');\n}\n\nfunction formatNumber(value: number): string {\n if (Number.isNaN(value)) return 'NaN';\n if (!Number.isFinite(value)) return value > 0 ? '+Inf' : '-Inf';\n if (Number.isInteger(value)) return String(value);\n return value.toString();\n}\n\nfunction computeQuantile(samples: ReadonlyArray<number>, q: number): number | undefined {\n if (samples.length === 0) return undefined;\n const sorted = [...samples].sort((a, b) => a - b);\n const idx = Math.min(sorted.length - 1, Math.max(0, Math.ceil(q * sorted.length) - 1));\n return sorted[idx];\n}\n"],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ import { ServerVariables } from "../internal/context.js";
2
+ import { AuditDb, AuditEntryInput } from "@graphorin/security/audit";
3
+ import { MiddlewareHandler } from "hono";
4
+
5
+ //#region src/middleware/audit.d.ts
6
+
7
+ /**
8
+ * Canonical action discriminator emitted on every authenticated REST
9
+ * request per the Phase 14a spec (`§ Audit middleware`). Exposed as
10
+ * a constant so consumers (downstream filters, dashboards) can grep
11
+ * for it without restating the literal everywhere.
12
+ *
13
+ * @stable
14
+ */
15
+ declare const HTTP_REQUEST_AUDIT_ACTION: "http:request";
16
+ /**
17
+ * Optional telemetry sink. The default ignores errors; production
18
+ * deployments wire this into their structured logger.
19
+ *
20
+ * @stable
21
+ */
22
+ type AuditErrorSink = (err: unknown, entry: Omit<AuditEntryInput, 'ts'>) => void;
23
+ /**
24
+ * @stable
25
+ */
26
+ interface AuditMiddlewareOptions {
27
+ readonly auditDb: AuditDb;
28
+ /** Optional override for the time source. */
29
+ readonly now?: () => number;
30
+ /** Optional error sink. Defaults to swallow. */
31
+ readonly onError?: AuditErrorSink;
32
+ /**
33
+ * When `true` (the default), include the path + method + status on
34
+ * the audit entry's metadata. Disable for compliance-strict
35
+ * deployments that already log the request envelope elsewhere.
36
+ */
37
+ readonly recordMetadata?: boolean;
38
+ }
39
+ /**
40
+ * @stable
41
+ */
42
+ declare function createAuditMiddleware(options: AuditMiddlewareOptions): MiddlewareHandler<{
43
+ Variables: ServerVariables;
44
+ }>;
45
+ //#endregion
46
+ export { AuditErrorSink, AuditMiddlewareOptions, HTTP_REQUEST_AUDIT_ACTION, createAuditMiddleware };
47
+ //# sourceMappingURL=audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.d.ts","names":[],"sources":["../../src/middleware/audit.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;cA4Ba;;;;;;;KAQD,cAAA,yBAAuC,KAAK;;;;UAKvC,sBAAA;oBACG;;;;qBAIC;;;;;;;;;;;iBAmBL,qBAAA,UACL,yBACR;aAA+B"}
@@ -0,0 +1,71 @@
1
+ import { appendAudit } from "@graphorin/security/audit";
2
+
3
+ //#region src/middleware/audit.ts
4
+ /**
5
+ * Audit middleware. Forwards every authenticated request through
6
+ * `appendAudit` (`@graphorin/security/audit`) so the operator gets a
7
+ * tamper-evident chain of every state-changing call. Append failures
8
+ * never break the request hot path; they are recorded on a best-
9
+ * effort error sink.
10
+ *
11
+ * @packageDocumentation
12
+ */
13
+ /**
14
+ * Canonical action discriminator emitted on every authenticated REST
15
+ * request per the Phase 14a spec (`§ Audit middleware`). Exposed as
16
+ * a constant so consumers (downstream filters, dashboards) can grep
17
+ * for it without restating the literal everywhere.
18
+ *
19
+ * @stable
20
+ */
21
+ const HTTP_REQUEST_AUDIT_ACTION = "http:request";
22
+ function decisionForStatus(status) {
23
+ if (status >= 500) return "error";
24
+ if (status === 404) return "not-found";
25
+ if (status >= 400) return "denied";
26
+ return "success";
27
+ }
28
+ /**
29
+ * @stable
30
+ */
31
+ function createAuditMiddleware(options) {
32
+ const now = options.now ?? Date.now;
33
+ const onError = options.onError ?? (() => {});
34
+ const recordMetadata = options.recordMetadata ?? true;
35
+ return async (c, next) => {
36
+ const start = now();
37
+ await next();
38
+ const status = c.res?.status ?? 0;
39
+ const auth = c.get("state").auth;
40
+ if (auth.kind !== "token") return;
41
+ const token = auth.token;
42
+ const entry = {
43
+ ts: now(),
44
+ actor: {
45
+ kind: "token",
46
+ id: token.tokenId,
47
+ ...token.label !== void 0 ? { label: token.label } : {}
48
+ },
49
+ action: HTTP_REQUEST_AUDIT_ACTION,
50
+ target: c.req.path,
51
+ decision: decisionForStatus(status),
52
+ ...recordMetadata ? { metadata: {
53
+ method: c.req.method,
54
+ status,
55
+ durationMs: now() - start,
56
+ requestId: c.get("state").requestId,
57
+ ...c.get("state").idempotencyKey !== void 0 ? { idempotencyKey: c.get("state").idempotencyKey } : {},
58
+ ...c.get("state").idempotencyReplay === true ? { idempotencyReplayed: true } : {}
59
+ } } : {}
60
+ };
61
+ try {
62
+ await appendAudit(options.auditDb, entry);
63
+ } catch (err) {
64
+ onError(err, entry);
65
+ }
66
+ };
67
+ }
68
+
69
+ //#endregion
70
+ export { HTTP_REQUEST_AUDIT_ACTION, createAuditMiddleware };
71
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.js","names":["entry: AuditEntryInput"],"sources":["../../src/middleware/audit.ts"],"sourcesContent":["/**\n * Audit middleware. Forwards every authenticated request through\n * `appendAudit` (`@graphorin/security/audit`) so the operator gets a\n * tamper-evident chain of every state-changing call. Append failures\n * never break the request hot path; they are recorded on a best-\n * effort error sink.\n *\n * @packageDocumentation\n */\n\nimport {\n type AuditDb,\n type AuditDecision,\n type AuditEntryInput,\n appendAudit,\n} from '@graphorin/security/audit';\nimport type { MiddlewareHandler } from 'hono';\n\nimport type { ServerVariables } from '../internal/context.js';\n\n/**\n * Canonical action discriminator emitted on every authenticated REST\n * request per the Phase 14a spec (`§ Audit middleware`). Exposed as\n * a constant so consumers (downstream filters, dashboards) can grep\n * for it without restating the literal everywhere.\n *\n * @stable\n */\nexport const HTTP_REQUEST_AUDIT_ACTION = 'http:request' as const;\n\n/**\n * Optional telemetry sink. The default ignores errors; production\n * deployments wire this into their structured logger.\n *\n * @stable\n */\nexport type AuditErrorSink = (err: unknown, entry: Omit<AuditEntryInput, 'ts'>) => void;\n\n/**\n * @stable\n */\nexport interface AuditMiddlewareOptions {\n readonly auditDb: AuditDb;\n /** Optional override for the time source. */\n readonly now?: () => number;\n /** Optional error sink. Defaults to swallow. */\n readonly onError?: AuditErrorSink;\n /**\n * When `true` (the default), include the path + method + status on\n * the audit entry's metadata. Disable for compliance-strict\n * deployments that already log the request envelope elsewhere.\n */\n readonly recordMetadata?: boolean;\n}\n\nfunction decisionForStatus(status: number): AuditDecision {\n if (status >= 500) return 'error';\n if (status === 404) return 'not-found';\n if (status >= 400) return 'denied';\n return 'success';\n}\n\n/**\n * @stable\n */\nexport function createAuditMiddleware(\n options: AuditMiddlewareOptions,\n): MiddlewareHandler<{ Variables: ServerVariables }> {\n const now = options.now ?? Date.now;\n const onError = options.onError ?? (() => {});\n const recordMetadata = options.recordMetadata ?? true;\n\n return async (c, next) => {\n const start = now();\n await next();\n const status = c.res?.status ?? 0;\n const auth = c.get('state').auth;\n if (auth.kind !== 'token') {\n // Anonymous calls (health, etc.) are not audited.\n return;\n }\n const token = auth.token;\n const entry: AuditEntryInput = {\n ts: now(),\n actor: {\n kind: 'token',\n id: token.tokenId,\n ...(token.label !== undefined ? { label: token.label } : {}),\n },\n action: HTTP_REQUEST_AUDIT_ACTION,\n target: c.req.path,\n decision: decisionForStatus(status),\n ...(recordMetadata\n ? {\n metadata: {\n method: c.req.method,\n status,\n durationMs: now() - start,\n requestId: c.get('state').requestId,\n ...(c.get('state').idempotencyKey !== undefined\n ? { idempotencyKey: c.get('state').idempotencyKey }\n : {}),\n ...(c.get('state').idempotencyReplay === true ? { idempotencyReplayed: true } : {}),\n },\n }\n : {}),\n };\n try {\n await appendAudit(options.auditDb, entry);\n } catch (err) {\n onError(err, entry);\n }\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA4BA,MAAa,4BAA4B;AA2BzC,SAAS,kBAAkB,QAA+B;AACxD,KAAI,UAAU,IAAK,QAAO;AAC1B,KAAI,WAAW,IAAK,QAAO;AAC3B,KAAI,UAAU,IAAK,QAAO;AAC1B,QAAO;;;;;AAMT,SAAgB,sBACd,SACmD;CACnD,MAAM,MAAM,QAAQ,OAAO,KAAK;CAChC,MAAM,UAAU,QAAQ,kBAAkB;CAC1C,MAAM,iBAAiB,QAAQ,kBAAkB;AAEjD,QAAO,OAAO,GAAG,SAAS;EACxB,MAAM,QAAQ,KAAK;AACnB,QAAM,MAAM;EACZ,MAAM,SAAS,EAAE,KAAK,UAAU;EAChC,MAAM,OAAO,EAAE,IAAI,QAAQ,CAAC;AAC5B,MAAI,KAAK,SAAS,QAEhB;EAEF,MAAM,QAAQ,KAAK;EACnB,MAAMA,QAAyB;GAC7B,IAAI,KAAK;GACT,OAAO;IACL,MAAM;IACN,IAAI,MAAM;IACV,GAAI,MAAM,UAAU,SAAY,EAAE,OAAO,MAAM,OAAO,GAAG,EAAE;IAC5D;GACD,QAAQ;GACR,QAAQ,EAAE,IAAI;GACd,UAAU,kBAAkB,OAAO;GACnC,GAAI,iBACA,EACE,UAAU;IACR,QAAQ,EAAE,IAAI;IACd;IACA,YAAY,KAAK,GAAG;IACpB,WAAW,EAAE,IAAI,QAAQ,CAAC;IAC1B,GAAI,EAAE,IAAI,QAAQ,CAAC,mBAAmB,SAClC,EAAE,gBAAgB,EAAE,IAAI,QAAQ,CAAC,gBAAgB,GACjD,EAAE;IACN,GAAI,EAAE,IAAI,QAAQ,CAAC,sBAAsB,OAAO,EAAE,qBAAqB,MAAM,GAAG,EAAE;IACnF,EACF,GACD,EAAE;GACP;AACD,MAAI;AACF,SAAM,YAAY,QAAQ,SAAS,MAAM;WAClC,KAAK;AACZ,WAAQ,KAAK,MAAM"}
@@ -0,0 +1,50 @@
1
+ import { ServerVariables } from "../internal/context.js";
2
+ import { MiddlewareHandler } from "hono";
3
+ import { TokenVerifier } from "@graphorin/security/auth";
4
+
5
+ //#region src/middleware/auth.d.ts
6
+
7
+ /**
8
+ * Build the no-auth middleware mounted when `auth.kind = 'none'`. It stamps
9
+ * `state.auth = { kind: 'anonymous', grantedScopes: [admin:*] }` so the
10
+ * scope middleware, SSE handler and replay routes all treat the request as a
11
+ * fully-authorized principal. This is the documented trusted-loopback /
12
+ * single-operator mode — never mount it on a non-loopback deployment without
13
+ * understanding that every endpoint becomes open.
14
+ *
15
+ * @stable
16
+ */
17
+ declare function createAnonymousAuthMiddleware(): MiddlewareHandler<{
18
+ Variables: ServerVariables;
19
+ }>;
20
+ /**
21
+ * Options accepted by {@link createAuthMiddleware}. Tests inject a
22
+ * stub verifier; production wiring uses the verifier built during the
23
+ * server's pre-bind step.
24
+ *
25
+ * @stable
26
+ */
27
+ interface AuthMiddlewareOptions {
28
+ readonly verifier: TokenVerifier;
29
+ /**
30
+ * Whether to allow unauthenticated requests through. Used by
31
+ * `health` and (when explicitly opted-in) by the public read
32
+ * endpoints. When `false` (the default), missing / malformed /
33
+ * invalid tokens short-circuit the request with `401`.
34
+ */
35
+ readonly allowAnonymous?: boolean;
36
+ }
37
+ /**
38
+ * Build the bearer-token middleware. The middleware always sets
39
+ * `c.var.state.auth`, even on the unauthenticated branch, so
40
+ * downstream code can pattern-match the discriminated union without
41
+ * a separate "is anonymous?" check.
42
+ *
43
+ * @stable
44
+ */
45
+ declare function createAuthMiddleware(options: AuthMiddlewareOptions): MiddlewareHandler<{
46
+ Variables: ServerVariables;
47
+ }>;
48
+ //#endregion
49
+ export { AuthMiddlewareOptions, createAnonymousAuthMiddleware, createAuthMiddleware };
50
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","names":[],"sources":["../../src/middleware/auth.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;iBAqCgB,6BAAA,CAAA,GAAiC;aACpC;;;;;;;;;UAkBI,qBAAA;qBACI;;;;;;;;;;;;;;;;;iBAwEL,oBAAA,UACL,wBACR;aAA+B"}
@@ -0,0 +1,164 @@
1
+ import { parseScope } from "@graphorin/security/auth";
2
+
3
+ //#region src/middleware/auth.ts
4
+ /**
5
+ * Bearer-token authentication middleware. Wraps the
6
+ * `@graphorin/security` token verifier so handlers see
7
+ * a stable {@link import('../internal/context.js').AuthState}
8
+ * structure on `c.var.state.auth`.
9
+ *
10
+ * @packageDocumentation
11
+ */
12
+ /**
13
+ * IP-13: the scope set granted to every request when `auth.kind = 'none'`.
14
+ * `admin:*` matches every required scope (see `@graphorin/security/auth`
15
+ * `scopeMatches`), so the trusted-loopback operator is uniformly authorized
16
+ * without special-casing each route.
17
+ */
18
+ const ANONYMOUS_GRANTED_SCOPES = Object.freeze([parseScope("admin:*")]);
19
+ /**
20
+ * Build the no-auth middleware mounted when `auth.kind = 'none'`. It stamps
21
+ * `state.auth = { kind: 'anonymous', grantedScopes: [admin:*] }` so the
22
+ * scope middleware, SSE handler and replay routes all treat the request as a
23
+ * fully-authorized principal. This is the documented trusted-loopback /
24
+ * single-operator mode — never mount it on a non-loopback deployment without
25
+ * understanding that every endpoint becomes open.
26
+ *
27
+ * @stable
28
+ */
29
+ function createAnonymousAuthMiddleware() {
30
+ return async (c, next) => {
31
+ c.set("state", {
32
+ ...c.get("state"),
33
+ auth: {
34
+ kind: "anonymous",
35
+ grantedScopes: ANONYMOUS_GRANTED_SCOPES
36
+ }
37
+ });
38
+ await next();
39
+ };
40
+ }
41
+ const HEADER_NAME = "authorization";
42
+ const BEARER_PREFIX = "bearer ";
43
+ function extractBearer(c) {
44
+ const raw = c.req.header(HEADER_NAME);
45
+ if (raw === void 0 || raw === null) return void 0;
46
+ const trimmed = raw.trim();
47
+ if (trimmed.length < 7) return void 0;
48
+ if (trimmed.slice(0, 7).toLowerCase() !== BEARER_PREFIX) return void 0;
49
+ return trimmed.slice(7).trim();
50
+ }
51
+ function reasonToStatus(reason) {
52
+ switch (reason) {
53
+ case "malformed": return {
54
+ status: 401,
55
+ body: {
56
+ error: "auth-invalid",
57
+ message: "Malformed bearer token."
58
+ }
59
+ };
60
+ case "unknown-token": return {
61
+ status: 401,
62
+ body: {
63
+ error: "auth-invalid",
64
+ message: "Unknown bearer token."
65
+ }
66
+ };
67
+ case "revoked": return {
68
+ status: 401,
69
+ body: {
70
+ error: "auth-revoked",
71
+ message: "Bearer token has been revoked."
72
+ }
73
+ };
74
+ case "expired": return {
75
+ status: 401,
76
+ body: {
77
+ error: "auth-expired",
78
+ message: "Bearer token has expired."
79
+ }
80
+ };
81
+ case "ip-locked-out": return {
82
+ status: 429,
83
+ body: {
84
+ error: "auth-locked-out",
85
+ message: "Too many failed authentications from this address.",
86
+ hint: "Wait for the lockout window to elapse before retrying."
87
+ }
88
+ };
89
+ case "token-locked-out": return {
90
+ status: 429,
91
+ body: {
92
+ error: "auth-locked-out",
93
+ message: "Bearer token is locked out after repeated failures.",
94
+ hint: "Operator must rotate the token."
95
+ }
96
+ };
97
+ default: return {
98
+ status: 401,
99
+ body: {
100
+ error: "auth-invalid",
101
+ message: `Authentication failed: ${reason}.`
102
+ }
103
+ };
104
+ }
105
+ }
106
+ /**
107
+ * Build the bearer-token middleware. The middleware always sets
108
+ * `c.var.state.auth`, even on the unauthenticated branch, so
109
+ * downstream code can pattern-match the discriminated union without
110
+ * a separate "is anonymous?" check.
111
+ *
112
+ * @stable
113
+ */
114
+ function createAuthMiddleware(options) {
115
+ const allowAnonymous = options.allowAnonymous ?? false;
116
+ return async (c, next) => {
117
+ const token = extractBearer(c);
118
+ const ip = c.get("state").clientIp;
119
+ if (token === void 0) {
120
+ if (allowAnonymous) {
121
+ c.set("state", {
122
+ ...c.get("state"),
123
+ auth: { kind: "unauthenticated" }
124
+ });
125
+ await next();
126
+ return;
127
+ }
128
+ return c.json({
129
+ error: "auth-required",
130
+ message: "Bearer token required.",
131
+ hint: "Set 'Authorization: Bearer <token>'."
132
+ }, 401);
133
+ }
134
+ const result = await options.verifier.verify(token, ip !== void 0 ? { ip } : {});
135
+ if (!result.ok) {
136
+ const mapped = reasonToStatus(result.reason);
137
+ const headers = {};
138
+ if (result.retryAfterMs !== void 0) headers["Retry-After"] = Math.ceil(result.retryAfterMs / 1e3).toString();
139
+ return c.json(mapped.body, mapped.status, headers);
140
+ }
141
+ const grantedScopes = [];
142
+ for (const scope of result.token.scopes) grantedScopes.push(scope);
143
+ c.set("state", {
144
+ ...c.get("state"),
145
+ auth: {
146
+ kind: "token",
147
+ token: result.token,
148
+ grantedScopes: Object.freeze(grantedScopes)
149
+ }
150
+ });
151
+ c.set("token", {
152
+ id: result.token.tokenId,
153
+ label: result.token.label,
154
+ scopes: Object.freeze(grantedScopes.slice()),
155
+ env: result.token.env,
156
+ expiresAt: result.token.expiresAt
157
+ });
158
+ await next();
159
+ };
160
+ }
161
+
162
+ //#endregion
163
+ export { createAnonymousAuthMiddleware, createAuthMiddleware };
164
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","names":["ANONYMOUS_GRANTED_SCOPES: ReadonlyArray<ParsedScope>","headers: Record<string, string>","grantedScopes: ParsedScope[]"],"sources":["../../src/middleware/auth.ts"],"sourcesContent":["/**\n * Bearer-token authentication middleware. Wraps the\n * `@graphorin/security` token verifier so handlers see\n * a stable {@link import('../internal/context.js').AuthState}\n * structure on `c.var.state.auth`.\n *\n * @packageDocumentation\n */\n\nimport {\n type ParsedScope,\n parseScope,\n type TokenVerifier,\n tryParseScope,\n} from '@graphorin/security/auth';\nimport type { Context, MiddlewareHandler, Next } from 'hono';\n\nimport type { ServerVariables } from '../internal/context.js';\n\n/**\n * IP-13: the scope set granted to every request when `auth.kind = 'none'`.\n * `admin:*` matches every required scope (see `@graphorin/security/auth`\n * `scopeMatches`), so the trusted-loopback operator is uniformly authorized\n * without special-casing each route.\n */\nconst ANONYMOUS_GRANTED_SCOPES: ReadonlyArray<ParsedScope> = Object.freeze([parseScope('admin:*')]);\n\n/**\n * Build the no-auth middleware mounted when `auth.kind = 'none'`. It stamps\n * `state.auth = { kind: 'anonymous', grantedScopes: [admin:*] }` so the\n * scope middleware, SSE handler and replay routes all treat the request as a\n * fully-authorized principal. This is the documented trusted-loopback /\n * single-operator mode — never mount it on a non-loopback deployment without\n * understanding that every endpoint becomes open.\n *\n * @stable\n */\nexport function createAnonymousAuthMiddleware(): MiddlewareHandler<{\n Variables: ServerVariables;\n}> {\n return async (c, next: Next) => {\n c.set('state', {\n ...c.get('state'),\n auth: { kind: 'anonymous' as const, grantedScopes: ANONYMOUS_GRANTED_SCOPES },\n });\n await next();\n };\n}\n\n/**\n * Options accepted by {@link createAuthMiddleware}. Tests inject a\n * stub verifier; production wiring uses the verifier built during the\n * server's pre-bind step.\n *\n * @stable\n */\nexport interface AuthMiddlewareOptions {\n readonly verifier: TokenVerifier;\n /**\n * Whether to allow unauthenticated requests through. Used by\n * `health` and (when explicitly opted-in) by the public read\n * endpoints. When `false` (the default), missing / malformed /\n * invalid tokens short-circuit the request with `401`.\n */\n readonly allowAnonymous?: boolean;\n}\n\nconst HEADER_NAME = 'authorization';\nconst BEARER_PREFIX = 'bearer ';\n\nfunction extractBearer(c: Context): string | undefined {\n const raw = c.req.header(HEADER_NAME);\n if (raw === undefined || raw === null) return undefined;\n const trimmed = raw.trim();\n if (trimmed.length < BEARER_PREFIX.length) return undefined;\n if (trimmed.slice(0, BEARER_PREFIX.length).toLowerCase() !== BEARER_PREFIX) return undefined;\n return trimmed.slice(BEARER_PREFIX.length).trim();\n}\n\nfunction reasonToStatus(reason: string): {\n status: number;\n body: { error: string; message: string; hint?: string };\n} {\n switch (reason) {\n case 'malformed':\n return { status: 401, body: { error: 'auth-invalid', message: 'Malformed bearer token.' } };\n case 'unknown-token':\n return { status: 401, body: { error: 'auth-invalid', message: 'Unknown bearer token.' } };\n case 'revoked':\n return {\n status: 401,\n body: { error: 'auth-revoked', message: 'Bearer token has been revoked.' },\n };\n case 'expired':\n return { status: 401, body: { error: 'auth-expired', message: 'Bearer token has expired.' } };\n case 'ip-locked-out':\n return {\n status: 429,\n body: {\n error: 'auth-locked-out',\n message: 'Too many failed authentications from this address.',\n hint: 'Wait for the lockout window to elapse before retrying.',\n },\n };\n case 'token-locked-out':\n return {\n status: 429,\n body: {\n error: 'auth-locked-out',\n message: 'Bearer token is locked out after repeated failures.',\n hint: 'Operator must rotate the token.',\n },\n };\n default:\n return {\n status: 401,\n body: { error: 'auth-invalid', message: `Authentication failed: ${reason}.` },\n };\n }\n}\n\n/**\n * Build the bearer-token middleware. The middleware always sets\n * `c.var.state.auth`, even on the unauthenticated branch, so\n * downstream code can pattern-match the discriminated union without\n * a separate \"is anonymous?\" check.\n *\n * @stable\n */\nexport function createAuthMiddleware(\n options: AuthMiddlewareOptions,\n): MiddlewareHandler<{ Variables: ServerVariables }> {\n const allowAnonymous = options.allowAnonymous ?? false;\n return async (c, next: Next) => {\n const token = extractBearer(c);\n // IP-11: the request-state middleware is the ONLY IP authority —\n // it resolves the client IP per the configured trustProxy policy\n // (proxy headers when trusted, else the socket address). The old\n // fallback read attacker-controlled X-Real-IP unconditionally,\n // letting header rotation evade per-IP lockout and a spoofed\n // victim IP trigger a lockout against them.\n const ip = c.get('state').clientIp;\n if (token === undefined) {\n if (allowAnonymous) {\n c.set('state', { ...c.get('state'), auth: { kind: 'unauthenticated' as const } });\n await next();\n return;\n }\n return c.json(\n {\n error: 'auth-required',\n message: 'Bearer token required.',\n hint: \"Set 'Authorization: Bearer <token>'.\",\n },\n 401,\n );\n }\n const result = await options.verifier.verify(token, ip !== undefined ? { ip } : {});\n if (!result.ok) {\n const mapped = reasonToStatus(result.reason);\n const headers: Record<string, string> = {};\n if (result.retryAfterMs !== undefined) {\n headers['Retry-After'] = Math.ceil(result.retryAfterMs / 1000).toString();\n }\n return c.json(mapped.body, mapped.status as 401 | 429, headers);\n }\n const grantedScopes: ParsedScope[] = [];\n for (const scope of result.token.scopes) {\n grantedScopes.push(scope);\n }\n // Defensive: any string scopes coming from the store also parse here\n // even though the verifier itself parses them — see scope.ts.\n void tryParseScope;\n c.set('state', {\n ...c.get('state'),\n auth: {\n kind: 'token' as const,\n token: result.token,\n grantedScopes: Object.freeze(grantedScopes),\n },\n });\n // Mirror the verified token onto the documented `c.var.token`\n // slot so route handlers can read it without unwrapping the auth\n // discriminator. The shape is the one called out in the Phase\n // 14a spec: { id, label, scopes, env, expiresAt? }.\n c.set('token', {\n id: result.token.tokenId,\n label: result.token.label,\n scopes: Object.freeze(grantedScopes.slice()),\n env: result.token.env,\n expiresAt: result.token.expiresAt,\n });\n await next();\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import { ServerConfigSpec } from "../config.js";
2
+ import { ServerVariables } from "../internal/context.js";
3
+ import { MiddlewareHandler } from "hono";
4
+
5
+ //#region src/middleware/cors.d.ts
6
+
7
+ /**
8
+ * @stable
9
+ */
10
+ declare function createCorsMiddleware(config: ServerConfigSpec['server']['cors']): MiddlewareHandler<{
11
+ Variables: ServerVariables;
12
+ }>;
13
+ //#endregion
14
+ export { createCorsMiddleware };
15
+ //# sourceMappingURL=cors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cors.d.ts","names":[],"sources":["../../src/middleware/cors.ts"],"sourcesContent":[],"mappings":";;;;;;;;;iBAgBgB,oBAAA,SACN,qCACP;aAA+B"}