scene-capability-engine 3.5.2 → 3.6.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,594 @@
1
+ const path = require('path');
2
+ const fs = require('fs-extra');
3
+
4
+ const DEFAULT_BACKEND = 'sqlite';
5
+ const DEFAULT_DB_RELATIVE_PATH = path.join('.sce', 'state', 'sce-state.sqlite');
6
+ const SUPPORTED_BACKENDS = new Set(['sqlite']);
7
+
8
+ function normalizeString(value) {
9
+ if (typeof value !== 'string') {
10
+ return '';
11
+ }
12
+ return value.trim();
13
+ }
14
+
15
+ function normalizeInteger(value, fallback = 0) {
16
+ const parsed = Number.parseInt(`${value}`, 10);
17
+ if (!Number.isFinite(parsed) || parsed <= 0) {
18
+ return fallback;
19
+ }
20
+ return parsed;
21
+ }
22
+
23
+ function parseJsonSafe(value, fallback) {
24
+ if (typeof value !== 'string' || !value.trim()) {
25
+ return fallback;
26
+ }
27
+ try {
28
+ return JSON.parse(value);
29
+ } catch (_error) {
30
+ return fallback;
31
+ }
32
+ }
33
+
34
+ function formatSegment(value) {
35
+ const normalized = normalizeInteger(value, 0);
36
+ if (normalized <= 0) {
37
+ return '00';
38
+ }
39
+ return `${normalized}`.padStart(2, '0');
40
+ }
41
+
42
+ function buildTaskRef(sceneNo, specNo, taskNo) {
43
+ return `${formatSegment(sceneNo)}.${formatSegment(specNo)}.${formatSegment(taskNo)}`;
44
+ }
45
+
46
+ function resolveBackend(explicitBackend = '', env = process.env) {
47
+ const backendFromEnv = normalizeString(env && env.SCE_STATE_BACKEND);
48
+ const normalized = normalizeString(explicitBackend || backendFromEnv || DEFAULT_BACKEND).toLowerCase();
49
+ if (!SUPPORTED_BACKENDS.has(normalized)) {
50
+ return DEFAULT_BACKEND;
51
+ }
52
+ return normalized;
53
+ }
54
+
55
+ function loadNodeSqlite(sqliteModule) {
56
+ if (sqliteModule) {
57
+ return sqliteModule;
58
+ }
59
+ try {
60
+ return require('node:sqlite');
61
+ } catch (_error) {
62
+ return null;
63
+ }
64
+ }
65
+
66
+ class SceStateStore {
67
+ constructor(projectPath = process.cwd(), options = {}) {
68
+ this.projectPath = projectPath;
69
+ this.fileSystem = options.fileSystem || fs;
70
+ this.env = options.env || process.env;
71
+ this.backend = resolveBackend(options.backend, this.env);
72
+ this.dbPath = options.dbPath || path.join(projectPath, DEFAULT_DB_RELATIVE_PATH);
73
+ this.now = typeof options.now === 'function'
74
+ ? options.now
75
+ : () => new Date().toISOString();
76
+
77
+ const sqlite = loadNodeSqlite(options.sqliteModule);
78
+ this.DatabaseSync = sqlite && sqlite.DatabaseSync ? sqlite.DatabaseSync : null;
79
+ this._db = null;
80
+ this._ready = false;
81
+ this._memory = {
82
+ scenes: {},
83
+ specs: {},
84
+ tasks: {},
85
+ refs: {},
86
+ sequences: {
87
+ scene_next: 1,
88
+ spec_next_by_scene: {},
89
+ task_next_by_scene_spec: {}
90
+ },
91
+ events_by_job: {}
92
+ };
93
+ }
94
+
95
+ isSqliteConfigured() {
96
+ return this.backend === 'sqlite';
97
+ }
98
+
99
+ isSqliteAvailable() {
100
+ return this.isSqliteConfigured() && Boolean(this.DatabaseSync);
101
+ }
102
+
103
+ getStoreRelativePath() {
104
+ if (!this.isSqliteConfigured()) {
105
+ return null;
106
+ }
107
+ return path.relative(this.projectPath, this.dbPath).replace(/\\/g, '/');
108
+ }
109
+
110
+ async ensureReady() {
111
+ if (!this.isSqliteAvailable()) {
112
+ return false;
113
+ }
114
+ if (this._ready && this._db) {
115
+ return true;
116
+ }
117
+
118
+ await this.fileSystem.ensureDir(path.dirname(this.dbPath));
119
+ this._db = new this.DatabaseSync(this.dbPath);
120
+ this._initializeSchema();
121
+ this._ready = true;
122
+ return true;
123
+ }
124
+
125
+ _useMemoryBackend() {
126
+ if (this.isSqliteAvailable()) {
127
+ return false;
128
+ }
129
+ const memoryFallbackFlag = normalizeString(this.env && this.env.SCE_STATE_ALLOW_MEMORY_FALLBACK) === '1';
130
+ const isTestEnv = normalizeString(this.env && this.env.NODE_ENV).toLowerCase() === 'test';
131
+ return memoryFallbackFlag || isTestEnv;
132
+ }
133
+
134
+ _initializeSchema() {
135
+ this._db.exec('PRAGMA journal_mode = WAL;');
136
+ this._db.exec('PRAGMA foreign_keys = ON;');
137
+ this._db.exec(`
138
+ CREATE TABLE IF NOT EXISTS scene_registry (
139
+ scene_id TEXT PRIMARY KEY,
140
+ scene_no INTEGER NOT NULL UNIQUE,
141
+ created_at TEXT NOT NULL,
142
+ updated_at TEXT NOT NULL
143
+ );
144
+
145
+ CREATE TABLE IF NOT EXISTS spec_registry (
146
+ scene_id TEXT NOT NULL,
147
+ spec_id TEXT NOT NULL,
148
+ spec_no INTEGER NOT NULL,
149
+ created_at TEXT NOT NULL,
150
+ updated_at TEXT NOT NULL,
151
+ PRIMARY KEY (scene_id, spec_id),
152
+ UNIQUE (scene_id, spec_no),
153
+ FOREIGN KEY (scene_id) REFERENCES scene_registry(scene_id) ON DELETE CASCADE
154
+ );
155
+
156
+ CREATE TABLE IF NOT EXISTS task_ref_registry (
157
+ task_ref TEXT PRIMARY KEY,
158
+ scene_id TEXT NOT NULL,
159
+ spec_id TEXT NOT NULL,
160
+ task_key TEXT NOT NULL,
161
+ task_no INTEGER NOT NULL,
162
+ source TEXT NOT NULL,
163
+ metadata_json TEXT,
164
+ created_at TEXT NOT NULL,
165
+ updated_at TEXT NOT NULL,
166
+ UNIQUE (scene_id, spec_id, task_key),
167
+ UNIQUE (scene_id, spec_id, task_no),
168
+ FOREIGN KEY (scene_id, spec_id) REFERENCES spec_registry(scene_id, spec_id) ON DELETE CASCADE
169
+ );
170
+
171
+ CREATE TABLE IF NOT EXISTS studio_event_stream (
172
+ event_id TEXT PRIMARY KEY,
173
+ job_id TEXT NOT NULL,
174
+ event_type TEXT NOT NULL,
175
+ event_timestamp TEXT NOT NULL,
176
+ scene_id TEXT,
177
+ spec_id TEXT,
178
+ created_at TEXT NOT NULL,
179
+ raw_json TEXT NOT NULL
180
+ );
181
+
182
+ CREATE INDEX IF NOT EXISTS idx_studio_event_stream_job_ts
183
+ ON studio_event_stream(job_id, event_timestamp);
184
+ `);
185
+ }
186
+
187
+ _withTransaction(callback) {
188
+ this._db.exec('BEGIN IMMEDIATE');
189
+ try {
190
+ const result = callback();
191
+ this._db.exec('COMMIT');
192
+ return result;
193
+ } catch (error) {
194
+ try {
195
+ this._db.exec('ROLLBACK');
196
+ } catch (_rollbackError) {
197
+ // Ignore rollback failure.
198
+ }
199
+ throw error;
200
+ }
201
+ }
202
+
203
+ _ensureSceneRow(sceneId, nowIso) {
204
+ const existing = this._db
205
+ .prepare('SELECT scene_no FROM scene_registry WHERE scene_id = ?')
206
+ .get(sceneId);
207
+ if (existing && Number.isFinite(existing.scene_no)) {
208
+ this._db
209
+ .prepare('UPDATE scene_registry SET updated_at = ? WHERE scene_id = ?')
210
+ .run(nowIso, sceneId);
211
+ return Number(existing.scene_no);
212
+ }
213
+
214
+ const next = this._db
215
+ .prepare('SELECT COALESCE(MAX(scene_no), 0) + 1 AS next_no FROM scene_registry')
216
+ .get();
217
+ const sceneNo = normalizeInteger(next && next.next_no, 1);
218
+ this._db
219
+ .prepare('INSERT INTO scene_registry(scene_id, scene_no, created_at, updated_at) VALUES(?, ?, ?, ?)')
220
+ .run(sceneId, sceneNo, nowIso, nowIso);
221
+ return sceneNo;
222
+ }
223
+
224
+ _ensureSpecRow(sceneId, specId, nowIso) {
225
+ const existing = this._db
226
+ .prepare('SELECT spec_no FROM spec_registry WHERE scene_id = ? AND spec_id = ?')
227
+ .get(sceneId, specId);
228
+ if (existing && Number.isFinite(existing.spec_no)) {
229
+ this._db
230
+ .prepare('UPDATE spec_registry SET updated_at = ? WHERE scene_id = ? AND spec_id = ?')
231
+ .run(nowIso, sceneId, specId);
232
+ return Number(existing.spec_no);
233
+ }
234
+
235
+ const next = this._db
236
+ .prepare('SELECT COALESCE(MAX(spec_no), 0) + 1 AS next_no FROM spec_registry WHERE scene_id = ?')
237
+ .get(sceneId);
238
+ const specNo = normalizeInteger(next && next.next_no, 1);
239
+ this._db
240
+ .prepare('INSERT INTO spec_registry(scene_id, spec_id, spec_no, created_at, updated_at) VALUES(?, ?, ?, ?, ?)')
241
+ .run(sceneId, specId, specNo, nowIso, nowIso);
242
+ return specNo;
243
+ }
244
+
245
+ _mapTaskRefRow(row) {
246
+ if (!row) {
247
+ return null;
248
+ }
249
+
250
+ const sceneNo = normalizeInteger(row.scene_no, 0);
251
+ const specNo = normalizeInteger(row.spec_no, 0);
252
+ const taskNo = normalizeInteger(row.task_no, 0);
253
+
254
+ return {
255
+ task_ref: normalizeString(row.task_ref),
256
+ scene_id: normalizeString(row.scene_id),
257
+ spec_id: normalizeString(row.spec_id),
258
+ task_key: normalizeString(row.task_key),
259
+ scene_no: sceneNo,
260
+ spec_no: specNo,
261
+ task_no: taskNo,
262
+ source: normalizeString(row.source) || 'unknown',
263
+ metadata: parseJsonSafe(row.metadata_json, {}) || {}
264
+ };
265
+ }
266
+
267
+ async resolveOrCreateTaskRef(options = {}) {
268
+ const sceneId = normalizeString(options.sceneId);
269
+ const specId = normalizeString(options.specId);
270
+ const taskKey = normalizeString(options.taskKey);
271
+ if (!sceneId || !specId || !taskKey) {
272
+ throw new Error('sceneId/specId/taskKey are required for sqlite task ref assignment');
273
+ }
274
+
275
+ const source = normalizeString(options.source) || 'unknown';
276
+ const metadata = options.metadata && typeof options.metadata === 'object'
277
+ ? options.metadata
278
+ : {};
279
+
280
+ if (this._useMemoryBackend()) {
281
+ return this._resolveOrCreateTaskRefInMemory({
282
+ sceneId,
283
+ specId,
284
+ taskKey,
285
+ source,
286
+ metadata
287
+ });
288
+ }
289
+
290
+ if (!await this.ensureReady()) {
291
+ return null;
292
+ }
293
+
294
+ const result = this._withTransaction(() => {
295
+ const existing = this._db
296
+ .prepare(`
297
+ SELECT t.task_ref, t.scene_id, t.spec_id, t.task_key, t.task_no, t.source, t.metadata_json,
298
+ s.scene_no, p.spec_no
299
+ FROM task_ref_registry t
300
+ INNER JOIN scene_registry s ON s.scene_id = t.scene_id
301
+ INNER JOIN spec_registry p ON p.scene_id = t.scene_id AND p.spec_id = t.spec_id
302
+ WHERE t.scene_id = ? AND t.spec_id = ? AND t.task_key = ?
303
+ `)
304
+ .get(sceneId, specId, taskKey);
305
+
306
+ if (existing) {
307
+ const nowIso = this.now();
308
+ const mergedMetadata = {
309
+ ...(parseJsonSafe(existing.metadata_json, {}) || {}),
310
+ ...metadata
311
+ };
312
+ this._db
313
+ .prepare('UPDATE task_ref_registry SET source = ?, metadata_json = ?, updated_at = ? WHERE task_ref = ?')
314
+ .run(source, JSON.stringify(mergedMetadata), nowIso, existing.task_ref);
315
+
316
+ return this._db
317
+ .prepare(`
318
+ SELECT t.task_ref, t.scene_id, t.spec_id, t.task_key, t.task_no, t.source, t.metadata_json,
319
+ s.scene_no, p.spec_no
320
+ FROM task_ref_registry t
321
+ INNER JOIN scene_registry s ON s.scene_id = t.scene_id
322
+ INNER JOIN spec_registry p ON p.scene_id = t.scene_id AND p.spec_id = t.spec_id
323
+ WHERE t.task_ref = ?
324
+ `)
325
+ .get(existing.task_ref);
326
+ }
327
+
328
+ const nowIso = this.now();
329
+ const sceneNo = this._ensureSceneRow(sceneId, nowIso);
330
+ const specNo = this._ensureSpecRow(sceneId, specId, nowIso);
331
+
332
+ const nextTask = this._db
333
+ .prepare('SELECT COALESCE(MAX(task_no), 0) + 1 AS next_no FROM task_ref_registry WHERE scene_id = ? AND spec_id = ?')
334
+ .get(sceneId, specId);
335
+ const taskNo = normalizeInteger(nextTask && nextTask.next_no, 1);
336
+ const taskRef = buildTaskRef(sceneNo, specNo, taskNo);
337
+
338
+ this._db
339
+ .prepare(`
340
+ INSERT INTO task_ref_registry(task_ref, scene_id, spec_id, task_key, task_no, source, metadata_json, created_at, updated_at)
341
+ VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)
342
+ `)
343
+ .run(taskRef, sceneId, specId, taskKey, taskNo, source, JSON.stringify(metadata), nowIso, nowIso);
344
+
345
+ return this._db
346
+ .prepare(`
347
+ SELECT t.task_ref, t.scene_id, t.spec_id, t.task_key, t.task_no, t.source, t.metadata_json,
348
+ s.scene_no, p.spec_no
349
+ FROM task_ref_registry t
350
+ INNER JOIN scene_registry s ON s.scene_id = t.scene_id
351
+ INNER JOIN spec_registry p ON p.scene_id = t.scene_id AND p.spec_id = t.spec_id
352
+ WHERE t.task_ref = ?
353
+ `)
354
+ .get(taskRef);
355
+ });
356
+
357
+ return this._mapTaskRefRow(result);
358
+ }
359
+
360
+ async lookupTaskRef(taskRef) {
361
+ const normalizedTaskRef = normalizeString(taskRef);
362
+ if (!normalizedTaskRef) {
363
+ return null;
364
+ }
365
+
366
+ if (this._useMemoryBackend()) {
367
+ const row = this._memory.refs[normalizedTaskRef];
368
+ return row ? { ...row, metadata: { ...(row.metadata || {}) } } : null;
369
+ }
370
+
371
+ if (!await this.ensureReady()) {
372
+ return null;
373
+ }
374
+
375
+ const row = this._db
376
+ .prepare(`
377
+ SELECT t.task_ref, t.scene_id, t.spec_id, t.task_key, t.task_no, t.source, t.metadata_json,
378
+ s.scene_no, p.spec_no
379
+ FROM task_ref_registry t
380
+ INNER JOIN scene_registry s ON s.scene_id = t.scene_id
381
+ INNER JOIN spec_registry p ON p.scene_id = t.scene_id AND p.spec_id = t.spec_id
382
+ WHERE t.task_ref = ?
383
+ `)
384
+ .get(normalizedTaskRef);
385
+
386
+ return this._mapTaskRefRow(row);
387
+ }
388
+
389
+ async lookupTaskTuple(options = {}) {
390
+ const sceneId = normalizeString(options.sceneId);
391
+ const specId = normalizeString(options.specId);
392
+ const taskKey = normalizeString(options.taskKey);
393
+ if (!sceneId || !specId || !taskKey) {
394
+ return null;
395
+ }
396
+
397
+ if (this._useMemoryBackend()) {
398
+ const tupleKey = `${sceneId}::${specId}::${taskKey}`;
399
+ const row = this._memory.tasks[tupleKey];
400
+ return row ? { ...row, metadata: { ...(row.metadata || {}) } } : null;
401
+ }
402
+
403
+ if (!await this.ensureReady()) {
404
+ return null;
405
+ }
406
+
407
+ const row = this._db
408
+ .prepare(`
409
+ SELECT t.task_ref, t.scene_id, t.spec_id, t.task_key, t.task_no, t.source, t.metadata_json,
410
+ s.scene_no, p.spec_no
411
+ FROM task_ref_registry t
412
+ INNER JOIN scene_registry s ON s.scene_id = t.scene_id
413
+ INNER JOIN spec_registry p ON p.scene_id = t.scene_id AND p.spec_id = t.spec_id
414
+ WHERE t.scene_id = ? AND t.spec_id = ? AND t.task_key = ?
415
+ `)
416
+ .get(sceneId, specId, taskKey);
417
+
418
+ return this._mapTaskRefRow(row);
419
+ }
420
+
421
+ async appendStudioEvent(event = {}) {
422
+ const eventId = normalizeString(event.event_id);
423
+ const jobId = normalizeString(event.job_id);
424
+ const eventType = normalizeString(event.event_type);
425
+ const timestamp = normalizeString(event.timestamp) || this.now();
426
+ if (!eventId || !jobId || !eventType) {
427
+ return false;
428
+ }
429
+
430
+ if (this._useMemoryBackend()) {
431
+ if (!this._memory.events_by_job[jobId]) {
432
+ this._memory.events_by_job[jobId] = [];
433
+ }
434
+ const existingIndex = this._memory.events_by_job[jobId]
435
+ .findIndex((item) => normalizeString(item.event_id) === eventId);
436
+ const normalized = {
437
+ ...event,
438
+ event_id: eventId,
439
+ job_id: jobId,
440
+ event_type: eventType,
441
+ timestamp: timestamp
442
+ };
443
+ if (existingIndex >= 0) {
444
+ this._memory.events_by_job[jobId][existingIndex] = normalized;
445
+ } else {
446
+ this._memory.events_by_job[jobId].push(normalized);
447
+ }
448
+ this._memory.events_by_job[jobId].sort((left, right) => {
449
+ const l = Date.parse(left.timestamp || '') || 0;
450
+ const r = Date.parse(right.timestamp || '') || 0;
451
+ return l - r;
452
+ });
453
+ return true;
454
+ }
455
+
456
+ if (!await this.ensureReady()) {
457
+ return false;
458
+ }
459
+
460
+ const sceneId = normalizeString(event.scene_id) || null;
461
+ const specId = normalizeString(event.spec_id) || null;
462
+ const rawJson = JSON.stringify(event);
463
+
464
+ this._db
465
+ .prepare(`
466
+ INSERT OR REPLACE INTO studio_event_stream(event_id, job_id, event_type, event_timestamp, scene_id, spec_id, created_at, raw_json)
467
+ VALUES(?, ?, ?, ?, ?, ?, ?, ?)
468
+ `)
469
+ .run(eventId, jobId, eventType, timestamp, sceneId, specId, this.now(), rawJson);
470
+
471
+ return true;
472
+ }
473
+
474
+ async listStudioEvents(jobId, options = {}) {
475
+ const normalizedJobId = normalizeString(jobId);
476
+ if (!normalizedJobId) {
477
+ return [];
478
+ }
479
+
480
+ if (this._useMemoryBackend()) {
481
+ const events = Array.isArray(this._memory.events_by_job[normalizedJobId])
482
+ ? [...this._memory.events_by_job[normalizedJobId]]
483
+ : [];
484
+ const limit = normalizeInteger(options.limit, 50);
485
+ if (limit <= 0) {
486
+ return events;
487
+ }
488
+ return events.slice(-limit);
489
+ }
490
+
491
+ if (!await this.ensureReady()) {
492
+ return null;
493
+ }
494
+
495
+ const limit = normalizeInteger(options.limit, 50);
496
+ const query = limit > 0
497
+ ? 'SELECT raw_json FROM studio_event_stream WHERE job_id = ? ORDER BY event_timestamp DESC LIMIT ?'
498
+ : 'SELECT raw_json FROM studio_event_stream WHERE job_id = ? ORDER BY event_timestamp DESC';
499
+
500
+ const statement = this._db.prepare(query);
501
+ const rows = limit > 0
502
+ ? statement.all(normalizedJobId, limit)
503
+ : statement.all(normalizedJobId);
504
+
505
+ const events = rows
506
+ .map((row) => parseJsonSafe(row.raw_json, null))
507
+ .filter(Boolean)
508
+ .reverse();
509
+
510
+ return events;
511
+ }
512
+
513
+ _resolveOrCreateTaskRefInMemory(options = {}) {
514
+ const sceneId = normalizeString(options.sceneId);
515
+ const specId = normalizeString(options.specId);
516
+ const taskKey = normalizeString(options.taskKey);
517
+ const source = normalizeString(options.source) || 'unknown';
518
+ const metadata = options.metadata && typeof options.metadata === 'object'
519
+ ? options.metadata
520
+ : {};
521
+ const nowIso = this.now();
522
+
523
+ if (!this._memory.scenes[sceneId]) {
524
+ this._memory.scenes[sceneId] = normalizeInteger(this._memory.sequences.scene_next, 1);
525
+ this._memory.sequences.scene_next = this._memory.scenes[sceneId] + 1;
526
+ }
527
+ const sceneNo = this._memory.scenes[sceneId];
528
+
529
+ const sceneSpecKey = `${sceneId}::${specId}`;
530
+ if (!this._memory.specs[sceneSpecKey]) {
531
+ const nextSpec = normalizeInteger(this._memory.sequences.spec_next_by_scene[sceneId], 1);
532
+ this._memory.specs[sceneSpecKey] = nextSpec;
533
+ this._memory.sequences.spec_next_by_scene[sceneId] = nextSpec + 1;
534
+ }
535
+ const specNo = this._memory.specs[sceneSpecKey];
536
+
537
+ const tupleKey = `${sceneId}::${specId}::${taskKey}`;
538
+ if (this._memory.tasks[tupleKey]) {
539
+ const existing = this._memory.tasks[tupleKey];
540
+ existing.source = source;
541
+ existing.metadata = { ...(existing.metadata || {}), ...metadata };
542
+ existing.updated_at = nowIso;
543
+ this._memory.refs[existing.task_ref] = existing;
544
+ return { ...existing, metadata: { ...(existing.metadata || {}) } };
545
+ }
546
+
547
+ const nextTask = normalizeInteger(this._memory.sequences.task_next_by_scene_spec[sceneSpecKey], 1);
548
+ const taskNo = nextTask;
549
+ this._memory.sequences.task_next_by_scene_spec[sceneSpecKey] = nextTask + 1;
550
+ const taskRef = buildTaskRef(sceneNo, specNo, taskNo);
551
+
552
+ const row = {
553
+ task_ref: taskRef,
554
+ scene_id: sceneId,
555
+ spec_id: specId,
556
+ task_key: taskKey,
557
+ scene_no: sceneNo,
558
+ spec_no: specNo,
559
+ task_no: taskNo,
560
+ source,
561
+ metadata: { ...metadata },
562
+ created_at: nowIso,
563
+ updated_at: nowIso
564
+ };
565
+ this._memory.tasks[tupleKey] = row;
566
+ this._memory.refs[taskRef] = row;
567
+ return { ...row, metadata: { ...(row.metadata || {}) } };
568
+ }
569
+ }
570
+
571
+ const STORE_CACHE = new Map();
572
+
573
+ function getSceStateStore(projectPath = process.cwd(), options = {}) {
574
+ const normalizedRoot = path.resolve(projectPath);
575
+ if (options.noCache === true) {
576
+ return new SceStateStore(normalizedRoot, options);
577
+ }
578
+
579
+ if (!STORE_CACHE.has(normalizedRoot)) {
580
+ STORE_CACHE.set(normalizedRoot, new SceStateStore(normalizedRoot, options));
581
+ }
582
+
583
+ return STORE_CACHE.get(normalizedRoot);
584
+ }
585
+
586
+ module.exports = {
587
+ DEFAULT_BACKEND,
588
+ DEFAULT_DB_RELATIVE_PATH,
589
+ SceStateStore,
590
+ getSceStateStore,
591
+ resolveBackend,
592
+ buildTaskRef,
593
+ formatSegment
594
+ };