@openfairygui/backend 0.2.0-alpha.3 → 0.2.0-alpha.31

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/src/runtime.ts CHANGED
@@ -1,18 +1,5 @@
1
- import {
2
- UAM_SUPPORTED_MATERIALIZATION_SCOPE,
3
- UAM_SUPPORTED_TRANSACTION_SCOPE,
4
- type UamProject,
5
- type UamTransactionOperation,
6
- } from '@openfairygui/core/uam';
7
1
  import type { ApplyUamTransactionAppError } from '@openfairygui/functions/uam';
8
- import {
9
- BACKEND_CAPABILITY_SCHEMA_VERSION,
10
- BACKEND_COMPATIBILITY_POLICY,
11
- BACKEND_CONTRACT_VERSION,
12
- type BackendResponseMeta,
13
- } from './contracts.js';
14
- import { createRuntimePathPolicy, type PathPolicyViolationError } from './path-policy.js';
15
- import { createArtifactCapabilities } from './services/artifact-service.js';
2
+ import type { PathPolicyViolationError } from './path-policy.js';
16
3
  import { AuthoringService } from './services/authoring-service.js';
17
4
  import { CacheService } from './services/cache-service.js';
18
5
  import type { BackendContext, BackendSessionState } from './services/context.js';
@@ -20,537 +7,46 @@ import { EventService } from './services/event-service.js';
20
7
  import { JobService } from './services/job-service.js';
21
8
  import { ReadService } from './services/read-service.js';
22
9
  import { RuntimeService } from './services/runtime-service.js';
23
-
24
- export interface BackendFileHandle {
25
- writeFile(content: string): Promise<void>;
26
- close(): Promise<void>;
27
- }
28
-
29
- export interface BackendFileStat {
30
- isFile(): boolean;
31
- isDirectory(): boolean;
32
- }
33
-
34
- export interface BackendFileSystem {
35
- stat(filePath: string): Promise<BackendFileStat>;
36
- readdir(dirPath: string): Promise<string[]>;
37
- readFile(filePath: string): Promise<string>;
38
- readFileRaw(filePath: string): Promise<Uint8Array>;
39
- writeFile(filePath: string, content: string): Promise<void>;
40
- writeFileRaw(filePath: string, data: Uint8Array): Promise<void>;
41
- mkdir(dirPath: string, options?: { recursive?: boolean }): Promise<void>;
42
- resolvePath(filePath: string): Promise<string>;
43
- openExclusive(filePath: string): Promise<BackendFileHandle>;
44
- unlink(filePath: string): Promise<void>;
45
- join(...paths: string[]): string;
46
- dirname(filePath: string): string;
47
- resolve(...paths: string[]): string;
48
- }
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'];
70
- };
71
- host: {
72
- injected: true;
73
- requiredFor: readonly ['advisoryLockMetadata'];
74
- };
75
- };
76
- executionBoundaries: {
77
- projectSession: 'in-process-browser-safe';
78
- fileBackedSession: 'adapter-backed';
79
- artifactPublish: BackendArtifactBridgeCapability;
80
- artifactRestore: BackendArtifactBridgeCapability;
81
- };
82
- diagnostics: {
83
- stableCodes: true;
84
- errorDiagnosticMirror: true;
85
- };
86
- }
87
-
88
- export interface BackendCapabilities {
89
- contractVersion: typeof BACKEND_CONTRACT_VERSION;
90
- capabilitySchemaVersion: typeof BACKEND_CAPABILITY_SCHEMA_VERSION;
91
- transactionKernelOwner: '@openfairygui/core';
92
- appSeamOwner: '@openfairygui/functions';
93
- runtimeOwner: '@openfairygui/backend';
94
- methods: readonly [
95
- 'getCapabilities',
96
- 'openSession',
97
- 'openProjectSession',
98
- 'getSession',
99
- 'applyTransaction',
100
- 'saveSession',
101
- 'closeSession',
102
- 'getEvents',
103
- 'getJob',
104
- 'listJobs',
105
- 'cancelJob',
106
- 'getCacheSnapshot',
107
- 'refreshCache',
108
- ];
109
- read: {
110
- capabilitySnapshot: true;
111
- sessionSnapshot: true;
112
- };
113
- authoring: {
114
- applyTransaction: true;
115
- saveSession: true;
116
- resourceKinds: readonly string[];
117
- nodeKinds: readonly string[];
118
- gearKinds: readonly string[];
119
- transactionScope: {
120
- resourceKinds: readonly string[];
121
- nodeKinds: readonly string[];
122
- gearKinds: readonly string[];
123
- };
124
- unsupported: readonly ['artifact.publish', 'artifact.restore'];
125
- };
126
- artifact: {
127
- publish: false;
128
- restore: false;
129
- status: 'bridge-required';
130
- publishBridge: BackendArtifactBridgeCapability;
131
- restoreBridge: BackendArtifactBridgeCapability;
132
- };
133
- manifest: BackendCapabilityManifest;
134
- compatibilityPolicy: typeof BACKEND_COMPATIBILITY_POLICY;
135
- runtime: {
136
- sessionRuntime: true;
137
- advisoryLocking: true;
138
- coordinatedSave: true;
139
- atomicSave: false;
140
- staleRevisionProtection: true;
141
- pathPolicy: {
142
- canonicalization: 'realpath+normalized-casefold';
143
- sessionIdentity: 'project-root';
144
- saveTarget: 'opened-project-only';
145
- outputTargets: 'deferred';
146
- workspaceBoundary: 'project-root-only';
147
- };
148
- events: {
149
- polling: true;
150
- subscriptions: false;
151
- retentionLimit: 1000;
152
- sequenceScope: 'runtime';
153
- };
154
- jobs: {
155
- inMemory: true;
156
- cooperativeCancel: true;
157
- persistent: false;
158
- supportedKinds: readonly ['cache.refresh'];
159
- artifactJobs: false;
160
- completedRetentionLimit: 100;
161
- };
162
- cache: {
163
- derivedReadOnly: true;
164
- keyedBy: 'canonicalPathKey';
165
- sourceOfTruth: false;
166
- refreshMethod: 'refreshCache';
167
- };
168
- };
169
- }
170
-
171
- export 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
-
181
- export interface BackendSuccess<T> {
182
- ok: true;
183
- meta: BackendResponseMeta;
184
- data: T;
185
- }
186
-
187
- export interface BackendFailure<E extends BackendError = BackendError> {
188
- ok: false;
189
- meta: BackendResponseMeta;
190
- error: E;
191
- session?: BackendSessionSnapshot;
192
- }
193
-
194
- export type BackendResult<T, E extends BackendError = BackendError> = BackendSuccess<T> | BackendFailure<E>;
195
-
196
- export interface SessionNotFoundError {
197
- code: 'session_not_found';
198
- message: string;
199
- sessionId: string;
200
- }
201
-
202
- export interface SessionStaleWriteError {
203
- code: 'stale_write';
204
- message: string;
205
- sessionId: string;
206
- canonicalPathKey: string;
207
- expectedRevision: number;
208
- actualRevision: number;
209
- }
210
-
211
- export interface InProcessLockConflictError {
212
- code: 'lock_conflict';
213
- kind: 'in_process_session_exists';
214
- message: string;
215
- canonicalPathKey: string;
216
- holderSessionId: string;
217
- lockFilePath?: string;
218
- }
219
-
220
- export interface AdvisoryLockConflictError {
221
- code: 'lock_conflict';
222
- kind: 'advisory_lock_conflict';
223
- message: string;
224
- canonicalPathKey: string;
225
- holderSessionId?: string;
226
- lockFilePath: string;
227
- }
228
-
229
- export interface SavePartialFailureError {
230
- code: 'save_partial_failure';
231
- message: string;
232
- sessionId: string;
233
- canonicalPathKey: string;
234
- attemptedRevision: number;
235
- lastSavedRevision: number;
236
- committedPaths: string[];
237
- failedPaths: string[];
238
- diskMayBePartiallyUpdated: true;
239
- }
240
-
241
- export type BackendEventKind =
242
- | 'session.opened'
243
- | 'transaction.applied'
244
- | 'transaction.rejected'
245
- | 'save.started'
246
- | 'save.completed'
247
- | 'save.failed'
248
- | 'session.closeRequested'
249
- | 'session.closed'
250
- | 'cache.invalidated'
251
- | 'cache.updated'
252
- | 'job.created'
253
- | 'job.started'
254
- | 'job.progress'
255
- | 'job.cancelRequested'
256
- | 'job.cancelled'
257
- | 'job.completed'
258
- | 'job.failed';
259
-
260
- export interface BackendEvent {
261
- sequence: number;
262
- kind: BackendEventKind;
263
- timestamp: string;
264
- sessionId?: string;
265
- canonicalPathKey?: string;
266
- revision?: number;
267
- cacheRevision?: number;
268
- jobId?: string;
269
- diagnostics: import('./contracts.js').BackendDiagnostic[];
270
- payload?: unknown;
271
- }
272
-
273
- export interface GetEventsInput {
274
- sessionId: string;
275
- after?: string;
276
- limit?: number;
277
- }
278
-
279
- export interface GetEventsSnapshot {
280
- events: BackendEvent[];
281
- oldestSequence: number;
282
- currentSequence: number;
283
- cursorExpired: boolean;
284
- }
285
-
286
- export interface EventCursorInvalidError {
287
- code: 'event_cursor_invalid';
288
- message: string;
289
- sessionId: string;
290
- after: string;
291
- }
292
-
293
- export type BackendJobStatus = 'queued' | 'running' | 'completed' | 'failed' | 'cancelled';
294
- export type BackendJobKind = 'cache.refresh';
295
- export type BackendJobListStatusFilter = BackendJobStatus | 'active' | 'terminal';
296
-
297
- export interface BackendJobProgress {
298
- completed: number;
299
- total?: number;
300
- message?: string;
301
- }
302
-
303
- export interface BackendJobSnapshot {
304
- jobId: string;
305
- kind: BackendJobKind;
306
- status: BackendJobStatus;
307
- createdAt: string;
308
- startedAt?: string;
309
- finishedAt?: string;
310
- sessionId?: string;
311
- canonicalPathKey?: string;
312
- revision?: number;
313
- cacheRevision?: number;
314
- diagnostics: import('./contracts.js').BackendDiagnostic[];
315
- progress?: BackendJobProgress;
316
- result?: unknown;
317
- error?: BackendError;
318
- }
319
-
320
- export interface BackendJobListSnapshot {
321
- jobs: BackendJobSnapshot[];
322
- }
323
-
324
- export interface GetJobInput {
325
- sessionId: string;
326
- jobId: string;
327
- }
328
-
329
- export interface ListJobsInput {
330
- sessionId: string;
331
- status?: BackendJobListStatusFilter;
332
- kind?: BackendJobKind;
333
- limit?: number;
334
- }
335
-
336
- export interface CancelJobInput {
337
- sessionId: string;
338
- jobId: string;
339
- }
340
-
341
- export interface BackendJobNotFoundError {
342
- code: 'job_not_found';
343
- message: string;
344
- sessionId: string;
345
- jobId: string;
346
- }
347
-
348
- export interface BackendJobNotCancellableError {
349
- code: 'job_not_cancellable';
350
- message: string;
351
- sessionId: string;
352
- jobId: string;
353
- status: 'completed' | 'failed' | 'cancelled';
354
- }
355
-
356
- export interface BackendJobCancelledError {
357
- code: 'job_cancelled';
358
- message: string;
359
- sessionId: string;
360
- jobId: string;
361
- }
362
-
363
- export interface CacheRefreshFailedError {
364
- code: 'cache_refresh_failed';
365
- message: string;
366
- sessionId: string;
367
- jobId: string;
368
- causeCode?: string;
369
- }
370
-
371
- export interface BackendCapabilityUnavailableError {
372
- code: 'capability_unavailable';
373
- message: string;
374
- capability: 'fileSystem' | 'artifact.publish' | 'artifact.restore';
375
- requiredAdapter?: 'BackendFileSystem';
376
- requiredHost?: 'node';
377
- bridgeBoundary?: 'external-bridge';
378
- }
379
-
380
- export type BackendJobErrors =
381
- | BackendJobNotFoundError
382
- | BackendJobNotCancellableError
383
- | BackendJobCancelledError
384
- | CacheRefreshFailedError;
385
-
386
- export interface BackendCacheSnapshot {
387
- cacheRevision: number;
388
- entries: BackendCacheEntry[];
389
- }
390
-
391
- export interface BackendCacheEntry {
392
- canonicalPathKey: string;
393
- sessionId?: string;
394
- revision: number;
395
- lastSavedRevision: number;
396
- dirty: boolean;
397
- valid: boolean;
398
- indexedAt: string;
399
- summary: {
400
- resourceCount: number;
401
- packageCount?: number;
402
- diagnostics: import('./contracts.js').BackendDiagnostic[];
403
- };
404
- }
405
-
406
- export interface GetCacheSnapshotInput {
407
- sessionId: string;
408
- }
409
-
410
- export interface RefreshCacheInput {
411
- sessionId: string;
412
- reason?: 'manual' | 'session_open' | 'after_save';
413
- }
414
-
415
- export type BackendError =
416
- | SessionNotFoundError
417
- | SessionStaleWriteError
418
- | InProcessLockConflictError
419
- | AdvisoryLockConflictError
420
- | SavePartialFailureError
421
- | PathPolicyViolationError
422
- | EventCursorInvalidError
423
- | BackendJobNotFoundError
424
- | BackendJobNotCancellableError
425
- | BackendJobCancelledError
426
- | CacheRefreshFailedError
427
- | BackendCapabilityUnavailableError
428
- | ApplyUamTransactionAppError;
429
-
430
- export interface ApplySessionTransactionInput {
431
- sessionId: string;
432
- expectedRevision: number;
433
- operations: UamTransactionOperation[];
434
- }
435
-
436
- export interface OpenProjectSessionInput {
437
- project: UamProject;
438
- sessionId?: string;
439
- canonicalProjectPath?: string;
440
- canonicalPathKey?: string;
441
- }
442
-
443
- export interface BackendRuntimeOptions {
444
- fileSystem?: BackendFileSystem;
445
- host?: BackendHostAdapter;
446
- }
447
-
448
- const BACKEND_METHODS = [
449
- 'getCapabilities',
450
- 'openSession',
451
- 'openProjectSession',
452
- 'getSession',
453
- 'applyTransaction',
454
- 'saveSession',
455
- 'closeSession',
456
- 'getEvents',
457
- 'getJob',
458
- 'listJobs',
459
- 'cancelJob',
460
- 'getCacheSnapshot',
461
- 'refreshCache',
462
- ] as const;
463
-
464
- const ARTIFACT_BRIDGE_CAPABILITY = {
465
- available: false,
466
- requiredHost: 'node',
467
- executionBoundary: 'external-bridge',
468
- bridgeEntrypoint: '@openfairygui/backend/node',
469
- reason: 'publish/restore require explicit Node-hosted filesystem and artifact execution.',
470
- } as const satisfies BackendArtifactBridgeCapability;
471
-
472
- function createCapabilities(): BackendCapabilities {
473
- return {
474
- contractVersion: BACKEND_CONTRACT_VERSION,
475
- capabilitySchemaVersion: BACKEND_CAPABILITY_SCHEMA_VERSION,
476
- transactionKernelOwner: '@openfairygui/core',
477
- appSeamOwner: '@openfairygui/functions',
478
- runtimeOwner: '@openfairygui/backend',
479
- methods: BACKEND_METHODS,
480
- read: {
481
- capabilitySnapshot: true,
482
- sessionSnapshot: true,
483
- },
484
- authoring: {
485
- applyTransaction: true,
486
- saveSession: true,
487
- resourceKinds: [...UAM_SUPPORTED_MATERIALIZATION_SCOPE.resourceKinds],
488
- nodeKinds: [...UAM_SUPPORTED_MATERIALIZATION_SCOPE.nodeKinds],
489
- gearKinds: [...UAM_SUPPORTED_MATERIALIZATION_SCOPE.gearKinds],
490
- transactionScope: {
491
- resourceKinds: [...UAM_SUPPORTED_TRANSACTION_SCOPE.resourceKinds],
492
- nodeKinds: [...UAM_SUPPORTED_TRANSACTION_SCOPE.nodeKinds],
493
- gearKinds: [...UAM_SUPPORTED_TRANSACTION_SCOPE.gearKinds],
494
- },
495
- unsupported: ['artifact.publish', 'artifact.restore'],
496
- },
497
- artifact: createArtifactCapabilities(),
498
- manifest: {
499
- browserSafe: true,
500
- rootEntrypoint: '@openfairygui/backend',
501
- nodeEntrypoint: '@openfairygui/backend/node',
502
- adapters: {
503
- fileSystem: {
504
- injected: true,
505
- requiredFor: ['openSession', 'saveSession'],
506
- },
507
- host: {
508
- injected: true,
509
- requiredFor: ['advisoryLockMetadata'],
510
- },
511
- },
512
- executionBoundaries: {
513
- projectSession: 'in-process-browser-safe',
514
- fileBackedSession: 'adapter-backed',
515
- artifactPublish: ARTIFACT_BRIDGE_CAPABILITY,
516
- artifactRestore: ARTIFACT_BRIDGE_CAPABILITY,
517
- },
518
- diagnostics: {
519
- stableCodes: true,
520
- errorDiagnosticMirror: true,
521
- },
522
- },
523
- compatibilityPolicy: BACKEND_COMPATIBILITY_POLICY,
524
- runtime: {
525
- sessionRuntime: true,
526
- advisoryLocking: true,
527
- coordinatedSave: true,
528
- atomicSave: false,
529
- staleRevisionProtection: true,
530
- pathPolicy: createRuntimePathPolicy(),
531
- events: {
532
- polling: true,
533
- subscriptions: false,
534
- retentionLimit: 1000,
535
- sequenceScope: 'runtime',
536
- },
537
- jobs: {
538
- inMemory: true,
539
- cooperativeCancel: true,
540
- persistent: false,
541
- supportedKinds: ['cache.refresh'],
542
- artifactJobs: false,
543
- completedRetentionLimit: 100,
544
- },
545
- cache: {
546
- derivedReadOnly: true,
547
- keyedBy: 'canonicalPathKey',
548
- sourceOfTruth: false,
549
- refreshMethod: 'refreshCache',
550
- },
551
- },
552
- };
553
- }
10
+ import { createCapabilities } from './runtime/capabilities.js';
11
+ import type {
12
+ AdvisoryLockConflictError,
13
+ ApplySessionTransactionInput,
14
+ BackendCacheEntry,
15
+ BackendCacheSnapshot,
16
+ BackendCapabilities,
17
+ BackendCapabilityUnavailableError,
18
+ BackendEvent,
19
+ BackendFileSystem,
20
+ BackendJobListSnapshot,
21
+ BackendJobNotCancellableError,
22
+ BackendJobNotFoundError,
23
+ BackendJobSnapshot,
24
+ BackendResult,
25
+ BackendRuntimeOptions,
26
+ BackendSessionSnapshot,
27
+ BackendSuccess,
28
+ CancelJobInput,
29
+ EventCursorInvalidError,
30
+ GetCacheSnapshotInput,
31
+ GetEventsInput,
32
+ GetEventsSnapshot,
33
+ GetJobInput,
34
+ InProcessLockConflictError,
35
+ ListJobsInput,
36
+ MaterializeSessionInput,
37
+ MaterializeSessionSnapshot,
38
+ MaterializeValidationFailedError,
39
+ MaterializeWriteFailedError,
40
+ OpenProjectSessionInput,
41
+ RefreshCacheInput,
42
+ SavePartialFailureError,
43
+ SaveSessionInput,
44
+ SessionNotFoundError,
45
+ SessionStaleWriteError,
46
+ UamFidelityUnsupportedError,
47
+ } from './runtime/contracts.js';
48
+
49
+ export * from './runtime/contracts.js';
554
50
 
555
51
  export class BackendRuntime {
556
52
  private readonly fileSystem?: BackendFileSystem;
@@ -628,23 +124,43 @@ export class BackendRuntime {
628
124
  return this.authoringService.applyTransaction(input);
629
125
  }
630
126
 
631
- public async saveSession(input: {
632
- sessionId: string;
633
- expectedRevision?: number;
634
- targetPath?: string;
635
- }): Promise<
127
+ public async saveSession(
128
+ input: SaveSessionInput,
129
+ ): Promise<
636
130
  BackendResult<
637
- BackendSessionSnapshot,
131
+ BackendSessionSnapshot | MaterializeSessionSnapshot,
638
132
  | SessionNotFoundError
639
133
  | SessionStaleWriteError
640
134
  | SavePartialFailureError
135
+ | UamFidelityUnsupportedError
136
+ | MaterializeValidationFailedError
137
+ | MaterializeWriteFailedError
641
138
  | PathPolicyViolationError
139
+ | InProcessLockConflictError
642
140
  | BackendCapabilityUnavailableError
643
141
  >
644
142
  > {
645
143
  return this.authoringService.saveSession(input);
646
144
  }
647
145
 
146
+ public async materializeSession(
147
+ input: MaterializeSessionInput,
148
+ ): Promise<
149
+ BackendResult<
150
+ MaterializeSessionSnapshot,
151
+ | SessionNotFoundError
152
+ | SessionStaleWriteError
153
+ | UamFidelityUnsupportedError
154
+ | MaterializeValidationFailedError
155
+ | MaterializeWriteFailedError
156
+ | PathPolicyViolationError
157
+ | InProcessLockConflictError
158
+ | BackendCapabilityUnavailableError
159
+ >
160
+ > {
161
+ return this.authoringService.materializeSession(input);
162
+ }
163
+
648
164
  public async closeSession(input: {
649
165
  sessionId: string;
650
166
  }): Promise<BackendResult<{ sessionId: string; closed: true }, SessionNotFoundError>> {