watch-tail 0.1.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 (110) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +210 -0
  3. package/build/client/_app/immutable/assets/0.B1NsRRrX.css +2 -0
  4. package/build/client/_app/immutable/assets/0.B1NsRRrX.css.br +0 -0
  5. package/build/client/_app/immutable/assets/0.B1NsRRrX.css.gz +0 -0
  6. package/build/client/_app/immutable/chunks/Bjy-W4x2.js +81 -0
  7. package/build/client/_app/immutable/chunks/Bjy-W4x2.js.br +0 -0
  8. package/build/client/_app/immutable/chunks/Bjy-W4x2.js.gz +0 -0
  9. package/build/client/_app/immutable/chunks/DCKWcE8r.js +1 -0
  10. package/build/client/_app/immutable/chunks/DCKWcE8r.js.br +0 -0
  11. package/build/client/_app/immutable/chunks/DCKWcE8r.js.gz +0 -0
  12. package/build/client/_app/immutable/chunks/DDRhiz1-.js +1 -0
  13. package/build/client/_app/immutable/chunks/DDRhiz1-.js.br +0 -0
  14. package/build/client/_app/immutable/chunks/DDRhiz1-.js.gz +0 -0
  15. package/build/client/_app/immutable/chunks/ZAD2y5xJ.js +3 -0
  16. package/build/client/_app/immutable/chunks/ZAD2y5xJ.js.br +0 -0
  17. package/build/client/_app/immutable/chunks/ZAD2y5xJ.js.gz +0 -0
  18. package/build/client/_app/immutable/chunks/xihTtKlq.js +1 -0
  19. package/build/client/_app/immutable/chunks/xihTtKlq.js.br +1 -0
  20. package/build/client/_app/immutable/chunks/xihTtKlq.js.gz +0 -0
  21. package/build/client/_app/immutable/entry/app.DIoggGwf.js +2 -0
  22. package/build/client/_app/immutable/entry/app.DIoggGwf.js.br +0 -0
  23. package/build/client/_app/immutable/entry/app.DIoggGwf.js.gz +0 -0
  24. package/build/client/_app/immutable/entry/start._YXgjuSB.js +1 -0
  25. package/build/client/_app/immutable/entry/start._YXgjuSB.js.br +0 -0
  26. package/build/client/_app/immutable/entry/start._YXgjuSB.js.gz +0 -0
  27. package/build/client/_app/immutable/nodes/0.D5bYt7iX.js +1 -0
  28. package/build/client/_app/immutable/nodes/0.D5bYt7iX.js.br +0 -0
  29. package/build/client/_app/immutable/nodes/0.D5bYt7iX.js.gz +0 -0
  30. package/build/client/_app/immutable/nodes/1.DBX83mYX.js +1 -0
  31. package/build/client/_app/immutable/nodes/1.DBX83mYX.js.br +0 -0
  32. package/build/client/_app/immutable/nodes/1.DBX83mYX.js.gz +0 -0
  33. package/build/client/_app/immutable/nodes/2.DiH-2moC.js +5 -0
  34. package/build/client/_app/immutable/nodes/2.DiH-2moC.js.br +0 -0
  35. package/build/client/_app/immutable/nodes/2.DiH-2moC.js.gz +0 -0
  36. package/build/client/_app/version.json +1 -0
  37. package/build/client/_app/version.json.br +0 -0
  38. package/build/client/_app/version.json.gz +0 -0
  39. package/build/client/robots.txt +3 -0
  40. package/build/client/robots.txt.br +0 -0
  41. package/build/client/robots.txt.gz +0 -0
  42. package/build/env.js +70 -0
  43. package/build/env.js.map +1 -0
  44. package/build/handler.js +24 -0
  45. package/build/handler.js.map +1 -0
  46. package/build/index.js +147 -0
  47. package/build/index.js.map +1 -0
  48. package/build/server/chunks/chunks/env.js-l0sNRNKZ.js +2 -0
  49. package/build/server/chunks/chunks/env.js-l0sNRNKZ.js.map +1 -0
  50. package/build/server/chunks/chunks/env2.js-D7xrfc37.js +316 -0
  51. package/build/server/chunks/chunks/env2.js-D7xrfc37.js.map +1 -0
  52. package/build/server/chunks/chunks/exports.js-CvFPPbaH.js +263 -0
  53. package/build/server/chunks/chunks/exports.js-CvFPPbaH.js.map +1 -0
  54. package/build/server/chunks/chunks/internal.js-DdmUz1r3.js +1191 -0
  55. package/build/server/chunks/chunks/internal.js-DdmUz1r3.js.map +1 -0
  56. package/build/server/chunks/chunks/internal2.js-DZ8ZzzjN.js +22 -0
  57. package/build/server/chunks/chunks/internal2.js-DZ8ZzzjN.js.map +1 -0
  58. package/build/server/chunks/chunks/regions.js-IQPMyP5C.js +52 -0
  59. package/build/server/chunks/chunks/regions.js-IQPMyP5C.js.map +1 -0
  60. package/build/server/chunks/chunks/server.js-ByNB8uti.js +3659 -0
  61. package/build/server/chunks/chunks/server.js-ByNB8uti.js.map +1 -0
  62. package/build/server/chunks/chunks/shared-server.js-Dn5XheXH.js +22 -0
  63. package/build/server/chunks/chunks/shared-server.js-Dn5XheXH.js.map +1 -0
  64. package/build/server/chunks/chunks/shared.js-CgjUIvRK.js +776 -0
  65. package/build/server/chunks/chunks/shared.js-CgjUIvRK.js.map +1 -0
  66. package/build/server/chunks/chunks/state.js-ClLTNPGs.js +86 -0
  67. package/build/server/chunks/chunks/state.js-ClLTNPGs.js.map +1 -0
  68. package/build/server/chunks/chunks/time-range.js-BLMT8aU3.js +102 -0
  69. package/build/server/chunks/chunks/time-range.js-BLMT8aU3.js.map +1 -0
  70. package/build/server/chunks/chunks/uneval.js-DaakSYFQ.js +488 -0
  71. package/build/server/chunks/chunks/uneval.js-DaakSYFQ.js.map +1 -0
  72. package/build/server/chunks/chunks/utils.js-BIlHjgGW.js +933 -0
  73. package/build/server/chunks/chunks/utils.js-BIlHjgGW.js.map +1 -0
  74. package/build/server/chunks/entries/endpoints/api/health/_server.ts.js-aiNZfkco.js +41 -0
  75. package/build/server/chunks/entries/endpoints/api/health/_server.ts.js-aiNZfkco.js.map +1 -0
  76. package/build/server/chunks/entries/endpoints/api/log-groups/_server.ts.js-D143-vRl.js +120 -0
  77. package/build/server/chunks/entries/endpoints/api/log-groups/_server.ts.js-D143-vRl.js.map +1 -0
  78. package/build/server/chunks/entries/endpoints/api/regions/_server.ts.js-CDNL7msU.js +88 -0
  79. package/build/server/chunks/entries/endpoints/api/regions/_server.ts.js-CDNL7msU.js.map +1 -0
  80. package/build/server/chunks/entries/endpoints/api/stream/_server.ts.js-D_i-xW6_.js +610 -0
  81. package/build/server/chunks/entries/endpoints/api/stream/_server.ts.js-D_i-xW6_.js.map +1 -0
  82. package/build/server/chunks/entries/fallbacks/error.svelte.js-qFLa8EVy.js +17 -0
  83. package/build/server/chunks/entries/fallbacks/error.svelte.js-qFLa8EVy.js.map +1 -0
  84. package/build/server/chunks/entries/pages/_layout.svelte.js-Bx7lXkAi.js +22 -0
  85. package/build/server/chunks/entries/pages/_layout.svelte.js-Bx7lXkAi.js.map +1 -0
  86. package/build/server/chunks/entries/pages/_page.svelte.js-BsvaKqbk.js +1264 -0
  87. package/build/server/chunks/entries/pages/_page.svelte.js-BsvaKqbk.js.map +1 -0
  88. package/build/server/chunks/handler-nVqa-QPz.js +1735 -0
  89. package/build/server/chunks/handler-nVqa-QPz.js.map +1 -0
  90. package/build/server/chunks/index.js-CtbhHm7L.js +3803 -0
  91. package/build/server/chunks/index.js-CtbhHm7L.js.map +1 -0
  92. package/build/server/chunks/manifest.js-BwUys8gK.js +70 -0
  93. package/build/server/chunks/manifest.js-BwUys8gK.js.map +1 -0
  94. package/build/server/chunks/nodes/0.js-Dl0J9d3p.js +9 -0
  95. package/build/server/chunks/nodes/0.js-Dl0J9d3p.js.map +1 -0
  96. package/build/server/chunks/nodes/1.js-dXZVW92o.js +9 -0
  97. package/build/server/chunks/nodes/1.js-dXZVW92o.js.map +1 -0
  98. package/build/server/chunks/nodes/2.js-aMMUenQc.js +9 -0
  99. package/build/server/chunks/nodes/2.js-aMMUenQc.js.map +1 -0
  100. package/build/shims.js +33 -0
  101. package/build/shims.js.map +1 -0
  102. package/dist/cli/bin.js +9 -0
  103. package/dist/cli/completions.js +99 -0
  104. package/dist/cli/index.js +183 -0
  105. package/dist/cli/options.js +171 -0
  106. package/dist/cli/server.js +126 -0
  107. package/dist/cli/ui.js +75 -0
  108. package/dist/lib/cli/aws.js +264 -0
  109. package/dist/lib/time-range.js +106 -0
  110. package/package.json +106 -0
@@ -0,0 +1,610 @@
1
+ import { a as apiError, p as parseRegionParam, b as readEnv, r as resolveAwsConfig, c as createLogsClient, d as resolveEffectiveRegion, e as describeAwsError, R as REGION_PARAM_HINT } from '../../../../chunks/env2.js-D7xrfc37.js';
2
+ import { H as HISTORIC_PRESETS, p as presetDurationMs, i as isHistoricPreset } from '../../../../chunks/time-range.js-BLMT8aU3.js';
3
+ import '../../../../chunks/utils.js-BIlHjgGW.js';
4
+ import { FilterLogEventsCommand } from '@aws-sdk/client-cloudwatch-logs';
5
+ import '../../../../chunks/shared-server.js-Dn5XheXH.js';
6
+ import 'node:fs';
7
+ import 'node:path';
8
+ import '../../../../chunks/shared.js-CgjUIvRK.js';
9
+ import '../../../../chunks/uneval.js-DaakSYFQ.js';
10
+
11
+ //#region src/lib/server/filter.ts
12
+ /** Default lookback window when neither `startTime` nor `lookback` is usable. */
13
+ var DEFAULT_LOOKBACK_MS = 3e5;
14
+ /** CloudWatch Logs rejects `startTime` older than 14 days. */
15
+ var MAX_LOOKBACK_MS = 12096e5;
16
+ var DEFAULT_POLL_MS$1 = 1e3;
17
+ var MIN_POLL_MS = 250;
18
+ var MAX_POLL_MS = 15e3;
19
+ var DURATION_UNITS = {
20
+ ms: 1,
21
+ s: 1e3,
22
+ m: 6e4,
23
+ h: 36e5,
24
+ d: 864e5,
25
+ w: 6048e5
26
+ };
27
+ var DURATION_PATTERN = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d|w)$/i;
28
+ var EPOCH_PATTERN = /^\d+$/;
29
+ /**
30
+ * Parses a single-unit duration such as `500ms`, `30s`, `15m`, `2h`, `3d` or
31
+ * `1w`. Returns `null` for anything invalid, negative or zero.
32
+ */
33
+ function parseDurationMs(value) {
34
+ if (typeof value !== "string") return null;
35
+ const match = DURATION_PATTERN.exec(value.trim());
36
+ if (match === null) return null;
37
+ const amount = Number(match[1]);
38
+ const unit = DURATION_UNITS[match[2].toLowerCase()];
39
+ if (unit === void 0 || !Number.isFinite(amount)) return null;
40
+ const ms = amount * unit;
41
+ if (!Number.isFinite(ms) || ms <= 0) return null;
42
+ return Math.round(ms);
43
+ }
44
+ /**
45
+ * Parses a start point into epoch milliseconds.
46
+ *
47
+ * Accepts epoch milliseconds (`1700000000000`), a duration relative to `now`
48
+ * (`15m`) and ISO 8601 timestamps. Returns `null` when the value is missing or
49
+ * cannot be understood.
50
+ */
51
+ function parseStartTime(value, now) {
52
+ if (typeof value !== "string") return null;
53
+ const trimmed = value.trim();
54
+ if (trimmed.length === 0) return null;
55
+ if (EPOCH_PATTERN.test(trimmed)) return Number(trimmed);
56
+ const duration = parseDurationMs(trimmed);
57
+ if (duration !== null) return now - duration;
58
+ const parsed = Date.parse(trimmed);
59
+ return Number.isNaN(parsed) ? null : parsed;
60
+ }
61
+ /** Resolves the effective start time, clamped to the last 14 days and to `now`. */
62
+ function resolveStartTime(input) {
63
+ const { now } = input;
64
+ let start = parseStartTime(input.startTime, now);
65
+ if (start === null) start = now - (parseDurationMs(input.lookback ?? "") ?? DEFAULT_LOOKBACK_MS);
66
+ const oldestAllowed = now - MAX_LOOKBACK_MS;
67
+ if (start > now) start = now;
68
+ if (start < oldestAllowed) start = oldestAllowed;
69
+ return Math.round(start);
70
+ }
71
+ /** Parses a poll interval in milliseconds, defaulting to 1000 and clamped to 250..15000. */
72
+ function clampPollMs(value) {
73
+ if (typeof value !== "string") return DEFAULT_POLL_MS$1;
74
+ const trimmed = value.trim();
75
+ if (trimmed.length === 0) return DEFAULT_POLL_MS$1;
76
+ const parsed = Number(trimmed);
77
+ if (!Number.isFinite(parsed)) return DEFAULT_POLL_MS$1;
78
+ return Math.min(Math.max(Math.round(parsed), MIN_POLL_MS), MAX_POLL_MS);
79
+ }
80
+ /** Default historic window when neither `range` nor `from`/`to` is given. */
81
+ var DEFAULT_HISTORIC_PRESET = "15m";
82
+ /** Parses the `mode` parameter; `null` for an unrecognised value. */
83
+ function parseMode(value) {
84
+ if (typeof value !== "string") return null;
85
+ const trimmed = value.trim().toLowerCase();
86
+ if (trimmed === "live" || trimmed === "historic") return trimmed;
87
+ return null;
88
+ }
89
+ /**
90
+ * Parses an absolute instant: epoch milliseconds or an ISO 8601 timestamp.
91
+ *
92
+ * Relative durations are deliberately rejected here so an end bound can never
93
+ * move with the clock.
94
+ */
95
+ function parseInstant(value) {
96
+ if (typeof value !== "string") return null;
97
+ const trimmed = value.trim();
98
+ if (trimmed.length === 0) return null;
99
+ if (EPOCH_PATTERN.test(trimmed)) return Number(trimmed);
100
+ const parsed = Date.parse(trimmed);
101
+ return Number.isNaN(parsed) ? null : parsed;
102
+ }
103
+ /**
104
+ * Clamps a window to what CloudWatch Logs can return: 14 days back, ending now.
105
+ *
106
+ * Returns `null` when the requested window ends before the retention floor, so
107
+ * a request for month-old data fails loudly instead of returning a stub window.
108
+ */
109
+ function clampWindow(start, end, now) {
110
+ const oldest = now - MAX_LOOKBACK_MS;
111
+ if (end <= oldest) return null;
112
+ const clampedStart = Math.min(Math.max(start, oldest), now);
113
+ const clampedEnd = Math.min(Math.max(end, clampedStart + 1), now);
114
+ return {
115
+ start: Math.round(clampedStart),
116
+ end: Math.round(clampedEnd),
117
+ clamped: clampedStart !== Math.round(start) || clampedEnd !== Math.round(end)
118
+ };
119
+ }
120
+ /**
121
+ * Resolves the request window into a start time plus, for historic mode, an end
122
+ * time.
123
+ *
124
+ * Historic windows come from a preset (`range=24h`), from explicit bounds
125
+ * (`from`/`to`, epoch ms or ISO 8601) or, when neither is given, from the
126
+ * smallest preset. Windows are clamped to the last 14 days and to `now` so the
127
+ * request can never ask CloudWatch for data it will not return.
128
+ */
129
+ function resolveWindow(input) {
130
+ const { now } = input;
131
+ const requestedMode = input.mode === null || input.mode === void 0 ? "live" : input.mode.trim();
132
+ const mode = requestedMode === "" ? "live" : parseMode(requestedMode);
133
+ if (mode === null) return {
134
+ ok: false,
135
+ code: "invalid-mode",
136
+ message: "Invalid mode: expected \"live\" or \"historic\""
137
+ };
138
+ if (mode === "live") return {
139
+ ok: true,
140
+ mode,
141
+ startTime: resolveStartTime({
142
+ startTime: input.startTime,
143
+ lookback: input.lookback,
144
+ now
145
+ }),
146
+ endTime: null,
147
+ preset: null,
148
+ clamped: false
149
+ };
150
+ const from = input.from?.trim() ?? "";
151
+ const to = input.to?.trim() ?? "";
152
+ if (from.length > 0 || to.length > 0) {
153
+ if (from.length === 0 || to.length === 0) return {
154
+ ok: false,
155
+ code: "invalid-window",
156
+ message: "A custom window needs both \"from\" and \"to\""
157
+ };
158
+ const parsedFrom = parseStartTime(from, now);
159
+ const parsedTo = parseInstant(to);
160
+ if (parsedFrom === null) return {
161
+ ok: false,
162
+ code: "invalid-time",
163
+ message: `Invalid "from": expected epoch milliseconds, a duration such as 3h, or an ISO 8601 timestamp`
164
+ };
165
+ if (parsedTo === null) return {
166
+ ok: false,
167
+ code: "invalid-time",
168
+ message: "Invalid \"to\": expected epoch milliseconds or an ISO 8601 timestamp"
169
+ };
170
+ if (parsedTo <= parsedFrom) return {
171
+ ok: false,
172
+ code: "invalid-window",
173
+ message: "The end of the window must be after its start"
174
+ };
175
+ const clamped = clampWindow(parsedFrom, parsedTo, now);
176
+ if (clamped === null || clamped.end <= clamped.start) return {
177
+ ok: false,
178
+ code: "invalid-window",
179
+ message: "The requested window is outside the 14 days CloudWatch Logs keeps"
180
+ };
181
+ return {
182
+ ok: true,
183
+ mode,
184
+ startTime: clamped.start,
185
+ endTime: clamped.end,
186
+ preset: null,
187
+ clamped: clamped.clamped
188
+ };
189
+ }
190
+ const requestedRange = input.range?.trim() ?? "";
191
+ const preset = requestedRange === "" ? DEFAULT_HISTORIC_PRESET : isHistoricPreset(requestedRange) ? requestedRange : null;
192
+ if (preset === null) return {
193
+ ok: false,
194
+ code: "invalid-range",
195
+ message: `Invalid range: expected one of ${HISTORIC_PRESETS.join(", ")} or an explicit from/to window`
196
+ };
197
+ const end = Math.round(now);
198
+ return {
199
+ ok: true,
200
+ mode,
201
+ startTime: Math.round(now - presetDurationMs(preset)),
202
+ endTime: end,
203
+ preset,
204
+ clamped: false
205
+ };
206
+ }
207
+ //#endregion
208
+ //#region src/lib/server/sse.ts
209
+ var NEWLINE_PATTERN = /\r?\n/g;
210
+ /**
211
+ * Encodes a single server-sent event frame.
212
+ *
213
+ * The JSON payload is always kept on one line, so the frame is
214
+ * `event: <name>\ndata: <json>\n\n`.
215
+ */
216
+ function sseFrame(event, data) {
217
+ return `event: ${event.replace(NEWLINE_PATTERN, " ").trim()}\ndata: ${(JSON.stringify(data) ?? "null").replace(NEWLINE_PATTERN, " ")}\n\n`;
218
+ }
219
+ //#endregion
220
+ //#region src/lib/server/tail.ts
221
+ var DEFAULT_SEEN_CAPACITY = 5e3;
222
+ var DEFAULT_POLL_MS = 1e3;
223
+ var DEFAULT_LIMIT = 1e3;
224
+ var DEFAULT_MAX_CONSECUTIVE_ERRORS = 8;
225
+ var MAX_BACKOFF_MS = 15e3;
226
+ /** Empty polls that end a historic window: CloudWatch can lag behind ingestion. */
227
+ var DEFAULT_IDLE_POLLS = 2;
228
+ /** Upper bound on events yielded for one historic window. */
229
+ var DEFAULT_MAX_EVENTS = 1e4;
230
+ /** Returns true when the value is an abort error raised by the fetch/SDK layer. */
231
+ function isAbortError(error) {
232
+ if (error instanceof Error) return error.name === "AbortError" || error.code === "ABORT_ERR";
233
+ return false;
234
+ }
235
+ /**
236
+ * Abort-aware sleep used by default.
237
+ *
238
+ * Resolves after `ms`, resolves immediately when the signal aborts and always
239
+ * clears its timer. An injected sleep may instead reject on abort, which
240
+ * {@link tailLogEvents} handles by re-checking the signal.
241
+ */
242
+ var defaultSleep = (ms, signal) => new Promise((resolve) => {
243
+ if (signal?.aborted === true || !Number.isFinite(ms) || ms <= 0) {
244
+ resolve();
245
+ return;
246
+ }
247
+ const onAbort = () => {
248
+ clearTimeout(timer);
249
+ resolve();
250
+ };
251
+ const timer = setTimeout(() => {
252
+ signal?.removeEventListener("abort", onAbort);
253
+ resolve();
254
+ }, ms);
255
+ timer.unref?.();
256
+ signal?.addEventListener("abort", onAbort, { once: true });
257
+ });
258
+ /**
259
+ * Bounded first-in-first-out set of event ids.
260
+ *
261
+ * Keeps at most `capacity` ids (default 5000) so memory stays flat on long
262
+ * streams. The oldest id is evicted first.
263
+ */
264
+ var SeenEventIds = class {
265
+ ids = /* @__PURE__ */ new Set();
266
+ capacity;
267
+ constructor(capacity = DEFAULT_SEEN_CAPACITY) {
268
+ const parsed = Number.isFinite(capacity) ? Math.floor(capacity) : DEFAULT_SEEN_CAPACITY;
269
+ this.capacity = parsed > 0 ? parsed : DEFAULT_SEEN_CAPACITY;
270
+ }
271
+ /** True when the id was recorded before. Null and empty ids are never recorded. */
272
+ has(id) {
273
+ return id !== null && id.length > 0 && this.ids.has(id);
274
+ }
275
+ /** Records an id, evicting the oldest entry when the capacity is reached. */
276
+ add(id) {
277
+ if (id === null || id.length === 0 || this.ids.has(id)) return;
278
+ if (this.ids.size >= this.capacity) {
279
+ const oldest = this.ids.values().next();
280
+ if (!oldest.done) this.ids.delete(oldest.value);
281
+ }
282
+ this.ids.add(id);
283
+ }
284
+ /** Number of ids currently remembered. */
285
+ get size() {
286
+ return this.ids.size;
287
+ }
288
+ };
289
+ /**
290
+ * Converts a poll result into the events worth sending downstream.
291
+ *
292
+ * Drops ids seen in earlier polls and duplicates inside the same batch, then
293
+ * records the ids of the accepted events. Events without an id are always kept
294
+ * and never recorded.
295
+ */
296
+ function selectNewEvents(events, seen) {
297
+ const selected = [];
298
+ if (events === void 0 || events.length === 0) return selected;
299
+ const freshIds = /* @__PURE__ */ new Set();
300
+ for (const event of events) {
301
+ const id = event.eventId !== void 0 && event.eventId.length > 0 ? event.eventId : null;
302
+ if (id !== null) {
303
+ if (seen.has(id) || freshIds.has(id)) continue;
304
+ freshIds.add(id);
305
+ }
306
+ const dto = {
307
+ id,
308
+ timestamp: typeof event.timestamp === "number" ? event.timestamp : 0,
309
+ message: event.message ?? ""
310
+ };
311
+ if (event.logStreamName !== void 0) dto.streamName = event.logStreamName;
312
+ if (typeof event.ingestionTime === "number") dto.ingestionTime = event.ingestionTime;
313
+ selected.push(dto);
314
+ }
315
+ for (const id of freshIds) seen.add(id);
316
+ return selected;
317
+ }
318
+ /** Highest timestamp in `events`, never lower than `previous`. */
319
+ function nextCursor(previous, events) {
320
+ let cursor = Number.isFinite(previous) ? previous : 0;
321
+ for (const event of events) if (Number.isFinite(event.timestamp) && event.timestamp > cursor) cursor = event.timestamp;
322
+ return cursor;
323
+ }
324
+ /**
325
+ * Polls `FilterLogEvents` and yields batches as they arrive.
326
+ *
327
+ * `startTime` is inclusive upstream, so the cursor advances to the newest
328
+ * timestamp seen and repeated events are removed by {@link SeenEventIds}.
329
+ * Nothing is new in a poll => sleep `pollIntervalMs`. Errors are yielded as
330
+ * `error` batches and retried with exponential backoff
331
+ * (`pollIntervalMs * 2^consecutiveErrors`, capped at 15000 ms) until
332
+ * `maxConsecutiveErrors` (default 8) failures in a row, then the generator
333
+ * returns. An aborted signal ends the generator without throwing.
334
+ */
335
+ async function* tailLogEvents(options) {
336
+ const { client, logGroupName, startTime, pollIntervalMs = DEFAULT_POLL_MS, limit = DEFAULT_LIMIT, filterPattern, signal, sleep = defaultSleep, maxConsecutiveErrors = DEFAULT_MAX_CONSECUTIVE_ERRORS, logStreamNamePrefix, endTime = null, idlePolls = DEFAULT_IDLE_POLLS, maxEvents = DEFAULT_MAX_EVENTS } = options;
337
+ const historic = typeof endTime === "number" && Number.isFinite(endTime);
338
+ const windowEnd = historic ? endTime : null;
339
+ const idlePollsAllowed = Number.isFinite(idlePolls) && idlePolls > 0 ? Math.floor(idlePolls) : DEFAULT_IDLE_POLLS;
340
+ const eventCap = Number.isFinite(maxEvents) && maxEvents > 0 ? Math.floor(maxEvents) : DEFAULT_MAX_EVENTS;
341
+ const pollMs = Number.isFinite(pollIntervalMs) && pollIntervalMs > 0 ? Math.min(Math.round(pollIntervalMs), MAX_BACKOFF_MS) : DEFAULT_POLL_MS;
342
+ const pageLimit = Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : DEFAULT_LIMIT;
343
+ const maxErrors = Number.isFinite(maxConsecutiveErrors) && maxConsecutiveErrors > 0 ? Math.floor(maxConsecutiveErrors) : DEFAULT_MAX_CONSECUTIVE_ERRORS;
344
+ const baseInput = {
345
+ logGroupName,
346
+ startTime,
347
+ limit: pageLimit
348
+ };
349
+ if (windowEnd !== null) baseInput.endTime = windowEnd;
350
+ if (filterPattern !== void 0 && filterPattern.length > 0) baseInput.filterPattern = filterPattern;
351
+ if (logStreamNamePrefix !== void 0 && logStreamNamePrefix.length > 0) baseInput.logStreamNamePrefix = logStreamNamePrefix;
352
+ const isAborted = () => signal?.aborted === true;
353
+ const wait = async (ms) => {
354
+ if (isAborted()) return true;
355
+ try {
356
+ await sleep(ms, signal);
357
+ } catch {}
358
+ return isAborted();
359
+ };
360
+ const seen = new SeenEventIds();
361
+ let cursor = Number.isFinite(startTime) ? startTime : 0;
362
+ let consecutiveErrors = 0;
363
+ let emitted = 0;
364
+ let emptyPolls = 0;
365
+ for (;;) {
366
+ if (isAborted()) return;
367
+ try {
368
+ const input = {
369
+ ...baseInput,
370
+ startTime: cursor
371
+ };
372
+ const response = await client.send(new FilterLogEventsCommand(input), { abortSignal: signal });
373
+ consecutiveErrors = 0;
374
+ const events = selectNewEvents(response.events, seen);
375
+ if (events.length > 0) {
376
+ emptyPolls = 0;
377
+ cursor = nextCursor(cursor, events);
378
+ emitted += events.length;
379
+ yield {
380
+ type: "events",
381
+ events
382
+ };
383
+ if (historic && emitted >= eventCap) {
384
+ yield {
385
+ type: "end",
386
+ reason: "event-limit"
387
+ };
388
+ return;
389
+ }
390
+ if (windowEnd !== null && cursor >= windowEnd) {
391
+ yield {
392
+ type: "end",
393
+ reason: "window-complete"
394
+ };
395
+ return;
396
+ }
397
+ continue;
398
+ }
399
+ if (historic) {
400
+ emptyPolls += 1;
401
+ if (emptyPolls >= idlePollsAllowed) {
402
+ yield {
403
+ type: "end",
404
+ reason: "window-complete"
405
+ };
406
+ return;
407
+ }
408
+ }
409
+ } catch (error) {
410
+ if (isAborted() || isAbortError(error)) return;
411
+ consecutiveErrors += 1;
412
+ const described = describeAwsError(error);
413
+ yield {
414
+ type: "error",
415
+ message: described.message,
416
+ code: described.code
417
+ };
418
+ if (consecutiveErrors >= maxErrors) {
419
+ if (historic) yield {
420
+ type: "end",
421
+ reason: "repeated-errors"
422
+ };
423
+ return;
424
+ }
425
+ if (await wait(Math.min(pollMs * 2 ** consecutiveErrors, MAX_BACKOFF_MS))) return;
426
+ continue;
427
+ }
428
+ if (await wait(pollMs)) return;
429
+ }
430
+ }
431
+ //#endregion
432
+ //#region src/routes/api/stream/+server.ts
433
+ /** Send a `ping` after this much silence. */
434
+ var PING_INTERVAL_MS = 15e3;
435
+ /** Stop the stream after this many consecutive failed polls. */
436
+ var MAX_CONSECUTIVE_ERRORS = 8;
437
+ var SSE_HEADERS = {
438
+ "content-type": "text/event-stream; charset=utf-8",
439
+ "cache-control": "no-cache, no-transform",
440
+ connection: "keep-alive",
441
+ "x-accel-buffering": "no"
442
+ };
443
+ /**
444
+ * `GET /api/stream` - server-sent events for one log group.
445
+ *
446
+ * Emits `ready` first, then `log` per batch, `ping` after 15 s of silence and
447
+ * a final `end`. The region is optional: without it the tailer uses a client
448
+ * that resolves the region from the ambient AWS configuration. The tailer is
449
+ * cancelled through `request.signal`, and every timer and listener is removed
450
+ * so the dev server can exit.
451
+ */
452
+ var GET = async ({ url, request }) => {
453
+ const logGroupName = url.searchParams.get("group")?.trim() ?? "";
454
+ if (logGroupName.length === 0) return apiError(400, "Query parameter \"group\" is required", "missing-group");
455
+ const parsedRegion = parseRegionParam(url.searchParams.get("region"));
456
+ if (!parsedRegion.ok) return apiError(400, REGION_PARAM_HINT, "invalid-region");
457
+ const env = readEnv();
458
+ const config = resolveAwsConfig(env, parsedRegion.region);
459
+ const filterPattern = url.searchParams.get("filterPattern")?.trim();
460
+ const streamWindow = resolveWindow({
461
+ mode: url.searchParams.get("mode"),
462
+ range: url.searchParams.get("range"),
463
+ from: url.searchParams.get("from"),
464
+ to: url.searchParams.get("to"),
465
+ startTime: url.searchParams.get("startTime"),
466
+ lookback: url.searchParams.get("lookback"),
467
+ now: Date.now()
468
+ });
469
+ if (!streamWindow.ok) return apiError(400, streamWindow.message, streamWindow.code);
470
+ const pollIntervalMs = clampPollMs(url.searchParams.get("poll"));
471
+ let client;
472
+ let region;
473
+ try {
474
+ client = createLogsClient(config);
475
+ region = await resolveEffectiveRegion(client, config);
476
+ } catch (error) {
477
+ const described = describeAwsError(error);
478
+ return apiError(502, described.message, described.code);
479
+ }
480
+ const encoder = new TextEncoder();
481
+ const bodyAbort = new AbortController();
482
+ let streamController;
483
+ let closed = false;
484
+ let pingTimer;
485
+ let cleanedUp = false;
486
+ const clearPing = () => {
487
+ if (pingTimer !== void 0) {
488
+ clearTimeout(pingTimer);
489
+ pingTimer = void 0;
490
+ }
491
+ };
492
+ const armPing = () => {
493
+ if (closed) return;
494
+ clearPing();
495
+ pingTimer = setTimeout(() => {
496
+ pingTimer = void 0;
497
+ if (closed) return;
498
+ enqueue(sseFrame("ping", { at: Date.now() }), false);
499
+ armPing();
500
+ }, PING_INTERVAL_MS);
501
+ pingTimer.unref?.();
502
+ };
503
+ const enqueue = (chunk, rearm = true) => {
504
+ if (closed || streamController === void 0) return;
505
+ try {
506
+ streamController.enqueue(encoder.encode(chunk));
507
+ } catch {
508
+ closed = true;
509
+ return;
510
+ }
511
+ if (rearm) armPing();
512
+ };
513
+ const closeStream = () => {
514
+ closed = true;
515
+ try {
516
+ streamController?.close();
517
+ } catch {}
518
+ };
519
+ const writeEnd = (reason) => {
520
+ clearPing();
521
+ const payload = { reason };
522
+ try {
523
+ streamController?.enqueue(encoder.encode(sseFrame("end", payload)));
524
+ } catch {}
525
+ closeStream();
526
+ };
527
+ const onAbort = () => {
528
+ bodyAbort.abort();
529
+ };
530
+ const cleanup = () => {
531
+ if (cleanedUp) return;
532
+ cleanedUp = true;
533
+ clearPing();
534
+ request.signal.removeEventListener("abort", onAbort);
535
+ client.destroy();
536
+ };
537
+ const pump = async () => {
538
+ let consecutiveErrors = 0;
539
+ let reason = "completed";
540
+ try {
541
+ for await (const batch of tailLogEvents({
542
+ client,
543
+ logGroupName,
544
+ startTime: streamWindow.startTime,
545
+ endTime: streamWindow.endTime,
546
+ pollIntervalMs,
547
+ filterPattern,
548
+ signal: bodyAbort.signal,
549
+ maxConsecutiveErrors: MAX_CONSECUTIVE_ERRORS
550
+ })) {
551
+ if (closed || bodyAbort.signal.aborted) break;
552
+ if (batch.type === "events") {
553
+ const payload = { events: batch.events };
554
+ consecutiveErrors = 0;
555
+ enqueue(sseFrame("log", payload));
556
+ } else if (batch.type === "end") {
557
+ reason = batch.reason;
558
+ break;
559
+ } else {
560
+ const payload = { message: batch.message };
561
+ if (batch.code !== void 0) payload.code = batch.code;
562
+ consecutiveErrors += 1;
563
+ enqueue(sseFrame("error", payload));
564
+ }
565
+ }
566
+ if (reason === "completed") {
567
+ if (bodyAbort.signal.aborted) reason = "client-disconnected";
568
+ else if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) reason = "repeated-errors";
569
+ }
570
+ } catch (error) {
571
+ const described = describeAwsError(error);
572
+ const payload = { message: described.message };
573
+ if (described.code !== void 0) payload.code = described.code;
574
+ enqueue(sseFrame("error", payload));
575
+ reason = "failed";
576
+ } finally {
577
+ cleanup();
578
+ }
579
+ writeEnd(reason);
580
+ };
581
+ request.signal.addEventListener("abort", onAbort, { once: true });
582
+ const stream = new ReadableStream({
583
+ start(controller) {
584
+ streamController = controller;
585
+ const payload = {
586
+ region,
587
+ logGroupName,
588
+ endpoint: config.endpoint,
589
+ startTime: streamWindow.startTime,
590
+ endTime: streamWindow.endTime,
591
+ mode: streamWindow.mode,
592
+ preset: streamWindow.preset,
593
+ clamped: streamWindow.clamped
594
+ };
595
+ enqueue(sseFrame("ready", payload));
596
+ armPing();
597
+ pump();
598
+ },
599
+ cancel() {
600
+ bodyAbort.abort();
601
+ clearPing();
602
+ closeStream();
603
+ cleanup();
604
+ }
605
+ });
606
+ return new Response(stream, { headers: SSE_HEADERS });
607
+ };
608
+
609
+ export { GET };
610
+ //# sourceMappingURL=_server.ts.js-D_i-xW6_.js.map