@portel/photon-core 2.17.5 → 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.
Files changed (71) hide show
  1. package/dist/audit.d.ts +4 -0
  2. package/dist/audit.d.ts.map +1 -1
  3. package/dist/audit.js +107 -36
  4. package/dist/audit.js.map +1 -1
  5. package/dist/base.d.ts +81 -12
  6. package/dist/base.d.ts.map +1 -1
  7. package/dist/base.js +80 -7
  8. package/dist/base.js.map +1 -1
  9. package/dist/compiler.d.ts.map +1 -1
  10. package/dist/compiler.js +9 -1
  11. package/dist/compiler.js.map +1 -1
  12. package/dist/config.d.ts +14 -28
  13. package/dist/config.d.ts.map +1 -1
  14. package/dist/config.js +48 -46
  15. package/dist/config.js.map +1 -1
  16. package/dist/data-paths.d.ts +115 -0
  17. package/dist/data-paths.d.ts.map +1 -0
  18. package/dist/data-paths.js +243 -0
  19. package/dist/data-paths.js.map +1 -0
  20. package/dist/dependency-manager.d.ts +1 -1
  21. package/dist/dependency-manager.d.ts.map +1 -1
  22. package/dist/dependency-manager.js +13 -5
  23. package/dist/dependency-manager.js.map +1 -1
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +3 -0
  27. package/dist/index.js.map +1 -1
  28. package/dist/instance-store.d.ts +22 -11
  29. package/dist/instance-store.d.ts.map +1 -1
  30. package/dist/instance-store.js +63 -28
  31. package/dist/instance-store.js.map +1 -1
  32. package/dist/memory.d.ts +8 -6
  33. package/dist/memory.d.ts.map +1 -1
  34. package/dist/memory.js +49 -35
  35. package/dist/memory.js.map +1 -1
  36. package/dist/mixins.d.ts.map +1 -1
  37. package/dist/mixins.js +22 -3
  38. package/dist/mixins.js.map +1 -1
  39. package/dist/path-resolver.d.ts +3 -1
  40. package/dist/path-resolver.d.ts.map +1 -1
  41. package/dist/path-resolver.js +49 -2
  42. package/dist/path-resolver.js.map +1 -1
  43. package/dist/photon-loader-lite.d.ts +2 -0
  44. package/dist/photon-loader-lite.d.ts.map +1 -1
  45. package/dist/photon-loader-lite.js +3 -3
  46. package/dist/photon-loader-lite.js.map +1 -1
  47. package/dist/schedule.d.ts.map +1 -1
  48. package/dist/schedule.js +11 -7
  49. package/dist/schedule.js.map +1 -1
  50. package/dist/schema-extractor.js +1 -1
  51. package/dist/schema-extractor.js.map +1 -1
  52. package/dist/stateful.d.ts +2 -1
  53. package/dist/stateful.d.ts.map +1 -1
  54. package/dist/stateful.js +4 -3
  55. package/dist/stateful.js.map +1 -1
  56. package/package.json +1 -1
  57. package/src/audit.ts +111 -38
  58. package/src/base.ts +117 -19
  59. package/src/compiler.ts +10 -1
  60. package/src/config.ts +59 -46
  61. package/src/data-paths.ts +289 -0
  62. package/src/dependency-manager.ts +13 -5
  63. package/src/index.ts +4 -0
  64. package/src/instance-store.ts +70 -30
  65. package/src/memory.ts +60 -38
  66. package/src/mixins.ts +24 -3
  67. package/src/path-resolver.ts +52 -2
  68. package/src/photon-loader-lite.ts +5 -3
  69. package/src/schedule.ts +11 -7
  70. package/src/schema-extractor.ts +1 -1
  71. 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
- // Store dependencies in ~/.cache/photon-mcp/dependencies/
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';
@@ -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 (matching daemon convention):
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 → { current: "name" }
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
- import * as os from 'os';
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 baseDir: string;
37
+ private namespace: string;
38
+ private baseDir?: string;
29
39
 
30
40
  constructor(photonName: string, options?: InstanceStoreOptions) {
31
41
  this.photonName = photonName;
32
- this.baseDir = getBaseDir(options);
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.baseDir, 'state', this.photonName);
50
+ return path.join(getPhotonDataDir(this.namespace, this.photonName, this.baseDir), 'state');
40
51
  }
41
52
 
42
53
  /**
43
- * Get the context file path for this photon
54
+ * Get the context file path, with fallback to legacy
44
55
  */
45
56
  private contextPath(): string {
46
- return path.join(this.baseDir, 'context', `${this.photonName}.json`);
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
- const files = await fs.readdir(this.stateDir());
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.contextPath();
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 = InstanceStore.statePath(this.photonName, name, this.baseDir);
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 = InstanceStore.statePath(this.photonName, instanceName, this.baseDir);
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 = InstanceStore.statePath(this.photonName, instanceName, this.baseDir);
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 = InstanceStore.statePath(this.photonName, name, this.baseDir);
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
- const dir = baseDir || getBaseDir();
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
- const dir = baseDir || getBaseDir();
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
  }