@ceralive/cerastream 2026.6.0-rc.1
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 +88 -0
- package/dist/client.d.ts +58 -0
- package/dist/client.js +274 -0
- package/dist/config.d.ts +16 -0
- package/dist/config.js +54 -0
- package/dist/constants.d.ts +31 -0
- package/dist/constants.js +35 -0
- package/dist/envelope.d.ts +49 -0
- package/dist/envelope.js +52 -0
- package/dist/errors.d.ts +71 -0
- package/dist/errors.js +95 -0
- package/dist/events.d.ts +334 -0
- package/dist/events.js +97 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +13 -0
- package/dist/messages.d.ts +394 -0
- package/dist/messages.js +152 -0
- package/dist/paths.d.ts +14 -0
- package/dist/paths.js +72 -0
- package/dist/transport.d.ts +30 -0
- package/dist/transport.js +98 -0
- package/dist/types.d.ts +110 -0
- package/dist/types.js +69 -0
- package/package.json +41 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { MAX_LINE_BYTES } from "./constants.js";
|
|
2
|
+
/**
|
|
3
|
+
* A single Unix-socket connection that frames bytes into NDJSON lines. Construct,
|
|
4
|
+
* `open()` (rejects if the socket can't be reached), `send()` lines, `close()`
|
|
5
|
+
* when done. Not reusable across reconnects — the client makes a new one.
|
|
6
|
+
*/
|
|
7
|
+
export class LineSocket {
|
|
8
|
+
socketPath;
|
|
9
|
+
socket;
|
|
10
|
+
buffer = "";
|
|
11
|
+
bufferedBytes = 0;
|
|
12
|
+
handlers;
|
|
13
|
+
closed = false;
|
|
14
|
+
constructor(socketPath) {
|
|
15
|
+
this.socketPath = socketPath;
|
|
16
|
+
}
|
|
17
|
+
/** Connect to the socket and start framing. Rejects on a connect failure. */
|
|
18
|
+
async open(handlers) {
|
|
19
|
+
this.handlers = handlers;
|
|
20
|
+
this.socket = await Bun.connect({
|
|
21
|
+
unix: this.socketPath,
|
|
22
|
+
socket: {
|
|
23
|
+
data: (_s, chunk) => this.onData(chunk),
|
|
24
|
+
close: () => this.fireClose(),
|
|
25
|
+
end: () => this.fireClose(),
|
|
26
|
+
error: (_s, err) => this.fireClose(err),
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/** Write one NDJSON line (the trailing newline is added here). */
|
|
31
|
+
send(line) {
|
|
32
|
+
if (this.closed || !this.socket) {
|
|
33
|
+
throw new Error("LineSocket.send: socket is not open");
|
|
34
|
+
}
|
|
35
|
+
this.socket.write(`${line}\n`);
|
|
36
|
+
this.socket.flush();
|
|
37
|
+
}
|
|
38
|
+
/** Close the connection. Idempotent; does not fire {@link LineSocketHandlers.onClose}. */
|
|
39
|
+
close() {
|
|
40
|
+
if (this.closed)
|
|
41
|
+
return;
|
|
42
|
+
this.closed = true;
|
|
43
|
+
this.handlers = undefined;
|
|
44
|
+
try {
|
|
45
|
+
this.socket?.end();
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// already gone
|
|
49
|
+
}
|
|
50
|
+
this.socket = undefined;
|
|
51
|
+
}
|
|
52
|
+
onData(chunk) {
|
|
53
|
+
if (this.closed)
|
|
54
|
+
return;
|
|
55
|
+
this.buffer += chunk.toString("utf8");
|
|
56
|
+
this.bufferedBytes += chunk.byteLength;
|
|
57
|
+
let nl = this.buffer.indexOf("\n");
|
|
58
|
+
if (nl === -1) {
|
|
59
|
+
// No complete line yet — guard the unbounded-line case (fatal framing error).
|
|
60
|
+
if (this.bufferedBytes > MAX_LINE_BYTES) {
|
|
61
|
+
this.fail(new Error(`line exceeded the ${MAX_LINE_BYTES}-byte framing cap (fatal protocol error)`));
|
|
62
|
+
}
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
while (nl !== -1) {
|
|
66
|
+
const line = this.buffer.slice(0, nl);
|
|
67
|
+
this.buffer = this.buffer.slice(nl + 1);
|
|
68
|
+
if (line.length > MAX_LINE_BYTES) {
|
|
69
|
+
this.fail(new Error(`line exceeded the ${MAX_LINE_BYTES}-byte framing cap (fatal protocol error)`));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (line.length > 0)
|
|
73
|
+
this.handlers?.onLine(line);
|
|
74
|
+
nl = this.buffer.indexOf("\n");
|
|
75
|
+
}
|
|
76
|
+
this.bufferedBytes = Buffer.byteLength(this.buffer, "utf8");
|
|
77
|
+
}
|
|
78
|
+
fail(err) {
|
|
79
|
+
this.fireClose(err);
|
|
80
|
+
try {
|
|
81
|
+
this.socket?.end();
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// already gone
|
|
85
|
+
}
|
|
86
|
+
this.socket = undefined;
|
|
87
|
+
this.closed = true;
|
|
88
|
+
}
|
|
89
|
+
fireClose(err) {
|
|
90
|
+
if (this.closed)
|
|
91
|
+
return;
|
|
92
|
+
const handlers = this.handlers;
|
|
93
|
+
this.handlers = undefined;
|
|
94
|
+
this.closed = true;
|
|
95
|
+
this.socket = undefined;
|
|
96
|
+
handlers?.onClose(err);
|
|
97
|
+
}
|
|
98
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const streamStateSchema: z.ZodEnum<{
|
|
3
|
+
idle: "idle";
|
|
4
|
+
starting: "starting";
|
|
5
|
+
streaming: "streaming";
|
|
6
|
+
stopping: "stopping";
|
|
7
|
+
}>;
|
|
8
|
+
export type StreamState = z.infer<typeof streamStateSchema>;
|
|
9
|
+
export declare const inputModeSchema: z.ZodEnum<{
|
|
10
|
+
manual: "manual";
|
|
11
|
+
auto: "auto";
|
|
12
|
+
}>;
|
|
13
|
+
export type InputMode = z.infer<typeof inputModeSchema>;
|
|
14
|
+
export declare const balancerAlgorithmSchema: z.ZodEnum<{
|
|
15
|
+
adaptive: "adaptive";
|
|
16
|
+
fixed: "fixed";
|
|
17
|
+
aimd: "aimd";
|
|
18
|
+
}>;
|
|
19
|
+
export type BalancerAlgorithm = z.infer<typeof balancerAlgorithmSchema>;
|
|
20
|
+
export declare const mediaClassSchema: z.ZodEnum<{
|
|
21
|
+
video: "video";
|
|
22
|
+
audio: "audio";
|
|
23
|
+
}>;
|
|
24
|
+
export type MediaClass = z.infer<typeof mediaClassSchema>;
|
|
25
|
+
export declare const previewTierSchema: z.ZodEnum<{
|
|
26
|
+
webcodecs: "webcodecs";
|
|
27
|
+
webrtc: "webrtc";
|
|
28
|
+
}>;
|
|
29
|
+
export type PreviewTier = z.infer<typeof previewTierSchema>;
|
|
30
|
+
/** SRT transport config. Mirrors schema.md `start.srt` exactly. */
|
|
31
|
+
export declare const srtConfigSchema: z.ZodObject<{
|
|
32
|
+
host: z.ZodString;
|
|
33
|
+
port: z.ZodNumber;
|
|
34
|
+
streamid: z.ZodOptional<z.ZodString>;
|
|
35
|
+
latency_ms: z.ZodNumber;
|
|
36
|
+
reduced_packet_size: z.ZodOptional<z.ZodBoolean>;
|
|
37
|
+
}, z.core.$strip>;
|
|
38
|
+
export type SrtConfig = z.infer<typeof srtConfigSchema>;
|
|
39
|
+
/** Bitrate / balancer config. Mirrors schema.md `start.bitrate` exactly. */
|
|
40
|
+
export declare const bitrateConfigSchema: z.ZodObject<{
|
|
41
|
+
min_bitrate: z.ZodNumber;
|
|
42
|
+
max_bitrate: z.ZodNumber;
|
|
43
|
+
balancer: z.ZodDefault<z.ZodEnum<{
|
|
44
|
+
adaptive: "adaptive";
|
|
45
|
+
fixed: "fixed";
|
|
46
|
+
aimd: "aimd";
|
|
47
|
+
}>>;
|
|
48
|
+
}, z.core.$strip>;
|
|
49
|
+
export type BitrateConfig = z.infer<typeof bitrateConfigSchema>;
|
|
50
|
+
/**
|
|
51
|
+
* Unified cerastream engine config — the complete shape needed to `start` a
|
|
52
|
+
* stream and the canonical persisted profile. `start` params ARE this config
|
|
53
|
+
* (messages.ts re-exports it), so the wire contract and the stored config can
|
|
54
|
+
* never drift.
|
|
55
|
+
*/
|
|
56
|
+
export declare const cerastreamConfigSchema: z.ZodObject<{
|
|
57
|
+
pipeline: z.ZodString;
|
|
58
|
+
srt: z.ZodObject<{
|
|
59
|
+
host: z.ZodString;
|
|
60
|
+
port: z.ZodNumber;
|
|
61
|
+
streamid: z.ZodOptional<z.ZodString>;
|
|
62
|
+
latency_ms: z.ZodNumber;
|
|
63
|
+
reduced_packet_size: z.ZodOptional<z.ZodBoolean>;
|
|
64
|
+
}, z.core.$strip>;
|
|
65
|
+
bitrate: z.ZodObject<{
|
|
66
|
+
min_bitrate: z.ZodNumber;
|
|
67
|
+
max_bitrate: z.ZodNumber;
|
|
68
|
+
balancer: z.ZodDefault<z.ZodEnum<{
|
|
69
|
+
adaptive: "adaptive";
|
|
70
|
+
fixed: "fixed";
|
|
71
|
+
aimd: "aimd";
|
|
72
|
+
}>>;
|
|
73
|
+
}, z.core.$strip>;
|
|
74
|
+
input_id: z.ZodOptional<z.ZodString>;
|
|
75
|
+
}, z.core.$strip>;
|
|
76
|
+
export type CerastreamConfig = z.infer<typeof cerastreamConfigSchema>;
|
|
77
|
+
export type PartialCerastreamConfig = z.input<typeof cerastreamConfigSchema>;
|
|
78
|
+
export declare const DEFAULT_BITRATE_CONFIG: {
|
|
79
|
+
readonly min_bitrate: 300;
|
|
80
|
+
readonly max_bitrate: 6000;
|
|
81
|
+
readonly balancer: "adaptive";
|
|
82
|
+
};
|
|
83
|
+
export declare const DEFAULT_SRT_LATENCY_MS = 2000;
|
|
84
|
+
export declare const captureCapSchema: z.ZodObject<{
|
|
85
|
+
width: z.ZodOptional<z.ZodNumber>;
|
|
86
|
+
height: z.ZodOptional<z.ZodNumber>;
|
|
87
|
+
framerate: z.ZodOptional<z.ZodString>;
|
|
88
|
+
}, z.core.$strip>;
|
|
89
|
+
export type CaptureCap = z.infer<typeof captureCapSchema>;
|
|
90
|
+
export declare const captureDeviceSchema: z.ZodObject<{
|
|
91
|
+
input_id: z.ZodString;
|
|
92
|
+
device_path: z.ZodString;
|
|
93
|
+
display_name: z.ZodString;
|
|
94
|
+
media_class: z.ZodEnum<{
|
|
95
|
+
video: "video";
|
|
96
|
+
audio: "audio";
|
|
97
|
+
}>;
|
|
98
|
+
caps: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
99
|
+
width: z.ZodOptional<z.ZodNumber>;
|
|
100
|
+
height: z.ZodOptional<z.ZodNumber>;
|
|
101
|
+
framerate: z.ZodOptional<z.ZodString>;
|
|
102
|
+
}, z.core.$strip>>>;
|
|
103
|
+
}, z.core.$strip>;
|
|
104
|
+
export type CaptureDevice = z.infer<typeof captureDeviceSchema>;
|
|
105
|
+
export declare const srtStatsSchema: z.ZodObject<{
|
|
106
|
+
rtt_ms: z.ZodNumber;
|
|
107
|
+
send_buffer: z.ZodNumber;
|
|
108
|
+
pkt_loss: z.ZodNumber;
|
|
109
|
+
}, z.core.$strip>;
|
|
110
|
+
export type SrtStats = z.infer<typeof srtStatsSchema>;
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { DEFAULT_BALANCER, DEFAULT_MAX_BITRATE, DEFAULT_MIN_BITRATE, DEFAULT_SRT_LATENCY, } from "./constants.js";
|
|
3
|
+
// Shared domain enums + the unified engine config. Field names are domain-level
|
|
4
|
+
// (snake_case, matching the ceracoder config convention) — never engine
|
|
5
|
+
// internals (schema.md "Conventions").
|
|
6
|
+
export const streamStateSchema = z.enum([
|
|
7
|
+
"idle",
|
|
8
|
+
"starting",
|
|
9
|
+
"streaming",
|
|
10
|
+
"stopping",
|
|
11
|
+
]);
|
|
12
|
+
export const inputModeSchema = z.enum(["manual", "auto"]);
|
|
13
|
+
// re-used from ceracoder (schema.md §"v1 messages" shared fragments)
|
|
14
|
+
export const balancerAlgorithmSchema = z.enum(["adaptive", "fixed", "aimd"]);
|
|
15
|
+
export const mediaClassSchema = z.enum(["video", "audio"]);
|
|
16
|
+
export const previewTierSchema = z.enum(["webcodecs", "webrtc"]);
|
|
17
|
+
// ---- config sub-schemas (canonical; reused by `start` + the unified config) ----
|
|
18
|
+
/** SRT transport config. Mirrors schema.md `start.srt` exactly. */
|
|
19
|
+
export const srtConfigSchema = z.object({
|
|
20
|
+
host: z.string(),
|
|
21
|
+
port: z.number().int().min(1).max(65535),
|
|
22
|
+
streamid: z.string().optional(),
|
|
23
|
+
latency_ms: z.number().int().min(100).max(10_000),
|
|
24
|
+
reduced_packet_size: z.boolean().optional(),
|
|
25
|
+
});
|
|
26
|
+
/** Bitrate / balancer config. Mirrors schema.md `start.bitrate` exactly. */
|
|
27
|
+
export const bitrateConfigSchema = z.object({
|
|
28
|
+
min_bitrate: z.number().int().min(1),
|
|
29
|
+
max_bitrate: z.number().int().min(1),
|
|
30
|
+
balancer: balancerAlgorithmSchema.default(DEFAULT_BALANCER),
|
|
31
|
+
});
|
|
32
|
+
/**
|
|
33
|
+
* Unified cerastream engine config — the complete shape needed to `start` a
|
|
34
|
+
* stream and the canonical persisted profile. `start` params ARE this config
|
|
35
|
+
* (messages.ts re-exports it), so the wire contract and the stored config can
|
|
36
|
+
* never drift.
|
|
37
|
+
*/
|
|
38
|
+
export const cerastreamConfigSchema = z.object({
|
|
39
|
+
pipeline: z.string(), // pipeline id / profile name (NOT a Rust graph)
|
|
40
|
+
srt: srtConfigSchema,
|
|
41
|
+
bitrate: bitrateConfigSchema,
|
|
42
|
+
input_id: z.string().optional(), // initial active input; defaults to primary
|
|
43
|
+
});
|
|
44
|
+
// convenience defaults for building a config client-side
|
|
45
|
+
export const DEFAULT_BITRATE_CONFIG = {
|
|
46
|
+
min_bitrate: DEFAULT_MIN_BITRATE,
|
|
47
|
+
max_bitrate: DEFAULT_MAX_BITRATE,
|
|
48
|
+
balancer: DEFAULT_BALANCER,
|
|
49
|
+
};
|
|
50
|
+
export const DEFAULT_SRT_LATENCY_MS = DEFAULT_SRT_LATENCY;
|
|
51
|
+
// ---- capture device (list-devices result + device event) ----
|
|
52
|
+
export const captureCapSchema = z.object({
|
|
53
|
+
width: z.number().int().optional(),
|
|
54
|
+
height: z.number().int().optional(),
|
|
55
|
+
framerate: z.string().optional(), // e.g. "30/1"
|
|
56
|
+
});
|
|
57
|
+
export const captureDeviceSchema = z.object({
|
|
58
|
+
input_id: z.string(), // stable id used by switch-input
|
|
59
|
+
device_path: z.string(), // e.g. /dev/video0 (dedup key)
|
|
60
|
+
display_name: z.string(),
|
|
61
|
+
media_class: mediaClassSchema,
|
|
62
|
+
caps: z.array(captureCapSchema).optional(),
|
|
63
|
+
});
|
|
64
|
+
// ---- transport telemetry (srt-stats event payload) ----
|
|
65
|
+
export const srtStatsSchema = z.object({
|
|
66
|
+
rtt_ms: z.number(),
|
|
67
|
+
send_buffer: z.number().int(),
|
|
68
|
+
pkt_loss: z.number(),
|
|
69
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ceralive/cerastream",
|
|
3
|
+
"version": "2026.6.0-rc.1",
|
|
4
|
+
"description": "Type-safe TypeScript bindings + IPC client for the cerastream Rust streaming engine (JSON-RPC 2.0 / NDJSON over UDS). See ADR-0002.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"private": false,
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public",
|
|
14
|
+
"registry": "https://registry.npmjs.org/"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "bun x tsc -p tsconfig.json",
|
|
18
|
+
"typecheck": "bun x tsc -p tsconfig.json --noEmit",
|
|
19
|
+
"lint": "bun x tsc -p tsconfig.json --noEmit",
|
|
20
|
+
"test": "bun test",
|
|
21
|
+
"prepare": "bun run build"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"zod": "^4.3.5"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/bun": "^1.3.5",
|
|
28
|
+
"typescript": "^5.9.3"
|
|
29
|
+
},
|
|
30
|
+
"license": "GPL-3.0",
|
|
31
|
+
"keywords": [
|
|
32
|
+
"cerastream",
|
|
33
|
+
"srt",
|
|
34
|
+
"srtla",
|
|
35
|
+
"gstreamer",
|
|
36
|
+
"ceralive",
|
|
37
|
+
"json-rpc",
|
|
38
|
+
"ipc",
|
|
39
|
+
"schema"
|
|
40
|
+
]
|
|
41
|
+
}
|