11import path from 'path'
22import { CliComponents } from '../../../components'
3+ import { getObject } from '../../../logic/coordinates'
34
45// Find the sdk-commands package root by resolving its package.json
56const SDK_COMMANDS_ROOT = path . dirname ( require . resolve ( '@dcl/sdk-commands/package.json' ) )
@@ -9,18 +10,34 @@ const SERVER_STORAGE_FILE = 'server-storage.json'
910/**
1011 * Structure for all server-side storage data.
1112 * Stored in sdk-commands package directory (hidden from users).
13+ *
14+ * `world` is namespaced by scene base coordinates (`"x,y"`) so that previewing
15+ * different scenes does not share the same scene-storage bucket.
1216 */
1317export interface ServerStorage {
1418 env : Record < string , string >
15- world : Record < string , unknown >
19+ world : Record < string , Record < string , unknown > >
1620 players : Record < string , Record < string , unknown > >
1721}
1822
19- const DEFAULT_STORAGE : ServerStorage = {
23+ /**
24+ * Normalizes a scene base parcel (e.g. `"60, -9"`) into the canonical `"x,y"`
25+ * key used to namespace world storage, so equivalent spellings map to one bucket.
26+ */
27+ export function getSceneStorageKey ( base : string ) : string {
28+ const { x, y } = getObject ( base )
29+ return `${ x } ,${ y } `
30+ }
31+
32+ const isPlainObject = ( value : unknown ) : boolean => typeof value === 'object' && value !== null && ! Array . isArray ( value )
33+
34+ // Factory (not a shared const): each call returns fresh nested objects so callers
35+ // can never mutate a shared default and leak state into later loads.
36+ const createDefaultStorage = ( ) : ServerStorage => ( {
2037 env : { } ,
2138 world : { } ,
2239 players : { }
23- }
40+ } )
2441
2542/**
2643 * Ensures the runtime data directory exists.
@@ -45,21 +62,31 @@ export async function loadServerStorage(components: Pick<CliComponents, 'fs' | '
4562 try {
4663 const exists = await components . fs . fileExists ( storagePath )
4764 if ( ! exists ) {
48- return { ... DEFAULT_STORAGE }
65+ return createDefaultStorage ( )
4966 }
5067
5168 const content = await components . fs . readFile ( storagePath , 'utf-8' )
5269 const parsed = JSON . parse ( content ) as Partial < ServerStorage >
5370
71+ // `world` is now namespaced by scene coordinates (`"x,y"` -> key -> value).
72+ // A file with any non-object top-level `world` entry predates that change
73+ // (flat `key -> value`); its data has no scene to attribute to, so discard it.
74+ // Local preview storage is disposable, so resetting is preferable to migrating.
75+ const rawWorld = parsed . world ?? { }
76+ const isLegacyWorld = Object . values ( rawWorld ) . some ( ( value ) => ! isPlainObject ( value ) )
77+ if ( isLegacyWorld ) {
78+ components . logger . debug ( 'Resetting legacy local preview world storage (pre scene-coordinate namespacing)' )
79+ }
80+
5481 // Merge with defaults to ensure all keys exist
5582 return {
5683 env : parsed . env ?? { } ,
57- world : parsed . world ?? { } ,
84+ world : isLegacyWorld ? { } : rawWorld ,
5885 players : parsed . players ?? { }
5986 }
6087 } catch ( error ) {
6188 components . logger . error ( `Failed to load ${ SERVER_STORAGE_FILE } : ${ error } ` )
62- return { ... DEFAULT_STORAGE }
89+ return createDefaultStorage ( )
6390 }
6491}
6592
@@ -183,52 +210,59 @@ export async function deleteEnvValue(components: Pick<CliComponents, 'fs' | 'log
183210}
184211
185212/**
186- * Gets all world storage data.
213+ * Gets all world storage data for a scene, keyed by its base-coordinate bucket .
187214 */
188215export async function getWorldStorage (
189- components : Pick < CliComponents , 'fs' | 'logger' >
216+ components : Pick < CliComponents , 'fs' | 'logger' > ,
217+ sceneKey : string
190218) : Promise < Record < string , unknown > > {
191219 const storage = await loadServerStorage ( components )
192- return storage . world
220+ return storage . world [ sceneKey ] ?? { }
193221}
194222
195223/**
196- * Gets a value from world storage.
224+ * Gets a value from a scene's world storage.
197225 */
198226export async function getWorldValue (
199227 components : Pick < CliComponents , 'fs' | 'logger' > ,
228+ sceneKey : string ,
200229 key : string
201230) : Promise < unknown | undefined > {
202231 const storage = await loadServerStorage ( components )
203- return storage . world [ key ]
232+ return storage . world [ sceneKey ] ?. [ key ]
204233}
205234
206235/**
207- * Sets a value in world storage.
236+ * Sets a value in a scene's world storage.
208237 */
209238export async function setWorldValue (
210239 components : Pick < CliComponents , 'fs' | 'logger' > ,
240+ sceneKey : string ,
211241 key : string ,
212242 value : unknown
213243) : Promise < void > {
214244 const storage = await loadServerStorage ( components )
215- storage . world [ key ] = value
245+ if ( ! storage . world [ sceneKey ] ) {
246+ storage . world [ sceneKey ] = { }
247+ }
248+ storage . world [ sceneKey ] [ key ] = value
216249 await saveServerStorage ( components , storage )
217250}
218251
219252/**
220- * Deletes a value from world storage.
253+ * Deletes a value from a scene's world storage.
221254 * Returns true if key existed and was deleted, false otherwise.
222255 */
223256export async function deleteWorldValue (
224257 components : Pick < CliComponents , 'fs' | 'logger' > ,
258+ sceneKey : string ,
225259 key : string
226260) : Promise < boolean > {
227261 const storage = await loadServerStorage ( components )
228- if ( ! ( key in storage . world ) ) {
262+ if ( ! storage . world [ sceneKey ] || ! ( key in storage . world [ sceneKey ] ) ) {
229263 return false
230264 }
231- delete storage . world [ key ]
265+ delete storage . world [ sceneKey ] [ key ]
232266 await saveServerStorage ( components , storage )
233267 return true
234268}
0 commit comments