@lv-agent/logdb-client 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.
package/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # @lv-agent/logdb-client — TypeScript SDK for logdbd
2
+
3
+ ## Install
4
+
5
+ ```bash
6
+ npm install @lv-agent/logdb-client
7
+ ```
8
+
9
+ ## Quick Start
10
+
11
+ ```typescript
12
+ import { Client } from '@lv-agent/logdb-client';
13
+
14
+ const client = new Client('127.0.0.1:50051');
15
+
16
+ // Append
17
+ const { seq } = await client.append('my-app', 'main', 'test.event', Buffer.from('hello'));
18
+ console.log('appended at seq', seq);
19
+
20
+ // Read
21
+ const rec = await client.read('my-app', 'main', 1);
22
+ console.log(rec?.eventType);
23
+
24
+ // Scan all
25
+ const records = await client.scanAll('my-app', 'main', 0);
26
+ for (const r of records) {
27
+ console.log(`[${r.seq}] ${r.eventType}`);
28
+ }
29
+
30
+ // Tail (live subscription)
31
+ const stream = client.tail('my-app', 'main', { fromSeq: 0, consumerGroup: 'workers', consumerId: 'w1' });
32
+ for await (const rec of stream) {
33
+ console.log(`[${rec.seq}] ${rec.eventType}`);
34
+ // Commit progress
35
+ await client.commitOffset('my-app', 'main', 'workers', 'w1', rec.seq);
36
+ }
37
+ ```
38
+
39
+ ## API
40
+
41
+ | Method | Description |
42
+ |--------|-------------|
43
+ | `append(ns, stream, eventType, content)` | Write a record |
44
+ | `read(ns, stream, seq)` | Point read |
45
+ | `scanAll(ns, stream, fromSeq)` | Scan all records |
46
+ | `tail(ns, stream, opts)` | Live subscription (async iterable) |
47
+ | `listNamespaces()` | List all namespaces |
48
+ | `listStreams(ns)` | List streams in a namespace |
49
+ | `status()` | Node status |
50
+ | `verifyChain(ns, stream)` | Verify hash chain |
51
+ | `commitOffset(...)` / `committedOffset(...)` | Consumer group offset management |
52
+
53
+ ## TLS / mTLS
54
+
55
+ ```typescript
56
+ import * as fs from 'fs';
57
+
58
+ const client = new Client('logdbd.example.com:50051', {
59
+ tlsCa: fs.readFileSync('/etc/certs/ca.crt'),
60
+ tlsCert: fs.readFileSync('/etc/certs/client.crt'), // mTLS
61
+ tlsKey: fs.readFileSync('/etc/certs/client.key'), // mTLS
62
+ });
63
+ ```
64
+
65
+ ## License
66
+
67
+ Apache-2.0
@@ -0,0 +1,64 @@
1
+ import { TailStream } from './tail-stream';
2
+ import type { AppendResult, BatchAppendResult, NamespaceInfo, LogRecord, StreamInfo, StatusResult, Watermark, VerifyResult, TailOptions } from './types';
3
+ export interface ClientOptions {
4
+ /** TLS certificate authority (PEM). */
5
+ tlsCa?: Buffer;
6
+ /** TLS client cert (PEM) for mTLS. */
7
+ tlsCert?: Buffer;
8
+ /** TLS client key (PEM) for mTLS. */
9
+ tlsKey?: Buffer;
10
+ /** Bearer auth token. */
11
+ authToken?: string;
12
+ }
13
+ export declare class Client {
14
+ private client;
15
+ private closed;
16
+ /**
17
+ * Create a new logdbd client.
18
+ *
19
+ * @param addr — host:port, e.g. "127.0.0.1:50051"
20
+ * @param opts — optional TLS and auth settings
21
+ */
22
+ constructor(addr: string, opts?: ClientOptions);
23
+ /** Append a record. Returns the assigned seq. */
24
+ append(namespace: string, stream: string, eventType: string, content: Buffer): Promise<AppendResult>;
25
+ /** Append with full options. */
26
+ appendFull(opts: {
27
+ namespace: string;
28
+ stream: string;
29
+ eventType: string;
30
+ contentType?: string;
31
+ metadata?: Record<string, string>;
32
+ timestampNs?: number;
33
+ content: Buffer;
34
+ }): Promise<AppendResult>;
35
+ /** Batch append — all in same namespace+stream. */
36
+ appendBatch(requests: Array<{
37
+ namespace: string;
38
+ stream: string;
39
+ eventType: string;
40
+ content: Buffer;
41
+ }>): Promise<BatchAppendResult>;
42
+ /** Read a record by seq. Returns null if not found. */
43
+ read(namespace: string, stream: string, seq: number): Promise<LogRecord | null>;
44
+ /** Scan records in range. Returns array of records. */
45
+ scanAll(namespace: string, stream: string, fromSeq: number, limit?: number): Promise<LogRecord[]>;
46
+ /** Subscribe to new records via Tail. */
47
+ tail(namespace: string, stream: string, opts?: TailOptions): TailStream;
48
+ /** Get the watermark for a namespace/stream. */
49
+ watermark(namespace: string, stream: string): Promise<Watermark>;
50
+ /** List all namespaces. */
51
+ listNamespaces(): Promise<NamespaceInfo[]>;
52
+ /** List streams in a namespace. */
53
+ listStreams(namespace: string): Promise<StreamInfo[]>;
54
+ /** Get node status. */
55
+ status(): Promise<StatusResult>;
56
+ /** Verify hash chain for a stream. */
57
+ verifyChain(namespace: string, stream: string, fromSeq?: number, toSeq?: number): Promise<VerifyResult>;
58
+ /** Commit consumer offset. */
59
+ commitOffset(namespace: string, stream: string, consumerGroup: string, consumerId: string, seq: number): Promise<void>;
60
+ /** Get committed offset for a consumer. */
61
+ committedOffset(namespace: string, stream: string, consumerGroup: string, consumerId: string): Promise<number>;
62
+ /** Close the client connection. */
63
+ close(): void;
64
+ }
package/dist/client.js ADDED
@@ -0,0 +1,289 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.Client = void 0;
37
+ const grpc = __importStar(require("@grpc/grpc-js"));
38
+ const protoLoader = __importStar(require("@grpc/proto-loader"));
39
+ const path = __importStar(require("path"));
40
+ const tail_stream_1 = require("./tail-stream");
41
+ let protoDef = null;
42
+ function loadProto() {
43
+ if (protoDef)
44
+ return protoDef;
45
+ const protoPath = path.join(__dirname, '..', 'proto', 'logdbd.proto');
46
+ const pkgDef = protoLoader.loadSync(protoPath, {
47
+ keepCase: true,
48
+ longs: String,
49
+ enums: String,
50
+ defaults: true,
51
+ oneofs: true,
52
+ });
53
+ protoDef = grpc.loadPackageDefinition(pkgDef).logdbd;
54
+ return protoDef;
55
+ }
56
+ class Client {
57
+ client;
58
+ closed = false;
59
+ /**
60
+ * Create a new logdbd client.
61
+ *
62
+ * @param addr — host:port, e.g. "127.0.0.1:50051"
63
+ * @param opts — optional TLS and auth settings
64
+ */
65
+ constructor(addr, opts) {
66
+ const proto = loadProto();
67
+ const isTls = opts?.tlsCa || addr.startsWith('https://');
68
+ let creds;
69
+ if (isTls && opts?.tlsCert) {
70
+ creds = grpc.credentials.createSsl(opts.tlsCa, opts.tlsKey, opts.tlsCert);
71
+ }
72
+ else if (isTls) {
73
+ creds = grpc.credentials.createSsl(opts?.tlsCa);
74
+ }
75
+ else {
76
+ creds = grpc.credentials.createInsecure();
77
+ }
78
+ const target = addr.startsWith('http') ? addr : (isTls ? `https://${addr}` : `http://${addr}`);
79
+ // Strip protocol for grpc-js
80
+ const grpcTarget = target.replace(/^https?:\/\//, '');
81
+ this.client = new proto.LogDbService(grpcTarget, creds, {
82
+ 'grpc.keepalive_time_ms': 30000,
83
+ 'grpc.keepalive_timeout_ms': 10000,
84
+ });
85
+ }
86
+ // ── Write ──────────────────────────────────────────────────────────
87
+ /** Append a record. Returns the assigned seq. */
88
+ append(namespace, stream, eventType, content) {
89
+ return this.appendFull({
90
+ namespace, stream, eventType,
91
+ content, contentType: 'application/json',
92
+ metadata: {}, timestampNs: 0,
93
+ });
94
+ }
95
+ /** Append with full options. */
96
+ appendFull(opts) {
97
+ return new Promise((resolve, reject) => {
98
+ this.client.Append({
99
+ namespace: opts.namespace,
100
+ stream: opts.stream,
101
+ eventType: opts.eventType,
102
+ content: opts.content,
103
+ contentType: opts.contentType || 'application/json',
104
+ metadata: (opts.metadata || {}),
105
+ timestampNs: opts.timestampNs || 0,
106
+ }, (err, resp) => {
107
+ if (err)
108
+ return reject(err);
109
+ resolve({
110
+ namespaceId: resp.namespaceId,
111
+ streamId: resp.streamId,
112
+ seq: resp.seq,
113
+ gid: resp.gid,
114
+ });
115
+ });
116
+ });
117
+ }
118
+ /** Batch append — all in same namespace+stream. */
119
+ appendBatch(requests) {
120
+ return new Promise((resolve, reject) => {
121
+ this.client.BatchAppend({ requests }, (err, resp) => {
122
+ if (err)
123
+ return reject(err);
124
+ resolve({
125
+ records: (resp.records || []).map((r) => ({
126
+ namespaceId: r.namespaceId,
127
+ streamId: r.streamId,
128
+ seq: r.seq,
129
+ gid: r.gid,
130
+ })),
131
+ error: resp.error ? { code: resp.error.code, message: resp.error.message } : undefined,
132
+ });
133
+ });
134
+ });
135
+ }
136
+ // ── Read ───────────────────────────────────────────────────────────
137
+ /** Read a record by seq. Returns null if not found. */
138
+ read(namespace, stream, seq) {
139
+ return new Promise((resolve, reject) => {
140
+ this.client.Read({ namespace, stream, seq }, (err, resp) => {
141
+ if (err)
142
+ return reject(err);
143
+ if (!resp.found || !resp.record)
144
+ return resolve(null);
145
+ resolve(convertRecord(resp.record));
146
+ });
147
+ });
148
+ }
149
+ /** Scan records in range. Returns array of records. */
150
+ scanAll(namespace, stream, fromSeq, limit = 10000) {
151
+ return new Promise((resolve, reject) => {
152
+ const call = this.client.Scan({ namespace, stream, fromSeq, toSeq: 0, limit });
153
+ const records = [];
154
+ call.on('data', (resp) => {
155
+ for (const r of resp.records || [])
156
+ records.push(convertRecord(r));
157
+ });
158
+ call.on('end', () => resolve(records));
159
+ call.on('error', (err) => reject(err));
160
+ });
161
+ }
162
+ /** Subscribe to new records via Tail. */
163
+ tail(namespace, stream, opts) {
164
+ const call = this.client.Tail({
165
+ namespace, stream,
166
+ fromSeq: opts?.fromSeq ?? 0,
167
+ batchSize: opts?.batchSize ?? 100,
168
+ consumerGroup: opts?.consumerGroup ?? '',
169
+ consumerId: opts?.consumerId ?? '',
170
+ });
171
+ return new tail_stream_1.TailStream(call, convertRecord);
172
+ }
173
+ // ── Watermark ──────────────────────────────────────────────────────
174
+ /** Get the watermark for a namespace/stream. */
175
+ watermark(namespace, stream) {
176
+ return new Promise((resolve, reject) => {
177
+ this.client.GetWatermark({ namespace, stream }, (err, resp) => {
178
+ if (err)
179
+ return reject(err);
180
+ resolve({
181
+ namespace: resp.namespace,
182
+ stream: resp.stream,
183
+ oldestSeq: Number(resp.oldestSeq),
184
+ durableSeq: Number(resp.durableSeq),
185
+ replicatedSeq: Number(resp.replicatedSeq),
186
+ nodeId: resp.nodeId,
187
+ role: resp.role,
188
+ });
189
+ });
190
+ });
191
+ }
192
+ // ── Admin ──────────────────────────────────────────────────────────
193
+ /** List all namespaces. */
194
+ listNamespaces() {
195
+ return new Promise((resolve, reject) => {
196
+ this.client.ListNamespaces({}, (err, resp) => {
197
+ if (err)
198
+ return reject(err);
199
+ resolve((resp.namespaces || []).map((n) => ({
200
+ name: n.name, id: n.id, streamCount: Number(n.streamCount),
201
+ })));
202
+ });
203
+ });
204
+ }
205
+ /** List streams in a namespace. */
206
+ listStreams(namespace) {
207
+ return new Promise((resolve, reject) => {
208
+ this.client.ListStreams({ namespace }, (err, resp) => {
209
+ if (err)
210
+ return reject(err);
211
+ resolve((resp.streams || []).map((s) => ({
212
+ name: s.name, id: Number(s.id),
213
+ firstSeq: Number(s.firstSeq), durableSeq: Number(s.durableSeq),
214
+ recordCount: Number(s.recordCount),
215
+ })));
216
+ });
217
+ });
218
+ }
219
+ /** Get node status. */
220
+ status() {
221
+ return new Promise((resolve, reject) => {
222
+ this.client.Status({}, (err, resp) => {
223
+ if (err)
224
+ return reject(err);
225
+ resolve({
226
+ nodeId: resp.nodeId,
227
+ role: resp.role || '',
228
+ durableSequence: Number(resp.durableSequence),
229
+ checkpoint: Number(resp.checkpoint),
230
+ walBytesUsed: Number(resp.walBytesUsed),
231
+ walBytesTotal: Number(resp.walBytesTotal),
232
+ });
233
+ });
234
+ });
235
+ }
236
+ /** Verify hash chain for a stream. */
237
+ verifyChain(namespace, stream, fromSeq = 0, toSeq = 0) {
238
+ return new Promise((resolve, reject) => {
239
+ this.client.VerifyChain({ namespace, stream, fromSeq, toSeq }, (err, resp) => {
240
+ if (err)
241
+ return reject(err);
242
+ resolve({
243
+ ok: resp.ok,
244
+ verifiedFrom: Number(resp.verifiedFrom),
245
+ verifiedTo: Number(resp.verifiedTo),
246
+ errorAtSeq: Number(resp.errorAtSeq),
247
+ errorMessage: resp.errorMessage,
248
+ });
249
+ });
250
+ });
251
+ }
252
+ /** Commit consumer offset. */
253
+ commitOffset(namespace, stream, consumerGroup, consumerId, seq) {
254
+ return new Promise((resolve, reject) => {
255
+ this.client.CommitOffset({ namespace, stream, consumerGroup, consumerId, committedSeq: seq }, (err) => { if (err)
256
+ return reject(err); resolve(); });
257
+ });
258
+ }
259
+ /** Get committed offset for a consumer. */
260
+ committedOffset(namespace, stream, consumerGroup, consumerId) {
261
+ return new Promise((resolve, reject) => {
262
+ this.client.GetCommittedOffset({ namespace, stream, consumerGroup, consumerId }, (err, resp) => {
263
+ if (err)
264
+ return reject(err);
265
+ resolve(Number(resp.committedSeq));
266
+ });
267
+ });
268
+ }
269
+ /** Close the client connection. */
270
+ close() {
271
+ if (!this.closed) {
272
+ this.closed = true;
273
+ grpc.closeClient(this.client);
274
+ }
275
+ }
276
+ }
277
+ exports.Client = Client;
278
+ function convertRecord(r) {
279
+ return {
280
+ namespaceId: r.namespaceId,
281
+ streamId: Number(r.streamId),
282
+ seq: Number(r.seq),
283
+ eventType: r.eventType,
284
+ timestampNs: Number(r.timestampNs),
285
+ contentType: r.contentType,
286
+ metadata: r.metadata || {},
287
+ content: Buffer.isBuffer(r.content) ? r.content : Buffer.from(r.content || ''),
288
+ };
289
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * logdb-client — TypeScript SDK for logdbd
3
+ *
4
+ * ```typescript
5
+ * import { Client } from 'logdb-client';
6
+ *
7
+ * const client = new Client('127.0.0.1:50051');
8
+ *
9
+ * // Append
10
+ * const { seq } = await client.append('my-app', 'main', 'test.event', Buffer.from('hello'));
11
+ *
12
+ * // Read
13
+ * const rec = await client.read('my-app', 'main', 1);
14
+ *
15
+ * // Tail (live subscription)
16
+ * for await (const rec of client.tail('my-app', 'main', { fromSeq: 0 })) {
17
+ * console.log(rec.seq, rec.eventType);
18
+ * }
19
+ * ```
20
+ */
21
+ export { Client, ClientOptions } from './client';
22
+ export { TailStream } from './tail-stream';
23
+ export { LogRecord, AppendResult, Watermark, VerifyResult, TailOptions } from './types';
package/dist/index.js ADDED
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ /**
3
+ * logdb-client — TypeScript SDK for logdbd
4
+ *
5
+ * ```typescript
6
+ * import { Client } from 'logdb-client';
7
+ *
8
+ * const client = new Client('127.0.0.1:50051');
9
+ *
10
+ * // Append
11
+ * const { seq } = await client.append('my-app', 'main', 'test.event', Buffer.from('hello'));
12
+ *
13
+ * // Read
14
+ * const rec = await client.read('my-app', 'main', 1);
15
+ *
16
+ * // Tail (live subscription)
17
+ * for await (const rec of client.tail('my-app', 'main', { fromSeq: 0 })) {
18
+ * console.log(rec.seq, rec.eventType);
19
+ * }
20
+ * ```
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.TailStream = exports.Client = void 0;
24
+ var client_1 = require("./client");
25
+ Object.defineProperty(exports, "Client", { enumerable: true, get: function () { return client_1.Client; } });
26
+ var tail_stream_1 = require("./tail-stream");
27
+ Object.defineProperty(exports, "TailStream", { enumerable: true, get: function () { return tail_stream_1.TailStream; } });
@@ -0,0 +1,14 @@
1
+ import type { LogRecord } from './types';
2
+ /** Async iterable stream of records from a Tail subscription. */
3
+ export declare class TailStream implements AsyncIterable<LogRecord> {
4
+ private readonly convert;
5
+ private call;
6
+ private buffer;
7
+ private done;
8
+ constructor(call: any, convert: (r: any) => LogRecord);
9
+ /** Get the next record (waits for new data). */
10
+ next(): Promise<LogRecord | null>;
11
+ /** Cancel the subscription. */
12
+ cancel(): void;
13
+ [Symbol.asyncIterator](): AsyncIterator<LogRecord>;
14
+ }
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TailStream = void 0;
4
+ /** Async iterable stream of records from a Tail subscription. */
5
+ class TailStream {
6
+ convert;
7
+ call = null;
8
+ buffer = [];
9
+ done = false;
10
+ constructor(call, convert) {
11
+ this.convert = convert;
12
+ this.call = call;
13
+ call.on('data', (resp) => {
14
+ if (resp.heartbeat)
15
+ return;
16
+ for (const r of resp.records || []) {
17
+ this.buffer.push(convert(r));
18
+ }
19
+ });
20
+ call.on('end', () => { this.done = true; });
21
+ call.on('error', () => { this.done = true; });
22
+ }
23
+ /** Get the next record (waits for new data). */
24
+ async next() {
25
+ while (this.buffer.length === 0 && !this.done) {
26
+ await new Promise(resolve => setTimeout(resolve, 10));
27
+ }
28
+ if (this.buffer.length > 0) {
29
+ return this.buffer.shift();
30
+ }
31
+ return null;
32
+ }
33
+ /** Cancel the subscription. */
34
+ cancel() {
35
+ if (this.call) {
36
+ this.call.cancel();
37
+ this.call = null;
38
+ }
39
+ this.done = true;
40
+ }
41
+ [Symbol.asyncIterator]() {
42
+ return {
43
+ next: async () => {
44
+ const rec = await this.next();
45
+ return rec ? { value: rec, done: false } : { value: undefined, done: true };
46
+ }
47
+ };
48
+ }
49
+ }
50
+ exports.TailStream = TailStream;
@@ -0,0 +1,74 @@
1
+ /** A logdbd record. */
2
+ export interface LogRecord {
3
+ namespaceId: number;
4
+ streamId: number;
5
+ seq: number;
6
+ eventType: string;
7
+ timestampNs: number;
8
+ contentType: string;
9
+ metadata: Record<string, string>;
10
+ content: Buffer;
11
+ }
12
+ /** Response from append(). */
13
+ export interface AppendResult {
14
+ namespaceId: number;
15
+ streamId: number;
16
+ seq: number;
17
+ gid: number;
18
+ }
19
+ /** Batch append response. */
20
+ export interface BatchAppendResult {
21
+ records: AppendResult[];
22
+ error?: {
23
+ code: string;
24
+ message: string;
25
+ };
26
+ }
27
+ /** Namespace info. */
28
+ export interface NamespaceInfo {
29
+ name: string;
30
+ id: number;
31
+ streamCount: number;
32
+ }
33
+ /** Stream info. */
34
+ export interface StreamInfo {
35
+ name: string;
36
+ id: number;
37
+ firstSeq: number;
38
+ durableSeq: number;
39
+ recordCount: number;
40
+ }
41
+ /** Watermark. */
42
+ export interface Watermark {
43
+ namespace: string;
44
+ stream: string;
45
+ oldestSeq: number;
46
+ durableSeq: number;
47
+ replicatedSeq: number;
48
+ nodeId: string;
49
+ role: string;
50
+ }
51
+ /** Node status. */
52
+ export interface StatusResult {
53
+ nodeId: string;
54
+ role: string;
55
+ durableSequence: number;
56
+ checkpoint: number;
57
+ walBytesUsed: number;
58
+ walBytesTotal: number;
59
+ }
60
+ /** Hash chain verification result. */
61
+ export interface VerifyResult {
62
+ ok: boolean;
63
+ verifiedFrom: number;
64
+ verifiedTo: number;
65
+ errorAtSeq: number;
66
+ errorMessage: string;
67
+ }
68
+ /** Tail options. */
69
+ export interface TailOptions {
70
+ fromSeq?: number;
71
+ batchSize?: number;
72
+ consumerGroup?: string;
73
+ consumerId?: string;
74
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@lv-agent/logdb-client",
3
+ "version": "0.1.0",
4
+ "description": "TypeScript SDK for logdbd — append-only audit log database",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": ["dist", "proto"],
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "prepublishOnly": "npm run build"
11
+ },
12
+ "dependencies": {
13
+ "@grpc/grpc-js": "^1.12.0",
14
+ "@grpc/proto-loader": "^0.7.0"
15
+ },
16
+ "devDependencies": {
17
+ "@types/node": "^22.0.0",
18
+ "typescript": "^5.6.0"
19
+ }
20
+ }
@@ -0,0 +1,262 @@
1
+ syntax = "proto3";
2
+ package logdbd;
3
+
4
+ service LogDbService {
5
+ rpc Append(AppendRequest) returns (AppendResponse);
6
+ rpc BatchAppend(BatchAppendRequest) returns (AppendBatchResponse);
7
+ rpc Read(ReadRequest) returns (ReadResponse);
8
+ rpc Scan(ScanRequest) returns (stream ScanResponse);
9
+ rpc Tail(TailRequest) returns (stream TailResponse);
10
+ rpc Checkpoint(CheckpointRequest) returns (CheckpointResponse);
11
+ rpc Status(StatusRequest) returns (StatusResponse);
12
+ rpc GetWatermark(GetWatermarkRequest) returns (Watermark);
13
+ rpc VerifyChain(VerifyChainRequest) returns (VerifyChainResponse);
14
+
15
+ // Consumer group — server-side offset tracking
16
+ rpc CommitOffset(CommitOffsetRequest) returns (CommitOffsetResponse);
17
+ rpc GetCommittedOffset(GetCommittedOffsetRequest) returns (GetCommittedOffsetResponse);
18
+
19
+ // Catalog — namespace & stream management
20
+ rpc ListNamespaces(ListNamespacesRequest) returns (ListNamespacesResponse);
21
+ rpc ListStreams(ListStreamsRequest) returns (ListStreamsResponse);
22
+ }
23
+
24
+ // ─── Record (full logdbd record) ─────────────────────────────────────────
25
+
26
+ message Record {
27
+ uint32 namespace_id = 1;
28
+ uint64 stream_id = 2;
29
+ uint64 seq = 3;
30
+ string event_type = 4;
31
+ uint64 timestamp_ns = 5;
32
+ string content_type = 6;
33
+ map<string, string> metadata = 7;
34
+ bytes content = 8;
35
+ }
36
+
37
+ // ─── Write ───────────────────────────────────────────────────────────────
38
+
39
+ message AppendRequest {
40
+ string namespace = 1;
41
+ string stream = 2;
42
+ string event_type = 3;
43
+ uint64 timestamp_ns = 4;
44
+ string content_type = 5;
45
+ map<string, string> metadata = 6;
46
+ bytes content = 7;
47
+ }
48
+
49
+ message AppendResponse {
50
+ uint32 namespace_id = 1;
51
+ uint64 stream_id = 2;
52
+ uint64 seq = 3;
53
+ uint64 gid = 4; // logdb internal gid (opaque to caller)
54
+ }
55
+
56
+ message BatchAppendRequest {
57
+ repeated AppendRequest requests = 1;
58
+ }
59
+
60
+ message AppendBatchResponse {
61
+ repeated AppendResponse records = 1;
62
+ Error error = 2;
63
+ }
64
+
65
+ message Error {
66
+ string code = 1;
67
+ string message = 2;
68
+ bool retryable = 3;
69
+ }
70
+
71
+ // ─── Read ────────────────────────────────────────────────────────────────
72
+
73
+ message ReadRequest {
74
+ string namespace = 1;
75
+ string stream = 2;
76
+ uint64 seq = 3;
77
+ }
78
+
79
+ message ReadResponse {
80
+ Record record = 1;
81
+ bool found = 2;
82
+ }
83
+
84
+ message ScanRequest {
85
+ string namespace = 1;
86
+ string stream = 2;
87
+ uint64 from_seq = 3;
88
+ uint64 to_seq = 4; // 0 = durable tail
89
+ uint32 limit = 5;
90
+ }
91
+
92
+ message ScanResponse {
93
+ repeated Record records = 1;
94
+ uint64 next_seq = 2;
95
+ bool has_more = 3;
96
+ }
97
+
98
+ message TailRequest {
99
+ string namespace = 1;
100
+ string stream = 2;
101
+ uint64 from_seq = 3;
102
+ uint32 batch_size = 4;
103
+ string consumer_group = 5; // consumer group name (for shared offset tracking)
104
+ string consumer_id = 6; // unique consumer ID within the group
105
+ }
106
+
107
+ message TailResponse {
108
+ repeated Record records = 1;
109
+ uint64 durable_seq = 2;
110
+ bool heartbeat = 3;
111
+ }
112
+
113
+ // ─── Watermark ───────────────────────────────────────────────────────────
114
+
115
+ message GetWatermarkRequest {
116
+ string namespace = 1; // required
117
+ string stream = 2; // optional
118
+ }
119
+
120
+ message Watermark {
121
+ string namespace = 1;
122
+ string stream = 2;
123
+ uint64 oldest_seq = 3;
124
+ uint64 durable_seq = 4;
125
+ uint64 replicated_seq = 5;
126
+ string node_id = 6;
127
+ string role = 7;
128
+ }
129
+
130
+ // ─── Admin ───────────────────────────────────────────────────────────────
131
+
132
+ message CheckpointRequest {
133
+ uint64 sequence = 1;
134
+ }
135
+
136
+ message CheckpointResponse {}
137
+
138
+ message StatusRequest {}
139
+
140
+ message StatusResponse {
141
+ uint64 durable_sequence = 1;
142
+ uint64 checkpoint = 2;
143
+ uint64 wal_bytes_used = 3;
144
+ uint64 wal_bytes_total = 4;
145
+ string node_id = 5;
146
+ string role = 6;
147
+ }
148
+
149
+ // ─── VerifyChain ─────────────────────────────────────────────────────────
150
+
151
+ message VerifyChainRequest {
152
+ string namespace = 1;
153
+ string stream = 2;
154
+ uint64 from_seq = 3;
155
+ uint64 to_seq = 4; // 0 = durable tail
156
+ }
157
+
158
+ message VerifyChainResponse {
159
+ bool ok = 1;
160
+ uint64 verified_from = 2;
161
+ uint64 verified_to = 3;
162
+ uint64 error_at_seq = 4;
163
+ string error_message = 5;
164
+ }
165
+
166
+ // ─── Consumer offset tracking ────────────────────────────────────────────
167
+
168
+ message CommitOffsetRequest {
169
+ string namespace = 1;
170
+ string stream = 2;
171
+ string consumer_group = 3;
172
+ string consumer_id = 4;
173
+ uint64 committed_seq = 5; // last successfully processed seq
174
+ }
175
+
176
+ message CommitOffsetResponse {
177
+ bool ok = 1;
178
+ string message = 2;
179
+ }
180
+
181
+ // Resolve the committed offset for a consumer
182
+ message GetCommittedOffsetRequest {
183
+ string namespace = 1;
184
+ string stream = 2;
185
+ string consumer_group = 3;
186
+ string consumer_id = 4;
187
+ }
188
+
189
+ message GetCommittedOffsetResponse {
190
+ uint64 committed_seq = 1; // 0 = no offset committed yet
191
+ }
192
+
193
+ // Internal replication service (primary → standby).
194
+ // Transfers raw encoded bytes — independent of the public Record schema.
195
+ service ReplicationService {
196
+ rpc Sync(ReplicationRequest) returns (ReplicationResponse);
197
+ }
198
+
199
+ message ReplicationRecord {
200
+ uint64 gid = 1; // logdb global id (preserved on standby)
201
+ uint64 timestamp_ns = 2;
202
+ bytes content = 3; // raw encoded logdbd record (header + user_content)
203
+ }
204
+
205
+ message ReplicationRequest {
206
+ string cluster_id = 1;
207
+ uint64 epoch = 2;
208
+ repeated ReplicationRecord records = 3;
209
+ }
210
+
211
+ message ReplicationResponse {
212
+ uint64 last_gid = 1; // highest gid applied
213
+ }
214
+
215
+ // ─── Snapshot service (primary → standby full sync) ──────────────────────────
216
+
217
+ service SnapshotService {
218
+ // Standby requests a snapshot; primary streams segment files.
219
+ rpc PullSnapshot(SnapshotRequest) returns (stream SnapshotChunk);
220
+ }
221
+
222
+ message SnapshotRequest {
223
+ string cluster_id = 1;
224
+ uint64 epoch = 2;
225
+ }
226
+
227
+ message SnapshotChunk {
228
+ string file_name = 1; // e.g. "segment-00000001.log"
229
+ uint64 offset = 2; // byte offset within file
230
+ bytes data = 3; // chunk payload
231
+ bool last = 4; // true if this is the last chunk of the snapshot
232
+ }
233
+
234
+ // ─── Catalog messages ───────────────────────────────────────────────────────
235
+
236
+ message ListNamespacesRequest {}
237
+
238
+ message NamespaceInfo {
239
+ string name = 1;
240
+ uint32 id = 2;
241
+ uint64 stream_count = 3;
242
+ }
243
+
244
+ message ListNamespacesResponse {
245
+ repeated NamespaceInfo namespaces = 1;
246
+ }
247
+
248
+ message ListStreamsRequest {
249
+ string namespace = 1;
250
+ }
251
+
252
+ message StreamInfo {
253
+ string name = 1;
254
+ uint64 id = 2;
255
+ uint64 first_seq = 3;
256
+ uint64 durable_seq = 4;
257
+ uint64 record_count = 5;
258
+ }
259
+
260
+ message ListStreamsResponse {
261
+ repeated StreamInfo streams = 1;
262
+ }