@portel/photon-core 2.19.1 → 2.19.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.
package/dist/memory.d.ts CHANGED
@@ -35,7 +35,13 @@ export declare class MemoryProvider {
35
35
  private _namespace;
36
36
  private _sessionId?;
37
37
  private _baseDir?;
38
+ private _locks;
38
39
  constructor(photonId: string, sessionId?: string, namespace?: string, baseDir?: string);
40
+ /**
41
+ * Serialize file operations per key to prevent concurrent write corruption.
42
+ * Reads also go through the lock to avoid reading a partially-written file.
43
+ */
44
+ private withKeyLock;
39
45
  /**
40
46
  * Current session ID (can be updated by the runtime)
41
47
  */
@@ -91,10 +97,8 @@ export declare class MemoryProvider {
91
97
  */
92
98
  getAll<T = any>(scope?: MemoryScope): Promise<Record<string, T>>;
93
99
  /**
94
- * Update a value with read-modify-write
95
- *
96
- * Note: Not truly atomic under concurrent access. For concurrent
97
- * writes, use distributed locking via `withLock()`.
100
+ * Atomic read-modify-write for a key.
101
+ * Serialized per key so concurrent updates don't corrupt data.
98
102
  *
99
103
  * @param key The key to update
100
104
  * @param updater Function that receives current value and returns new value
@@ -1 +1 @@
1
- {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../src/memory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAeH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;AAmE1D;;;;;GAKG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,UAAU,CAAC,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAC,CAAS;gBAEd,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM;IAOtF;;OAEG;IACH,IAAI,SAAS,IAAI,MAAM,GAAG,SAAS,CAElC;IAED,IAAI,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,EAEnC;IAED;;;;;;OAMG;IACG,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,GAAE,WAAsB,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAajF;;;;;;OAMG;IACG,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,GAAE,WAAsB,GAAG,OAAO,CAAC,IAAI,CAAC;IAWvF;;;;;;OAMG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,GAAE,WAAsB,GAAG,OAAO,CAAC,OAAO,CAAC;IAa1E;;;;;OAKG;IACG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,GAAE,WAAsB,GAAG,OAAO,CAAC,OAAO,CAAC;IAKvE;;;;OAIG;IACG,IAAI,CAAC,KAAK,GAAE,WAAsB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAc5D;;;;OAIG;IACG,KAAK,CAAC,KAAK,GAAE,WAAsB,GAAG,OAAO,CAAC,IAAI,CAAC;IAazD;;;;OAIG;IACG,MAAM,CAAC,CAAC,GAAG,GAAG,EAAE,KAAK,GAAE,WAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAchF;;;;;;;;;OASG;IACG,MAAM,CAAC,CAAC,GAAG,GAAG,EAClB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,KAAK,CAAC,EACjC,KAAK,GAAE,WAAsB,GAC5B,OAAO,CAAC,CAAC,CAAC;CAMd"}
1
+ {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../src/memory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAeH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;AAmE1D;;;;;GAKG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,UAAU,CAAC,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAC,CAAS;IAC1B,OAAO,CAAC,MAAM,CAAoC;gBAEtC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM;IAOtF;;;OAGG;YACW,WAAW;IAiBzB;;OAEG;IACH,IAAI,SAAS,IAAI,MAAM,GAAG,SAAS,CAElC;IAED,IAAI,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,EAEnC;IAED;;;;;;OAMG;IACG,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,GAAE,WAAsB,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAejF;;;;;;OAMG;IACG,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,GAAE,WAAsB,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBvF;;;;;;OAMG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,GAAE,WAAsB,GAAG,OAAO,CAAC,OAAO,CAAC;IAe1E;;;;;OAKG;IACG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,GAAE,WAAsB,GAAG,OAAO,CAAC,OAAO,CAAC;IAKvE;;;;OAIG;IACG,IAAI,CAAC,KAAK,GAAE,WAAsB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAc5D;;;;OAIG;IACG,KAAK,CAAC,KAAK,GAAE,WAAsB,GAAG,OAAO,CAAC,IAAI,CAAC;IAazD;;;;OAIG;IACG,MAAM,CAAC,CAAC,GAAG,GAAG,EAAE,KAAK,GAAE,WAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAchF;;;;;;;OAOG;IACG,MAAM,CAAC,CAAC,GAAG,GAAG,EAClB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,KAAK,CAAC,EACjC,KAAK,GAAE,WAAsB,GAC5B,OAAO,CAAC,CAAC,CAAC;CAwBd"}
package/dist/memory.js CHANGED
@@ -98,12 +98,34 @@ export class MemoryProvider {
98
98
  _namespace;
99
99
  _sessionId;
100
100
  _baseDir;
101
+ _locks = new Map();
101
102
  constructor(photonId, sessionId, namespace, baseDir) {
102
103
  this._photonId = photonId;
103
104
  this._namespace = namespace || 'local';
104
105
  this._sessionId = sessionId;
105
106
  this._baseDir = baseDir;
106
107
  }
108
+ /**
109
+ * Serialize file operations per key to prevent concurrent write corruption.
110
+ * Reads also go through the lock to avoid reading a partially-written file.
111
+ */
112
+ async withKeyLock(key, scope, fn) {
113
+ const lockKey = `${scope}:${key}`;
114
+ const prev = this._locks.get(lockKey) ?? Promise.resolve();
115
+ let resolve;
116
+ const next = new Promise(r => { resolve = r; });
117
+ this._locks.set(lockKey, next);
118
+ try {
119
+ await prev;
120
+ return await fn();
121
+ }
122
+ finally {
123
+ resolve();
124
+ if (this._locks.get(lockKey) === next) {
125
+ this._locks.delete(lockKey);
126
+ }
127
+ }
128
+ }
107
129
  /**
108
130
  * Current session ID (can be updated by the runtime)
109
131
  */
@@ -121,17 +143,19 @@ export class MemoryProvider {
121
143
  * @returns The stored value, or null if not found
122
144
  */
123
145
  async get(key, scope = 'photon') {
124
- const dir = resolveDir(this._photonId, this._namespace, scope, this._sessionId, this._baseDir);
125
- const filePath = keyPath(dir, key);
126
- try {
127
- const content = await fs.readFile(filePath, 'utf-8');
128
- return JSON.parse(content);
129
- }
130
- catch (error) {
131
- if (error.code === 'ENOENT')
132
- return null;
133
- throw error;
134
- }
146
+ return this.withKeyLock(key, scope, async () => {
147
+ const dir = resolveDir(this._photonId, this._namespace, scope, this._sessionId, this._baseDir);
148
+ const filePath = keyPath(dir, key);
149
+ try {
150
+ const content = await fs.readFile(filePath, 'utf-8');
151
+ return JSON.parse(content);
152
+ }
153
+ catch (error) {
154
+ if (error.code === 'ENOENT')
155
+ return null;
156
+ throw error;
157
+ }
158
+ });
135
159
  }
136
160
  /**
137
161
  * Set a value in memory
@@ -141,12 +165,17 @@ export class MemoryProvider {
141
165
  * @param scope Storage scope (default: 'photon')
142
166
  */
143
167
  async set(key, value, scope = 'photon') {
144
- const dir = resolveDir(this._photonId, this._namespace, scope, this._sessionId, this._baseDir);
145
- if (!await pathExists(dir)) {
146
- await fs.mkdir(dir, { recursive: true });
147
- }
148
- const filePath = keyPath(dir, key);
149
- await fs.writeFile(filePath, JSON.stringify(value, null, 2));
168
+ return this.withKeyLock(key, scope, async () => {
169
+ const dir = resolveDir(this._photonId, this._namespace, scope, this._sessionId, this._baseDir);
170
+ if (!await pathExists(dir)) {
171
+ await fs.mkdir(dir, { recursive: true });
172
+ }
173
+ const filePath = keyPath(dir, key);
174
+ // Write to temp file then rename for atomic replacement
175
+ const tmpPath = filePath + '.tmp';
176
+ await fs.writeFile(tmpPath, JSON.stringify(value, null, 2));
177
+ await fs.rename(tmpPath, filePath);
178
+ });
150
179
  }
151
180
  /**
152
181
  * Delete a key from memory
@@ -156,17 +185,19 @@ export class MemoryProvider {
156
185
  * @returns true if the key existed and was deleted
157
186
  */
158
187
  async delete(key, scope = 'photon') {
159
- const dir = resolveDir(this._photonId, this._namespace, scope, this._sessionId, this._baseDir);
160
- const filePath = keyPath(dir, key);
161
- try {
162
- await fs.unlink(filePath);
163
- return true;
164
- }
165
- catch (error) {
166
- if (error.code === 'ENOENT')
167
- return false;
168
- throw error;
169
- }
188
+ return this.withKeyLock(key, scope, async () => {
189
+ const dir = resolveDir(this._photonId, this._namespace, scope, this._sessionId, this._baseDir);
190
+ const filePath = keyPath(dir, key);
191
+ try {
192
+ await fs.unlink(filePath);
193
+ return true;
194
+ }
195
+ catch (error) {
196
+ if (error.code === 'ENOENT')
197
+ return false;
198
+ throw error;
199
+ }
200
+ });
170
201
  }
171
202
  /**
172
203
  * Check if a key exists in memory
@@ -232,20 +263,35 @@ export class MemoryProvider {
232
263
  return result;
233
264
  }
234
265
  /**
235
- * Update a value with read-modify-write
236
- *
237
- * Note: Not truly atomic under concurrent access. For concurrent
238
- * writes, use distributed locking via `withLock()`.
266
+ * Atomic read-modify-write for a key.
267
+ * Serialized per key so concurrent updates don't corrupt data.
239
268
  *
240
269
  * @param key The key to update
241
270
  * @param updater Function that receives current value and returns new value
242
271
  * @param scope Storage scope (default: 'photon')
243
272
  */
244
273
  async update(key, updater, scope = 'photon') {
245
- const current = await this.get(key, scope);
246
- const updated = updater(current);
247
- await this.set(key, updated, scope);
248
- return updated;
274
+ return this.withKeyLock(key, scope, async () => {
275
+ const dir = resolveDir(this._photonId, this._namespace, scope, this._sessionId, this._baseDir);
276
+ const filePath = keyPath(dir, key);
277
+ let current = null;
278
+ try {
279
+ const content = await fs.readFile(filePath, 'utf-8');
280
+ current = JSON.parse(content);
281
+ }
282
+ catch (error) {
283
+ if (error.code !== 'ENOENT')
284
+ throw error;
285
+ }
286
+ const updated = updater(current);
287
+ if (!await pathExists(dir)) {
288
+ await fs.mkdir(dir, { recursive: true });
289
+ }
290
+ const tmpPath = filePath + '.tmp';
291
+ await fs.writeFile(tmpPath, JSON.stringify(updated, null, 2));
292
+ await fs.rename(tmpPath, filePath);
293
+ return updated;
294
+ });
249
295
  }
250
296
  }
251
297
  //# sourceMappingURL=memory.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"memory.js","sourceRoot":"","sources":["../src/memory.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"memory.js","sourceRoot":"","sources":["../src/memory.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portel/photon-core",
3
- "version": "2.19.1",
3
+ "version": "2.19.2",
4
4
  "description": "Core library for parsing, loading, and managing .photon.ts files - runtime-agnostic foundation for building custom Photon runtimes",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
package/src/memory.ts CHANGED
@@ -115,6 +115,7 @@ export class MemoryProvider {
115
115
  private _namespace: string;
116
116
  private _sessionId?: string;
117
117
  private _baseDir?: string;
118
+ private _locks = new Map<string, Promise<void>>();
118
119
 
119
120
  constructor(photonId: string, sessionId?: string, namespace?: string, baseDir?: string) {
120
121
  this._photonId = photonId;
@@ -123,6 +124,27 @@ export class MemoryProvider {
123
124
  this._baseDir = baseDir;
124
125
  }
125
126
 
127
+ /**
128
+ * Serialize file operations per key to prevent concurrent write corruption.
129
+ * Reads also go through the lock to avoid reading a partially-written file.
130
+ */
131
+ private async withKeyLock<T>(key: string, scope: MemoryScope, fn: () => Promise<T>): Promise<T> {
132
+ const lockKey = `${scope}:${key}`;
133
+ const prev = this._locks.get(lockKey) ?? Promise.resolve();
134
+ let resolve!: () => void;
135
+ const next = new Promise<void>(r => { resolve = r; });
136
+ this._locks.set(lockKey, next);
137
+ try {
138
+ await prev;
139
+ return await fn();
140
+ } finally {
141
+ resolve();
142
+ if (this._locks.get(lockKey) === next) {
143
+ this._locks.delete(lockKey);
144
+ }
145
+ }
146
+ }
147
+
126
148
  /**
127
149
  * Current session ID (can be updated by the runtime)
128
150
  */
@@ -142,16 +164,18 @@ export class MemoryProvider {
142
164
  * @returns The stored value, or null if not found
143
165
  */
144
166
  async get<T = any>(key: string, scope: MemoryScope = 'photon'): Promise<T | null> {
145
- const dir = resolveDir(this._photonId, this._namespace, scope, this._sessionId, this._baseDir);
146
- const filePath = keyPath(dir, key);
167
+ return this.withKeyLock(key, scope, async () => {
168
+ const dir = resolveDir(this._photonId, this._namespace, scope, this._sessionId, this._baseDir);
169
+ const filePath = keyPath(dir, key);
147
170
 
148
- try {
149
- const content = await fs.readFile(filePath, 'utf-8');
150
- return JSON.parse(content) as T;
151
- } catch (error: any) {
152
- if (error.code === 'ENOENT') return null;
153
- throw error;
154
- }
171
+ try {
172
+ const content = await fs.readFile(filePath, 'utf-8');
173
+ return JSON.parse(content) as T;
174
+ } catch (error: any) {
175
+ if (error.code === 'ENOENT') return null;
176
+ throw error;
177
+ }
178
+ });
155
179
  }
156
180
 
157
181
  /**
@@ -162,14 +186,19 @@ export class MemoryProvider {
162
186
  * @param scope Storage scope (default: 'photon')
163
187
  */
164
188
  async set<T = any>(key: string, value: T, scope: MemoryScope = 'photon'): Promise<void> {
165
- const dir = resolveDir(this._photonId, this._namespace, scope, this._sessionId, this._baseDir);
189
+ return this.withKeyLock(key, scope, async () => {
190
+ const dir = resolveDir(this._photonId, this._namespace, scope, this._sessionId, this._baseDir);
166
191
 
167
- if (!await pathExists(dir)) {
168
- await fs.mkdir(dir, { recursive: true });
169
- }
192
+ if (!await pathExists(dir)) {
193
+ await fs.mkdir(dir, { recursive: true });
194
+ }
170
195
 
171
- const filePath = keyPath(dir, key);
172
- await fs.writeFile(filePath, JSON.stringify(value, null, 2));
196
+ const filePath = keyPath(dir, key);
197
+ // Write to temp file then rename for atomic replacement
198
+ const tmpPath = filePath + '.tmp';
199
+ await fs.writeFile(tmpPath, JSON.stringify(value, null, 2));
200
+ await fs.rename(tmpPath, filePath);
201
+ });
173
202
  }
174
203
 
175
204
  /**
@@ -180,16 +209,18 @@ export class MemoryProvider {
180
209
  * @returns true if the key existed and was deleted
181
210
  */
182
211
  async delete(key: string, scope: MemoryScope = 'photon'): Promise<boolean> {
183
- const dir = resolveDir(this._photonId, this._namespace, scope, this._sessionId, this._baseDir);
184
- const filePath = keyPath(dir, key);
212
+ return this.withKeyLock(key, scope, async () => {
213
+ const dir = resolveDir(this._photonId, this._namespace, scope, this._sessionId, this._baseDir);
214
+ const filePath = keyPath(dir, key);
185
215
 
186
- try {
187
- await fs.unlink(filePath);
188
- return true;
189
- } catch (error: any) {
190
- if (error.code === 'ENOENT') return false;
191
- throw error;
192
- }
216
+ try {
217
+ await fs.unlink(filePath);
218
+ return true;
219
+ } catch (error: any) {
220
+ if (error.code === 'ENOENT') return false;
221
+ throw error;
222
+ }
223
+ });
193
224
  }
194
225
 
195
226
  /**
@@ -260,10 +291,8 @@ export class MemoryProvider {
260
291
  }
261
292
 
262
293
  /**
263
- * Update a value with read-modify-write
264
- *
265
- * Note: Not truly atomic under concurrent access. For concurrent
266
- * writes, use distributed locking via `withLock()`.
294
+ * Atomic read-modify-write for a key.
295
+ * Serialized per key so concurrent updates don't corrupt data.
267
296
  *
268
297
  * @param key The key to update
269
298
  * @param updater Function that receives current value and returns new value
@@ -274,9 +303,27 @@ export class MemoryProvider {
274
303
  updater: (current: T | null) => T,
275
304
  scope: MemoryScope = 'photon'
276
305
  ): Promise<T> {
277
- const current = await this.get<T>(key, scope);
278
- const updated = updater(current);
279
- await this.set(key, updated, scope);
280
- return updated;
306
+ return this.withKeyLock(key, scope, async () => {
307
+ const dir = resolveDir(this._photonId, this._namespace, scope, this._sessionId, this._baseDir);
308
+ const filePath = keyPath(dir, key);
309
+
310
+ let current: T | null = null;
311
+ try {
312
+ const content = await fs.readFile(filePath, 'utf-8');
313
+ current = JSON.parse(content) as T;
314
+ } catch (error: any) {
315
+ if (error.code !== 'ENOENT') throw error;
316
+ }
317
+
318
+ const updated = updater(current);
319
+
320
+ if (!await pathExists(dir)) {
321
+ await fs.mkdir(dir, { recursive: true });
322
+ }
323
+ const tmpPath = filePath + '.tmp';
324
+ await fs.writeFile(tmpPath, JSON.stringify(updated, null, 2));
325
+ await fs.rename(tmpPath, filePath);
326
+ return updated;
327
+ });
281
328
  }
282
329
  }