@openfairygui/backend 0.2.0-alpha.0 → 0.2.0-alpha.2

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.
@@ -0,0 +1,424 @@
1
+ import { UamProject, UamTransactionOperation } from "@openfairygui/core/uam";
2
+ import { ApplyUamTransactionAppError } from "@openfairygui/functions/uam";
3
+
4
+ //#region src/contracts.d.ts
5
+ declare const BACKEND_CONTRACT_VERSION: "1.1.0-p2";
6
+ declare const BACKEND_CAPABILITY_SCHEMA_VERSION: 2;
7
+ declare const BACKEND_COMPATIBILITY_POLICY: {
8
+ readonly incompatibleChange: "requires contractVersion bump";
9
+ readonly capabilitySchemaChange: "requires capabilitySchemaVersion bump";
10
+ readonly additiveChange: "allowed without breaking existing consumers";
11
+ };
12
+ type BackendStage = 'read' | 'authoring' | 'runtime';
13
+ interface BackendMessage {
14
+ code: string;
15
+ message: string;
16
+ }
17
+ interface BackendDiagnostic {
18
+ code: string;
19
+ message: string;
20
+ severity: 'info' | 'warning' | 'error';
21
+ }
22
+ interface BackendResponseMeta {
23
+ requestId: string;
24
+ sessionId?: string;
25
+ revision?: number;
26
+ durationMs: number;
27
+ warnings: BackendMessage[];
28
+ diagnostics: BackendDiagnostic[];
29
+ stage: BackendStage;
30
+ contractVersion: typeof BACKEND_CONTRACT_VERSION;
31
+ capabilitySchemaVersion: typeof BACKEND_CAPABILITY_SCHEMA_VERSION;
32
+ }
33
+ //#endregion
34
+ //#region src/path-policy.d.ts
35
+ interface PathPolicyViolationError {
36
+ code: 'path_policy_violation';
37
+ message: string;
38
+ policy: 'save_target';
39
+ attemptedPath: string;
40
+ allowedPath: string;
41
+ }
42
+ //#endregion
43
+ //#region src/runtime.d.ts
44
+ interface BackendFileHandle {
45
+ writeFile(content: string): Promise<void>;
46
+ close(): Promise<void>;
47
+ }
48
+ interface BackendFileStat {
49
+ isFile(): boolean;
50
+ isDirectory(): boolean;
51
+ }
52
+ interface BackendFileSystem {
53
+ stat(filePath: string): Promise<BackendFileStat>;
54
+ readdir(dirPath: string): Promise<string[]>;
55
+ readFile(filePath: string): Promise<string>;
56
+ readFileRaw(filePath: string): Promise<Uint8Array>;
57
+ writeFile(filePath: string, content: string): Promise<void>;
58
+ writeFileRaw(filePath: string, data: Uint8Array): Promise<void>;
59
+ mkdir(dirPath: string, options?: {
60
+ recursive?: boolean;
61
+ }): Promise<void>;
62
+ resolvePath(filePath: string): Promise<string>;
63
+ openExclusive(filePath: string): Promise<BackendFileHandle>;
64
+ unlink(filePath: string): Promise<void>;
65
+ join(...paths: string[]): string;
66
+ dirname(filePath: string): string;
67
+ resolve(...paths: string[]): string;
68
+ }
69
+ interface BackendHostAdapter {
70
+ lockMetadata?(input: {
71
+ canonicalPathKey: string;
72
+ canonicalProjectPath: string;
73
+ lockFilePath: string;
74
+ }): unknown;
75
+ }
76
+ interface BackendArtifactBridgeCapability {
77
+ available: false;
78
+ requiredHost: 'node';
79
+ executionBoundary: 'external-bridge';
80
+ bridgeEntrypoint: '@openfairygui/backend/node';
81
+ reason: string;
82
+ }
83
+ interface BackendCapabilityManifest {
84
+ browserSafe: true;
85
+ rootEntrypoint: '@openfairygui/backend';
86
+ nodeEntrypoint: '@openfairygui/backend/node';
87
+ adapters: {
88
+ fileSystem: {
89
+ injected: true;
90
+ requiredFor: readonly ['openSession', 'saveSession'];
91
+ };
92
+ host: {
93
+ injected: true;
94
+ requiredFor: readonly ['advisoryLockMetadata'];
95
+ };
96
+ };
97
+ executionBoundaries: {
98
+ projectSession: 'in-process-browser-safe';
99
+ fileBackedSession: 'adapter-backed';
100
+ artifactPublish: BackendArtifactBridgeCapability;
101
+ artifactRestore: BackendArtifactBridgeCapability;
102
+ };
103
+ diagnostics: {
104
+ stableCodes: true;
105
+ errorDiagnosticMirror: true;
106
+ };
107
+ }
108
+ interface BackendCapabilities {
109
+ contractVersion: typeof BACKEND_CONTRACT_VERSION;
110
+ capabilitySchemaVersion: typeof BACKEND_CAPABILITY_SCHEMA_VERSION;
111
+ transactionKernelOwner: '@openfairygui/core';
112
+ appSeamOwner: '@openfairygui/functions';
113
+ runtimeOwner: '@openfairygui/backend';
114
+ methods: readonly ['getCapabilities', 'openSession', 'openProjectSession', 'getSession', 'applyTransaction', 'saveSession', 'closeSession', 'getEvents', 'getJob', 'listJobs', 'cancelJob', 'getCacheSnapshot', 'refreshCache'];
115
+ read: {
116
+ capabilitySnapshot: true;
117
+ sessionSnapshot: true;
118
+ };
119
+ authoring: {
120
+ applyTransaction: true;
121
+ saveSession: true;
122
+ resourceKinds: readonly string[];
123
+ nodeKinds: readonly string[];
124
+ gearKinds: readonly string[];
125
+ unsupported: readonly ['artifact.publish', 'artifact.restore'];
126
+ };
127
+ artifact: {
128
+ publish: false;
129
+ restore: false;
130
+ status: 'bridge-required';
131
+ publishBridge: BackendArtifactBridgeCapability;
132
+ restoreBridge: BackendArtifactBridgeCapability;
133
+ };
134
+ manifest: BackendCapabilityManifest;
135
+ compatibilityPolicy: typeof BACKEND_COMPATIBILITY_POLICY;
136
+ runtime: {
137
+ sessionRuntime: true;
138
+ advisoryLocking: true;
139
+ coordinatedSave: true;
140
+ atomicSave: false;
141
+ staleRevisionProtection: true;
142
+ pathPolicy: {
143
+ canonicalization: 'realpath+normalized-casefold';
144
+ sessionIdentity: 'project-root';
145
+ saveTarget: 'opened-project-only';
146
+ outputTargets: 'deferred';
147
+ workspaceBoundary: 'project-root-only';
148
+ };
149
+ events: {
150
+ polling: true;
151
+ subscriptions: false;
152
+ retentionLimit: 1000;
153
+ sequenceScope: 'runtime';
154
+ };
155
+ jobs: {
156
+ inMemory: true;
157
+ cooperativeCancel: true;
158
+ persistent: false;
159
+ supportedKinds: readonly ['cache.refresh'];
160
+ artifactJobs: false;
161
+ completedRetentionLimit: 100;
162
+ };
163
+ cache: {
164
+ derivedReadOnly: true;
165
+ keyedBy: 'canonicalPathKey';
166
+ sourceOfTruth: false;
167
+ refreshMethod: 'refreshCache';
168
+ };
169
+ };
170
+ }
171
+ interface BackendSessionSnapshot {
172
+ sessionId: string;
173
+ canonicalProjectPath: string;
174
+ revision: number;
175
+ lastSavedRevision: number;
176
+ dirty: boolean;
177
+ lockHeld: boolean;
178
+ capabilities: BackendCapabilities;
179
+ }
180
+ interface BackendSuccess<T> {
181
+ ok: true;
182
+ meta: BackendResponseMeta;
183
+ data: T;
184
+ }
185
+ interface BackendFailure<E extends BackendError = BackendError> {
186
+ ok: false;
187
+ meta: BackendResponseMeta;
188
+ error: E;
189
+ session?: BackendSessionSnapshot;
190
+ }
191
+ type BackendResult<T, E extends BackendError = BackendError> = BackendSuccess<T> | BackendFailure<E>;
192
+ interface SessionNotFoundError {
193
+ code: 'session_not_found';
194
+ message: string;
195
+ sessionId: string;
196
+ }
197
+ interface SessionStaleWriteError {
198
+ code: 'stale_write';
199
+ message: string;
200
+ sessionId: string;
201
+ canonicalPathKey: string;
202
+ expectedRevision: number;
203
+ actualRevision: number;
204
+ }
205
+ interface InProcessLockConflictError {
206
+ code: 'lock_conflict';
207
+ kind: 'in_process_session_exists';
208
+ message: string;
209
+ canonicalPathKey: string;
210
+ holderSessionId: string;
211
+ lockFilePath?: string;
212
+ }
213
+ interface AdvisoryLockConflictError {
214
+ code: 'lock_conflict';
215
+ kind: 'advisory_lock_conflict';
216
+ message: string;
217
+ canonicalPathKey: string;
218
+ holderSessionId?: string;
219
+ lockFilePath: string;
220
+ }
221
+ interface SavePartialFailureError {
222
+ code: 'save_partial_failure';
223
+ message: string;
224
+ sessionId: string;
225
+ canonicalPathKey: string;
226
+ attemptedRevision: number;
227
+ lastSavedRevision: number;
228
+ committedPaths: string[];
229
+ failedPaths: string[];
230
+ diskMayBePartiallyUpdated: true;
231
+ }
232
+ type BackendEventKind = 'session.opened' | 'transaction.applied' | 'transaction.rejected' | 'save.started' | 'save.completed' | 'save.failed' | 'session.closeRequested' | 'session.closed' | 'cache.invalidated' | 'cache.updated' | 'job.created' | 'job.started' | 'job.progress' | 'job.cancelRequested' | 'job.cancelled' | 'job.completed' | 'job.failed';
233
+ interface BackendEvent {
234
+ sequence: number;
235
+ kind: BackendEventKind;
236
+ timestamp: string;
237
+ sessionId?: string;
238
+ canonicalPathKey?: string;
239
+ revision?: number;
240
+ cacheRevision?: number;
241
+ jobId?: string;
242
+ diagnostics: BackendDiagnostic[];
243
+ payload?: unknown;
244
+ }
245
+ interface GetEventsInput {
246
+ sessionId: string;
247
+ after?: string;
248
+ limit?: number;
249
+ }
250
+ interface GetEventsSnapshot {
251
+ events: BackendEvent[];
252
+ oldestSequence: number;
253
+ currentSequence: number;
254
+ cursorExpired: boolean;
255
+ }
256
+ interface EventCursorInvalidError {
257
+ code: 'event_cursor_invalid';
258
+ message: string;
259
+ sessionId: string;
260
+ after: string;
261
+ }
262
+ type BackendJobStatus = 'queued' | 'running' | 'completed' | 'failed' | 'cancelled';
263
+ type BackendJobKind = 'cache.refresh';
264
+ type BackendJobListStatusFilter = BackendJobStatus | 'active' | 'terminal';
265
+ interface BackendJobProgress {
266
+ completed: number;
267
+ total?: number;
268
+ message?: string;
269
+ }
270
+ interface BackendJobSnapshot {
271
+ jobId: string;
272
+ kind: BackendJobKind;
273
+ status: BackendJobStatus;
274
+ createdAt: string;
275
+ startedAt?: string;
276
+ finishedAt?: string;
277
+ sessionId?: string;
278
+ canonicalPathKey?: string;
279
+ revision?: number;
280
+ cacheRevision?: number;
281
+ diagnostics: BackendDiagnostic[];
282
+ progress?: BackendJobProgress;
283
+ result?: unknown;
284
+ error?: BackendError;
285
+ }
286
+ interface BackendJobListSnapshot {
287
+ jobs: BackendJobSnapshot[];
288
+ }
289
+ interface GetJobInput {
290
+ sessionId: string;
291
+ jobId: string;
292
+ }
293
+ interface ListJobsInput {
294
+ sessionId: string;
295
+ status?: BackendJobListStatusFilter;
296
+ kind?: BackendJobKind;
297
+ limit?: number;
298
+ }
299
+ interface CancelJobInput {
300
+ sessionId: string;
301
+ jobId: string;
302
+ }
303
+ interface BackendJobNotFoundError {
304
+ code: 'job_not_found';
305
+ message: string;
306
+ sessionId: string;
307
+ jobId: string;
308
+ }
309
+ interface BackendJobNotCancellableError {
310
+ code: 'job_not_cancellable';
311
+ message: string;
312
+ sessionId: string;
313
+ jobId: string;
314
+ status: 'completed' | 'failed' | 'cancelled';
315
+ }
316
+ interface BackendJobCancelledError {
317
+ code: 'job_cancelled';
318
+ message: string;
319
+ sessionId: string;
320
+ jobId: string;
321
+ }
322
+ interface CacheRefreshFailedError {
323
+ code: 'cache_refresh_failed';
324
+ message: string;
325
+ sessionId: string;
326
+ jobId: string;
327
+ causeCode?: string;
328
+ }
329
+ interface BackendCapabilityUnavailableError {
330
+ code: 'capability_unavailable';
331
+ message: string;
332
+ capability: 'fileSystem' | 'artifact.publish' | 'artifact.restore';
333
+ requiredAdapter?: 'BackendFileSystem';
334
+ requiredHost?: 'node';
335
+ bridgeBoundary?: 'external-bridge';
336
+ }
337
+ type BackendJobErrors = BackendJobNotFoundError | BackendJobNotCancellableError | BackendJobCancelledError | CacheRefreshFailedError;
338
+ interface BackendCacheSnapshot {
339
+ cacheRevision: number;
340
+ entries: BackendCacheEntry[];
341
+ }
342
+ interface BackendCacheEntry {
343
+ canonicalPathKey: string;
344
+ sessionId?: string;
345
+ revision: number;
346
+ lastSavedRevision: number;
347
+ dirty: boolean;
348
+ valid: boolean;
349
+ indexedAt: string;
350
+ summary: {
351
+ resourceCount: number;
352
+ packageCount?: number;
353
+ diagnostics: BackendDiagnostic[];
354
+ };
355
+ }
356
+ interface GetCacheSnapshotInput {
357
+ sessionId: string;
358
+ }
359
+ interface RefreshCacheInput {
360
+ sessionId: string;
361
+ reason?: 'manual' | 'session_open' | 'after_save';
362
+ }
363
+ type BackendError = SessionNotFoundError | SessionStaleWriteError | InProcessLockConflictError | AdvisoryLockConflictError | SavePartialFailureError | PathPolicyViolationError | EventCursorInvalidError | BackendJobNotFoundError | BackendJobNotCancellableError | BackendJobCancelledError | CacheRefreshFailedError | BackendCapabilityUnavailableError | ApplyUamTransactionAppError;
364
+ interface ApplySessionTransactionInput {
365
+ sessionId: string;
366
+ expectedRevision: number;
367
+ operations: UamTransactionOperation[];
368
+ }
369
+ interface OpenProjectSessionInput {
370
+ project: UamProject;
371
+ sessionId?: string;
372
+ canonicalProjectPath?: string;
373
+ canonicalPathKey?: string;
374
+ }
375
+ interface BackendRuntimeOptions {
376
+ fileSystem?: BackendFileSystem;
377
+ host?: BackendHostAdapter;
378
+ }
379
+ declare class BackendRuntime {
380
+ private readonly fileSystem?;
381
+ private readonly capabilities;
382
+ private readonly sessions;
383
+ private readonly sessionsByPath;
384
+ private readonly eventsBySession;
385
+ private readonly jobsBySession;
386
+ private readonly cacheBySession;
387
+ private eventSequence;
388
+ private readonly context;
389
+ private readonly readService;
390
+ private readonly runtimeService;
391
+ private readonly authoringService;
392
+ private readonly cacheService;
393
+ private readonly eventService;
394
+ private readonly jobService;
395
+ constructor(options?: BackendRuntimeOptions);
396
+ getCapabilities(): BackendSuccess<BackendCapabilities>;
397
+ openSession(input: {
398
+ projectPath: string;
399
+ }): Promise<BackendResult<BackendSessionSnapshot, InProcessLockConflictError | AdvisoryLockConflictError | BackendCapabilityUnavailableError>>;
400
+ openProjectSession(input: OpenProjectSessionInput): BackendResult<BackendSessionSnapshot>;
401
+ getSession(input: {
402
+ sessionId: string;
403
+ }): BackendResult<BackendSessionSnapshot, SessionNotFoundError>;
404
+ applyTransaction(input: ApplySessionTransactionInput): Promise<BackendResult<BackendSessionSnapshot, SessionNotFoundError | SessionStaleWriteError | ApplyUamTransactionAppError>>;
405
+ saveSession(input: {
406
+ sessionId: string;
407
+ expectedRevision?: number;
408
+ targetPath?: string;
409
+ }): Promise<BackendResult<BackendSessionSnapshot, SessionNotFoundError | SessionStaleWriteError | SavePartialFailureError | PathPolicyViolationError | BackendCapabilityUnavailableError>>;
410
+ closeSession(input: {
411
+ sessionId: string;
412
+ }): Promise<BackendResult<{
413
+ sessionId: string;
414
+ closed: true;
415
+ }, SessionNotFoundError>>;
416
+ getEvents(input: GetEventsInput): BackendResult<GetEventsSnapshot, SessionNotFoundError | EventCursorInvalidError>;
417
+ getJob(input: GetJobInput): BackendResult<BackendJobSnapshot, SessionNotFoundError | BackendJobNotFoundError>;
418
+ listJobs(input: ListJobsInput): BackendResult<BackendJobListSnapshot, SessionNotFoundError>;
419
+ cancelJob(input: CancelJobInput): BackendResult<BackendJobSnapshot, SessionNotFoundError | BackendJobNotFoundError | BackendJobNotCancellableError>;
420
+ getCacheSnapshot(input: GetCacheSnapshotInput): BackendResult<BackendCacheSnapshot, SessionNotFoundError>;
421
+ refreshCache(input: RefreshCacheInput): BackendResult<BackendJobSnapshot, SessionNotFoundError>;
422
+ }
423
+ //#endregion
424
+ export { BackendSuccess as A, OpenProjectSessionInput as B, BackendJobProgress as C, BackendRuntime as D, BackendResult as E, GetEventsInput as F, BACKEND_CAPABILITY_SCHEMA_VERSION as G, SavePartialFailureError as H, GetEventsSnapshot as I, BackendDiagnostic as J, BACKEND_COMPATIBILITY_POLICY as K, GetJobInput as L, CancelJobInput as M, EventCursorInvalidError as N, BackendRuntimeOptions as O, GetCacheSnapshotInput as P, InProcessLockConflictError as R, BackendJobNotFoundError as S, BackendJobStatus as T, SessionNotFoundError as U, RefreshCacheInput as V, SessionStaleWriteError as W, BackendResponseMeta as X, BackendMessage as Y, BackendStage as Z, BackendJobErrors as _, BackendCacheSnapshot as a, BackendJobListStatusFilter as b, BackendCapabilityUnavailableError as c, BackendEventKind as d, BackendFailure as f, BackendHostAdapter as g, BackendFileSystem as h, BackendCacheEntry as i, CacheRefreshFailedError as j, BackendSessionSnapshot as k, BackendError as l, BackendFileStat as m, ApplySessionTransactionInput as n, BackendCapabilities as o, BackendFileHandle as p, BACKEND_CONTRACT_VERSION as q, BackendArtifactBridgeCapability as r, BackendCapabilityManifest as s, AdvisoryLockConflictError as t, BackendEvent as u, BackendJobKind as v, BackendJobSnapshot as w, BackendJobNotCancellableError as x, BackendJobListSnapshot as y, ListJobsInput as z };
package/package.json CHANGED
@@ -1,54 +1,66 @@
1
1
  {
2
- "name": "@openfairygui/backend",
3
- "version": "0.2.0-alpha.0",
4
- "description": "FairyGUI Headless Authoring SDK — stateful backend runtime and session services.",
5
- "author": "OpenFairyGUI Contributors",
6
- "license": "MIT",
7
- "repository": {
8
- "type": "git",
9
- "url": "git+https://github.com/OpenFairyGUI/OpenFairyGUI.git",
10
- "directory": "packages/backend"
11
- },
12
- "homepage": "https://github.com/OpenFairyGUI/OpenFairyGUI#readme",
13
- "bugs": {
14
- "url": "https://github.com/OpenFairyGUI/OpenFairYGUI/issues"
15
- },
16
- "type": "module",
17
- "sideEffects": false,
18
- "main": "./dist/index.cjs",
19
- "module": "./dist/index.mjs",
20
- "types": "./dist/index.d.mts",
21
- "exports": {
22
- "require": {
23
- "types": "./dist/index.d.cts",
24
- "default": "./dist/index.cjs"
25
- },
26
- "default": {
27
- "types": "./dist/index.d.mts",
28
- "default": "./dist/index.mjs"
29
- }
30
- },
31
- "scripts": {
32
- "build": "tsdown --format esm,cjs --platform node --external node:fs --external node:fs/promises --external node:path --env.PACKAGE_VERSION=$npm_package_version",
33
- "build:watch": "tsdown --watch --format esm,cjs --platform node --env.PACKAGE_VERSION=$npm_package_version"
34
- },
35
- "files": [
36
- "dist/",
37
- "src/"
38
- ],
39
- "keywords": [
40
- "fairygui",
41
- "backend",
42
- "session",
43
- "authoring",
44
- "runtime"
45
- ],
46
- "dependencies": {
47
- "@openfairygui/core": "workspace:*",
48
- "@openfairygui/functions": "workspace:*"
49
- },
50
- "devDependencies": {
51
- "ava": "^7.0.0",
52
- "tsx": "^4.0.0"
53
- }
54
- }
2
+ "name": "@openfairygui/backend",
3
+ "version": "0.2.0-alpha.2",
4
+ "description": "FairyGUI Headless Authoring SDK — stateful backend runtime and session services.",
5
+ "author": "OpenFairyGUI Contributors",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/OpenFairyGUI/OpenFairyGUI.git",
10
+ "directory": "packages/backend"
11
+ },
12
+ "homepage": "https://github.com/OpenFairyGUI/OpenFairyGUI#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/OpenFairyGUI/OpenFairYGUI/issues"
15
+ },
16
+ "type": "module",
17
+ "sideEffects": false,
18
+ "main": "./dist/index.cjs",
19
+ "module": "./dist/index.mjs",
20
+ "types": "./dist/index.d.mts",
21
+ "exports": {
22
+ ".": {
23
+ "require": {
24
+ "types": "./dist/index.d.cts",
25
+ "default": "./dist/index.cjs"
26
+ },
27
+ "default": {
28
+ "types": "./dist/index.d.mts",
29
+ "default": "./dist/index.mjs"
30
+ }
31
+ },
32
+ "./node": {
33
+ "require": {
34
+ "types": "./dist/node.d.cts",
35
+ "default": "./dist/node.cjs"
36
+ },
37
+ "default": {
38
+ "types": "./dist/node.d.mts",
39
+ "default": "./dist/node.mjs"
40
+ }
41
+ }
42
+ },
43
+ "files": [
44
+ "dist/",
45
+ "src/"
46
+ ],
47
+ "keywords": [
48
+ "fairygui",
49
+ "backend",
50
+ "session",
51
+ "authoring",
52
+ "runtime"
53
+ ],
54
+ "dependencies": {
55
+ "@openfairygui/core": "0.2.0-alpha.2",
56
+ "@openfairygui/functions": "0.2.0-alpha.2"
57
+ },
58
+ "devDependencies": {
59
+ "ava": "^7.0.0",
60
+ "tsx": "^4.0.0"
61
+ },
62
+ "scripts": {
63
+ "build": "tsdown src/index.ts src/node.ts --format esm,cjs --platform node --external node:fs --external node:fs/promises --external node:path --env.PACKAGE_VERSION=$npm_package_version",
64
+ "build:watch": "tsdown src/index.ts src/node.ts --watch --format esm,cjs --platform node --external node:fs --external node:fs/promises --external node:path --env.PACKAGE_VERSION=$npm_package_version"
65
+ }
66
+ }
package/src/index.ts CHANGED
@@ -1,17 +1,21 @@
1
1
  export {
2
2
  BackendRuntime,
3
- createNodeBackendFileSystem,
4
3
  type AdvisoryLockConflictError,
5
4
  type ApplySessionTransactionInput,
5
+ type BackendArtifactBridgeCapability,
6
6
  type BackendCacheEntry,
7
7
  type BackendCacheSnapshot,
8
+ type BackendCapabilityManifest,
9
+ type BackendCapabilityUnavailableError,
8
10
  type BackendCapabilities,
9
11
  type BackendError,
10
12
  type BackendEvent,
11
13
  type BackendEventKind,
12
14
  type BackendFailure,
13
15
  type BackendFileHandle,
16
+ type BackendFileStat,
14
17
  type BackendFileSystem,
18
+ type BackendHostAdapter,
15
19
  type BackendJobKind,
16
20
  type BackendJobListSnapshot,
17
21
  type BackendJobListStatusFilter,
@@ -34,6 +38,7 @@ export {
34
38
  type GetJobInput,
35
39
  type InProcessLockConflictError,
36
40
  type ListJobsInput,
41
+ type OpenProjectSessionInput,
37
42
  type RefreshCacheInput,
38
43
  type SavePartialFailureError,
39
44
  type SessionNotFoundError,
package/src/node.ts ADDED
@@ -0,0 +1,96 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import {
4
+ BackendRuntime,
5
+ type BackendFileHandle,
6
+ type BackendFileStat,
7
+ type BackendFileSystem,
8
+ type BackendHostAdapter,
9
+ type BackendRuntimeOptions,
10
+ } from './runtime.js';
11
+
12
+ export function createNodeBackendFileSystem(): BackendFileSystem {
13
+ return {
14
+ stat(filePath: string): Promise<BackendFileStat> {
15
+ return fs.stat(filePath);
16
+ },
17
+ readdir(dirPath: string): Promise<string[]> {
18
+ return fs.readdir(dirPath);
19
+ },
20
+ readFile(filePath: string): Promise<string> {
21
+ return fs.readFile(filePath, 'utf-8');
22
+ },
23
+ async readFileRaw(filePath: string): Promise<Uint8Array> {
24
+ const buffer = await fs.readFile(filePath);
25
+ return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
26
+ },
27
+ writeFile(filePath: string, content: string): Promise<void> {
28
+ return fs.writeFile(filePath, content, 'utf-8');
29
+ },
30
+ writeFileRaw(filePath: string, data: Uint8Array): Promise<void> {
31
+ return fs.writeFile(filePath, data);
32
+ },
33
+ async mkdir(dirPath: string, options?: { recursive?: boolean }): Promise<void> {
34
+ await fs.mkdir(dirPath, { recursive: options?.recursive ?? false });
35
+ },
36
+ async resolvePath(filePath: string): Promise<string> {
37
+ try {
38
+ return await fs.realpath(filePath);
39
+ } catch {
40
+ return path.resolve(filePath);
41
+ }
42
+ },
43
+ async openExclusive(filePath: string): Promise<BackendFileHandle> {
44
+ const handle = await fs.open(filePath, 'wx');
45
+ return {
46
+ writeFile(content: string): Promise<void> {
47
+ return handle.writeFile(content, 'utf-8');
48
+ },
49
+ close(): Promise<void> {
50
+ return handle.close();
51
+ },
52
+ };
53
+ },
54
+ unlink(filePath: string): Promise<void> {
55
+ return fs.unlink(filePath);
56
+ },
57
+ join(...paths: string[]): string {
58
+ return path.join(...paths);
59
+ },
60
+ dirname(filePath: string): string {
61
+ return path.dirname(filePath);
62
+ },
63
+ resolve(...paths: string[]): string {
64
+ return path.resolve(...paths);
65
+ },
66
+ };
67
+ }
68
+
69
+ export function createNodeBackendHostAdapter(): BackendHostAdapter {
70
+ return {
71
+ lockMetadata(input) {
72
+ return {
73
+ pid: process.pid,
74
+ createdAt: new Date().toISOString(),
75
+ canonicalPathKey: input.canonicalPathKey,
76
+ };
77
+ },
78
+ };
79
+ }
80
+
81
+ export function createNodeBackendRuntime(options: BackendRuntimeOptions = {}): BackendRuntime {
82
+ return new BackendRuntime({
83
+ ...options,
84
+ fileSystem: options.fileSystem ?? createNodeBackendFileSystem(),
85
+ host: options.host ?? createNodeBackendHostAdapter(),
86
+ });
87
+ }
88
+
89
+ export { BackendRuntime };
90
+ export type {
91
+ BackendFileHandle,
92
+ BackendFileStat,
93
+ BackendFileSystem,
94
+ BackendHostAdapter,
95
+ BackendRuntimeOptions,
96
+ } from './runtime.js';