datool 0.0.1 → 0.0.3
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/README.md +4 -0
- package/package.json +1 -1
- package/src/client/App.tsx +24 -7
- package/src/client/stream-state.ts +20 -0
- package/src/node/server.ts +17 -2
- package/src/node/sources/file.ts +22 -8
- package/src/shared/types.ts +4 -0
package/README.md
CHANGED
|
@@ -169,10 +169,14 @@ Tails a local file and optionally honors `history`.
|
|
|
169
169
|
|
|
170
170
|
```ts
|
|
171
171
|
sources.file({
|
|
172
|
+
defaultHistory: 5,
|
|
172
173
|
path: "./app.log",
|
|
173
174
|
})
|
|
174
175
|
```
|
|
175
176
|
|
|
177
|
+
`defaultHistory` lets the file source emit existing lines on startup when the URL
|
|
178
|
+
does not include a `history` query param.
|
|
179
|
+
|
|
176
180
|
### `sources.command(...)`
|
|
177
181
|
|
|
178
182
|
Spawns a local process and streams stdout lines.
|
package/package.json
CHANGED
package/src/client/App.tsx
CHANGED
|
@@ -25,6 +25,7 @@ import type {
|
|
|
25
25
|
DatoolClientConfig,
|
|
26
26
|
DatoolClientStream,
|
|
27
27
|
DatoolColumn,
|
|
28
|
+
DatoolSseEndEvent,
|
|
28
29
|
DatoolRowEvent,
|
|
29
30
|
DatoolSseErrorEvent,
|
|
30
31
|
} from "../shared/types"
|
|
@@ -45,6 +46,7 @@ import {
|
|
|
45
46
|
writeDatoolUrlState,
|
|
46
47
|
} from "@/lib/datool-url-state"
|
|
47
48
|
import { LOG_VIEWER_ICONS } from "@/lib/datool-icons"
|
|
49
|
+
import { upsertViewerRow } from "./stream-state"
|
|
48
50
|
|
|
49
51
|
type ViewerRow = Record<string, unknown> & {
|
|
50
52
|
__datoolRowId: string
|
|
@@ -242,6 +244,10 @@ function parseErrorEvent(event: MessageEvent<string>) {
|
|
|
242
244
|
return JSON.parse(event.data) as DatoolSseErrorEvent
|
|
243
245
|
}
|
|
244
246
|
|
|
247
|
+
function parseEndEvent(event: MessageEvent<string>) {
|
|
248
|
+
return JSON.parse(event.data) as DatoolSseEndEvent
|
|
249
|
+
}
|
|
250
|
+
|
|
245
251
|
function stringifyRowActionValue(value: unknown) {
|
|
246
252
|
if (value === null || value === undefined) {
|
|
247
253
|
return ""
|
|
@@ -773,13 +779,12 @@ export default function App() {
|
|
|
773
779
|
const handleRow = (event: MessageEvent<string>) => {
|
|
774
780
|
const payload = parseRowEvent(event)
|
|
775
781
|
|
|
776
|
-
setRows((currentRows) =>
|
|
777
|
-
|
|
778
|
-
{
|
|
782
|
+
setRows((currentRows) =>
|
|
783
|
+
upsertViewerRow(currentRows, {
|
|
779
784
|
...payload.row,
|
|
780
785
|
__datoolRowId: payload.id,
|
|
781
|
-
}
|
|
782
|
-
|
|
786
|
+
})
|
|
787
|
+
)
|
|
783
788
|
}
|
|
784
789
|
|
|
785
790
|
const handleRuntimeError = (event: MessageEvent<string>) => {
|
|
@@ -788,6 +793,13 @@ export default function App() {
|
|
|
788
793
|
setErrorMessage(payload.message)
|
|
789
794
|
}
|
|
790
795
|
|
|
796
|
+
const handleEnd = (event: MessageEvent<string>) => {
|
|
797
|
+
parseEndEvent(event)
|
|
798
|
+
eventSource.close()
|
|
799
|
+
eventSourceRef.current = null
|
|
800
|
+
setIsConnected(false)
|
|
801
|
+
}
|
|
802
|
+
|
|
791
803
|
eventSource.onopen = () => {
|
|
792
804
|
setIsConnected(true)
|
|
793
805
|
}
|
|
@@ -797,14 +809,19 @@ export default function App() {
|
|
|
797
809
|
}
|
|
798
810
|
|
|
799
811
|
eventSource.addEventListener("row", handleRow as EventListener)
|
|
800
|
-
eventSource.addEventListener(
|
|
812
|
+
eventSource.addEventListener(
|
|
813
|
+
"runtime-error",
|
|
814
|
+
handleRuntimeError as EventListener
|
|
815
|
+
)
|
|
816
|
+
eventSource.addEventListener("end", handleEnd as EventListener)
|
|
801
817
|
|
|
802
818
|
return () => {
|
|
803
819
|
eventSource.removeEventListener("row", handleRow as EventListener)
|
|
804
820
|
eventSource.removeEventListener(
|
|
805
|
-
"error",
|
|
821
|
+
"runtime-error",
|
|
806
822
|
handleRuntimeError as EventListener
|
|
807
823
|
)
|
|
824
|
+
eventSource.removeEventListener("end", handleEnd as EventListener)
|
|
808
825
|
eventSource.close()
|
|
809
826
|
setIsConnected(false)
|
|
810
827
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type StreamViewerRow = Record<string, unknown> & {
|
|
2
|
+
__datoolRowId: string
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function upsertViewerRow<T extends StreamViewerRow>(
|
|
6
|
+
currentRows: T[],
|
|
7
|
+
nextRow: T
|
|
8
|
+
) {
|
|
9
|
+
const existingIndex = currentRows.findIndex(
|
|
10
|
+
(row) => row.__datoolRowId === nextRow.__datoolRowId
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
if (existingIndex < 0) {
|
|
14
|
+
return [...currentRows, nextRow]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return currentRows.map((row, index) =>
|
|
18
|
+
index === existingIndex ? nextRow : row
|
|
19
|
+
)
|
|
20
|
+
}
|
package/src/node/server.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
DatoolActionResolveResult,
|
|
11
11
|
DatoolActionResponse,
|
|
12
12
|
DatoolConfig,
|
|
13
|
+
DatoolSseEndEvent,
|
|
13
14
|
DatoolSseErrorEvent,
|
|
14
15
|
} from "../shared/types"
|
|
15
16
|
|
|
@@ -74,6 +75,12 @@ function toErrorPayload(error: unknown): DatoolSseErrorEvent {
|
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
|
|
78
|
+
function toEndPayload(reason: DatoolSseEndEvent["reason"]): DatoolSseEndEvent {
|
|
79
|
+
return {
|
|
80
|
+
reason,
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
77
84
|
function encodeSseEvent(event: string, data: unknown) {
|
|
78
85
|
return `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`
|
|
79
86
|
}
|
|
@@ -279,15 +286,23 @@ function createSseResponse(
|
|
|
279
286
|
abortController.signal,
|
|
280
287
|
{
|
|
281
288
|
async onError(error) {
|
|
282
|
-
send("error", toErrorPayload(error))
|
|
289
|
+
send("runtime-error", toErrorPayload(error))
|
|
283
290
|
},
|
|
284
291
|
async onRow(payload) {
|
|
285
292
|
send("row", payload)
|
|
286
293
|
},
|
|
287
294
|
}
|
|
288
295
|
)
|
|
296
|
+
.then(() => {
|
|
297
|
+
if (!abortController.signal.aborted) {
|
|
298
|
+
send("end", toEndPayload("completed"))
|
|
299
|
+
}
|
|
300
|
+
})
|
|
289
301
|
.catch((error) => {
|
|
290
|
-
|
|
302
|
+
if (!abortController.signal.aborted) {
|
|
303
|
+
send("runtime-error", toErrorPayload(error))
|
|
304
|
+
send("end", toEndPayload("error"))
|
|
305
|
+
}
|
|
291
306
|
})
|
|
292
307
|
.finally(() => {
|
|
293
308
|
clearInterval(heartbeat)
|
package/src/node/sources/file.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { DatoolSource } from "../../shared/types"
|
|
|
6
6
|
type Resolver<T> = T | ((context: { query: URLSearchParams }) => T)
|
|
7
7
|
|
|
8
8
|
export type FileSourceOptions = {
|
|
9
|
+
defaultHistory?: number
|
|
9
10
|
historyParam?: string
|
|
10
11
|
path: Resolver<string>
|
|
11
12
|
pollIntervalMs?: number
|
|
@@ -21,20 +22,28 @@ function resolveValue<T>(value: Resolver<T>, query: URLSearchParams) {
|
|
|
21
22
|
return value
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
function
|
|
25
|
+
function normalizeHistoryLineCount(value: number) {
|
|
26
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
27
|
+
return 0
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return Math.floor(value)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function resolveHistoryLineCount(
|
|
34
|
+
query: URLSearchParams,
|
|
35
|
+
historyParam: string,
|
|
36
|
+
defaultHistory: number
|
|
37
|
+
) {
|
|
25
38
|
const rawValue = query.get(historyParam)
|
|
26
39
|
|
|
27
40
|
if (!rawValue) {
|
|
28
|
-
return
|
|
41
|
+
return normalizeHistoryLineCount(defaultHistory)
|
|
29
42
|
}
|
|
30
43
|
|
|
31
44
|
const parsedValue = Number.parseInt(rawValue, 10)
|
|
32
45
|
|
|
33
|
-
|
|
34
|
-
return 0
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return parsedValue
|
|
46
|
+
return normalizeHistoryLineCount(parsedValue)
|
|
38
47
|
}
|
|
39
48
|
|
|
40
49
|
function normalizeLines(content: string) {
|
|
@@ -76,10 +85,15 @@ async function readAppendedText(filePath: string, start: number, end: number) {
|
|
|
76
85
|
export function fileSource(options: FileSourceOptions): DatoolSource {
|
|
77
86
|
return {
|
|
78
87
|
async open(context) {
|
|
88
|
+
const defaultHistory = options.defaultHistory ?? 0
|
|
79
89
|
const historyParam = options.historyParam ?? "history"
|
|
80
90
|
const pollIntervalMs = options.pollIntervalMs ?? 250
|
|
81
91
|
const filePath = resolveValue(options.path, context.query)
|
|
82
|
-
const historyLineCount =
|
|
92
|
+
const historyLineCount = resolveHistoryLineCount(
|
|
93
|
+
context.query,
|
|
94
|
+
historyParam,
|
|
95
|
+
defaultHistory
|
|
96
|
+
)
|
|
83
97
|
let stat = await fs.stat(filePath)
|
|
84
98
|
let position = stat.size
|
|
85
99
|
let remainder = ""
|
package/src/shared/types.ts
CHANGED