@openfairygui/backend 0.2.0-alpha.0 → 0.2.0-alpha.10
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/LICENSE +21 -0
- package/README.md +62 -1
- package/dist/index.cjs +102 -1071
- package/dist/index.d.cts +24 -355
- package/dist/index.d.mts +24 -355
- package/dist/index.mjs +99 -1044
- package/dist/node.cjs +107 -0
- package/dist/node.d.cts +8 -0
- package/dist/node.d.mts +8 -0
- package/dist/node.mjs +78 -0
- package/dist/runtime-DFatY9W0.mjs +1417 -0
- package/dist/runtime-GKzsXJdO.cjs +1441 -0
- package/dist/runtime-GyNVxAQ0.d.mts +494 -0
- package/dist/runtime-Jec6FcF5.d.cts +494 -0
- package/package.json +65 -53
- package/src/contracts.ts +8 -0
- package/src/index.ts +18 -1
- package/src/node.ts +96 -0
- package/src/runtime.ts +256 -80
- package/src/services/artifact-service.ts +11 -1
- package/src/services/authoring-service.ts +466 -37
- package/src/services/context.ts +15 -3
- package/src/services/event-service.ts +1 -1
- package/src/services/runtime-service.ts +164 -25
- package/src/storage.ts +192 -0
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';
|
package/src/runtime.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
UAM_SUPPORTED_MATERIALIZATION_SCOPE,
|
|
3
|
+
UAM_SUPPORTED_TRANSACTION_SCOPE,
|
|
4
|
+
type UamProject,
|
|
3
5
|
type UamTransactionOperation,
|
|
4
|
-
} from '@openfairygui/core';
|
|
5
|
-
import type { ApplyUamTransactionAppError } from '@openfairygui/functions';
|
|
6
|
-
import fs from 'node:fs/promises';
|
|
7
|
-
import type { Stats } from 'node:fs';
|
|
8
|
-
import path from 'node:path';
|
|
6
|
+
} from '@openfairygui/core/uam';
|
|
7
|
+
import type { ApplyUamTransactionAppError } from '@openfairygui/functions/uam';
|
|
9
8
|
import {
|
|
10
9
|
BACKEND_CAPABILITY_SCHEMA_VERSION,
|
|
11
10
|
BACKEND_COMPATIBILITY_POLICY,
|
|
@@ -27,8 +26,13 @@ export interface BackendFileHandle {
|
|
|
27
26
|
close(): Promise<void>;
|
|
28
27
|
}
|
|
29
28
|
|
|
29
|
+
export interface BackendFileStat {
|
|
30
|
+
isFile(): boolean;
|
|
31
|
+
isDirectory(): boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
30
34
|
export interface BackendFileSystem {
|
|
31
|
-
stat(filePath: string): Promise<
|
|
35
|
+
stat(filePath: string): Promise<BackendFileStat>;
|
|
32
36
|
readdir(dirPath: string): Promise<string[]>;
|
|
33
37
|
readFile(filePath: string): Promise<string>;
|
|
34
38
|
readFileRaw(filePath: string): Promise<Uint8Array>;
|
|
@@ -43,6 +47,50 @@ export interface BackendFileSystem {
|
|
|
43
47
|
resolve(...paths: string[]): string;
|
|
44
48
|
}
|
|
45
49
|
|
|
50
|
+
export interface BackendHostAdapter {
|
|
51
|
+
lockMetadata?(input: { canonicalPathKey: string; canonicalProjectPath: string; lockFilePath: string }): unknown;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface BackendArtifactBridgeCapability {
|
|
55
|
+
available: false;
|
|
56
|
+
requiredHost: 'node';
|
|
57
|
+
executionBoundary: 'external-bridge';
|
|
58
|
+
bridgeEntrypoint: '@openfairygui/backend/node';
|
|
59
|
+
reason: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface BackendCapabilityManifest {
|
|
63
|
+
browserSafe: true;
|
|
64
|
+
rootEntrypoint: '@openfairygui/backend';
|
|
65
|
+
nodeEntrypoint: '@openfairygui/backend/node';
|
|
66
|
+
adapters: {
|
|
67
|
+
fileSystem: {
|
|
68
|
+
injected: true;
|
|
69
|
+
requiredFor: readonly ['openSession', 'saveSession', 'materializeSession'];
|
|
70
|
+
};
|
|
71
|
+
projectStorage: {
|
|
72
|
+
injected: true;
|
|
73
|
+
browserSafe: true;
|
|
74
|
+
requiredFor: readonly ['openProjectSession.writeback', 'saveSession', 'materializeSession'];
|
|
75
|
+
adapterFactory: 'createBackendStorageFileSystem';
|
|
76
|
+
};
|
|
77
|
+
host: {
|
|
78
|
+
injected: true;
|
|
79
|
+
requiredFor: readonly ['advisoryLockMetadata'];
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
executionBoundaries: {
|
|
83
|
+
projectSession: 'in-process-browser-safe';
|
|
84
|
+
fileBackedSession: 'adapter-backed';
|
|
85
|
+
artifactPublish: BackendArtifactBridgeCapability;
|
|
86
|
+
artifactRestore: BackendArtifactBridgeCapability;
|
|
87
|
+
};
|
|
88
|
+
diagnostics: {
|
|
89
|
+
stableCodes: true;
|
|
90
|
+
errorDiagnosticMirror: true;
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
46
94
|
export interface BackendCapabilities {
|
|
47
95
|
contractVersion: typeof BACKEND_CONTRACT_VERSION;
|
|
48
96
|
capabilitySchemaVersion: typeof BACKEND_CAPABILITY_SCHEMA_VERSION;
|
|
@@ -52,9 +100,11 @@ export interface BackendCapabilities {
|
|
|
52
100
|
methods: readonly [
|
|
53
101
|
'getCapabilities',
|
|
54
102
|
'openSession',
|
|
103
|
+
'openProjectSession',
|
|
55
104
|
'getSession',
|
|
56
105
|
'applyTransaction',
|
|
57
106
|
'saveSession',
|
|
107
|
+
'materializeSession',
|
|
58
108
|
'closeSession',
|
|
59
109
|
'getEvents',
|
|
60
110
|
'getJob',
|
|
@@ -73,13 +123,21 @@ export interface BackendCapabilities {
|
|
|
73
123
|
resourceKinds: readonly string[];
|
|
74
124
|
nodeKinds: readonly string[];
|
|
75
125
|
gearKinds: readonly string[];
|
|
126
|
+
transactionScope: {
|
|
127
|
+
resourceKinds: readonly string[];
|
|
128
|
+
nodeKinds: readonly string[];
|
|
129
|
+
gearKinds: readonly string[];
|
|
130
|
+
};
|
|
76
131
|
unsupported: readonly ['artifact.publish', 'artifact.restore'];
|
|
77
132
|
};
|
|
78
133
|
artifact: {
|
|
79
134
|
publish: false;
|
|
80
135
|
restore: false;
|
|
81
|
-
status: '
|
|
136
|
+
status: 'bridge-required';
|
|
137
|
+
publishBridge: BackendArtifactBridgeCapability;
|
|
138
|
+
restoreBridge: BackendArtifactBridgeCapability;
|
|
82
139
|
};
|
|
140
|
+
manifest: BackendCapabilityManifest;
|
|
83
141
|
compatibilityPolicy: typeof BACKEND_COMPATIBILITY_POLICY;
|
|
84
142
|
runtime: {
|
|
85
143
|
sessionRuntime: true;
|
|
@@ -127,6 +185,16 @@ export interface BackendSessionSnapshot {
|
|
|
127
185
|
capabilities: BackendCapabilities;
|
|
128
186
|
}
|
|
129
187
|
|
|
188
|
+
export interface MaterializeSessionSnapshot extends BackendSessionSnapshot {
|
|
189
|
+
mode: 'fullProject';
|
|
190
|
+
reason?: string;
|
|
191
|
+
materializeRevision: number;
|
|
192
|
+
saveRevision: number;
|
|
193
|
+
writtenPaths: string[];
|
|
194
|
+
skippedPaths: string[];
|
|
195
|
+
diagnostics: import('./contracts.js').BackendDiagnostic[];
|
|
196
|
+
}
|
|
197
|
+
|
|
130
198
|
export interface BackendSuccess<T> {
|
|
131
199
|
ok: true;
|
|
132
200
|
meta: BackendResponseMeta;
|
|
@@ -140,9 +208,7 @@ export interface BackendFailure<E extends BackendError = BackendError> {
|
|
|
140
208
|
session?: BackendSessionSnapshot;
|
|
141
209
|
}
|
|
142
210
|
|
|
143
|
-
export type BackendResult<T, E extends BackendError = BackendError> =
|
|
144
|
-
| BackendSuccess<T>
|
|
145
|
-
| BackendFailure<E>;
|
|
211
|
+
export type BackendResult<T, E extends BackendError = BackendError> = BackendSuccess<T> | BackendFailure<E>;
|
|
146
212
|
|
|
147
213
|
export interface SessionNotFoundError {
|
|
148
214
|
code: 'session_not_found';
|
|
@@ -189,6 +255,29 @@ export interface SavePartialFailureError {
|
|
|
189
255
|
diskMayBePartiallyUpdated: true;
|
|
190
256
|
}
|
|
191
257
|
|
|
258
|
+
export interface MaterializeValidationFailedError {
|
|
259
|
+
code: 'materialize_validation_failed';
|
|
260
|
+
message: string;
|
|
261
|
+
sessionId: string;
|
|
262
|
+
canonicalPathKey: string;
|
|
263
|
+
issueCount: number;
|
|
264
|
+
diagnostics: import('./contracts.js').BackendDiagnostic[];
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export interface MaterializeWriteFailedError {
|
|
268
|
+
code: 'write_failed';
|
|
269
|
+
message: string;
|
|
270
|
+
sessionId: string;
|
|
271
|
+
canonicalPathKey: string;
|
|
272
|
+
attemptedRevision: number;
|
|
273
|
+
lastSavedRevision: number;
|
|
274
|
+
writtenPaths: string[];
|
|
275
|
+
failedPaths: string[];
|
|
276
|
+
skippedPaths: string[];
|
|
277
|
+
diagnostics: import('./contracts.js').BackendDiagnostic[];
|
|
278
|
+
diskMayBePartiallyUpdated: true;
|
|
279
|
+
}
|
|
280
|
+
|
|
192
281
|
export type BackendEventKind =
|
|
193
282
|
| 'session.opened'
|
|
194
283
|
| 'transaction.applied'
|
|
@@ -319,6 +408,15 @@ export interface CacheRefreshFailedError {
|
|
|
319
408
|
causeCode?: string;
|
|
320
409
|
}
|
|
321
410
|
|
|
411
|
+
export interface BackendCapabilityUnavailableError {
|
|
412
|
+
code: 'capability_unavailable';
|
|
413
|
+
message: string;
|
|
414
|
+
capability: 'fileSystem' | 'artifact.publish' | 'artifact.restore';
|
|
415
|
+
requiredAdapter?: 'BackendFileSystem';
|
|
416
|
+
requiredHost?: 'node';
|
|
417
|
+
bridgeBoundary?: 'external-bridge';
|
|
418
|
+
}
|
|
419
|
+
|
|
322
420
|
export type BackendJobErrors =
|
|
323
421
|
| BackendJobNotFoundError
|
|
324
422
|
| BackendJobNotCancellableError
|
|
@@ -360,12 +458,15 @@ export type BackendError =
|
|
|
360
458
|
| InProcessLockConflictError
|
|
361
459
|
| AdvisoryLockConflictError
|
|
362
460
|
| SavePartialFailureError
|
|
461
|
+
| MaterializeValidationFailedError
|
|
462
|
+
| MaterializeWriteFailedError
|
|
363
463
|
| PathPolicyViolationError
|
|
364
464
|
| EventCursorInvalidError
|
|
365
465
|
| BackendJobNotFoundError
|
|
366
466
|
| BackendJobNotCancellableError
|
|
367
467
|
| BackendJobCancelledError
|
|
368
468
|
| CacheRefreshFailedError
|
|
469
|
+
| BackendCapabilityUnavailableError
|
|
369
470
|
| ApplyUamTransactionAppError;
|
|
370
471
|
|
|
371
472
|
export interface ApplySessionTransactionInput {
|
|
@@ -374,16 +475,53 @@ export interface ApplySessionTransactionInput {
|
|
|
374
475
|
operations: UamTransactionOperation[];
|
|
375
476
|
}
|
|
376
477
|
|
|
478
|
+
export interface OpenProjectSessionInput {
|
|
479
|
+
project: UamProject;
|
|
480
|
+
sessionId?: string;
|
|
481
|
+
canonicalProjectPath?: string;
|
|
482
|
+
canonicalPathKey?: string;
|
|
483
|
+
storage?: BackendProjectSessionStorage;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
export interface BackendProjectSessionStorage {
|
|
487
|
+
fileSystem: BackendFileSystem;
|
|
488
|
+
fairyPath: string;
|
|
489
|
+
canonicalProjectPath?: string;
|
|
490
|
+
canonicalPathKey?: string;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
export interface SaveSessionInput {
|
|
494
|
+
sessionId: string;
|
|
495
|
+
expectedRevision?: number;
|
|
496
|
+
targetPath?: string;
|
|
497
|
+
fileSystem?: BackendFileSystem;
|
|
498
|
+
force?: boolean;
|
|
499
|
+
mode?: 'materializeCleanSession';
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
export interface MaterializeSessionInput {
|
|
503
|
+
sessionId: string;
|
|
504
|
+
expectedRevision?: number;
|
|
505
|
+
storage?: BackendProjectSessionStorage;
|
|
506
|
+
targetPath?: string;
|
|
507
|
+
fileSystem?: BackendFileSystem;
|
|
508
|
+
mode?: 'fullProject';
|
|
509
|
+
reason?: string;
|
|
510
|
+
}
|
|
511
|
+
|
|
377
512
|
export interface BackendRuntimeOptions {
|
|
378
513
|
fileSystem?: BackendFileSystem;
|
|
514
|
+
host?: BackendHostAdapter;
|
|
379
515
|
}
|
|
380
516
|
|
|
381
517
|
const BACKEND_METHODS = [
|
|
382
518
|
'getCapabilities',
|
|
383
519
|
'openSession',
|
|
520
|
+
'openProjectSession',
|
|
384
521
|
'getSession',
|
|
385
522
|
'applyTransaction',
|
|
386
523
|
'saveSession',
|
|
524
|
+
'materializeSession',
|
|
387
525
|
'closeSession',
|
|
388
526
|
'getEvents',
|
|
389
527
|
'getJob',
|
|
@@ -393,6 +531,14 @@ const BACKEND_METHODS = [
|
|
|
393
531
|
'refreshCache',
|
|
394
532
|
] as const;
|
|
395
533
|
|
|
534
|
+
const ARTIFACT_BRIDGE_CAPABILITY = {
|
|
535
|
+
available: false,
|
|
536
|
+
requiredHost: 'node',
|
|
537
|
+
executionBoundary: 'external-bridge',
|
|
538
|
+
bridgeEntrypoint: '@openfairygui/backend/node',
|
|
539
|
+
reason: 'publish/restore require explicit Node-hosted filesystem and artifact execution.',
|
|
540
|
+
} as const satisfies BackendArtifactBridgeCapability;
|
|
541
|
+
|
|
396
542
|
function createCapabilities(): BackendCapabilities {
|
|
397
543
|
return {
|
|
398
544
|
contractVersion: BACKEND_CONTRACT_VERSION,
|
|
@@ -411,9 +557,45 @@ function createCapabilities(): BackendCapabilities {
|
|
|
411
557
|
resourceKinds: [...UAM_SUPPORTED_MATERIALIZATION_SCOPE.resourceKinds],
|
|
412
558
|
nodeKinds: [...UAM_SUPPORTED_MATERIALIZATION_SCOPE.nodeKinds],
|
|
413
559
|
gearKinds: [...UAM_SUPPORTED_MATERIALIZATION_SCOPE.gearKinds],
|
|
560
|
+
transactionScope: {
|
|
561
|
+
resourceKinds: [...UAM_SUPPORTED_TRANSACTION_SCOPE.resourceKinds],
|
|
562
|
+
nodeKinds: [...UAM_SUPPORTED_TRANSACTION_SCOPE.nodeKinds],
|
|
563
|
+
gearKinds: [...UAM_SUPPORTED_TRANSACTION_SCOPE.gearKinds],
|
|
564
|
+
},
|
|
414
565
|
unsupported: ['artifact.publish', 'artifact.restore'],
|
|
415
566
|
},
|
|
416
567
|
artifact: createArtifactCapabilities(),
|
|
568
|
+
manifest: {
|
|
569
|
+
browserSafe: true,
|
|
570
|
+
rootEntrypoint: '@openfairygui/backend',
|
|
571
|
+
nodeEntrypoint: '@openfairygui/backend/node',
|
|
572
|
+
adapters: {
|
|
573
|
+
fileSystem: {
|
|
574
|
+
injected: true,
|
|
575
|
+
requiredFor: ['openSession', 'saveSession', 'materializeSession'],
|
|
576
|
+
},
|
|
577
|
+
projectStorage: {
|
|
578
|
+
injected: true,
|
|
579
|
+
browserSafe: true,
|
|
580
|
+
requiredFor: ['openProjectSession.writeback', 'saveSession', 'materializeSession'],
|
|
581
|
+
adapterFactory: 'createBackendStorageFileSystem',
|
|
582
|
+
},
|
|
583
|
+
host: {
|
|
584
|
+
injected: true,
|
|
585
|
+
requiredFor: ['advisoryLockMetadata'],
|
|
586
|
+
},
|
|
587
|
+
},
|
|
588
|
+
executionBoundaries: {
|
|
589
|
+
projectSession: 'in-process-browser-safe',
|
|
590
|
+
fileBackedSession: 'adapter-backed',
|
|
591
|
+
artifactPublish: ARTIFACT_BRIDGE_CAPABILITY,
|
|
592
|
+
artifactRestore: ARTIFACT_BRIDGE_CAPABILITY,
|
|
593
|
+
},
|
|
594
|
+
diagnostics: {
|
|
595
|
+
stableCodes: true,
|
|
596
|
+
errorDiagnosticMirror: true,
|
|
597
|
+
},
|
|
598
|
+
},
|
|
417
599
|
compatibilityPolicy: BACKEND_COMPATIBILITY_POLICY,
|
|
418
600
|
runtime: {
|
|
419
601
|
sessionRuntime: true,
|
|
@@ -446,65 +628,8 @@ function createCapabilities(): BackendCapabilities {
|
|
|
446
628
|
};
|
|
447
629
|
}
|
|
448
630
|
|
|
449
|
-
export function createNodeBackendFileSystem(): BackendFileSystem {
|
|
450
|
-
return {
|
|
451
|
-
stat(filePath: string): Promise<Stats> {
|
|
452
|
-
return fs.stat(filePath);
|
|
453
|
-
},
|
|
454
|
-
readdir(dirPath: string): Promise<string[]> {
|
|
455
|
-
return fs.readdir(dirPath);
|
|
456
|
-
},
|
|
457
|
-
readFile(filePath: string): Promise<string> {
|
|
458
|
-
return fs.readFile(filePath, 'utf-8');
|
|
459
|
-
},
|
|
460
|
-
async readFileRaw(filePath: string): Promise<Uint8Array> {
|
|
461
|
-
const buffer = await fs.readFile(filePath);
|
|
462
|
-
return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
463
|
-
},
|
|
464
|
-
writeFile(filePath: string, content: string): Promise<void> {
|
|
465
|
-
return fs.writeFile(filePath, content, 'utf-8');
|
|
466
|
-
},
|
|
467
|
-
writeFileRaw(filePath: string, data: Uint8Array): Promise<void> {
|
|
468
|
-
return fs.writeFile(filePath, data);
|
|
469
|
-
},
|
|
470
|
-
async mkdir(dirPath: string, options?: { recursive?: boolean }): Promise<void> {
|
|
471
|
-
await fs.mkdir(dirPath, { recursive: options?.recursive ?? false });
|
|
472
|
-
},
|
|
473
|
-
async resolvePath(filePath: string): Promise<string> {
|
|
474
|
-
try {
|
|
475
|
-
return await fs.realpath(filePath);
|
|
476
|
-
} catch {
|
|
477
|
-
return path.resolve(filePath);
|
|
478
|
-
}
|
|
479
|
-
},
|
|
480
|
-
async openExclusive(filePath: string): Promise<BackendFileHandle> {
|
|
481
|
-
const handle = await fs.open(filePath, 'wx');
|
|
482
|
-
return {
|
|
483
|
-
writeFile(content: string): Promise<void> {
|
|
484
|
-
return handle.writeFile(content, 'utf-8');
|
|
485
|
-
},
|
|
486
|
-
close(): Promise<void> {
|
|
487
|
-
return handle.close();
|
|
488
|
-
},
|
|
489
|
-
};
|
|
490
|
-
},
|
|
491
|
-
unlink(filePath: string): Promise<void> {
|
|
492
|
-
return fs.unlink(filePath);
|
|
493
|
-
},
|
|
494
|
-
join(...paths: string[]): string {
|
|
495
|
-
return path.join(...paths);
|
|
496
|
-
},
|
|
497
|
-
dirname(filePath: string): string {
|
|
498
|
-
return path.dirname(filePath);
|
|
499
|
-
},
|
|
500
|
-
resolve(...paths: string[]): string {
|
|
501
|
-
return path.resolve(...paths);
|
|
502
|
-
},
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
|
-
|
|
506
631
|
export class BackendRuntime {
|
|
507
|
-
private readonly fileSystem
|
|
632
|
+
private readonly fileSystem?: BackendFileSystem;
|
|
508
633
|
private readonly capabilities: BackendCapabilities;
|
|
509
634
|
private readonly sessions = new Map<string, BackendSessionState>();
|
|
510
635
|
private readonly sessionsByPath = new Map<string, string>();
|
|
@@ -521,10 +646,11 @@ export class BackendRuntime {
|
|
|
521
646
|
private readonly jobService: JobService;
|
|
522
647
|
|
|
523
648
|
public constructor(options: BackendRuntimeOptions = {}) {
|
|
524
|
-
this.fileSystem = options.fileSystem
|
|
649
|
+
this.fileSystem = options.fileSystem;
|
|
525
650
|
this.capabilities = createCapabilities();
|
|
526
651
|
this.context = {
|
|
527
652
|
fileSystem: this.fileSystem,
|
|
653
|
+
host: options.host,
|
|
528
654
|
capabilities: this.capabilities,
|
|
529
655
|
sessions: this.sessions,
|
|
530
656
|
sessionsByPath: this.sessionsByPath,
|
|
@@ -548,37 +674,82 @@ export class BackendRuntime {
|
|
|
548
674
|
return this.readService.getCapabilities() as BackendSuccess<BackendCapabilities>;
|
|
549
675
|
}
|
|
550
676
|
|
|
551
|
-
public async openSession(input: {
|
|
677
|
+
public async openSession(input: {
|
|
678
|
+
projectPath: string;
|
|
679
|
+
}): Promise<
|
|
680
|
+
BackendResult<
|
|
681
|
+
BackendSessionSnapshot,
|
|
682
|
+
InProcessLockConflictError | AdvisoryLockConflictError | BackendCapabilityUnavailableError
|
|
683
|
+
>
|
|
684
|
+
> {
|
|
552
685
|
return this.runtimeService.openSession(input);
|
|
553
686
|
}
|
|
554
687
|
|
|
688
|
+
public openProjectSession(input: OpenProjectSessionInput): BackendResult<BackendSessionSnapshot> {
|
|
689
|
+
return this.runtimeService.openProjectSession(input);
|
|
690
|
+
}
|
|
691
|
+
|
|
555
692
|
public getSession(input: { sessionId: string }): BackendResult<BackendSessionSnapshot, SessionNotFoundError> {
|
|
556
693
|
return this.readService.getSession(input);
|
|
557
694
|
}
|
|
558
695
|
|
|
559
696
|
public async applyTransaction(
|
|
560
697
|
input: ApplySessionTransactionInput,
|
|
561
|
-
): Promise<
|
|
698
|
+
): Promise<
|
|
699
|
+
BackendResult<
|
|
700
|
+
BackendSessionSnapshot,
|
|
701
|
+
SessionNotFoundError | SessionStaleWriteError | ApplyUamTransactionAppError
|
|
702
|
+
>
|
|
703
|
+
> {
|
|
562
704
|
return this.authoringService.applyTransaction(input);
|
|
563
705
|
}
|
|
564
706
|
|
|
565
|
-
public async saveSession(
|
|
566
|
-
|
|
567
|
-
|
|
707
|
+
public async saveSession(input: SaveSessionInput): Promise<
|
|
708
|
+
BackendResult<
|
|
709
|
+
BackendSessionSnapshot | MaterializeSessionSnapshot,
|
|
710
|
+
| SessionNotFoundError
|
|
711
|
+
| SessionStaleWriteError
|
|
712
|
+
| SavePartialFailureError
|
|
713
|
+
| MaterializeValidationFailedError
|
|
714
|
+
| MaterializeWriteFailedError
|
|
715
|
+
| PathPolicyViolationError
|
|
716
|
+
| InProcessLockConflictError
|
|
717
|
+
| BackendCapabilityUnavailableError
|
|
718
|
+
>
|
|
719
|
+
> {
|
|
568
720
|
return this.authoringService.saveSession(input);
|
|
569
721
|
}
|
|
570
722
|
|
|
571
|
-
public async
|
|
572
|
-
|
|
573
|
-
|
|
723
|
+
public async materializeSession(input: MaterializeSessionInput): Promise<
|
|
724
|
+
BackendResult<
|
|
725
|
+
MaterializeSessionSnapshot,
|
|
726
|
+
| SessionNotFoundError
|
|
727
|
+
| SessionStaleWriteError
|
|
728
|
+
| MaterializeValidationFailedError
|
|
729
|
+
| MaterializeWriteFailedError
|
|
730
|
+
| PathPolicyViolationError
|
|
731
|
+
| InProcessLockConflictError
|
|
732
|
+
| BackendCapabilityUnavailableError
|
|
733
|
+
>
|
|
734
|
+
> {
|
|
735
|
+
return this.authoringService.materializeSession(input);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
public async closeSession(input: {
|
|
739
|
+
sessionId: string;
|
|
740
|
+
}): Promise<BackendResult<{ sessionId: string; closed: true }, SessionNotFoundError>> {
|
|
574
741
|
return this.runtimeService.closeSession(input);
|
|
575
742
|
}
|
|
576
743
|
|
|
577
|
-
public getEvents(
|
|
744
|
+
public getEvents(
|
|
745
|
+
input: GetEventsInput,
|
|
746
|
+
): BackendResult<GetEventsSnapshot, SessionNotFoundError | EventCursorInvalidError> {
|
|
578
747
|
return this.eventService.getEvents(input);
|
|
579
748
|
}
|
|
580
749
|
|
|
581
|
-
public getJob(
|
|
750
|
+
public getJob(
|
|
751
|
+
input: GetJobInput,
|
|
752
|
+
): BackendResult<BackendJobSnapshot, SessionNotFoundError | BackendJobNotFoundError> {
|
|
582
753
|
return this.jobService.getJob(input);
|
|
583
754
|
}
|
|
584
755
|
|
|
@@ -586,7 +757,12 @@ export class BackendRuntime {
|
|
|
586
757
|
return this.jobService.listJobs(input);
|
|
587
758
|
}
|
|
588
759
|
|
|
589
|
-
public cancelJob(
|
|
760
|
+
public cancelJob(
|
|
761
|
+
input: CancelJobInput,
|
|
762
|
+
): BackendResult<
|
|
763
|
+
BackendJobSnapshot,
|
|
764
|
+
SessionNotFoundError | BackendJobNotFoundError | BackendJobNotCancellableError
|
|
765
|
+
> {
|
|
590
766
|
return this.jobService.cancelJob(input);
|
|
591
767
|
}
|
|
592
768
|
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import type { BackendCapabilities } from '../runtime.js';
|
|
2
2
|
|
|
3
3
|
export function createArtifactCapabilities(): BackendCapabilities['artifact'] {
|
|
4
|
+
const bridge = {
|
|
5
|
+
available: false,
|
|
6
|
+
requiredHost: 'node',
|
|
7
|
+
executionBoundary: 'external-bridge',
|
|
8
|
+
bridgeEntrypoint: '@openfairygui/backend/node',
|
|
9
|
+
reason: 'publish/restore require explicit Node-hosted filesystem and artifact execution.',
|
|
10
|
+
} as const;
|
|
11
|
+
|
|
4
12
|
return {
|
|
5
13
|
publish: false,
|
|
6
14
|
restore: false,
|
|
7
|
-
status: '
|
|
15
|
+
status: 'bridge-required',
|
|
16
|
+
publishBridge: bridge,
|
|
17
|
+
restoreBridge: bridge,
|
|
8
18
|
};
|
|
9
19
|
}
|