@portel/photon-core 2.17.6 → 2.18.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.
- package/dist/audit.d.ts +4 -0
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js +107 -36
- package/dist/audit.js.map +1 -1
- package/dist/base.d.ts +81 -12
- package/dist/base.d.ts.map +1 -1
- package/dist/base.js +80 -7
- package/dist/base.js.map +1 -1
- package/dist/compiler.d.ts.map +1 -1
- package/dist/compiler.js +9 -1
- package/dist/compiler.js.map +1 -1
- package/dist/config.d.ts +14 -28
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +48 -46
- package/dist/config.js.map +1 -1
- package/dist/data-paths.d.ts +115 -0
- package/dist/data-paths.d.ts.map +1 -0
- package/dist/data-paths.js +243 -0
- package/dist/data-paths.js.map +1 -0
- package/dist/dependency-manager.d.ts +1 -1
- package/dist/dependency-manager.d.ts.map +1 -1
- package/dist/dependency-manager.js +13 -5
- package/dist/dependency-manager.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/instance-store.d.ts +22 -11
- package/dist/instance-store.d.ts.map +1 -1
- package/dist/instance-store.js +63 -28
- package/dist/instance-store.js.map +1 -1
- package/dist/memory.d.ts +8 -6
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +49 -35
- package/dist/memory.js.map +1 -1
- package/dist/mixins.d.ts.map +1 -1
- package/dist/mixins.js +22 -3
- package/dist/mixins.js.map +1 -1
- package/dist/path-resolver.d.ts +3 -1
- package/dist/path-resolver.d.ts.map +1 -1
- package/dist/path-resolver.js +49 -2
- package/dist/path-resolver.js.map +1 -1
- package/dist/photon-loader-lite.d.ts +2 -0
- package/dist/photon-loader-lite.d.ts.map +1 -1
- package/dist/photon-loader-lite.js +3 -3
- package/dist/photon-loader-lite.js.map +1 -1
- package/dist/schedule.d.ts.map +1 -1
- package/dist/schedule.js +11 -7
- package/dist/schedule.js.map +1 -1
- package/dist/schema-extractor.js +1 -1
- package/dist/schema-extractor.js.map +1 -1
- package/dist/stateful.d.ts +2 -1
- package/dist/stateful.d.ts.map +1 -1
- package/dist/stateful.js +4 -3
- package/dist/stateful.js.map +1 -1
- package/package.json +1 -1
- package/src/audit.ts +111 -38
- package/src/base.ts +117 -19
- package/src/compiler.ts +10 -1
- package/src/config.ts +59 -46
- package/src/data-paths.ts +289 -0
- package/src/dependency-manager.ts +13 -5
- package/src/index.ts +4 -0
- package/src/instance-store.ts +70 -30
- package/src/memory.ts +60 -38
- package/src/mixins.ts +24 -3
- package/src/path-resolver.ts +52 -2
- package/src/photon-loader-lite.ts +5 -3
- package/src/schedule.ts +11 -7
- package/src/schema-extractor.ts +1 -1
- package/src/stateful.ts +5 -2
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Central Data Path Resolver
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for ALL runtime data paths in photon.
|
|
5
|
+
* Everything lives under `.data/` inside the photon base directory.
|
|
6
|
+
*
|
|
7
|
+
* Structure:
|
|
8
|
+
* {baseDir}/.data/
|
|
9
|
+
* # Global
|
|
10
|
+
* daemon.sock / .pid / .log
|
|
11
|
+
* audit.jsonl
|
|
12
|
+
* .metadata.json
|
|
13
|
+
* .cache/
|
|
14
|
+
* _global/ ← global shared memory
|
|
15
|
+
* _sessions/{sessionId}/{ns}/{photon}/ ← session-scoped memory
|
|
16
|
+
* tasks/ ← ephemeral async task state
|
|
17
|
+
*
|
|
18
|
+
* # Per-photon (always namespaced)
|
|
19
|
+
* {namespace}/{photon-name}/
|
|
20
|
+
* state/{instance}/state.json + state.log
|
|
21
|
+
* memory/
|
|
22
|
+
* env.json
|
|
23
|
+
* context.json
|
|
24
|
+
* runs/
|
|
25
|
+
* logs/
|
|
26
|
+
* schedules/
|
|
27
|
+
* config.json
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import * as path from 'path';
|
|
31
|
+
import * as os from 'os';
|
|
32
|
+
import { execSync } from 'child_process';
|
|
33
|
+
|
|
34
|
+
const DEFAULT_BASE = path.join(os.homedir(), '.photon');
|
|
35
|
+
|
|
36
|
+
function getBase(baseDir?: string): string {
|
|
37
|
+
return baseDir || process.env.PHOTON_DIR || DEFAULT_BASE;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── Root ─────────────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
/** Root of all runtime data: {baseDir}/.data/ */
|
|
43
|
+
export function getDataRoot(baseDir?: string): string {
|
|
44
|
+
return path.join(getBase(baseDir), '.data');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ── Per-Photon ───────────────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
/** Per-photon data root: .data/{photonName}/ for local, .data/{namespace}/{photonName}/ for marketplace */
|
|
50
|
+
export function getPhotonDataDir(namespace: string, photonName: string, baseDir?: string): string {
|
|
51
|
+
if (!namespace || namespace === 'local') {
|
|
52
|
+
return path.join(getDataRoot(baseDir), photonName);
|
|
53
|
+
}
|
|
54
|
+
return path.join(getDataRoot(baseDir), namespace, photonName);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Instance state file: .data/{ns}/{name}/state/{instance}/state.json */
|
|
58
|
+
export function getPhotonStatePath(namespace: string, photonName: string, instance: string, baseDir?: string): string {
|
|
59
|
+
return path.join(getPhotonDataDir(namespace, photonName, baseDir), 'state', instance || 'default', 'state.json');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Instance event log: .data/{ns}/{name}/state/{instance}/state.log */
|
|
63
|
+
export function getPhotonStateLogPath(namespace: string, photonName: string, instance: string, baseDir?: string): string {
|
|
64
|
+
return path.join(getPhotonDataDir(namespace, photonName, baseDir), 'state', instance || 'default', 'state.log');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Per-photon memory directory: .data/{ns}/{name}/memory/ */
|
|
68
|
+
export function getPhotonMemoryDir(namespace: string, photonName: string, baseDir?: string): string {
|
|
69
|
+
return path.join(getPhotonDataDir(namespace, photonName, baseDir), 'memory');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Per-photon env vars: .data/{ns}/{name}/env.json */
|
|
73
|
+
export function getPhotonEnvPath(namespace: string, photonName: string, baseDir?: string): string {
|
|
74
|
+
return path.join(getPhotonDataDir(namespace, photonName, baseDir), 'env.json');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Current instance selector: .data/{ns}/{name}/context.json */
|
|
78
|
+
export function getPhotonContextPath(namespace: string, photonName: string, baseDir?: string): string {
|
|
79
|
+
return path.join(getPhotonDataDir(namespace, photonName, baseDir), 'context.json');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Workflow checkpoint runs: .data/{ns}/{name}/runs/ */
|
|
83
|
+
export function getPhotonRunsDir(namespace: string, photonName: string, baseDir?: string): string {
|
|
84
|
+
return path.join(getPhotonDataDir(namespace, photonName, baseDir), 'runs');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Execution audit logs: .data/{ns}/{name}/logs/ */
|
|
88
|
+
export function getPhotonLogsDir(namespace: string, photonName: string, baseDir?: string): string {
|
|
89
|
+
return path.join(getPhotonDataDir(namespace, photonName, baseDir), 'logs');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Schedule definitions: .data/{ns}/{name}/schedules/ */
|
|
93
|
+
export function getPhotonSchedulesDir(namespace: string, photonName: string, baseDir?: string): string {
|
|
94
|
+
return path.join(getPhotonDataDir(namespace, photonName, baseDir), 'schedules');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Per-photon config: .data/{ns}/{name}/config.json */
|
|
98
|
+
export function getPhotonConfigPath(namespace: string, photonName: string, baseDir?: string): string {
|
|
99
|
+
return path.join(getPhotonDataDir(namespace, photonName, baseDir), 'config.json');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ── Global ───────────────────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
/** Global shared memory: .data/_global/ */
|
|
105
|
+
export function getGlobalMemoryDir(baseDir?: string): string {
|
|
106
|
+
return path.join(getDataRoot(baseDir), '_global');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Session-scoped memory: .data/_sessions/{sessionId}/{photon}/ or .data/_sessions/{sessionId}/{ns}/{photon}/ */
|
|
110
|
+
export function getSessionMemoryDir(sessionId: string, namespace: string, photonName: string, baseDir?: string): string {
|
|
111
|
+
const safeSession = sessionId.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
112
|
+
if (!namespace || namespace === 'local') {
|
|
113
|
+
return path.join(getDataRoot(baseDir), '_sessions', safeSession, photonName);
|
|
114
|
+
}
|
|
115
|
+
return path.join(getDataRoot(baseDir), '_sessions', safeSession, namespace, photonName);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Compilation & marketplace cache: .data/.cache/ */
|
|
119
|
+
export function getCacheDir(baseDir?: string): string {
|
|
120
|
+
return path.join(getDataRoot(baseDir), '.cache');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Ephemeral async task state: .data/tasks/ */
|
|
124
|
+
export function getTasksDir(baseDir?: string): string {
|
|
125
|
+
return path.join(getDataRoot(baseDir), 'tasks');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Global audit log: .data/audit.jsonl */
|
|
129
|
+
export function getAuditPath(baseDir?: string): string {
|
|
130
|
+
return path.join(getDataRoot(baseDir), 'audit.jsonl');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Marketplace install metadata: .data/.metadata.json */
|
|
134
|
+
export function getMetadataPath(baseDir?: string): string {
|
|
135
|
+
return path.join(getDataRoot(baseDir), '.metadata.json');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ── Daemon (always global ~/.photon/.data/) ──────────────────────────────────
|
|
139
|
+
|
|
140
|
+
/** Daemon socket: always ~/.photon/.data/daemon.sock (global, one per user) */
|
|
141
|
+
export function getDaemonSocketPath(): string {
|
|
142
|
+
if (process.platform === 'win32') {
|
|
143
|
+
return '\\\\.\\pipe\\photon-daemon';
|
|
144
|
+
}
|
|
145
|
+
return path.join(DEFAULT_BASE, '.data', 'daemon.sock');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Daemon PID file: always ~/.photon/.data/daemon.pid */
|
|
149
|
+
export function getDaemonPidPath(): string {
|
|
150
|
+
return path.join(DEFAULT_BASE, '.data', 'daemon.pid');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Daemon log file: always ~/.photon/.data/daemon.log */
|
|
154
|
+
export function getDaemonLogPath(): string {
|
|
155
|
+
return path.join(DEFAULT_BASE, '.data', 'daemon.log');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ── Namespace Detection ──────────────────────────────────────────────────────
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Detect the namespace for a photon directory by reading git remote origin.
|
|
162
|
+
* Returns the owner/org from the remote URL, or '' if not a git repo.
|
|
163
|
+
*
|
|
164
|
+
* Examples:
|
|
165
|
+
* git@github.com:portel-dev/photons.git → 'portel-dev'
|
|
166
|
+
* https://github.com/arul-kumar/my-photons → 'arul-kumar'
|
|
167
|
+
* (no git remote) → ''
|
|
168
|
+
*/
|
|
169
|
+
export function detectNamespace(dir: string): string {
|
|
170
|
+
try {
|
|
171
|
+
const remote = execSync('git remote get-url origin', {
|
|
172
|
+
cwd: dir,
|
|
173
|
+
encoding: 'utf-8',
|
|
174
|
+
timeout: 3000,
|
|
175
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
176
|
+
}).trim();
|
|
177
|
+
|
|
178
|
+
// SSH: git@github.com:owner/repo.git
|
|
179
|
+
const sshMatch = remote.match(/git@[^:]+:([^/]+)\//);
|
|
180
|
+
if (sshMatch) return sshMatch[1];
|
|
181
|
+
|
|
182
|
+
// HTTPS: https://github.com/owner/repo[.git]
|
|
183
|
+
const httpsMatch = remote.match(/https?:\/\/[^/]+\/([^/]+)\//);
|
|
184
|
+
if (httpsMatch) return httpsMatch[1];
|
|
185
|
+
} catch {
|
|
186
|
+
// Not a git repo or no remote — expected
|
|
187
|
+
}
|
|
188
|
+
return '';
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ── Legacy Path Helpers (for migration fallback) ─────────────────────────────
|
|
192
|
+
|
|
193
|
+
/** Old state path: {baseDir}/state/{photonName}/{instance}.json */
|
|
194
|
+
export function getLegacyStatePath(photonName: string, instance: string, baseDir?: string): string {
|
|
195
|
+
return path.join(getBase(baseDir), 'state', photonName, `${instance || 'default'}.json`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Old state log: {baseDir}/state/{photonName}/{instance}.log */
|
|
199
|
+
export function getLegacyStateLogPath(photonName: string, instance: string, baseDir?: string): string {
|
|
200
|
+
return path.join(getBase(baseDir), 'state', photonName, `${instance || 'default'}.log`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Old context path: {baseDir}/context/{photonName}.json */
|
|
204
|
+
export function getLegacyContextPath(photonName: string, baseDir?: string): string {
|
|
205
|
+
return path.join(getBase(baseDir), 'context', `${photonName}.json`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Old env path: {baseDir}/env/{photonName}.json */
|
|
209
|
+
export function getLegacyEnvPath(photonName: string, baseDir?: string): string {
|
|
210
|
+
return path.join(getBase(baseDir), 'env', `${photonName}.json`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Old memory dir: {baseDir}/data/{photonId}/ */
|
|
214
|
+
export function getLegacyMemoryDir(photonName: string, baseDir?: string): string {
|
|
215
|
+
const safeName = photonName.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
216
|
+
return path.join(getBase(baseDir), 'data', safeName);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Old global memory: {baseDir}/data/_global/ */
|
|
220
|
+
export function getLegacyGlobalMemoryDir(baseDir?: string): string {
|
|
221
|
+
return path.join(getBase(baseDir), 'data', '_global');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Old session memory: {baseDir}/sessions/{sessionId}/{photonId}/ */
|
|
225
|
+
export function getLegacySessionMemoryDir(sessionId: string, photonName: string, baseDir?: string): string {
|
|
226
|
+
const safeName = photonName.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
227
|
+
const safeSession = sessionId.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
228
|
+
return path.join(getBase(baseDir), 'sessions', safeSession, safeName);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** Old runs dir: ~/.photon/runs/ */
|
|
232
|
+
export function getLegacyRunsDir(): string {
|
|
233
|
+
return path.join(DEFAULT_BASE, 'runs');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Old logs dir: ~/.photon/logs/{photonId}/ */
|
|
237
|
+
export function getLegacyLogsDir(photonName: string): string {
|
|
238
|
+
return path.join(DEFAULT_BASE, 'logs', photonName);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Old tasks dir: ~/.photon/tasks/ */
|
|
242
|
+
export function getLegacyTasksDir(): string {
|
|
243
|
+
return path.join(DEFAULT_BASE, 'tasks');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Old audit path: ~/.photon/audit.jsonl */
|
|
247
|
+
export function getLegacyAuditPath(): string {
|
|
248
|
+
return path.join(DEFAULT_BASE, 'audit.jsonl');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** Old metadata path: {baseDir}/.metadata.json */
|
|
252
|
+
export function getLegacyMetadataPath(baseDir?: string): string {
|
|
253
|
+
return path.join(getBase(baseDir), '.metadata.json');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Old daemon socket: ~/.photon/daemon.sock */
|
|
257
|
+
export function getLegacyDaemonSocketPath(): string {
|
|
258
|
+
if (process.platform === 'win32') {
|
|
259
|
+
return '\\\\.\\pipe\\photon-daemon';
|
|
260
|
+
}
|
|
261
|
+
return path.join(DEFAULT_BASE, 'daemon.sock');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Old daemon PID: ~/.photon/daemon.pid */
|
|
265
|
+
export function getLegacyDaemonPidPath(): string {
|
|
266
|
+
return path.join(DEFAULT_BASE, 'daemon.pid');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** Old daemon log: ~/.photon/daemon.log */
|
|
270
|
+
export function getLegacyDaemonLogPath(): string {
|
|
271
|
+
return path.join(DEFAULT_BASE, 'daemon.log');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Old cache dir: {baseDir}/.cache/ or {baseDir}/cache/ */
|
|
275
|
+
export function getLegacyCacheDir(baseDir?: string): string {
|
|
276
|
+
return path.join(getBase(baseDir), '.cache');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Old schedules dir: ~/.photon/schedules/{photonName}/ */
|
|
280
|
+
export function getLegacySchedulesDir(photonName: string): string {
|
|
281
|
+
const safeName = photonName.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
282
|
+
return path.join(DEFAULT_BASE, 'schedules', safeName);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Old per-photon config: ~/.photon/{photonName}/config.json */
|
|
286
|
+
export function getLegacyPhotonConfigPath(photonName: string): string {
|
|
287
|
+
const safeName = photonName.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
288
|
+
return path.join(DEFAULT_BASE, safeName, 'config.json');
|
|
289
|
+
}
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
|
|
8
8
|
import * as fs from 'fs/promises';
|
|
9
9
|
import * as path from 'path';
|
|
10
|
-
import * as os from 'os';
|
|
11
10
|
import { spawn } from 'child_process';
|
|
12
11
|
import { fileURLToPath } from 'url';
|
|
12
|
+
import { getCacheDir } from './data-paths.js';
|
|
13
13
|
|
|
14
14
|
interface DependencySpec {
|
|
15
15
|
name: string;
|
|
@@ -22,9 +22,8 @@ interface DependencySpec {
|
|
|
22
22
|
export class DependencyManager {
|
|
23
23
|
private cacheDir: string;
|
|
24
24
|
|
|
25
|
-
constructor() {
|
|
26
|
-
|
|
27
|
-
this.cacheDir = path.join(os.homedir(), '.cache', 'photon-mcp', 'dependencies');
|
|
25
|
+
constructor(cacheDir?: string) {
|
|
26
|
+
this.cacheDir = cacheDir || path.join(getCacheDir(), 'dependencies');
|
|
28
27
|
}
|
|
29
28
|
|
|
30
29
|
/**
|
|
@@ -155,10 +154,19 @@ export class DependencyManager {
|
|
|
155
154
|
}
|
|
156
155
|
}
|
|
157
156
|
|
|
158
|
-
// Check if node_modules exists
|
|
157
|
+
// Check if node_modules exists and has actual packages
|
|
159
158
|
const nodeModules = path.join(mcpDir, 'node_modules');
|
|
160
159
|
await fs.access(nodeModules);
|
|
161
160
|
|
|
161
|
+
// Verify at least one expected dependency is present (catches empty dirs from failed installs)
|
|
162
|
+
for (const dep of dependencies) {
|
|
163
|
+
try {
|
|
164
|
+
await fs.access(path.join(nodeModules, dep.name));
|
|
165
|
+
} catch {
|
|
166
|
+
return false; // Expected package missing — reinstall
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
162
170
|
return true;
|
|
163
171
|
} catch {
|
|
164
172
|
return false;
|
package/src/index.ts
CHANGED
|
@@ -583,3 +583,7 @@ export {
|
|
|
583
583
|
type FieldType as FormFieldType,
|
|
584
584
|
type FormOptions,
|
|
585
585
|
} from './ui-types/index.js';
|
|
586
|
+
|
|
587
|
+
// ===== DATA PATHS =====
|
|
588
|
+
// Central data path resolver — single source of truth for all runtime data locations
|
|
589
|
+
export * from './data-paths.js';
|
package/src/instance-store.ts
CHANGED
|
@@ -4,46 +4,75 @@
|
|
|
4
4
|
* Shared state persistence for named photon instances.
|
|
5
5
|
* Used by daemon, NCP, and Lumina to manage per-instance state.
|
|
6
6
|
*
|
|
7
|
-
* Paths (
|
|
7
|
+
* Paths (new .data/ layout):
|
|
8
|
+
* - State: .data/{namespace}/{photonName}/state/{instance}/state.json
|
|
9
|
+
* - Context: .data/{namespace}/{photonName}/context.json
|
|
10
|
+
*
|
|
11
|
+
* Falls back to legacy paths for migration:
|
|
8
12
|
* - State: ~/.photon/state/{photonName}/{instanceName}.json
|
|
9
|
-
* - Context: ~/.photon/context/{photonName}.json
|
|
13
|
+
* - Context: ~/.photon/context/{photonName}.json
|
|
10
14
|
*/
|
|
11
15
|
|
|
12
16
|
import * as fs from 'fs/promises';
|
|
13
17
|
import * as fsSync from 'fs';
|
|
14
18
|
import * as path from 'path';
|
|
15
|
-
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
getPhotonStatePath,
|
|
22
|
+
getPhotonContextPath,
|
|
23
|
+
getPhotonDataDir,
|
|
24
|
+
getLegacyStatePath,
|
|
25
|
+
getLegacyContextPath,
|
|
26
|
+
} from './data-paths.js';
|
|
16
27
|
|
|
17
28
|
export interface InstanceStoreOptions {
|
|
18
29
|
/** Base directory (default: ~/.photon) */
|
|
19
30
|
baseDir?: string;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
function getBaseDir(options?: InstanceStoreOptions): string {
|
|
23
|
-
return options?.baseDir || process.env.PHOTON_DIR || path.join(os.homedir(), '.photon');
|
|
31
|
+
/** Namespace (default: 'local') */
|
|
32
|
+
namespace?: string;
|
|
24
33
|
}
|
|
25
34
|
|
|
26
35
|
export class InstanceStore {
|
|
27
36
|
private photonName: string;
|
|
28
|
-
private
|
|
37
|
+
private namespace: string;
|
|
38
|
+
private baseDir?: string;
|
|
29
39
|
|
|
30
40
|
constructor(photonName: string, options?: InstanceStoreOptions) {
|
|
31
41
|
this.photonName = photonName;
|
|
32
|
-
this.
|
|
42
|
+
this.namespace = options?.namespace || 'local';
|
|
43
|
+
this.baseDir = options?.baseDir;
|
|
33
44
|
}
|
|
34
45
|
|
|
35
46
|
/**
|
|
36
|
-
* Get the state directory for this photon
|
|
47
|
+
* Get the state directory for this photon (new layout)
|
|
37
48
|
*/
|
|
38
49
|
private stateDir(): string {
|
|
39
|
-
return path.join(this.
|
|
50
|
+
return path.join(getPhotonDataDir(this.namespace, this.photonName, this.baseDir), 'state');
|
|
40
51
|
}
|
|
41
52
|
|
|
42
53
|
/**
|
|
43
|
-
* Get the context file path
|
|
54
|
+
* Get the context file path, with fallback to legacy
|
|
44
55
|
*/
|
|
45
56
|
private contextPath(): string {
|
|
46
|
-
|
|
57
|
+
const newPath = getPhotonContextPath(this.namespace, this.photonName, this.baseDir);
|
|
58
|
+
if (!fsSync.existsSync(newPath)) {
|
|
59
|
+
const legacyPath = getLegacyContextPath(this.photonName, this.baseDir);
|
|
60
|
+
if (fsSync.existsSync(legacyPath)) return legacyPath;
|
|
61
|
+
}
|
|
62
|
+
return newPath;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Resolve state file path with fallback to legacy
|
|
67
|
+
*/
|
|
68
|
+
private resolveStatePath(instanceName: string): string {
|
|
69
|
+
const name = instanceName || 'default';
|
|
70
|
+
const newPath = getPhotonStatePath(this.namespace, this.photonName, name, this.baseDir);
|
|
71
|
+
if (!fsSync.existsSync(newPath)) {
|
|
72
|
+
const legacyPath = getLegacyStatePath(this.photonName, name, this.baseDir);
|
|
73
|
+
if (fsSync.existsSync(legacyPath)) return legacyPath;
|
|
74
|
+
}
|
|
75
|
+
return newPath;
|
|
47
76
|
}
|
|
48
77
|
|
|
49
78
|
/**
|
|
@@ -51,7 +80,21 @@ export class InstanceStore {
|
|
|
51
80
|
*/
|
|
52
81
|
async list(): Promise<string[]> {
|
|
53
82
|
try {
|
|
54
|
-
|
|
83
|
+
// New layout: state/{instance}/ directories
|
|
84
|
+
const dir = this.stateDir();
|
|
85
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
86
|
+
const instances = entries
|
|
87
|
+
.filter((e) => e.isDirectory())
|
|
88
|
+
.map((e) => e.name);
|
|
89
|
+
if (instances.length > 0) return instances;
|
|
90
|
+
} catch {
|
|
91
|
+
// Fall through to legacy
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Legacy: state/{photonName}/*.json files
|
|
95
|
+
try {
|
|
96
|
+
const legacyDir = path.dirname(getLegacyStatePath(this.photonName, 'default', this.baseDir));
|
|
97
|
+
const files = await fs.readdir(legacyDir);
|
|
55
98
|
return files
|
|
56
99
|
.filter((f) => f.endsWith('.json'))
|
|
57
100
|
.map((f) => f.slice(0, -5));
|
|
@@ -76,10 +119,10 @@ export class InstanceStore {
|
|
|
76
119
|
}
|
|
77
120
|
|
|
78
121
|
/**
|
|
79
|
-
* Set the current instance name
|
|
122
|
+
* Set the current instance name (always writes to new path)
|
|
80
123
|
*/
|
|
81
124
|
async setCurrent(instanceName: string): Promise<void> {
|
|
82
|
-
const filePath = this.
|
|
125
|
+
const filePath = getPhotonContextPath(this.namespace, this.photonName, this.baseDir);
|
|
83
126
|
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
84
127
|
await fs.writeFile(filePath, JSON.stringify({ current: instanceName }, null, 2));
|
|
85
128
|
}
|
|
@@ -89,7 +132,7 @@ export class InstanceStore {
|
|
|
89
132
|
*/
|
|
90
133
|
async load<T = Record<string, unknown>>(instanceName?: string): Promise<T | null> {
|
|
91
134
|
const name = instanceName ?? await this.getCurrent();
|
|
92
|
-
const filePath =
|
|
135
|
+
const filePath = this.resolveStatePath(name);
|
|
93
136
|
try {
|
|
94
137
|
const content = await fs.readFile(filePath, 'utf-8');
|
|
95
138
|
return JSON.parse(content) as T;
|
|
@@ -100,10 +143,10 @@ export class InstanceStore {
|
|
|
100
143
|
}
|
|
101
144
|
|
|
102
145
|
/**
|
|
103
|
-
* Save state for an instance
|
|
146
|
+
* Save state for an instance (always writes to new path)
|
|
104
147
|
*/
|
|
105
148
|
async save(instanceName: string, state: Record<string, unknown>): Promise<void> {
|
|
106
|
-
const filePath =
|
|
149
|
+
const filePath = getPhotonStatePath(this.namespace, this.photonName, instanceName, this.baseDir);
|
|
107
150
|
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
108
151
|
await fs.writeFile(filePath, JSON.stringify(state, null, 2));
|
|
109
152
|
}
|
|
@@ -112,7 +155,7 @@ export class InstanceStore {
|
|
|
112
155
|
* Delete an instance's state
|
|
113
156
|
*/
|
|
114
157
|
async delete(instanceName: string): Promise<boolean> {
|
|
115
|
-
const filePath =
|
|
158
|
+
const filePath = this.resolveStatePath(instanceName);
|
|
116
159
|
try {
|
|
117
160
|
await fs.unlink(filePath);
|
|
118
161
|
return true;
|
|
@@ -127,7 +170,7 @@ export class InstanceStore {
|
|
|
127
170
|
*/
|
|
128
171
|
async exists(instanceName?: string): Promise<boolean> {
|
|
129
172
|
const name = instanceName ?? await this.getCurrent();
|
|
130
|
-
const filePath =
|
|
173
|
+
const filePath = this.resolveStatePath(name);
|
|
131
174
|
try {
|
|
132
175
|
await fs.access(filePath);
|
|
133
176
|
return true;
|
|
@@ -137,19 +180,16 @@ export class InstanceStore {
|
|
|
137
180
|
}
|
|
138
181
|
|
|
139
182
|
/**
|
|
140
|
-
* Get the file path for instance state
|
|
183
|
+
* Get the file path for instance state (new .data/ layout)
|
|
141
184
|
*/
|
|
142
|
-
static statePath(photonName: string, instanceName: string, baseDir?: string): string {
|
|
143
|
-
|
|
144
|
-
const name = instanceName || 'default';
|
|
145
|
-
return path.join(dir, 'state', photonName, `${name}.json`);
|
|
185
|
+
static statePath(photonName: string, instanceName: string, baseDir?: string, namespace?: string): string {
|
|
186
|
+
return getPhotonStatePath(namespace || 'local', photonName, instanceName, baseDir);
|
|
146
187
|
}
|
|
147
188
|
|
|
148
189
|
/**
|
|
149
|
-
* Get the context file path for a photon
|
|
190
|
+
* Get the context file path for a photon (new .data/ layout)
|
|
150
191
|
*/
|
|
151
|
-
static contextPath(photonName: string, baseDir?: string): string {
|
|
152
|
-
|
|
153
|
-
return path.join(dir, 'context', `${photonName}.json`);
|
|
192
|
+
static contextPath(photonName: string, baseDir?: string, namespace?: string): string {
|
|
193
|
+
return getPhotonContextPath(namespace || 'local', photonName, baseDir);
|
|
154
194
|
}
|
|
155
195
|
}
|