@bassfish/cli 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 (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +153 -0
  3. package/dist/api.d.ts +365 -0
  4. package/dist/api.js +113 -0
  5. package/dist/api.js.map +1 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +217 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/config.d.ts +20 -0
  10. package/dist/config.js +68 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/daemon.d.ts +4 -0
  13. package/dist/daemon.js +220 -0
  14. package/dist/daemon.js.map +1 -0
  15. package/dist/domain.d.ts +393 -0
  16. package/dist/domain.js +75 -0
  17. package/dist/domain.js.map +1 -0
  18. package/dist/export.d.ts +7 -0
  19. package/dist/export.js +37 -0
  20. package/dist/export.js.map +1 -0
  21. package/dist/ipc.d.ts +14 -0
  22. package/dist/ipc.js +125 -0
  23. package/dist/ipc.js.map +1 -0
  24. package/dist/lock.d.ts +2 -0
  25. package/dist/lock.js +18 -0
  26. package/dist/lock.js.map +1 -0
  27. package/dist/mcp.d.ts +16 -0
  28. package/dist/mcp.js +200 -0
  29. package/dist/mcp.js.map +1 -0
  30. package/dist/note-cli.d.ts +28 -0
  31. package/dist/note-cli.js +204 -0
  32. package/dist/note-cli.js.map +1 -0
  33. package/dist/note.d.ts +12 -0
  34. package/dist/note.js +181 -0
  35. package/dist/note.js.map +1 -0
  36. package/dist/repository.d.ts +1 -0
  37. package/dist/repository.js +21 -0
  38. package/dist/repository.js.map +1 -0
  39. package/dist/runtime.d.ts +12 -0
  40. package/dist/runtime.js +32 -0
  41. package/dist/runtime.js.map +1 -0
  42. package/dist/service.d.ts +106 -0
  43. package/dist/service.js +1185 -0
  44. package/dist/service.js.map +1 -0
  45. package/dist/setup.d.ts +21 -0
  46. package/dist/setup.js +108 -0
  47. package/dist/setup.js.map +1 -0
  48. package/dist/sql-worker.d.ts +6 -0
  49. package/dist/sql-worker.js +76 -0
  50. package/dist/sql-worker.js.map +1 -0
  51. package/dist/storage/control.d.ts +10 -0
  52. package/dist/storage/control.js +160 -0
  53. package/dist/storage/control.js.map +1 -0
  54. package/dist/storage/dolt.d.ts +32 -0
  55. package/dist/storage/dolt.js +494 -0
  56. package/dist/storage/dolt.js.map +1 -0
  57. package/dist/storage/search.d.ts +22 -0
  58. package/dist/storage/search.js +75 -0
  59. package/dist/storage/search.js.map +1 -0
  60. package/dist/supervisor.d.ts +11 -0
  61. package/dist/supervisor.js +97 -0
  62. package/dist/supervisor.js.map +1 -0
  63. package/dist/tasks.d.ts +52 -0
  64. package/dist/tasks.js +33 -0
  65. package/dist/tasks.js.map +1 -0
  66. package/dist/thread-cli.d.ts +2 -0
  67. package/dist/thread-cli.js +64 -0
  68. package/dist/thread-cli.js.map +1 -0
  69. package/package.json +64 -0
@@ -0,0 +1,393 @@
1
+ export declare class BassfishError extends Error {
2
+ readonly code: string;
3
+ readonly details?: unknown;
4
+ constructor(code: string, message: string, details?: unknown);
5
+ }
6
+ export declare function requireThat(condition: unknown, code: string, message: string): asserts condition;
7
+ export interface Actor {
8
+ projectId: string;
9
+ identityId: string;
10
+ instanceId: string;
11
+ name: string;
12
+ }
13
+ export interface Project {
14
+ id: string;
15
+ commonDir: string;
16
+ recovering: boolean;
17
+ }
18
+ export interface Identity {
19
+ id: string;
20
+ projectId: string;
21
+ name: string;
22
+ }
23
+ export interface Instance {
24
+ id: string;
25
+ projectId: string;
26
+ identityId: string;
27
+ handle: string;
28
+ epoch: string;
29
+ active: boolean;
30
+ lastSeen: number;
31
+ }
32
+ export type ContentResourceType = 'thread' | 'note';
33
+ export type ResourceType = ContentResourceType | 'project';
34
+ export interface Resource {
35
+ id: string;
36
+ projectId: string;
37
+ type: ResourceType;
38
+ fence: string;
39
+ queueSequence: string;
40
+ present: boolean;
41
+ }
42
+ export type FloorState = 'QUEUED' | 'READY' | 'OFFERED' | 'HELD' | 'COMMITTING' | 'COMMITTED' | 'RELEASED' | 'EXPIRED' | 'CANCELLED' | 'FAILED';
43
+ export interface FloorRequest {
44
+ id: string;
45
+ projectId: string;
46
+ resourceId: string;
47
+ resourceType: ResourceType;
48
+ identityId: string;
49
+ instanceId: string;
50
+ sequence: string;
51
+ state: FloorState;
52
+ createdAt: number;
53
+ queueUntil: number;
54
+ reconnectUntil?: number;
55
+ offerId?: string;
56
+ claimBy?: number;
57
+ floorId?: string;
58
+ fence?: string;
59
+ baseRevision?: string;
60
+ snapshotCommit?: string;
61
+ expiresAt?: number;
62
+ finishedAt?: number;
63
+ result?: StorageResult;
64
+ purpose?: 'snapshot' | 'export' | 'search' | 'restore';
65
+ deliveryMode: 'ticket' | 'task';
66
+ updatedAt: number;
67
+ }
68
+ export type TaskStatus = 'working' | 'completed' | 'failed' | 'cancelled';
69
+ export interface DurableTask {
70
+ id: string;
71
+ projectId: string;
72
+ identityId: string;
73
+ requestId: string;
74
+ status: TaskStatus;
75
+ statusMessage?: string;
76
+ createdAt: number;
77
+ updatedAt: number;
78
+ discardAt: number;
79
+ result?: Record<string, unknown>;
80
+ error?: {
81
+ code: number;
82
+ message: string;
83
+ data?: Record<string, unknown>;
84
+ };
85
+ }
86
+ export interface PendingCommit {
87
+ id: string;
88
+ projectId: string;
89
+ resourceId: string;
90
+ resourceType: ResourceType;
91
+ floorRequestId?: string;
92
+ startingHead: string;
93
+ kind: string;
94
+ actor: Actor;
95
+ }
96
+ export interface ControlState {
97
+ projects: Record<string, Project>;
98
+ identities: Record<string, Identity>;
99
+ instances: Record<string, Instance>;
100
+ resources: Record<string, Resource>;
101
+ requests: Record<string, FloorRequest>;
102
+ pending: Record<string, PendingCommit>;
103
+ tasks: Record<string, DurableTask>;
104
+ wallClockHighWaterMs: number;
105
+ }
106
+ export interface ControlStore {
107
+ view<T>(fn: (state: ControlState) => T): T;
108
+ update<T>(fn: (state: ControlState) => T): T;
109
+ close(): void;
110
+ }
111
+ export interface Clock {
112
+ now(): number;
113
+ wallNow(): number;
114
+ discontinuity(): boolean;
115
+ }
116
+ export type ThreadState = 'active' | 'archived' | 'deleted';
117
+ export interface Thread {
118
+ id: string;
119
+ title: string;
120
+ description: string;
121
+ state: ThreadState;
122
+ revision: string;
123
+ headSequence: string;
124
+ creator: string;
125
+ createdAt: string;
126
+ }
127
+ export type NoteState = 'active' | 'archived' | 'deleted';
128
+ export type LinkTargetType = 'note' | 'thread' | 'message';
129
+ export interface ResourceLink {
130
+ targetType: LinkTargetType;
131
+ targetId: string;
132
+ }
133
+ export interface Note {
134
+ id: string;
135
+ path: string;
136
+ title: string;
137
+ body: string;
138
+ labels: string[];
139
+ kind: string | null;
140
+ state: NoteState;
141
+ revision: string;
142
+ creator: string;
143
+ creatorName: string;
144
+ lastEditor: string;
145
+ lastEditorName: string;
146
+ createdAt: string;
147
+ updatedAt: string;
148
+ links: ResourceLink[];
149
+ }
150
+ export interface Message {
151
+ id: string;
152
+ threadId: string;
153
+ sequence: string;
154
+ identityId: string;
155
+ name: string;
156
+ instanceId: string;
157
+ createdAt: string;
158
+ body: string;
159
+ retracted?: boolean;
160
+ }
161
+ export interface Snapshot {
162
+ resourceType: 'thread';
163
+ thread: Thread;
164
+ commit: string;
165
+ messages: Message[];
166
+ truncated: boolean;
167
+ nextBefore: string | null;
168
+ }
169
+ export interface NotePage {
170
+ text: string;
171
+ startLine: number;
172
+ endLine: number;
173
+ startByte: number;
174
+ endByte: number;
175
+ truncated: boolean;
176
+ nextCursor: string | null;
177
+ }
178
+ export interface NoteSnapshot {
179
+ resourceType: 'note';
180
+ note: Note;
181
+ commit: string;
182
+ page: NotePage;
183
+ }
184
+ export type ResourceSnapshot = Snapshot | NoteSnapshot;
185
+ export type ThreadMutation = {
186
+ kind: 'appendMessage';
187
+ body: string;
188
+ } | {
189
+ kind: 'renameThread';
190
+ title: string;
191
+ } | {
192
+ kind: 'setThreadDescription';
193
+ description: string;
194
+ } | {
195
+ kind: 'archiveThread';
196
+ } | {
197
+ kind: 'activateThread';
198
+ } | {
199
+ kind: 'deleteThread';
200
+ } | {
201
+ kind: 'retractMessage';
202
+ messageId: string;
203
+ } | {
204
+ kind: 'reinstateMessage';
205
+ messageId: string;
206
+ } | {
207
+ kind: 'restoreThreadRevision';
208
+ targetRevision: string;
209
+ };
210
+ export type NoteMutation = {
211
+ kind: 'replaceNoteBody';
212
+ body: string;
213
+ } | {
214
+ kind: 'patchNoteBody';
215
+ patch: string;
216
+ } | {
217
+ kind: 'appendNoteBody';
218
+ body: string;
219
+ } | {
220
+ kind: 'prependNoteBody';
221
+ body: string;
222
+ } | {
223
+ kind: 'moveNote';
224
+ path: string;
225
+ } | {
226
+ kind: 'setNoteMetadata';
227
+ title?: string;
228
+ labels?: string[];
229
+ noteKind?: string | null;
230
+ } | {
231
+ kind: 'setLinks';
232
+ links: ResourceLink[];
233
+ } | {
234
+ kind: 'archiveNote';
235
+ } | {
236
+ kind: 'deleteNote';
237
+ } | {
238
+ kind: 'activateNote';
239
+ } | {
240
+ kind: 'replaceNoteText';
241
+ find: string;
242
+ replace: string;
243
+ expectedOccurrences: number;
244
+ } | {
245
+ kind: 'upsertNoteSection';
246
+ headingPath: string[];
247
+ body: string;
248
+ occurrence?: number;
249
+ createIfMissing: boolean;
250
+ } | {
251
+ kind: 'batchNote';
252
+ mutations: Exclude<NoteMutation, {
253
+ kind: 'batchNote';
254
+ }>[];
255
+ };
256
+ export type Mutation = ThreadMutation | NoteMutation;
257
+ export interface ThreadWriteOperation {
258
+ id: string;
259
+ actor: Actor;
260
+ resourceId: string;
261
+ at: string;
262
+ resourceType: 'thread';
263
+ thread: Thread;
264
+ mutation: ThreadMutation | {
265
+ kind: 'createThread';
266
+ };
267
+ visibilityChanges?: {
268
+ messageId: string;
269
+ visible: boolean;
270
+ }[];
271
+ }
272
+ export interface NoteWriteOperation {
273
+ id: string;
274
+ actor: Actor;
275
+ resourceId: string;
276
+ at: string;
277
+ resourceType: 'note';
278
+ note: Note;
279
+ mutation: NoteMutation | {
280
+ kind: 'createNote';
281
+ };
282
+ }
283
+ export type WriteOperation = ThreadWriteOperation | NoteWriteOperation;
284
+ export interface MutationResult {
285
+ resourceId: string;
286
+ previousRevision: string;
287
+ revision: string;
288
+ doltCommit: string;
289
+ messageId?: string;
290
+ sequence?: string;
291
+ }
292
+ export interface HistoryEntry {
293
+ operationId: string;
294
+ kind: string;
295
+ actorIdentityId: string;
296
+ actorName: string;
297
+ instanceId: string;
298
+ createdAt: string;
299
+ reason: string | null;
300
+ beforeRevision: string;
301
+ afterRevision: string;
302
+ doltCommit: string;
303
+ }
304
+ export interface MessageVisibility {
305
+ messageId: string;
306
+ threadId: string;
307
+ visible: boolean;
308
+ threadRevision: string;
309
+ operationId: string;
310
+ createdAt: string;
311
+ }
312
+ export interface ProjectSnapshot {
313
+ commit: string;
314
+ threads: Thread[];
315
+ messages: Message[];
316
+ notes: Note[];
317
+ visibility: MessageVisibility[];
318
+ }
319
+ export interface ProjectHistoryEntry {
320
+ operationId: string;
321
+ kind: string;
322
+ actorIdentityId: string;
323
+ actorName: string;
324
+ instanceId: string;
325
+ createdAt: string;
326
+ doltCommit: string;
327
+ }
328
+ export interface ProjectRestoreChange {
329
+ resourceType: ContentResourceType;
330
+ resourceId: string;
331
+ action: 'create' | 'update' | 'delete';
332
+ beforeRevision: string;
333
+ afterRevision: string;
334
+ }
335
+ export interface ProjectRestoreResult {
336
+ operationId: string;
337
+ targetCommit: string;
338
+ previousCommit: string;
339
+ doltCommit: string;
340
+ changes: ProjectRestoreChange[];
341
+ }
342
+ export interface ProjectRestoreOperation {
343
+ id: string;
344
+ actor: Actor;
345
+ resourceId: string;
346
+ resourceType: 'project';
347
+ at: string;
348
+ mutation: {
349
+ kind: 'restoreSnapshot';
350
+ targetCommit: string;
351
+ };
352
+ target: ProjectSnapshot;
353
+ current: ProjectSnapshot;
354
+ changes: ProjectRestoreChange[];
355
+ }
356
+ export type StorageResult = MutationResult | ProjectRestoreResult;
357
+ export type Resolution = {
358
+ state: 'committed';
359
+ result: StorageResult;
360
+ } | {
361
+ state: 'absent';
362
+ } | {
363
+ state: 'unknown';
364
+ };
365
+ export interface ContentStore {
366
+ ensureProject(projectId: string): Promise<void>;
367
+ head(projectId: string): Promise<string>;
368
+ listThreads(projectId: string): Promise<Thread[]>;
369
+ listNotes(projectId: string): Promise<Note[]>;
370
+ resourceType(projectId: string, resourceId: string): Promise<ContentResourceType>;
371
+ snapshot(projectId: string, resourceId: string, limit: number, before?: string, at?: string): Promise<Snapshot>;
372
+ noteSnapshot(projectId: string, resourceId: string, cursor?: string, at?: string): Promise<NoteSnapshot>;
373
+ history(projectId: string, resourceType: ContentResourceType, resourceId: string): Promise<HistoryEntry[]>;
374
+ commitAtRevision(projectId: string, resourceType: ContentResourceType, resourceId: string, revision: string): Promise<string>;
375
+ projectSnapshot(projectId: string, at?: string): Promise<ProjectSnapshot>;
376
+ projectHistory(projectId: string): Promise<ProjectHistoryEntry[]>;
377
+ maxRevision(projectId: string, resourceType: ContentResourceType, resourceId: string): Promise<string>;
378
+ maxSequence(projectId: string, threadId: string): Promise<string>;
379
+ historicalNotes(projectId: string, at: string): Promise<HistoricalNote[]>;
380
+ write(operation: WriteOperation | ProjectRestoreOperation): Promise<StorageResult>;
381
+ resolve(pending: PendingCommit): Promise<Resolution>;
382
+ close(): Promise<void>;
383
+ }
384
+ export interface HistoricalNote {
385
+ note: Note;
386
+ doltCommit: string;
387
+ changedAt: string;
388
+ }
389
+ export declare const activeStates: FloorState[];
390
+ export declare const reservedStates: FloorState[];
391
+ export declare const increment: (value: string) => string;
392
+ export declare function prepareMutation(thread: Thread, mutation: ThreadMutation): Thread;
393
+ export declare function notePage(body: string, cursor?: string, maxLines?: number, maxBytes?: number): NotePage;
package/dist/domain.js ADDED
@@ -0,0 +1,75 @@
1
+ export class BassfishError extends Error {
2
+ code;
3
+ details;
4
+ constructor(code, message, details) {
5
+ super(message);
6
+ this.code = code;
7
+ this.details = details;
8
+ this.name = 'BassfishError';
9
+ }
10
+ }
11
+ export function requireThat(condition, code, message) {
12
+ if (!condition)
13
+ throw new BassfishError(code, message);
14
+ }
15
+ export const activeStates = ['QUEUED', 'READY', 'OFFERED', 'HELD', 'COMMITTING'];
16
+ export const reservedStates = ['READY', 'OFFERED', 'HELD', 'COMMITTING'];
17
+ export const increment = (value) => (BigInt(value) + 1n).toString();
18
+ export function prepareMutation(thread, mutation) {
19
+ const next = { ...thread, revision: increment(thread.revision) };
20
+ switch (mutation.kind) {
21
+ case 'appendMessage':
22
+ requireThat(thread.state === 'active', 'RESOURCE_ARCHIVED', 'Restore the thread before appending.');
23
+ next.headSequence = increment(thread.headSequence);
24
+ break;
25
+ case 'renameThread':
26
+ requireThat(thread.title !== mutation.title, 'NO_CHANGE', 'The title is unchanged.');
27
+ next.title = mutation.title;
28
+ break;
29
+ case 'setThreadDescription':
30
+ requireThat(thread.description !== mutation.description, 'NO_CHANGE', 'The description is unchanged.');
31
+ next.description = mutation.description;
32
+ break;
33
+ case 'archiveThread':
34
+ requireThat(thread.state === 'active', 'NO_CHANGE', 'Only an active thread can be archived.');
35
+ next.state = 'archived';
36
+ break;
37
+ case 'activateThread':
38
+ requireThat(thread.state !== 'active', 'NO_CHANGE', 'The thread is already active.');
39
+ next.state = 'active';
40
+ break;
41
+ case 'deleteThread':
42
+ requireThat(thread.state !== 'deleted', 'NO_CHANGE', 'The thread is already deleted.');
43
+ next.state = 'deleted';
44
+ break;
45
+ case 'retractMessage':
46
+ case 'reinstateMessage':
47
+ case 'restoreThreadRevision':
48
+ break;
49
+ }
50
+ return next;
51
+ }
52
+ export function notePage(body, cursor, maxLines = 200, maxBytes = 32 * 1024) {
53
+ let startByte = 0;
54
+ if (cursor !== undefined) {
55
+ requireThat(/^(0|[1-9][0-9]*)$/.test(cursor), 'INVALID_CURSOR', 'The note cursor is invalid.');
56
+ startByte = Number(cursor);
57
+ }
58
+ const bytes = Buffer.from(body, 'utf8');
59
+ requireThat(startByte >= 0 && startByte <= bytes.length, 'INVALID_CURSOR', 'The note cursor is outside the body.');
60
+ let endByte = Math.min(bytes.length, startByte + maxBytes);
61
+ while (endByte > startByte && (bytes[endByte] ?? 0) >= 0x80 && (bytes[endByte] ?? 0) < 0xc0)
62
+ endByte--;
63
+ const prefix = bytes.subarray(0, startByte).toString('utf8');
64
+ const candidate = bytes.subarray(startByte, endByte).toString('utf8');
65
+ const lines = candidate.split('\n');
66
+ if (lines.length > maxLines) {
67
+ const limited = lines.slice(0, maxLines).join('\n') + '\n';
68
+ endByte = startByte + Buffer.byteLength(limited);
69
+ }
70
+ const text = bytes.subarray(startByte, endByte).toString('utf8');
71
+ const startLine = prefix.split('\n').length;
72
+ const endLine = startLine + Math.max(0, text.split('\n').length - 1);
73
+ return { text, startLine, endLine, startByte, endByte, truncated: endByte < bytes.length, nextCursor: endByte < bytes.length ? String(endByte) : null };
74
+ }
75
+ //# sourceMappingURL=domain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain.js","sourceRoot":"","sources":["../src/domain.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { ProjectSnapshot } from './domain.js';
2
+ export declare function exportProject(dataDir: string, projectId: string, snapshot: ProjectSnapshot): Promise<{
3
+ path: string;
4
+ bytes: number;
5
+ sha256: string;
6
+ snapshotCommit: string;
7
+ }>;
package/dist/export.js ADDED
@@ -0,0 +1,37 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { mkdir, rename, writeFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { strToU8, zipSync } from 'fflate';
5
+ const stable = (value) => JSON.stringify(value, (_key, item) => item && typeof item === 'object' && !Array.isArray(item)
6
+ ? Object.fromEntries(Object.entries(item).sort(([a], [b]) => a.localeCompare(b))) : item, 2) + '\n';
7
+ const fixed = new Date(1980, 1, 1, 0, 0, 0, 0);
8
+ export async function exportProject(dataDir, projectId, snapshot) {
9
+ const files = {};
10
+ const add = (path, value) => { files[path] = [strToU8(value), { mtime: fixed, level: 0 }]; };
11
+ for (const note of snapshot.notes) {
12
+ const { body, ...metadata } = note;
13
+ add(`notes/${note.id}.json`, stable(metadata));
14
+ add(`notes/${note.id}.md`, body);
15
+ }
16
+ for (const thread of snapshot.threads) {
17
+ const messages = snapshot.messages.filter(message => message.threadId === thread.id);
18
+ const visibility = snapshot.visibility.filter(item => item.threadId === thread.id);
19
+ add(`threads/${thread.id}.json`, stable({ thread, messages, visibility }));
20
+ const latest = new Map();
21
+ for (const item of visibility)
22
+ latest.set(item.messageId, item.visible);
23
+ add(`threads/${thread.id}.md`, `# ${thread.title}\n\n${messages.map(message => latest.get(message.id) === false ? `> [Message ${message.sequence} retracted]` : `**${message.name}:** ${message.body}`).join('\n\n')}\n`);
24
+ }
25
+ const inventory = Object.keys(files).sort().map(path => ({ path, sha256: createHash('sha256').update(files[path][0]).digest('hex') }));
26
+ add('manifest.json', stable({ schemaVersion: 1, projectId, snapshotCommit: snapshot.commit, inventory }));
27
+ const archive = zipSync(Object.fromEntries(Object.entries(files).sort(([a], [b]) => a.localeCompare(b))));
28
+ const sha256 = createHash('sha256').update(archive).digest('hex');
29
+ const directory = join(dataDir, 'exports');
30
+ await mkdir(directory, { recursive: true, mode: 0o700 });
31
+ const path = join(directory, `${projectId}-${snapshot.commit.slice(0, 12)}.zip`);
32
+ const temporary = `${path}.${process.pid}.tmp`;
33
+ await writeFile(temporary, archive, { mode: 0o600 });
34
+ await rename(temporary, path);
35
+ return { path, bytes: archive.byteLength, sha256, snapshotCommit: snapshot.commit };
36
+ }
37
+ //# sourceMappingURL=export.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export.js","sourceRoot":"","sources":["../src/export.ts"],"names":[],"mappings":"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"}
package/dist/ipc.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ import type { Server, Socket } from 'node:net';
2
+ export type RpcHandler = (method: string, params: unknown, signal: AbortSignal, socket: Socket) => Promise<unknown>;
3
+ export declare function listenRpc(path: string, handler: RpcHandler, disconnected: (socket: Socket) => void): Promise<{
4
+ server: Server;
5
+ close: () => Promise<void>;
6
+ }>;
7
+ export declare class RpcClient {
8
+ readonly socket: Socket;
9
+ private readonly requests;
10
+ private constructor();
11
+ static connect(path: string): Promise<RpcClient>;
12
+ call<T = unknown>(method: string, params?: unknown, signal?: AbortSignal): Promise<T>;
13
+ close(): void;
14
+ }
package/dist/ipc.js ADDED
@@ -0,0 +1,125 @@
1
+ import { createConnection, createServer } from 'node:net';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { BassfishError } from './domain.js';
4
+ import { z } from 'zod';
5
+ const requestSchema = z.object({ id: z.string().min(1).max(100), method: z.string().min(1).max(100), params: z.unknown().optional() }).strict();
6
+ const maximumFrame = 2_000_000;
7
+ function frames(socket, receive) {
8
+ let buffer = '';
9
+ socket.setEncoding('utf8');
10
+ socket.on('data', data => {
11
+ buffer += data;
12
+ if (Buffer.byteLength(buffer) > maximumFrame) {
13
+ socket.destroy(new Error('IPC frame limit exceeded'));
14
+ return;
15
+ }
16
+ let newline;
17
+ while ((newline = buffer.indexOf('\n')) >= 0) {
18
+ const line = buffer.slice(0, newline);
19
+ buffer = buffer.slice(newline + 1);
20
+ try {
21
+ receive(JSON.parse(line));
22
+ }
23
+ catch {
24
+ socket.destroy(new Error('Invalid IPC frame'));
25
+ return;
26
+ }
27
+ }
28
+ });
29
+ }
30
+ export async function listenRpc(path, handler, disconnected) {
31
+ const sockets = new Set();
32
+ const jobs = new Set();
33
+ const server = createServer(socket => {
34
+ sockets.add(socket);
35
+ const running = new Map();
36
+ socket.on('error', () => { });
37
+ socket.on('close', () => { for (const controller of running.values())
38
+ controller.abort(); sockets.delete(socket); disconnected(socket); });
39
+ frames(socket, frame => {
40
+ const parsed = requestSchema.safeParse(frame);
41
+ if (!parsed.success) {
42
+ socket.destroy();
43
+ return;
44
+ }
45
+ const request = parsed.data;
46
+ if (request.method === '$cancel') {
47
+ const p = request.params;
48
+ if (typeof p?.id === 'string')
49
+ running.get(p.id)?.abort();
50
+ return;
51
+ }
52
+ if (running.has(request.id)) {
53
+ socket.destroy();
54
+ return;
55
+ }
56
+ const controller = new AbortController();
57
+ running.set(request.id, controller);
58
+ const job = handler(request.method, request.params, controller.signal, socket).then(result => {
59
+ if (!socket.destroyed)
60
+ socket.write(JSON.stringify({ id: request.id, result }) + '\n');
61
+ }, error => {
62
+ const failure = error instanceof BassfishError ? error : new BassfishError('INTERNAL_ERROR', 'The backend operation failed; inspect daemon health.');
63
+ if (!socket.destroyed)
64
+ socket.write(JSON.stringify({ id: request.id, error: { code: failure.code, message: failure.message } }) + '\n');
65
+ }).finally(() => { running.delete(request.id); jobs.delete(job); });
66
+ jobs.add(job);
67
+ });
68
+ });
69
+ await new Promise((resolve, reject) => { server.once('error', reject); server.listen(path, resolve); });
70
+ return { server, close: async () => { for (const socket of sockets)
71
+ socket.destroy(); await new Promise(resolve => server.close(() => resolve())); await Promise.allSettled([...jobs]); } };
72
+ }
73
+ export class RpcClient {
74
+ socket;
75
+ requests = new Map();
76
+ constructor(socket) {
77
+ this.socket = socket;
78
+ frames(socket, raw => {
79
+ const reply = raw;
80
+ const request = this.requests.get(reply.id);
81
+ if (!request)
82
+ return;
83
+ this.requests.delete(reply.id);
84
+ if (reply.error)
85
+ request.reject(new BassfishError(reply.error.code, reply.error.message));
86
+ else
87
+ request.resolve(reply.result);
88
+ });
89
+ const failed = () => { for (const r of this.requests.values())
90
+ r.reject(new BassfishError('OUTCOME_UNKNOWN', 'The connection closed. No request was retried; inspect its outcome before writing again.')); this.requests.clear(); };
91
+ socket.on('error', failed);
92
+ socket.on('close', failed);
93
+ }
94
+ static async connect(path) {
95
+ const socket = createConnection(path);
96
+ await new Promise((resolve, reject) => { socket.once('connect', resolve); socket.once('error', reject); });
97
+ return new RpcClient(socket);
98
+ }
99
+ async call(method, params = {}, signal) {
100
+ if (signal?.aborted)
101
+ throw new BassfishError('CANCELLED', 'Request was cancelled before submission.');
102
+ if (this.socket.destroyed)
103
+ throw new BassfishError('CONNECTION_CLOSED', 'Reconnect explicitly before submitting a new operation.');
104
+ const id = randomUUID();
105
+ const abort = () => { if (!this.socket.destroyed)
106
+ this.socket.write(JSON.stringify({ id: randomUUID(), method: '$cancel', params: { id } }) + '\n'); };
107
+ const timer = setTimeout(() => {
108
+ this.requests.get(id)?.reject(new BassfishError('OUTCOME_UNKNOWN', 'The response deadline elapsed. No operation was retried; inspect current state.'));
109
+ this.requests.delete(id);
110
+ }, 35_000);
111
+ signal?.addEventListener('abort', abort, { once: true });
112
+ try {
113
+ return await new Promise((resolve, reject) => {
114
+ this.requests.set(id, { resolve: value => resolve(value), reject });
115
+ this.socket.write(JSON.stringify({ id, method, params }) + '\n');
116
+ });
117
+ }
118
+ finally {
119
+ clearTimeout(timer);
120
+ signal?.removeEventListener('abort', abort);
121
+ }
122
+ }
123
+ close() { this.socket.end(); }
124
+ }
125
+ //# sourceMappingURL=ipc.js.map