@epic-web/workshop-utils 6.20.4 → 6.20.6

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.
@@ -1 +1 @@
1
- {"version":3,"file":"cache.server.d.ts","sourceRoot":"","sources":["../../src/cache.server.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,CAAC,MAAM,qBAAqB,CAAA;AAcxC,OAAO,EAA2B,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAK1E,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EACyB,CAAA;AACtD,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EACwB,CAAA;AACpD,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EACwB,CAAA;AACpD,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAC2B,CAAA;AAC1D,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAAyC,CAAA;AAC/D,eAAO,MAAM,aAAa,iBAAgD,CAAA;AAC1E,eAAO,MAAM,cAAc,iBAAiD,CAAA;AAC5E,eAAO,MAAM,uBAAuB;;;;;CAEnC,CAAA;AACD,eAAO,MAAM,qBAAqB,iBAEjC,CAAA;AACD,eAAO,MAAM,iBAAiB,iBACoB,CAAA;AAClD,eAAO,MAAM,OAAO;;;;;CAAwC,CAAA;AAC5D,eAAO,MAAM,gCAAgC;UACtC,MAAM;WACL,MAAM,GAAG,IAAI;qBACH,KAAK,CAAC,MAAM,CAAC;EACO,CAAA;AACtC,eAAO,MAAM,oBAAoB;;;;;CAEhC,CAAA;AACD,eAAO,MAAM,eAAe;;;;;CAAiD,CAAA;AAC7E,eAAO,MAAM,oBAAoB;;;0BACd,OAAO;qBACZ,MAAM;sBACL,MAAM;kBACV,MAAM,GAAG,IAAI;;0BAHL,OAAO;qBACZ,MAAM;sBACL,MAAM;kBACV,MAAM,GAAG,IAAI;;;0BAHL,OAAO;qBACZ,MAAM;sBACL,MAAM;kBACV,MAAM,GAAG,IAAI;;;CACE,CAAA;AAC1B,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAC+B,CAAA;AAC9D,eAAO,MAAM,mBAAmB;;;;;CAE/B,CAAA;AAED,eAAO,MAAM,OAAO,kBAAkC,CAAA;AAuDtD,wBAAsB,sBAAsB,iCAE3C;AAED,wBAAsB,WAAW,8BAUhC;AAED,wBAAgB,kBAAkB,CAAC,cAAc,EAAE,IAAI,EAAE,MAAM;;;;;EAsB9D;AAED,wBAAgB,oBAAoB,CAAC,cAAc,EAAE,IAAI,EAAE,MAAM,2BA8GhE;AAED;;;;;;;;;GASG;AACH,wBAAsB,SAAS,CAAC,KAAK,EAAE,EACtC,OAAO,EACP,OAAO,EACP,GAAG,EACH,SAA2E,EAC3E,oBAAoB,EACpB,GAAG,OAAO,EACV,EAAE,IAAI,CAAC,CAAC,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,YAAY,CAAC,GAAG;IAClD,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,UAAU,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,oBAAoB,CAAC,EAAE,KAAK,CAAA;CAC5B,GAAG,OAAO,CAAC,KAAK,CAAC,CAwBjB;AAED,wBAAsB,gBAAgB,CAAC,EACtC,UAAU,EACV,OAAO,EACP,GAAG,GACH,EAAE;IACF,UAAU,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,GAAG,CAAC,EAAE,MAAM,CAAA;CACZ,oBAaA"}
1
+ {"version":3,"file":"cache.server.d.ts","sourceRoot":"","sources":["../../src/cache.server.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,CAAC,MAAM,qBAAqB,CAAA;AAexC,OAAO,EAA2B,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAK1E,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EACyB,CAAA;AACtD,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EACwB,CAAA;AACpD,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EACwB,CAAA;AACpD,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAC2B,CAAA;AAC1D,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAAyC,CAAA;AAC/D,eAAO,MAAM,aAAa,iBAAgD,CAAA;AAC1E,eAAO,MAAM,cAAc,iBAAiD,CAAA;AAC5E,eAAO,MAAM,uBAAuB;;;;;CAEnC,CAAA;AACD,eAAO,MAAM,qBAAqB,iBAEjC,CAAA;AACD,eAAO,MAAM,iBAAiB,iBACoB,CAAA;AAClD,eAAO,MAAM,OAAO;;;;;CAAwC,CAAA;AAC5D,eAAO,MAAM,gCAAgC;UACtC,MAAM;WACL,MAAM,GAAG,IAAI;qBACH,KAAK,CAAC,MAAM,CAAC;EACO,CAAA;AACtC,eAAO,MAAM,oBAAoB;;;;;CAEhC,CAAA;AACD,eAAO,MAAM,eAAe;;;;;CAAiD,CAAA;AAC7E,eAAO,MAAM,oBAAoB;;;0BACd,OAAO;qBACZ,MAAM;sBACL,MAAM;kBACV,MAAM,GAAG,IAAI;;0BAHL,OAAO;qBACZ,MAAM;sBACL,MAAM;kBACV,MAAM,GAAG,IAAI;;;0BAHL,OAAO;qBACZ,MAAM;sBACL,MAAM;kBACV,MAAM,GAAG,IAAI;;;CACE,CAAA;AAC1B,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAC+B,CAAA;AAC9D,eAAO,MAAM,mBAAmB;;;;;CAE/B,CAAA;AAED,eAAO,MAAM,OAAO,kBAAkC,CAAA;AAuDtD,wBAAsB,sBAAsB,iCAE3C;AAED,wBAAsB,WAAW,8BAUhC;AAED,wBAAgB,kBAAkB,CAAC,cAAc,EAAE,IAAI,EAAE,MAAM;;;;;EAsB9D;AAED,wBAAgB,oBAAoB,CAAC,cAAc,EAAE,IAAI,EAAE,MAAM,2BA8GhE;AAED;;;;;;;;;GASG;AACH,wBAAsB,SAAS,CAAC,KAAK,EAAE,EACtC,OAAO,EACP,OAAO,EACP,GAAG,EACH,SAA2E,EAC3E,oBAAoB,EACpB,GAAG,OAAO,EACV,EAAE,IAAI,CAAC,CAAC,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,YAAY,CAAC,GAAG;IAClD,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,UAAU,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,oBAAoB,CAAC,EAAE,KAAK,CAAA;CAC5B,GAAG,OAAO,CAAC,KAAK,CAAC,CAwBjB;AAED,wBAAsB,gBAAgB,CAAC,EACtC,UAAU,EACV,OAAO,EACP,GAAG,GACH,EAAE;IACF,UAAU,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,GAAG,CAAC,EAAE,MAAM,CAAA;CACZ,oBAaA"}
@@ -7,9 +7,10 @@ import { remember } from '@epic-web/remember';
7
7
  import fsExtra from 'fs-extra';
8
8
  import { LRUCache } from 'lru-cache';
9
9
  import md5 from 'md5-hex';
10
+ import { resolveCacheDir } from './data-storage.server.js';
10
11
  import { cachifiedTimingReporter } from './timing.server.js';
11
12
  import { checkConnectionCached } from './utils.server.js';
12
- const cacheDir = path.join(process.env.EPICSHOP_HOME_DIR, 'cache');
13
+ const cacheDir = resolveCacheDir();
13
14
  export const solutionAppCache = makeSingletonFsCache('SolutionAppCache');
14
15
  export const problemAppCache = makeSingletonFsCache('ProblemAppCache');
15
16
  export const exampleAppCache = makeSingletonFsCache('ExampleAppCache');
@@ -1 +1 @@
1
- {"version":3,"file":"cache.server.js","sourceRoot":"","sources":["../../src/cache.server.ts"],"names":[],"mappings":"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","sourcesContent":["// eslint-disable-next-line import/order -- this must be first\nimport { getEnv } from './init-env.js'\n\nimport path from 'path'\nimport * as C from '@epic-web/cachified'\nimport { verboseReporter, type CacheEntry } from '@epic-web/cachified'\nimport { remember } from '@epic-web/remember'\nimport fsExtra from 'fs-extra'\nimport { LRUCache } from 'lru-cache'\nimport md5 from 'md5-hex'\nimport {\n\ttype App,\n\ttype ExampleApp,\n\ttype PlaygroundApp,\n\ttype ProblemApp,\n\ttype SolutionApp,\n} from './apps.server.js'\nimport { type Notification } from './notifications.server.js'\nimport { cachifiedTimingReporter, type Timings } from './timing.server.js'\nimport { checkConnectionCached } from './utils.server.js'\n\nconst cacheDir = path.join(process.env.EPICSHOP_HOME_DIR, 'cache')\n\nexport const solutionAppCache =\n\tmakeSingletonFsCache<SolutionApp>('SolutionAppCache')\nexport const problemAppCache =\n\tmakeSingletonFsCache<ProblemApp>('ProblemAppCache')\nexport const exampleAppCache =\n\tmakeSingletonFsCache<ExampleApp>('ExampleAppCache')\nexport const playgroundAppCache =\n\tmakeSingletonFsCache<PlaygroundApp>('PlaygroundAppCache')\nexport const appsCache = makeSingletonFsCache<App>('AppsCache')\nexport const diffCodeCache = makeSingletonFsCache<string>('DiffCodeCache')\nexport const diffFilesCache = makeSingletonFsCache<string>('DiffFilesCache')\nexport const copyUnignoredFilesCache = makeSingletonCache<string>(\n\t'CopyUnignoredFilesCache',\n)\nexport const compiledMarkdownCache = makeSingletonFsCache<string>(\n\t'CompiledMarkdownCache',\n)\nexport const compiledCodeCache =\n\tmakeSingletonFsCache<string>('CompiledCodeCache')\nexport const ogCache = makeSingletonCache<string>('OgCache')\nexport const compiledInstructionMarkdownCache = makeSingletonFsCache<{\n\tcode: string\n\ttitle: string | null\n\tepicVideoEmbeds: Array<string>\n}>('CompiledInstructionMarkdownCache')\nexport const dirModifiedTimeCache = makeSingletonCache<number>(\n\t'DirModifiedTimeCache',\n)\nexport const connectionCache = makeSingletonCache<boolean>('ConnectionCache')\nexport const checkForUpdatesCache = makeSingletonCache<{\n\tupdatesAvailable: boolean\n\tlocalCommit: string\n\tremoteCommit: string\n\tdiffLink: string | null\n}>('CheckForUpdatesCache')\nexport const notificationsCache =\n\tmakeSingletonCache<Array<Notification>>('NotificationsCache')\nexport const directoryEmptyCache = makeSingletonCache<boolean>(\n\t'DirectoryEmptyCache',\n)\n\nexport const fsCache = makeSingletonFsCache('FsCache')\n\nasync function readJsonFilesInDirectory(\n\tdir: string,\n): Promise<Record<string, any>> {\n\tconst files = await fsExtra.readdir(dir)\n\tconst entries = await Promise.all(\n\t\tfiles.map(async (file) => {\n\t\t\tconst filePath = path.join(dir, file)\n\t\t\tconst stats = await fsExtra.stat(filePath)\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\tconst subEntries = await readJsonFilesInDirectory(filePath)\n\t\t\t\treturn [file, subEntries]\n\t\t\t} else {\n\t\t\t\tconst maxRetries = 2\n\t\t\t\tconst baseDelay = 25 // shorter delay for directory listing\n\n\t\t\t\tfor (let attempt = 0; attempt <= maxRetries; attempt++) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst data = await fsExtra.readJSON(filePath)\n\t\t\t\t\t\treturn [file, data]\n\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\t// Handle JSON parsing errors (could be race condition or corruption)\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\terror instanceof SyntaxError &&\n\t\t\t\t\t\t\terror.message.includes('JSON')\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t// If this is a retry attempt, it might be a race condition\n\t\t\t\t\t\t\tif (attempt < maxRetries) {\n\t\t\t\t\t\t\t\tconst delay = baseDelay * Math.pow(2, attempt)\n\t\t\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t\t\t`JSON parsing error on attempt ${attempt + 1}/${maxRetries + 1} for directory listing ${filePath}, retrying in ${delay}ms...`,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, delay))\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Final attempt failed, skip the file\n\t\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t\t`Skipping corrupted JSON file in directory listing after ${attempt + 1} attempts: ${filePath}`,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\treturn [file, null]\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthrow error\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// This should never be reached, but just in case\n\t\t\t\treturn [file, null]\n\t\t\t}\n\t\t}),\n\t)\n\treturn Object.fromEntries(entries)\n}\n\nexport async function getAllFileCacheEntries() {\n\treturn readJsonFilesInDirectory(cacheDir)\n}\n\nexport async function deleteCache() {\n\tif (getEnv().EPICSHOP_DEPLOYED) return null\n\n\ttry {\n\t\tif (await fsExtra.exists(cacheDir)) {\n\t\t\tawait fsExtra.remove(cacheDir)\n\t\t}\n\t} catch (error) {\n\t\tconsole.error(`Error deleting the cache in ${cacheDir}`, error)\n\t}\n}\n\nexport function makeSingletonCache<CacheEntryType>(name: string) {\n\treturn remember(name, () => {\n\t\tconst lruInstance = new LRUCache<string, CacheEntry<CacheEntryType>>({\n\t\t\tmax: 1000,\n\t\t})\n\n\t\tconst lru = {\n\t\t\tname,\n\t\t\tset: (key, value) => {\n\t\t\t\tconst ttl = C.totalTtl(value.metadata)\n\t\t\t\tlruInstance.set(key, value, {\n\t\t\t\t\tttl: ttl === Infinity ? undefined : ttl,\n\t\t\t\t\tstart: value.metadata.createdTime,\n\t\t\t\t})\n\t\t\t\treturn value\n\t\t\t},\n\t\t\tget: (key) => lruInstance.get(key),\n\t\t\tdelete: (key) => lruInstance.delete(key),\n\t\t} satisfies C.Cache<CacheEntryType>\n\n\t\treturn lru\n\t})\n}\n\nexport function makeSingletonFsCache<CacheEntryType>(name: string) {\n\treturn remember(name, () => {\n\t\tconst cacheInstanceDir = path.join(\n\t\t\tcacheDir,\n\t\t\tgetEnv().EPICSHOP_WORKSHOP_INSTANCE_ID,\n\t\t\tname,\n\t\t)\n\n\t\tconst fsCache: C.Cache<CacheEntryType> = {\n\t\t\tname: `Filesystem cache (${name})`,\n\t\t\tasync get(key) {\n\t\t\t\tconst filePath = path.join(cacheInstanceDir, md5(key))\n\t\t\t\tconst maxRetries = 3\n\t\t\t\tconst baseDelay = 10\n\n\t\t\t\tfor (let attempt = 0; attempt <= maxRetries; attempt++) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst data = await fsExtra.readJSON(filePath)\n\t\t\t\t\t\tif (data.entry) return data.entry\n\t\t\t\t\t\treturn null\n\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\terror instanceof Error &&\n\t\t\t\t\t\t\t'code' in error &&\n\t\t\t\t\t\t\terror.code === 'ENOENT'\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\treturn null\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Handle JSON parsing errors (could be race condition or corruption)\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\terror instanceof SyntaxError &&\n\t\t\t\t\t\t\terror.message.includes('JSON')\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t// If this is a retry attempt, it might be a race condition\n\t\t\t\t\t\t\tif (attempt < maxRetries) {\n\t\t\t\t\t\t\t\tconst delay = baseDelay * Math.pow(2, attempt) // exponential backoff\n\t\t\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t\t\t`JSON parsing error on attempt ${attempt + 1}/${maxRetries + 1} for ${filePath}, retrying in ${delay}ms...`,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, delay))\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Final attempt failed, treat as corrupted file\n\t\t\t\t\t\t\t// Log to Sentry if available\n\t\t\t\t\t\t\tif (getEnv().SENTRY_DSN && getEnv().EPICSHOP_IS_PUBLISHED) {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tconst Sentry = await import('@sentry/react-router')\n\t\t\t\t\t\t\t\t\tSentry.captureException(error, {\n\t\t\t\t\t\t\t\t\t\ttags: {\n\t\t\t\t\t\t\t\t\t\t\terror_type: 'corrupted_cache_file',\n\t\t\t\t\t\t\t\t\t\t\tcache_name: name,\n\t\t\t\t\t\t\t\t\t\t\tcache_key: key,\n\t\t\t\t\t\t\t\t\t\t\tretry_attempts: attempt.toString(),\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\textra: {\n\t\t\t\t\t\t\t\t\t\t\tfilePath,\n\t\t\t\t\t\t\t\t\t\t\terrorMessage: error.message,\n\t\t\t\t\t\t\t\t\t\t\tcacheName: name,\n\t\t\t\t\t\t\t\t\t\t\tcacheKey: key,\n\t\t\t\t\t\t\t\t\t\t\tretryAttempts: attempt,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t} catch (sentryError) {\n\t\t\t\t\t\t\t\t\tconsole.error('Failed to log to Sentry:', sentryError)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Delete the corrupted file\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tawait fsExtra.remove(filePath)\n\t\t\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t\t\t`Deleted corrupted cache file after ${attempt + 1} attempts: ${filePath}`,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t} catch (deleteError) {\n\t\t\t\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\t\t\t`Failed to delete corrupted cache file ${filePath}:`,\n\t\t\t\t\t\t\t\t\tdeleteError,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\treturn null\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// For other errors, don't retry\n\t\t\t\t\t\tthrow error\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// This should never be reached, but just in case\n\t\t\t\treturn null\n\t\t\t},\n\t\t\tasync set(key, entry) {\n\t\t\t\tconst filePath = path.join(cacheInstanceDir, md5(key))\n\t\t\t\tconst tempPath = `${filePath}.tmp`\n\t\t\t\tawait fsExtra.ensureDir(path.dirname(filePath))\n\t\t\t\t// Write to temp file first, then atomically move to final location\n\t\t\t\t// This prevents race conditions where readers see partially written JSON files\n\t\t\t\tawait fsExtra.writeJSON(tempPath, { key, entry })\n\t\t\t\tawait fsExtra.move(tempPath, filePath, { overwrite: true })\n\t\t\t},\n\t\t\tasync delete(key) {\n\t\t\t\tconst filePath = path.join(cacheInstanceDir, md5(key))\n\t\t\t\tawait fsExtra.remove(filePath)\n\t\t\t},\n\t\t}\n\n\t\treturn fsCache\n\t})\n}\n\n/**\n * This wraps @epic-web/cachified to add a few handy features:\n *\n * 1. Automatic timing for timing headers\n * 2. Automatic force refresh based on the request and enhancement of forceFresh\n * to support comma-separated keys to force\n * 3. Offline fallback support. If a fallback is given and we are detected to be\n * offline, then the cached value is used regardless of whether it's expired and\n * if one is not present then the given fallback will be used.\n */\nexport async function cachified<Value>({\n\trequest,\n\ttimings,\n\tkey,\n\ttimingKey = key.length > 18 ? `${key.slice(0, 7)}...${key.slice(-8)}` : key,\n\tofflineFallbackValue,\n\t...options\n}: Omit<C.CachifiedOptions<Value>, 'forceFresh'> & {\n\trequest?: Request\n\ttimings?: Timings\n\tforceFresh?: boolean | string\n\ttimingKey?: string\n\tofflineFallbackValue?: Value\n}): Promise<Value> {\n\tif (offlineFallbackValue !== undefined) {\n\t\tconst isOnline = await checkConnectionCached({ request, timings })\n\t\tif (!isOnline) {\n\t\t\tconst cacheEntry = await options.cache.get(key)\n\t\t\treturn cacheEntry?.value ?? offlineFallbackValue\n\t\t}\n\t}\n\tconst forceFresh = await shouldForceFresh({\n\t\tforceFresh: options.forceFresh,\n\t\trequest,\n\t\tkey,\n\t})\n\treturn C.cachified(\n\t\t{\n\t\t\t...options,\n\t\t\tkey,\n\t\t\tforceFresh,\n\t\t},\n\t\tC.mergeReporters(\n\t\t\tcachifiedTimingReporter(timings, timingKey),\n\t\t\tprocess.env.EPICSHOP_DEBUG_CACHE ? verboseReporter() : undefined,\n\t\t),\n\t)\n}\n\nexport async function shouldForceFresh({\n\tforceFresh,\n\trequest,\n\tkey,\n}: {\n\tforceFresh?: boolean | string\n\trequest?: Request\n\tkey?: string\n}) {\n\tif (typeof forceFresh === 'boolean') return forceFresh\n\tif (typeof forceFresh === 'string' && key) {\n\t\treturn forceFresh.split(',').includes(key)\n\t}\n\n\tif (!request) return false\n\tconst fresh = new URL(request.url).searchParams.get('fresh')\n\tif (typeof fresh !== 'string') return false\n\tif (fresh === '') return true\n\tif (!key) return false\n\n\treturn fresh.split(',').includes(key)\n}\n"]}
1
+ {"version":3,"file":"cache.server.js","sourceRoot":"","sources":["../../src/cache.server.ts"],"names":[],"mappings":"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","sourcesContent":["// eslint-disable-next-line import/order -- this must be first\nimport { getEnv } from './init-env.js'\n\nimport path from 'path'\nimport * as C from '@epic-web/cachified'\nimport { verboseReporter, type CacheEntry } from '@epic-web/cachified'\nimport { remember } from '@epic-web/remember'\nimport fsExtra from 'fs-extra'\nimport { LRUCache } from 'lru-cache'\nimport md5 from 'md5-hex'\nimport {\n\ttype App,\n\ttype ExampleApp,\n\ttype PlaygroundApp,\n\ttype ProblemApp,\n\ttype SolutionApp,\n} from './apps.server.js'\nimport { resolveCacheDir } from './data-storage.server.js'\nimport { type Notification } from './notifications.server.js'\nimport { cachifiedTimingReporter, type Timings } from './timing.server.js'\nimport { checkConnectionCached } from './utils.server.js'\n\nconst cacheDir = resolveCacheDir()\n\nexport const solutionAppCache =\n\tmakeSingletonFsCache<SolutionApp>('SolutionAppCache')\nexport const problemAppCache =\n\tmakeSingletonFsCache<ProblemApp>('ProblemAppCache')\nexport const exampleAppCache =\n\tmakeSingletonFsCache<ExampleApp>('ExampleAppCache')\nexport const playgroundAppCache =\n\tmakeSingletonFsCache<PlaygroundApp>('PlaygroundAppCache')\nexport const appsCache = makeSingletonFsCache<App>('AppsCache')\nexport const diffCodeCache = makeSingletonFsCache<string>('DiffCodeCache')\nexport const diffFilesCache = makeSingletonFsCache<string>('DiffFilesCache')\nexport const copyUnignoredFilesCache = makeSingletonCache<string>(\n\t'CopyUnignoredFilesCache',\n)\nexport const compiledMarkdownCache = makeSingletonFsCache<string>(\n\t'CompiledMarkdownCache',\n)\nexport const compiledCodeCache =\n\tmakeSingletonFsCache<string>('CompiledCodeCache')\nexport const ogCache = makeSingletonCache<string>('OgCache')\nexport const compiledInstructionMarkdownCache = makeSingletonFsCache<{\n\tcode: string\n\ttitle: string | null\n\tepicVideoEmbeds: Array<string>\n}>('CompiledInstructionMarkdownCache')\nexport const dirModifiedTimeCache = makeSingletonCache<number>(\n\t'DirModifiedTimeCache',\n)\nexport const connectionCache = makeSingletonCache<boolean>('ConnectionCache')\nexport const checkForUpdatesCache = makeSingletonCache<{\n\tupdatesAvailable: boolean\n\tlocalCommit: string\n\tremoteCommit: string\n\tdiffLink: string | null\n}>('CheckForUpdatesCache')\nexport const notificationsCache =\n\tmakeSingletonCache<Array<Notification>>('NotificationsCache')\nexport const directoryEmptyCache = makeSingletonCache<boolean>(\n\t'DirectoryEmptyCache',\n)\n\nexport const fsCache = makeSingletonFsCache('FsCache')\n\nasync function readJsonFilesInDirectory(\n\tdir: string,\n): Promise<Record<string, any>> {\n\tconst files = await fsExtra.readdir(dir)\n\tconst entries = await Promise.all(\n\t\tfiles.map(async (file) => {\n\t\t\tconst filePath = path.join(dir, file)\n\t\t\tconst stats = await fsExtra.stat(filePath)\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\tconst subEntries = await readJsonFilesInDirectory(filePath)\n\t\t\t\treturn [file, subEntries]\n\t\t\t} else {\n\t\t\t\tconst maxRetries = 2\n\t\t\t\tconst baseDelay = 25 // shorter delay for directory listing\n\n\t\t\t\tfor (let attempt = 0; attempt <= maxRetries; attempt++) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst data = await fsExtra.readJSON(filePath)\n\t\t\t\t\t\treturn [file, data]\n\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\t// Handle JSON parsing errors (could be race condition or corruption)\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\terror instanceof SyntaxError &&\n\t\t\t\t\t\t\terror.message.includes('JSON')\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t// If this is a retry attempt, it might be a race condition\n\t\t\t\t\t\t\tif (attempt < maxRetries) {\n\t\t\t\t\t\t\t\tconst delay = baseDelay * Math.pow(2, attempt)\n\t\t\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t\t\t`JSON parsing error on attempt ${attempt + 1}/${maxRetries + 1} for directory listing ${filePath}, retrying in ${delay}ms...`,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, delay))\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Final attempt failed, skip the file\n\t\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t\t`Skipping corrupted JSON file in directory listing after ${attempt + 1} attempts: ${filePath}`,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\treturn [file, null]\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthrow error\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// This should never be reached, but just in case\n\t\t\t\treturn [file, null]\n\t\t\t}\n\t\t}),\n\t)\n\treturn Object.fromEntries(entries)\n}\n\nexport async function getAllFileCacheEntries() {\n\treturn readJsonFilesInDirectory(cacheDir)\n}\n\nexport async function deleteCache() {\n\tif (getEnv().EPICSHOP_DEPLOYED) return null\n\n\ttry {\n\t\tif (await fsExtra.exists(cacheDir)) {\n\t\t\tawait fsExtra.remove(cacheDir)\n\t\t}\n\t} catch (error) {\n\t\tconsole.error(`Error deleting the cache in ${cacheDir}`, error)\n\t}\n}\n\nexport function makeSingletonCache<CacheEntryType>(name: string) {\n\treturn remember(name, () => {\n\t\tconst lruInstance = new LRUCache<string, CacheEntry<CacheEntryType>>({\n\t\t\tmax: 1000,\n\t\t})\n\n\t\tconst lru = {\n\t\t\tname,\n\t\t\tset: (key, value) => {\n\t\t\t\tconst ttl = C.totalTtl(value.metadata)\n\t\t\t\tlruInstance.set(key, value, {\n\t\t\t\t\tttl: ttl === Infinity ? undefined : ttl,\n\t\t\t\t\tstart: value.metadata.createdTime,\n\t\t\t\t})\n\t\t\t\treturn value\n\t\t\t},\n\t\t\tget: (key) => lruInstance.get(key),\n\t\t\tdelete: (key) => lruInstance.delete(key),\n\t\t} satisfies C.Cache<CacheEntryType>\n\n\t\treturn lru\n\t})\n}\n\nexport function makeSingletonFsCache<CacheEntryType>(name: string) {\n\treturn remember(name, () => {\n\t\tconst cacheInstanceDir = path.join(\n\t\t\tcacheDir,\n\t\t\tgetEnv().EPICSHOP_WORKSHOP_INSTANCE_ID,\n\t\t\tname,\n\t\t)\n\n\t\tconst fsCache: C.Cache<CacheEntryType> = {\n\t\t\tname: `Filesystem cache (${name})`,\n\t\t\tasync get(key) {\n\t\t\t\tconst filePath = path.join(cacheInstanceDir, md5(key))\n\t\t\t\tconst maxRetries = 3\n\t\t\t\tconst baseDelay = 10\n\n\t\t\t\tfor (let attempt = 0; attempt <= maxRetries; attempt++) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst data = await fsExtra.readJSON(filePath)\n\t\t\t\t\t\tif (data.entry) return data.entry\n\t\t\t\t\t\treturn null\n\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\terror instanceof Error &&\n\t\t\t\t\t\t\t'code' in error &&\n\t\t\t\t\t\t\terror.code === 'ENOENT'\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\treturn null\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Handle JSON parsing errors (could be race condition or corruption)\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\terror instanceof SyntaxError &&\n\t\t\t\t\t\t\terror.message.includes('JSON')\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t// If this is a retry attempt, it might be a race condition\n\t\t\t\t\t\t\tif (attempt < maxRetries) {\n\t\t\t\t\t\t\t\tconst delay = baseDelay * Math.pow(2, attempt) // exponential backoff\n\t\t\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t\t\t`JSON parsing error on attempt ${attempt + 1}/${maxRetries + 1} for ${filePath}, retrying in ${delay}ms...`,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, delay))\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Final attempt failed, treat as corrupted file\n\t\t\t\t\t\t\t// Log to Sentry if available\n\t\t\t\t\t\t\tif (getEnv().SENTRY_DSN && getEnv().EPICSHOP_IS_PUBLISHED) {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tconst Sentry = await import('@sentry/react-router')\n\t\t\t\t\t\t\t\t\tSentry.captureException(error, {\n\t\t\t\t\t\t\t\t\t\ttags: {\n\t\t\t\t\t\t\t\t\t\t\terror_type: 'corrupted_cache_file',\n\t\t\t\t\t\t\t\t\t\t\tcache_name: name,\n\t\t\t\t\t\t\t\t\t\t\tcache_key: key,\n\t\t\t\t\t\t\t\t\t\t\tretry_attempts: attempt.toString(),\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\textra: {\n\t\t\t\t\t\t\t\t\t\t\tfilePath,\n\t\t\t\t\t\t\t\t\t\t\terrorMessage: error.message,\n\t\t\t\t\t\t\t\t\t\t\tcacheName: name,\n\t\t\t\t\t\t\t\t\t\t\tcacheKey: key,\n\t\t\t\t\t\t\t\t\t\t\tretryAttempts: attempt,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t} catch (sentryError) {\n\t\t\t\t\t\t\t\t\tconsole.error('Failed to log to Sentry:', sentryError)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Delete the corrupted file\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tawait fsExtra.remove(filePath)\n\t\t\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t\t\t`Deleted corrupted cache file after ${attempt + 1} attempts: ${filePath}`,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t} catch (deleteError) {\n\t\t\t\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\t\t\t`Failed to delete corrupted cache file ${filePath}:`,\n\t\t\t\t\t\t\t\t\tdeleteError,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\treturn null\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// For other errors, don't retry\n\t\t\t\t\t\tthrow error\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// This should never be reached, but just in case\n\t\t\t\treturn null\n\t\t\t},\n\t\t\tasync set(key, entry) {\n\t\t\t\tconst filePath = path.join(cacheInstanceDir, md5(key))\n\t\t\t\tconst tempPath = `${filePath}.tmp`\n\t\t\t\tawait fsExtra.ensureDir(path.dirname(filePath))\n\t\t\t\t// Write to temp file first, then atomically move to final location\n\t\t\t\t// This prevents race conditions where readers see partially written JSON files\n\t\t\t\tawait fsExtra.writeJSON(tempPath, { key, entry })\n\t\t\t\tawait fsExtra.move(tempPath, filePath, { overwrite: true })\n\t\t\t},\n\t\t\tasync delete(key) {\n\t\t\t\tconst filePath = path.join(cacheInstanceDir, md5(key))\n\t\t\t\tawait fsExtra.remove(filePath)\n\t\t\t},\n\t\t}\n\n\t\treturn fsCache\n\t})\n}\n\n/**\n * This wraps @epic-web/cachified to add a few handy features:\n *\n * 1. Automatic timing for timing headers\n * 2. Automatic force refresh based on the request and enhancement of forceFresh\n * to support comma-separated keys to force\n * 3. Offline fallback support. If a fallback is given and we are detected to be\n * offline, then the cached value is used regardless of whether it's expired and\n * if one is not present then the given fallback will be used.\n */\nexport async function cachified<Value>({\n\trequest,\n\ttimings,\n\tkey,\n\ttimingKey = key.length > 18 ? `${key.slice(0, 7)}...${key.slice(-8)}` : key,\n\tofflineFallbackValue,\n\t...options\n}: Omit<C.CachifiedOptions<Value>, 'forceFresh'> & {\n\trequest?: Request\n\ttimings?: Timings\n\tforceFresh?: boolean | string\n\ttimingKey?: string\n\tofflineFallbackValue?: Value\n}): Promise<Value> {\n\tif (offlineFallbackValue !== undefined) {\n\t\tconst isOnline = await checkConnectionCached({ request, timings })\n\t\tif (!isOnline) {\n\t\t\tconst cacheEntry = await options.cache.get(key)\n\t\t\treturn cacheEntry?.value ?? offlineFallbackValue\n\t\t}\n\t}\n\tconst forceFresh = await shouldForceFresh({\n\t\tforceFresh: options.forceFresh,\n\t\trequest,\n\t\tkey,\n\t})\n\treturn C.cachified(\n\t\t{\n\t\t\t...options,\n\t\t\tkey,\n\t\t\tforceFresh,\n\t\t},\n\t\tC.mergeReporters(\n\t\t\tcachifiedTimingReporter(timings, timingKey),\n\t\t\tprocess.env.EPICSHOP_DEBUG_CACHE ? verboseReporter() : undefined,\n\t\t),\n\t)\n}\n\nexport async function shouldForceFresh({\n\tforceFresh,\n\trequest,\n\tkey,\n}: {\n\tforceFresh?: boolean | string\n\trequest?: Request\n\tkey?: string\n}) {\n\tif (typeof forceFresh === 'boolean') return forceFresh\n\tif (typeof forceFresh === 'string' && key) {\n\t\treturn forceFresh.split(',').includes(key)\n\t}\n\n\tif (!request) return false\n\tconst fresh = new URL(request.url).searchParams.get('fresh')\n\tif (typeof fresh !== 'string') return false\n\tif (fresh === '') return true\n\tif (!key) return false\n\n\treturn fresh.split(',').includes(key)\n}\n"]}
@@ -0,0 +1,14 @@
1
+ export declare function resolvePrimaryDir(): string;
2
+ export declare function resolveCacheDir(): string;
3
+ export declare function resolvePrimaryPath(fileName?: string): string;
4
+ export declare function resolveFallbackPath(fileName?: string): string;
5
+ export declare function saveJSON(data: unknown): Promise<{
6
+ path: string;
7
+ fallbackUsed: boolean;
8
+ }>;
9
+ export declare function loadJSON<T = unknown>(): Promise<{
10
+ path: string;
11
+ data: T | null;
12
+ }>;
13
+ export declare function migrateLegacyData(): Promise<void>;
14
+ //# sourceMappingURL=data-storage.server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-storage.server.d.ts","sourceRoot":"","sources":["../../src/data-storage.server.ts"],"names":[],"mappings":"AAQA,wBAAgB,iBAAiB,WAehC;AAED,wBAAgB,eAAe,WAc9B;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,SAAY,UAEtD;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,SAAY,UAGvD;AAmBD,wBAAsB,QAAQ,CAAC,IAAI,EAAE,OAAO;;;GAoB3C;AAED,wBAAsB,QAAQ,CAAC,CAAC,GAAG,OAAO;;UAQI,CAAC,GAAG,IAAI;GAIrD;AAED,wBAAsB,iBAAiB,kBAmEtC"}
@@ -0,0 +1,159 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { promises as fs } from 'node:fs';
3
+ import * as os from 'node:os';
4
+ import * as path from 'node:path';
5
+ const APP_NAME = 'epicshop';
6
+ const FILE_NAME = 'data.json';
7
+ export function resolvePrimaryDir() {
8
+ const p = process.platform;
9
+ if (p === 'darwin') {
10
+ return path.join(os.homedir(), 'Library', 'Application Support', APP_NAME);
11
+ }
12
+ if (p === 'win32') {
13
+ const base = process.env.LOCALAPPDATA ||
14
+ process.env.APPDATA ||
15
+ path.join(os.homedir(), 'AppData', 'Local');
16
+ return path.join(base, APP_NAME);
17
+ }
18
+ const base = process.env.XDG_STATE_HOME || path.join(os.homedir(), '.local', 'state');
19
+ return path.join(base, APP_NAME);
20
+ }
21
+ export function resolveCacheDir() {
22
+ const p = process.platform;
23
+ if (p === 'darwin') {
24
+ return path.join(os.homedir(), 'Library', 'Caches', APP_NAME);
25
+ }
26
+ if (p === 'win32') {
27
+ const base = process.env.LOCALAPPDATA ||
28
+ process.env.APPDATA ||
29
+ path.join(os.homedir(), 'AppData', 'Local');
30
+ return path.join(base, APP_NAME, 'Cache');
31
+ }
32
+ const base = process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache');
33
+ return path.join(base, APP_NAME);
34
+ }
35
+ export function resolvePrimaryPath(fileName = FILE_NAME) {
36
+ return path.join(resolvePrimaryDir(), fileName);
37
+ }
38
+ export function resolveFallbackPath(fileName = FILE_NAME) {
39
+ const dir = path.join(os.tmpdir(), APP_NAME);
40
+ return path.join(dir, fileName);
41
+ }
42
+ async function ensureDir(dir) {
43
+ try {
44
+ await fs.mkdir(dir, { recursive: true, mode: 0o700 });
45
+ }
46
+ catch { }
47
+ try {
48
+ await fs.chmod(dir, 0o700);
49
+ }
50
+ catch { }
51
+ }
52
+ async function atomicWriteJSON(filePath, data) {
53
+ const dir = path.dirname(filePath);
54
+ await ensureDir(dir);
55
+ const tmp = path.join(dir, `.tmp-${randomUUID()}`);
56
+ await fs.writeFile(tmp, JSON.stringify(data, null, 2), { mode: 0o600 });
57
+ await fs.rename(tmp, filePath);
58
+ }
59
+ export async function saveJSON(data) {
60
+ const primary = resolvePrimaryPath(FILE_NAME);
61
+ try {
62
+ await atomicWriteJSON(primary, data);
63
+ return { path: primary, fallbackUsed: false };
64
+ }
65
+ catch (err) {
66
+ if (err?.code !== 'EACCES' && err?.code !== 'EPERM')
67
+ throw err;
68
+ // Wrong ownership or preexisting read-only file; try to replace it once
69
+ try {
70
+ await fs.unlink(primary);
71
+ await atomicWriteJSON(primary, data);
72
+ return { path: primary, fallbackUsed: false };
73
+ }
74
+ catch { }
75
+ const fallback = resolveFallbackPath(FILE_NAME);
76
+ await ensureDir(path.dirname(fallback));
77
+ await atomicWriteJSON(fallback, data);
78
+ return { path: fallback, fallbackUsed: true };
79
+ }
80
+ }
81
+ export async function loadJSON() {
82
+ const candidates = [
83
+ resolvePrimaryPath(FILE_NAME),
84
+ resolveFallbackPath(FILE_NAME),
85
+ ];
86
+ for (const p of candidates) {
87
+ try {
88
+ const txt = await fs.readFile(p, 'utf8');
89
+ return { path: p, data: JSON.parse(txt) };
90
+ }
91
+ catch { }
92
+ }
93
+ return { path: resolvePrimaryPath(FILE_NAME), data: null };
94
+ }
95
+ export async function migrateLegacyData() {
96
+ const legacyDir = path.join(os.homedir(), '.epicshop');
97
+ const legacyDataPath = path.join(legacyDir, FILE_NAME);
98
+ const legacyCachePath = path.join(legacyDir, 'cache');
99
+ const primaryDataPath = resolvePrimaryPath(FILE_NAME);
100
+ const primaryCachePath = resolveCacheDir();
101
+ try {
102
+ // Check if legacy directory exists
103
+ const legacyDirStat = await fs.stat(legacyDir);
104
+ if (!legacyDirStat.isDirectory()) {
105
+ return;
106
+ }
107
+ // Migrate data file if it exists
108
+ try {
109
+ const dataStat = await fs.stat(legacyDataPath);
110
+ if (dataStat.isFile()) {
111
+ await ensureDir(path.dirname(primaryDataPath));
112
+ await fs.rename(legacyDataPath, primaryDataPath);
113
+ try {
114
+ await fs.chmod(primaryDataPath, 0o600);
115
+ }
116
+ catch { }
117
+ }
118
+ }
119
+ catch (err) {
120
+ // Log permission errors but continue with other migrations
121
+ if (err?.code === 'EACCES' || err?.code === 'EPERM') {
122
+ console.warn(`Legacy data file exists but is unreadable: ${legacyDataPath}. You can fix ownership or manually import it in-app.`);
123
+ }
124
+ }
125
+ // Migrate cache directory if it exists
126
+ try {
127
+ const cacheStat = await fs.stat(legacyCachePath);
128
+ if (cacheStat.isDirectory()) {
129
+ await ensureDir(path.dirname(primaryCachePath));
130
+ await fs.rename(legacyCachePath, primaryCachePath);
131
+ try {
132
+ await fs.chmod(primaryCachePath, 0o700);
133
+ }
134
+ catch { }
135
+ }
136
+ }
137
+ catch (err) {
138
+ // Log permission errors but continue with other migrations
139
+ if (err?.code === 'EACCES' || err?.code === 'EPERM') {
140
+ console.warn(`Legacy cache directory exists but is unreadable: ${legacyCachePath}. You can fix ownership or manually move it.`);
141
+ }
142
+ }
143
+ // Try to remove the legacy directory if it's empty
144
+ try {
145
+ const remainingFiles = await fs.readdir(legacyDir);
146
+ if (remainingFiles.length === 0) {
147
+ await fs.rmdir(legacyDir);
148
+ }
149
+ }
150
+ catch { }
151
+ }
152
+ catch (err) {
153
+ // If we can't access the legacy directory at all, log and continue
154
+ if (err?.code === 'EACCES' || err?.code === 'EPERM') {
155
+ console.warn(`Legacy directory exists but is unreadable: ${legacyDir}. You can fix delete it and start fresh, fix ownership, or manually migrate the data.`);
156
+ }
157
+ }
158
+ }
159
+ //# sourceMappingURL=data-storage.server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-storage.server.js","sourceRoot":"","sources":["../../src/data-storage.server.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from 'node:crypto'\nimport { promises as fs } from 'node:fs'\nimport * as os from 'node:os'\nimport * as path from 'node:path'\n\nconst APP_NAME = 'epicshop'\nconst FILE_NAME = 'data.json'\n\nexport function resolvePrimaryDir() {\n\tconst p = process.platform\n\tif (p === 'darwin') {\n\t\treturn path.join(os.homedir(), 'Library', 'Application Support', APP_NAME)\n\t}\n\tif (p === 'win32') {\n\t\tconst base =\n\t\t\tprocess.env.LOCALAPPDATA ||\n\t\t\tprocess.env.APPDATA ||\n\t\t\tpath.join(os.homedir(), 'AppData', 'Local')\n\t\treturn path.join(base, APP_NAME)\n\t}\n\tconst base =\n\t\tprocess.env.XDG_STATE_HOME || path.join(os.homedir(), '.local', 'state')\n\treturn path.join(base, APP_NAME)\n}\n\nexport function resolveCacheDir() {\n\tconst p = process.platform\n\tif (p === 'darwin') {\n\t\treturn path.join(os.homedir(), 'Library', 'Caches', APP_NAME)\n\t}\n\tif (p === 'win32') {\n\t\tconst base =\n\t\t\tprocess.env.LOCALAPPDATA ||\n\t\t\tprocess.env.APPDATA ||\n\t\t\tpath.join(os.homedir(), 'AppData', 'Local')\n\t\treturn path.join(base, APP_NAME, 'Cache')\n\t}\n\tconst base = process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache')\n\treturn path.join(base, APP_NAME)\n}\n\nexport function resolvePrimaryPath(fileName = FILE_NAME) {\n\treturn path.join(resolvePrimaryDir(), fileName)\n}\n\nexport function resolveFallbackPath(fileName = FILE_NAME) {\n\tconst dir = path.join(os.tmpdir(), APP_NAME)\n\treturn path.join(dir, fileName)\n}\n\nasync function ensureDir(dir: string) {\n\ttry {\n\t\tawait fs.mkdir(dir, { recursive: true, mode: 0o700 })\n\t} catch {}\n\ttry {\n\t\tawait fs.chmod(dir, 0o700)\n\t} catch {}\n}\n\nasync function atomicWriteJSON(filePath: string, data: unknown) {\n\tconst dir = path.dirname(filePath)\n\tawait ensureDir(dir)\n\tconst tmp = path.join(dir, `.tmp-${randomUUID()}`)\n\tawait fs.writeFile(tmp, JSON.stringify(data, null, 2), { mode: 0o600 })\n\tawait fs.rename(tmp, filePath)\n}\n\nexport async function saveJSON(data: unknown) {\n\tconst primary = resolvePrimaryPath(FILE_NAME)\n\ttry {\n\t\tawait atomicWriteJSON(primary, data)\n\t\treturn { path: primary, fallbackUsed: false }\n\t} catch (err: any) {\n\t\tif (err?.code !== 'EACCES' && err?.code !== 'EPERM') throw err\n\n\t\t// Wrong ownership or preexisting read-only file; try to replace it once\n\t\ttry {\n\t\t\tawait fs.unlink(primary)\n\t\t\tawait atomicWriteJSON(primary, data)\n\t\t\treturn { path: primary, fallbackUsed: false }\n\t\t} catch {}\n\n\t\tconst fallback = resolveFallbackPath(FILE_NAME)\n\t\tawait ensureDir(path.dirname(fallback))\n\t\tawait atomicWriteJSON(fallback, data)\n\t\treturn { path: fallback, fallbackUsed: true }\n\t}\n}\n\nexport async function loadJSON<T = unknown>() {\n\tconst candidates = [\n\t\tresolvePrimaryPath(FILE_NAME),\n\t\tresolveFallbackPath(FILE_NAME),\n\t]\n\tfor (const p of candidates) {\n\t\ttry {\n\t\t\tconst txt = await fs.readFile(p, 'utf8')\n\t\t\treturn { path: p, data: JSON.parse(txt) as T | null }\n\t\t} catch {}\n\t}\n\treturn { path: resolvePrimaryPath(FILE_NAME), data: null }\n}\n\nexport async function migrateLegacyData() {\n\tconst legacyDir = path.join(os.homedir(), '.epicshop')\n\tconst legacyDataPath = path.join(legacyDir, FILE_NAME)\n\tconst legacyCachePath = path.join(legacyDir, 'cache')\n\tconst primaryDataPath = resolvePrimaryPath(FILE_NAME)\n\tconst primaryCachePath = resolveCacheDir()\n\n\ttry {\n\t\t// Check if legacy directory exists\n\t\tconst legacyDirStat = await fs.stat(legacyDir)\n\t\tif (!legacyDirStat.isDirectory()) {\n\t\t\treturn\n\t\t}\n\n\t\t// Migrate data file if it exists\n\t\ttry {\n\t\t\tconst dataStat = await fs.stat(legacyDataPath)\n\t\t\tif (dataStat.isFile()) {\n\t\t\t\tawait ensureDir(path.dirname(primaryDataPath))\n\t\t\t\tawait fs.rename(legacyDataPath, primaryDataPath)\n\t\t\t\ttry {\n\t\t\t\t\tawait fs.chmod(primaryDataPath, 0o600)\n\t\t\t\t} catch {}\n\t\t\t}\n\t\t} catch (err: any) {\n\t\t\t// Log permission errors but continue with other migrations\n\t\t\tif (err?.code === 'EACCES' || err?.code === 'EPERM') {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`Legacy data file exists but is unreadable: ${legacyDataPath}. You can fix ownership or manually import it in-app.`,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\t// Migrate cache directory if it exists\n\t\ttry {\n\t\t\tconst cacheStat = await fs.stat(legacyCachePath)\n\t\t\tif (cacheStat.isDirectory()) {\n\t\t\t\tawait ensureDir(path.dirname(primaryCachePath))\n\t\t\t\tawait fs.rename(legacyCachePath, primaryCachePath)\n\t\t\t\ttry {\n\t\t\t\t\tawait fs.chmod(primaryCachePath, 0o700)\n\t\t\t\t} catch {}\n\t\t\t}\n\t\t} catch (err: any) {\n\t\t\t// Log permission errors but continue with other migrations\n\t\t\tif (err?.code === 'EACCES' || err?.code === 'EPERM') {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`Legacy cache directory exists but is unreadable: ${legacyCachePath}. You can fix ownership or manually move it.`,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\t// Try to remove the legacy directory if it's empty\n\t\ttry {\n\t\t\tconst remainingFiles = await fs.readdir(legacyDir)\n\t\t\tif (remainingFiles.length === 0) {\n\t\t\t\tawait fs.rmdir(legacyDir)\n\t\t\t}\n\t\t} catch {}\n\t} catch (err: any) {\n\t\t// If we can't access the legacy directory at all, log and continue\n\t\tif (err?.code === 'EACCES' || err?.code === 'EPERM') {\n\t\t\tconsole.warn(\n\t\t\t\t`Legacy directory exists but is unreadable: ${legacyDir}. You can fix delete it and start fresh, fix ownership, or manually migrate the data.`,\n\t\t\t)\n\t\t}\n\t}\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=data-storage.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-storage.test.d.ts","sourceRoot":"","sources":["../../src/data-storage.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,339 @@
1
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
2
+ if (value !== null && value !== void 0) {
3
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
4
+ var dispose, inner;
5
+ if (async) {
6
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
7
+ dispose = value[Symbol.asyncDispose];
8
+ }
9
+ if (dispose === void 0) {
10
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
11
+ dispose = value[Symbol.dispose];
12
+ if (async) inner = dispose;
13
+ }
14
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
15
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
16
+ env.stack.push({ value: value, dispose: dispose, async: async });
17
+ }
18
+ else if (async) {
19
+ env.stack.push({ async: true });
20
+ }
21
+ return value;
22
+ };
23
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
24
+ return function (env) {
25
+ function fail(e) {
26
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
27
+ env.hasError = true;
28
+ }
29
+ var r, s = 0;
30
+ function next() {
31
+ while (r = env.stack.pop()) {
32
+ try {
33
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
34
+ if (r.dispose) {
35
+ var result = r.dispose.call(r.value);
36
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
37
+ }
38
+ else s |= 1;
39
+ }
40
+ catch (e) {
41
+ fail(e);
42
+ }
43
+ }
44
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
45
+ if (env.hasError) throw env.error;
46
+ }
47
+ return next();
48
+ };
49
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
50
+ var e = new Error(message);
51
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
+ });
53
+ import { promises as fs } from 'node:fs';
54
+ import * as os from 'node:os';
55
+ import { test, expect, vi, beforeEach, afterEach } from 'vitest';
56
+ import { resolvePrimaryDir, resolveCacheDir, migrateLegacyData, } from './data-storage.server.js';
57
+ // Mock fs and os modules
58
+ vi.mock('node:fs', () => ({
59
+ promises: {
60
+ stat: vi.fn(),
61
+ mkdir: vi.fn(),
62
+ chmod: vi.fn(),
63
+ rename: vi.fn(),
64
+ rmdir: vi.fn(),
65
+ readdir: vi.fn(),
66
+ },
67
+ }));
68
+ vi.mock('node:os', () => ({
69
+ homedir: vi.fn(() => '/mock/home'),
70
+ }));
71
+ const mockFs = vi.mocked(fs);
72
+ const mockOs = vi.mocked(os);
73
+ beforeEach(() => {
74
+ vi.clearAllMocks();
75
+ mockOs.homedir.mockReturnValue('/mock/home');
76
+ });
77
+ afterEach(() => {
78
+ vi.restoreAllMocks();
79
+ });
80
+ function withPlatform(platform, envVars = {}) {
81
+ const originalPlatform = process.platform;
82
+ const originalEnvVars = {};
83
+ // Store original environment variables
84
+ for (const key of Object.keys(envVars)) {
85
+ originalEnvVars[key] = process.env[key];
86
+ }
87
+ // Set platform and environment variables
88
+ Object.defineProperty(process, 'platform', { value: platform });
89
+ for (const [key, value] of Object.entries(envVars)) {
90
+ if (value === undefined) {
91
+ delete process.env[key];
92
+ }
93
+ else {
94
+ process.env[key] = value;
95
+ }
96
+ }
97
+ return {
98
+ [Symbol.dispose]() {
99
+ // Restore original platform
100
+ Object.defineProperty(process, 'platform', { value: originalPlatform });
101
+ // Restore original environment variables
102
+ for (const [key, originalValue] of Object.entries(originalEnvVars)) {
103
+ if (originalValue === undefined) {
104
+ delete process.env[key];
105
+ }
106
+ else {
107
+ process.env[key] = originalValue;
108
+ }
109
+ }
110
+ },
111
+ };
112
+ }
113
+ test('resolvePrimaryDir returns correct path for darwin', () => {
114
+ const env_1 = { stack: [], error: void 0, hasError: false };
115
+ try {
116
+ const _ = __addDisposableResource(env_1, withPlatform('darwin'), false);
117
+ const result = resolvePrimaryDir();
118
+ expect(result).toBe('/mock/home/Library/Application Support/epicshop');
119
+ }
120
+ catch (e_1) {
121
+ env_1.error = e_1;
122
+ env_1.hasError = true;
123
+ }
124
+ finally {
125
+ __disposeResources(env_1);
126
+ }
127
+ });
128
+ test('resolvePrimaryDir returns correct path for win32', () => {
129
+ const env_2 = { stack: [], error: void 0, hasError: false };
130
+ try {
131
+ const _ = __addDisposableResource(env_2, withPlatform('win32', {
132
+ LOCALAPPDATA: undefined,
133
+ APPDATA: undefined,
134
+ }), false);
135
+ const result = resolvePrimaryDir();
136
+ expect(result).toBe('/mock/home/AppData/Local/epicshop');
137
+ }
138
+ catch (e_2) {
139
+ env_2.error = e_2;
140
+ env_2.hasError = true;
141
+ }
142
+ finally {
143
+ __disposeResources(env_2);
144
+ }
145
+ });
146
+ test('resolvePrimaryDir returns correct path for linux', () => {
147
+ const env_3 = { stack: [], error: void 0, hasError: false };
148
+ try {
149
+ const _ = __addDisposableResource(env_3, withPlatform('linux', {
150
+ XDG_STATE_HOME: undefined,
151
+ }), false);
152
+ const result = resolvePrimaryDir();
153
+ expect(result).toBe('/mock/home/.local/state/epicshop');
154
+ }
155
+ catch (e_3) {
156
+ env_3.error = e_3;
157
+ env_3.hasError = true;
158
+ }
159
+ finally {
160
+ __disposeResources(env_3);
161
+ }
162
+ });
163
+ test('resolveCacheDir returns correct path for darwin', () => {
164
+ const env_4 = { stack: [], error: void 0, hasError: false };
165
+ try {
166
+ const _ = __addDisposableResource(env_4, withPlatform('darwin'), false);
167
+ const result = resolveCacheDir();
168
+ expect(result).toBe('/mock/home/Library/Caches/epicshop');
169
+ }
170
+ catch (e_4) {
171
+ env_4.error = e_4;
172
+ env_4.hasError = true;
173
+ }
174
+ finally {
175
+ __disposeResources(env_4);
176
+ }
177
+ });
178
+ test('resolveCacheDir returns correct path for win32', () => {
179
+ const env_5 = { stack: [], error: void 0, hasError: false };
180
+ try {
181
+ const _ = __addDisposableResource(env_5, withPlatform('win32', {
182
+ LOCALAPPDATA: undefined,
183
+ APPDATA: undefined,
184
+ }), false);
185
+ const result = resolveCacheDir();
186
+ expect(result).toBe('/mock/home/AppData/Local/epicshop/Cache');
187
+ }
188
+ catch (e_5) {
189
+ env_5.error = e_5;
190
+ env_5.hasError = true;
191
+ }
192
+ finally {
193
+ __disposeResources(env_5);
194
+ }
195
+ });
196
+ test('resolveCacheDir returns correct path for linux', () => {
197
+ const env_6 = { stack: [], error: void 0, hasError: false };
198
+ try {
199
+ const _ = __addDisposableResource(env_6, withPlatform('linux', {
200
+ XDG_CACHE_HOME: undefined,
201
+ }), false);
202
+ const result = resolveCacheDir();
203
+ expect(result).toBe('/mock/home/.cache/epicshop');
204
+ }
205
+ catch (e_6) {
206
+ env_6.error = e_6;
207
+ env_6.hasError = true;
208
+ }
209
+ finally {
210
+ __disposeResources(env_6);
211
+ }
212
+ });
213
+ test('migrateLegacyData successfully migrates both data and cache on darwin', async () => {
214
+ const env_7 = { stack: [], error: void 0, hasError: false };
215
+ try {
216
+ const _ = __addDisposableResource(env_7, withPlatform('darwin'), false);
217
+ const legacyDataPath = '/mock/home/.epicshop/data.json';
218
+ const legacyCachePath = '/mock/home/.epicshop/cache';
219
+ const primaryDataPath = '/mock/home/Library/Application Support/epicshop/data.json';
220
+ const primaryCachePath = '/mock/home/Library/Caches/epicshop';
221
+ // Mock directory exists
222
+ mockFs.stat
223
+ .mockResolvedValueOnce({ isDirectory: () => true }) // legacyDir
224
+ .mockResolvedValueOnce({ isFile: () => true }) // data file
225
+ .mockResolvedValueOnce({ isDirectory: () => true }); // cache dir
226
+ mockFs.mkdir.mockResolvedValue(undefined);
227
+ mockFs.rename.mockResolvedValue(undefined);
228
+ mockFs.chmod.mockResolvedValue(undefined);
229
+ mockFs.readdir.mockResolvedValue([]); // empty directory
230
+ mockFs.rmdir.mockResolvedValue(undefined);
231
+ await migrateLegacyData();
232
+ expect(mockFs.rename).toHaveBeenCalledWith(legacyDataPath, primaryDataPath);
233
+ expect(mockFs.rename).toHaveBeenCalledWith(legacyCachePath, primaryCachePath);
234
+ expect(mockFs.rmdir).toHaveBeenCalledWith('/mock/home/.epicshop');
235
+ }
236
+ catch (e_7) {
237
+ env_7.error = e_7;
238
+ env_7.hasError = true;
239
+ }
240
+ finally {
241
+ __disposeResources(env_7);
242
+ }
243
+ });
244
+ test('migrateLegacyData successfully migrates both data and cache on win32', async () => {
245
+ const env_8 = { stack: [], error: void 0, hasError: false };
246
+ try {
247
+ const _ = __addDisposableResource(env_8, withPlatform('win32', {
248
+ LOCALAPPDATA: undefined,
249
+ APPDATA: undefined,
250
+ }), false);
251
+ const legacyDataPath = '/mock/home/.epicshop/data.json';
252
+ const legacyCachePath = '/mock/home/.epicshop/cache';
253
+ const primaryDataPath = '/mock/home/AppData/Local/epicshop/data.json';
254
+ const primaryCachePath = '/mock/home/AppData/Local/epicshop/Cache';
255
+ // Mock directory exists
256
+ mockFs.stat
257
+ .mockResolvedValueOnce({ isDirectory: () => true }) // legacyDir
258
+ .mockResolvedValueOnce({ isFile: () => true }) // data file
259
+ .mockResolvedValueOnce({ isDirectory: () => true }); // cache dir
260
+ mockFs.mkdir.mockResolvedValue(undefined);
261
+ mockFs.rename.mockResolvedValue(undefined);
262
+ mockFs.chmod.mockResolvedValue(undefined);
263
+ mockFs.readdir.mockResolvedValue([]); // empty directory
264
+ mockFs.rmdir.mockResolvedValue(undefined);
265
+ await migrateLegacyData();
266
+ expect(mockFs.rename).toHaveBeenCalledWith(legacyDataPath, primaryDataPath);
267
+ expect(mockFs.rename).toHaveBeenCalledWith(legacyCachePath, primaryCachePath);
268
+ expect(mockFs.rmdir).toHaveBeenCalledWith('/mock/home/.epicshop');
269
+ }
270
+ catch (e_8) {
271
+ env_8.error = e_8;
272
+ env_8.hasError = true;
273
+ }
274
+ finally {
275
+ __disposeResources(env_8);
276
+ }
277
+ });
278
+ test('migrateLegacyData successfully migrates both data and cache on linux', async () => {
279
+ const env_9 = { stack: [], error: void 0, hasError: false };
280
+ try {
281
+ const _ = __addDisposableResource(env_9, withPlatform('linux', {
282
+ XDG_STATE_HOME: undefined,
283
+ XDG_CACHE_HOME: undefined,
284
+ }), false);
285
+ const legacyDataPath = '/mock/home/.epicshop/data.json';
286
+ const legacyCachePath = '/mock/home/.epicshop/cache';
287
+ const primaryDataPath = '/mock/home/.local/state/epicshop/data.json';
288
+ const primaryCachePath = '/mock/home/.cache/epicshop';
289
+ // Mock directory exists
290
+ mockFs.stat
291
+ .mockResolvedValueOnce({ isDirectory: () => true }) // legacyDir
292
+ .mockResolvedValueOnce({ isFile: () => true }) // data file
293
+ .mockResolvedValueOnce({ isDirectory: () => true }); // cache dir
294
+ mockFs.mkdir.mockResolvedValue(undefined);
295
+ mockFs.rename.mockResolvedValue(undefined);
296
+ mockFs.chmod.mockResolvedValue(undefined);
297
+ mockFs.readdir.mockResolvedValue([]); // empty directory
298
+ mockFs.rmdir.mockResolvedValue(undefined);
299
+ await migrateLegacyData();
300
+ expect(mockFs.rename).toHaveBeenCalledWith(legacyDataPath, primaryDataPath);
301
+ expect(mockFs.rename).toHaveBeenCalledWith(legacyCachePath, primaryCachePath);
302
+ expect(mockFs.rmdir).toHaveBeenCalledWith('/mock/home/.epicshop');
303
+ }
304
+ catch (e_9) {
305
+ env_9.error = e_9;
306
+ env_9.hasError = true;
307
+ }
308
+ finally {
309
+ __disposeResources(env_9);
310
+ }
311
+ });
312
+ test('migrateLegacyData handles missing legacy directory', async () => {
313
+ mockFs.stat.mockRejectedValue({ code: 'ENOENT' });
314
+ await migrateLegacyData();
315
+ expect(mockFs.rename).not.toHaveBeenCalled();
316
+ expect(mockFs.rmdir).not.toHaveBeenCalled();
317
+ });
318
+ test('migrateLegacyData handles permission errors gracefully', async () => {
319
+ const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
320
+ mockFs.stat.mockRejectedValue({ code: 'EACCES' });
321
+ await migrateLegacyData();
322
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Legacy directory exists but is unreadable'));
323
+ consoleSpy.mockRestore();
324
+ });
325
+ test('migrateLegacyData removes legacy directory when empty', async () => {
326
+ mockFs.stat
327
+ .mockResolvedValueOnce({ isDirectory: () => true }) // legacyDir
328
+ .mockResolvedValueOnce({ isFile: () => false }) // no data file
329
+ .mockResolvedValueOnce({ isDirectory: () => false }); // no cache dir
330
+ mockFs.readdir.mockResolvedValue([]); // empty directory
331
+ mockFs.rmdir.mockResolvedValue(undefined);
332
+ await migrateLegacyData();
333
+ expect(mockFs.rmdir).toHaveBeenCalledWith('/mock/home/.epicshop');
334
+ });
335
+ /*
336
+ eslint
337
+ @typescript-eslint/no-unused-vars: "off",
338
+ */
339
+ //# sourceMappingURL=data-storage.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-storage.test.js","sourceRoot":"","sources":["../../src/data-storage.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { promises as fs } from 'node:fs'\nimport * as os from 'node:os'\nimport { test, expect, vi, beforeEach, afterEach } from 'vitest'\nimport {\n\tresolvePrimaryDir,\n\tresolveCacheDir,\n\tmigrateLegacyData,\n} from './data-storage.server.js'\n\n// Mock fs and os modules\nvi.mock('node:fs', () => ({\n\tpromises: {\n\t\tstat: vi.fn(),\n\t\tmkdir: vi.fn(),\n\t\tchmod: vi.fn(),\n\t\trename: vi.fn(),\n\t\trmdir: vi.fn(),\n\t\treaddir: vi.fn(),\n\t},\n}))\n\nvi.mock('node:os', () => ({\n\thomedir: vi.fn(() => '/mock/home'),\n}))\n\nconst mockFs = vi.mocked(fs)\nconst mockOs = vi.mocked(os)\n\nbeforeEach(() => {\n\tvi.clearAllMocks()\n\tmockOs.homedir.mockReturnValue('/mock/home')\n})\n\nafterEach(() => {\n\tvi.restoreAllMocks()\n})\n\nfunction withPlatform(\n\tplatform: string,\n\tenvVars: Record<string, string | undefined> = {},\n) {\n\tconst originalPlatform = process.platform\n\tconst originalEnvVars: Record<string, string | undefined> = {}\n\n\t// Store original environment variables\n\tfor (const key of Object.keys(envVars)) {\n\t\toriginalEnvVars[key] = process.env[key]\n\t}\n\n\t// Set platform and environment variables\n\tObject.defineProperty(process, 'platform', { value: platform })\n\tfor (const [key, value] of Object.entries(envVars)) {\n\t\tif (value === undefined) {\n\t\t\tdelete process.env[key]\n\t\t} else {\n\t\t\tprocess.env[key] = value\n\t\t}\n\t}\n\n\treturn {\n\t\t[Symbol.dispose]() {\n\t\t\t// Restore original platform\n\t\t\tObject.defineProperty(process, 'platform', { value: originalPlatform })\n\n\t\t\t// Restore original environment variables\n\t\t\tfor (const [key, originalValue] of Object.entries(originalEnvVars)) {\n\t\t\t\tif (originalValue === undefined) {\n\t\t\t\t\tdelete process.env[key]\n\t\t\t\t} else {\n\t\t\t\t\tprocess.env[key] = originalValue\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t}\n}\n\ntest('resolvePrimaryDir returns correct path for darwin', () => {\n\tusing _ = withPlatform('darwin')\n\n\tconst result = resolvePrimaryDir()\n\texpect(result).toBe('/mock/home/Library/Application Support/epicshop')\n})\n\ntest('resolvePrimaryDir returns correct path for win32', () => {\n\tusing _ = withPlatform('win32', {\n\t\tLOCALAPPDATA: undefined,\n\t\tAPPDATA: undefined,\n\t})\n\n\tconst result = resolvePrimaryDir()\n\texpect(result).toBe('/mock/home/AppData/Local/epicshop')\n})\n\ntest('resolvePrimaryDir returns correct path for linux', () => {\n\tusing _ = withPlatform('linux', {\n\t\tXDG_STATE_HOME: undefined,\n\t})\n\n\tconst result = resolvePrimaryDir()\n\texpect(result).toBe('/mock/home/.local/state/epicshop')\n})\n\ntest('resolveCacheDir returns correct path for darwin', () => {\n\tusing _ = withPlatform('darwin')\n\n\tconst result = resolveCacheDir()\n\texpect(result).toBe('/mock/home/Library/Caches/epicshop')\n})\n\ntest('resolveCacheDir returns correct path for win32', () => {\n\tusing _ = withPlatform('win32', {\n\t\tLOCALAPPDATA: undefined,\n\t\tAPPDATA: undefined,\n\t})\n\n\tconst result = resolveCacheDir()\n\texpect(result).toBe('/mock/home/AppData/Local/epicshop/Cache')\n})\n\ntest('resolveCacheDir returns correct path for linux', () => {\n\tusing _ = withPlatform('linux', {\n\t\tXDG_CACHE_HOME: undefined,\n\t})\n\n\tconst result = resolveCacheDir()\n\texpect(result).toBe('/mock/home/.cache/epicshop')\n})\n\ntest('migrateLegacyData successfully migrates both data and cache on darwin', async () => {\n\tusing _ = withPlatform('darwin')\n\n\tconst legacyDataPath = '/mock/home/.epicshop/data.json'\n\tconst legacyCachePath = '/mock/home/.epicshop/cache'\n\tconst primaryDataPath =\n\t\t'/mock/home/Library/Application Support/epicshop/data.json'\n\tconst primaryCachePath = '/mock/home/Library/Caches/epicshop'\n\n\t// Mock directory exists\n\tmockFs.stat\n\t\t.mockResolvedValueOnce({ isDirectory: () => true } as any) // legacyDir\n\t\t.mockResolvedValueOnce({ isFile: () => true } as any) // data file\n\t\t.mockResolvedValueOnce({ isDirectory: () => true } as any) // cache dir\n\n\tmockFs.mkdir.mockResolvedValue(undefined)\n\tmockFs.rename.mockResolvedValue(undefined)\n\tmockFs.chmod.mockResolvedValue(undefined)\n\tmockFs.readdir.mockResolvedValue([]) // empty directory\n\tmockFs.rmdir.mockResolvedValue(undefined)\n\n\tawait migrateLegacyData()\n\n\texpect(mockFs.rename).toHaveBeenCalledWith(legacyDataPath, primaryDataPath)\n\texpect(mockFs.rename).toHaveBeenCalledWith(legacyCachePath, primaryCachePath)\n\texpect(mockFs.rmdir).toHaveBeenCalledWith('/mock/home/.epicshop')\n})\n\ntest('migrateLegacyData successfully migrates both data and cache on win32', async () => {\n\tusing _ = withPlatform('win32', {\n\t\tLOCALAPPDATA: undefined,\n\t\tAPPDATA: undefined,\n\t})\n\n\tconst legacyDataPath = '/mock/home/.epicshop/data.json'\n\tconst legacyCachePath = '/mock/home/.epicshop/cache'\n\tconst primaryDataPath = '/mock/home/AppData/Local/epicshop/data.json'\n\tconst primaryCachePath = '/mock/home/AppData/Local/epicshop/Cache'\n\n\t// Mock directory exists\n\tmockFs.stat\n\t\t.mockResolvedValueOnce({ isDirectory: () => true } as any) // legacyDir\n\t\t.mockResolvedValueOnce({ isFile: () => true } as any) // data file\n\t\t.mockResolvedValueOnce({ isDirectory: () => true } as any) // cache dir\n\n\tmockFs.mkdir.mockResolvedValue(undefined)\n\tmockFs.rename.mockResolvedValue(undefined)\n\tmockFs.chmod.mockResolvedValue(undefined)\n\tmockFs.readdir.mockResolvedValue([]) // empty directory\n\tmockFs.rmdir.mockResolvedValue(undefined)\n\n\tawait migrateLegacyData()\n\n\texpect(mockFs.rename).toHaveBeenCalledWith(legacyDataPath, primaryDataPath)\n\texpect(mockFs.rename).toHaveBeenCalledWith(legacyCachePath, primaryCachePath)\n\texpect(mockFs.rmdir).toHaveBeenCalledWith('/mock/home/.epicshop')\n})\n\ntest('migrateLegacyData successfully migrates both data and cache on linux', async () => {\n\tusing _ = withPlatform('linux', {\n\t\tXDG_STATE_HOME: undefined,\n\t\tXDG_CACHE_HOME: undefined,\n\t})\n\n\tconst legacyDataPath = '/mock/home/.epicshop/data.json'\n\tconst legacyCachePath = '/mock/home/.epicshop/cache'\n\tconst primaryDataPath = '/mock/home/.local/state/epicshop/data.json'\n\tconst primaryCachePath = '/mock/home/.cache/epicshop'\n\n\t// Mock directory exists\n\tmockFs.stat\n\t\t.mockResolvedValueOnce({ isDirectory: () => true } as any) // legacyDir\n\t\t.mockResolvedValueOnce({ isFile: () => true } as any) // data file\n\t\t.mockResolvedValueOnce({ isDirectory: () => true } as any) // cache dir\n\n\tmockFs.mkdir.mockResolvedValue(undefined)\n\tmockFs.rename.mockResolvedValue(undefined)\n\tmockFs.chmod.mockResolvedValue(undefined)\n\tmockFs.readdir.mockResolvedValue([]) // empty directory\n\tmockFs.rmdir.mockResolvedValue(undefined)\n\n\tawait migrateLegacyData()\n\n\texpect(mockFs.rename).toHaveBeenCalledWith(legacyDataPath, primaryDataPath)\n\texpect(mockFs.rename).toHaveBeenCalledWith(legacyCachePath, primaryCachePath)\n\texpect(mockFs.rmdir).toHaveBeenCalledWith('/mock/home/.epicshop')\n})\n\ntest('migrateLegacyData handles missing legacy directory', async () => {\n\tmockFs.stat.mockRejectedValue({ code: 'ENOENT' })\n\n\tawait migrateLegacyData()\n\n\texpect(mockFs.rename).not.toHaveBeenCalled()\n\texpect(mockFs.rmdir).not.toHaveBeenCalled()\n})\n\ntest('migrateLegacyData handles permission errors gracefully', async () => {\n\tconst consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})\n\tmockFs.stat.mockRejectedValue({ code: 'EACCES' })\n\n\tawait migrateLegacyData()\n\n\texpect(consoleSpy).toHaveBeenCalledWith(\n\t\texpect.stringContaining('Legacy directory exists but is unreadable'),\n\t)\n\tconsoleSpy.mockRestore()\n})\n\ntest('migrateLegacyData removes legacy directory when empty', async () => {\n\tmockFs.stat\n\t\t.mockResolvedValueOnce({ isDirectory: () => true } as any) // legacyDir\n\t\t.mockResolvedValueOnce({ isFile: () => false } as any) // no data file\n\t\t.mockResolvedValueOnce({ isDirectory: () => false } as any) // no cache dir\n\n\tmockFs.readdir.mockResolvedValue([]) // empty directory\n\tmockFs.rmdir.mockResolvedValue(undefined)\n\n\tawait migrateLegacyData()\n\n\texpect(mockFs.rmdir).toHaveBeenCalledWith('/mock/home/.epicshop')\n})\n\n/*\neslint\n\t@typescript-eslint/no-unused-vars: \"off\",\n*/\n"]}
@@ -23,11 +23,11 @@ export declare const PlayerPreferencesSchema: z.ZodDefault<z.ZodOptional<z.ZodOb
23
23
  id: z.ZodDefault<z.ZodNullable<z.ZodString>>;
24
24
  mode: z.ZodDefault<z.ZodNullable<z.ZodUnion<[z.ZodUnion<[z.ZodLiteral<"disabled">, z.ZodLiteral<"hidden">]>, z.ZodLiteral<"showing">]>>>;
25
25
  }, "strip", z.ZodTypeAny, {
26
- id: string | null;
27
26
  mode: "disabled" | "hidden" | "showing" | null;
27
+ id: string | null;
28
28
  }, {
29
- id?: string | null | undefined;
30
29
  mode?: "disabled" | "hidden" | "showing" | null | undefined;
30
+ id?: string | null | undefined;
31
31
  }>>>;
32
32
  muted: z.ZodOptional<z.ZodBoolean>;
33
33
  theater: z.ZodOptional<z.ZodBoolean>;
@@ -35,8 +35,8 @@ export declare const PlayerPreferencesSchema: z.ZodDefault<z.ZodOptional<z.ZodOb
35
35
  activeSidebarTab: z.ZodOptional<z.ZodNumber>;
36
36
  }, "strip", z.ZodTypeAny, {
37
37
  subtitle: {
38
- id: string | null;
39
38
  mode: "disabled" | "hidden" | "showing" | null;
39
+ id: string | null;
40
40
  };
41
41
  minResolution?: number | undefined;
42
42
  maxResolution?: number | undefined;
@@ -49,8 +49,8 @@ export declare const PlayerPreferencesSchema: z.ZodDefault<z.ZodOptional<z.ZodOb
49
49
  activeSidebarTab?: number | undefined;
50
50
  }, {
51
51
  subtitle?: {
52
- id?: string | null | undefined;
53
52
  mode?: "disabled" | "hidden" | "showing" | null | undefined;
53
+ id?: string | null | undefined;
54
54
  } | undefined;
55
55
  minResolution?: number | undefined;
56
56
  maxResolution?: number | undefined;
@@ -81,11 +81,11 @@ declare const DataSchema: z.ZodObject<{
81
81
  id: z.ZodDefault<z.ZodNullable<z.ZodString>>;
82
82
  mode: z.ZodDefault<z.ZodNullable<z.ZodUnion<[z.ZodUnion<[z.ZodLiteral<"disabled">, z.ZodLiteral<"hidden">]>, z.ZodLiteral<"showing">]>>>;
83
83
  }, "strip", z.ZodTypeAny, {
84
- id: string | null;
85
84
  mode: "disabled" | "hidden" | "showing" | null;
85
+ id: string | null;
86
86
  }, {
87
- id?: string | null | undefined;
88
87
  mode?: "disabled" | "hidden" | "showing" | null | undefined;
88
+ id?: string | null | undefined;
89
89
  }>>>;
90
90
  muted: z.ZodOptional<z.ZodBoolean>;
91
91
  theater: z.ZodOptional<z.ZodBoolean>;
@@ -93,8 +93,8 @@ declare const DataSchema: z.ZodObject<{
93
93
  activeSidebarTab: z.ZodOptional<z.ZodNumber>;
94
94
  }, "strip", z.ZodTypeAny, {
95
95
  subtitle: {
96
- id: string | null;
97
96
  mode: "disabled" | "hidden" | "showing" | null;
97
+ id: string | null;
98
98
  };
99
99
  minResolution?: number | undefined;
100
100
  maxResolution?: number | undefined;
@@ -107,8 +107,8 @@ declare const DataSchema: z.ZodObject<{
107
107
  activeSidebarTab?: number | undefined;
108
108
  }, {
109
109
  subtitle?: {
110
- id?: string | null | undefined;
111
110
  mode?: "disabled" | "hidden" | "showing" | null | undefined;
111
+ id?: string | null | undefined;
112
112
  } | undefined;
113
113
  minResolution?: number | undefined;
114
114
  maxResolution?: number | undefined;
@@ -145,8 +145,8 @@ declare const DataSchema: z.ZodObject<{
145
145
  }, "strip", z.ZodTypeAny, {
146
146
  player: {
147
147
  subtitle: {
148
- id: string | null;
149
148
  mode: "disabled" | "hidden" | "showing" | null;
149
+ id: string | null;
150
150
  };
151
151
  minResolution?: number | undefined;
152
152
  maxResolution?: number | undefined;
@@ -171,8 +171,8 @@ declare const DataSchema: z.ZodObject<{
171
171
  }, {
172
172
  player?: {
173
173
  subtitle?: {
174
- id?: string | null | undefined;
175
174
  mode?: "disabled" | "hidden" | "showing" | null | undefined;
175
+ id?: string | null | undefined;
176
176
  } | undefined;
177
177
  minResolution?: number | undefined;
178
178
  maxResolution?: number | undefined;
@@ -278,8 +278,8 @@ declare const DataSchema: z.ZodObject<{
278
278
  preferences: {
279
279
  player: {
280
280
  subtitle: {
281
- id: string | null;
282
281
  mode: "disabled" | "hidden" | "showing" | null;
282
+ id: string | null;
283
283
  };
284
284
  minResolution?: number | undefined;
285
285
  maxResolution?: number | undefined;
@@ -331,8 +331,8 @@ declare const DataSchema: z.ZodObject<{
331
331
  preferences?: {
332
332
  player?: {
333
333
  subtitle?: {
334
- id?: string | null | undefined;
335
334
  mode?: "disabled" | "hidden" | "showing" | null | undefined;
335
+ id?: string | null | undefined;
336
336
  } | undefined;
337
337
  minResolution?: number | undefined;
338
338
  maxResolution?: number | undefined;
@@ -422,8 +422,8 @@ export declare function setAuthInfo({ id, tokenSet, email, name, }: {
422
422
  export declare function getPreferences(): Promise<{
423
423
  player: {
424
424
  subtitle: {
425
- id: string | null;
426
425
  mode: "disabled" | "hidden" | "showing" | null;
426
+ id: string | null;
427
427
  };
428
428
  minResolution?: number | undefined;
429
429
  maxResolution?: number | undefined;
@@ -449,8 +449,8 @@ export declare function getPreferences(): Promise<{
449
449
  export declare function setPreferences(preferences: z.input<typeof DataSchema>['preferences']): Promise<{
450
450
  player: {
451
451
  subtitle?: {
452
- id?: string | null | undefined;
453
452
  mode?: "disabled" | "hidden" | "showing" | null | undefined;
453
+ id?: string | null | undefined;
454
454
  } | undefined;
455
455
  minResolution?: number | undefined;
456
456
  maxResolution?: number | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"db.server.d.ts","sourceRoot":"","sources":["../../src/db.server.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAA;AAMtB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,QAAA,MAAM,cAAc;;;;;;;;;;;;EAIlB,CAAA;AACF,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAyBvB,CAAA;AAkBb,QAAA,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiCd,CAAA;AAKF,wBAAsB,WAAW,oBAQhC;AAED,wBAAsB,MAAM,kBAY3B;AAED,wBAAsB,QAAQ,8BAU7B;AA2DD,wBAAsB,WAAW;;;;;;;;;sBAuBhC;AAED,wBAAsB,eAAe,CAAC,EACrC,OAAO,EACP,UAAU,GACV,EAAE;IACF,OAAO,EAAE,OAAO,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B;;;;;;;;;GAeA;AAED,wBAAsB,WAAW,CAAC,EACjC,EAAE,EACF,QAAQ,EACR,KAA6B,EAC7B,IAAI,GACJ,EAAE;IACF,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC,CAAA;IACjD,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACpB;;;;;;;;;GAiBA;AAED,wBAAsB,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;UAGnC;AAED,wBAAsB,cAAc,CACnC,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC,aAAa,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;GAyBtD;AAED,wBAAsB,qBAAqB,sBAG1C;AAED,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,MAAM,qBAYhD;AAED,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,+BASvE;AAED,wBAAsB,qBAAqB,2BAG1C;AAED,wBAAsB,kBAAkB;;;;WAGvC;AAED,wBAAsB,0BAA0B,CAAC,QAAQ,EAAE,MAAM;;GAehE"}
1
+ {"version":3,"file":"db.server.d.ts","sourceRoot":"","sources":["../../src/db.server.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAA;AAKtB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAOvB,QAAA,MAAM,cAAc;;;;;;;;;;;;EAIlB,CAAA;AACF,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAyBvB,CAAA;AAkBb,QAAA,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiCd,CAAA;AAEF,wBAAsB,WAAW,oBAOhC;AAED,wBAAsB,MAAM,kBAY3B;AAED,wBAAsB,QAAQ,8BAW7B;AAiED,wBAAsB,WAAW;;;;;;;;;sBAuBhC;AAED,wBAAsB,eAAe,CAAC,EACrC,OAAO,EACP,UAAU,GACV,EAAE;IACF,OAAO,EAAE,OAAO,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B;;;;;;;;;GAeA;AAED,wBAAsB,WAAW,CAAC,EACjC,EAAE,EACF,QAAQ,EACR,KAA6B,EAC7B,IAAI,GACJ,EAAE;IACF,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC,CAAA;IACjD,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACpB;;;;;;;;;GAgBA;AAED,wBAAsB,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;UAGnC;AAED,wBAAsB,cAAc,CACnC,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC,aAAa,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;GAwBtD;AAED,wBAAsB,qBAAqB,sBAG1C;AAED,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,MAAM,qBAWhD;AAED,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,+BAQvE;AAED,wBAAsB,qBAAqB,2BAG1C;AAED,wBAAsB,kBAAkB;;;;WAGvC;AAED,wBAAsB,0BAA0B,CAAC,QAAQ,EAAE,MAAM;;GAchE"}
@@ -1,10 +1,12 @@
1
1
  import './init-env.js';
2
- import path from 'path';
3
2
  import { createId as cuid } from '@paralleldrive/cuid2';
4
3
  import fsExtra from 'fs-extra';
5
4
  import { redirect } from 'react-router';
6
5
  import { z } from 'zod';
7
6
  import { getWorkshopConfig } from './config.server.js';
7
+ import { saveJSON, loadJSON, migrateLegacyData } from './data-storage.server.js';
8
+ // Attempt migration from legacy ~/.epicshop
9
+ await migrateLegacyData().catch(() => { });
8
10
  const TokenSetSchema = z.object({
9
11
  access_token: z.string(),
10
12
  token_type: z.string(),
@@ -83,15 +85,12 @@ const DataSchema = z.object({
83
85
  clientId: z.string().optional(),
84
86
  mutedNotifications: MutedNotificationSchema.optional(),
85
87
  });
86
- const homeDir = process.env.EPICSHOP_HOME_DIR;
87
- const dbPath = path.join(homeDir, 'data.json');
88
88
  export async function getClientId() {
89
89
  const data = await readDb();
90
90
  if (data?.clientId)
91
91
  return data.clientId;
92
92
  const clientId = cuid();
93
- await fsExtra.ensureDir(homeDir);
94
- await fsExtra.writeJSON(dbPath, { ...data, clientId });
93
+ await saveJSON({ ...data, clientId });
95
94
  return clientId;
96
95
  }
97
96
  export async function logout() {
@@ -101,7 +100,7 @@ export async function logout() {
101
100
  const data = await readDb();
102
101
  const newAuthInfos = { ...data?.authInfos };
103
102
  delete newAuthInfos[host];
104
- await fsExtra.writeJSON(dbPath, {
103
+ await saveJSON({
105
104
  ...data,
106
105
  authInfos: newAuthInfos,
107
106
  });
@@ -111,12 +110,13 @@ export async function deleteDb() {
111
110
  if (process.env.EPICSHOP_DEPLOYED)
112
111
  return null;
113
112
  try {
114
- if (await fsExtra.exists(dbPath)) {
113
+ const { path: dbPath } = await loadJSON();
114
+ if (dbPath && (await fsExtra.exists(dbPath))) {
115
115
  await fsExtra.remove(dbPath);
116
116
  }
117
117
  }
118
118
  catch (error) {
119
- console.error(`Error deleting the database in ${dbPath}`, error);
119
+ console.error(`Error deleting the database`, error);
120
120
  }
121
121
  }
122
122
  async function readDb() {
@@ -126,8 +126,9 @@ async function readDb() {
126
126
  const baseDelay = 10;
127
127
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
128
128
  try {
129
- if (await fsExtra.exists(dbPath)) {
130
- const db = DataSchema.parse(await fsExtra.readJSON(dbPath));
129
+ const { data, path: dbPath } = await loadJSON();
130
+ if (data && dbPath) {
131
+ const db = DataSchema.parse(data);
131
132
  return db;
132
133
  }
133
134
  return null;
@@ -141,7 +142,7 @@ async function readDb() {
141
142
  continue;
142
143
  }
143
144
  // Final attempt failed, handle as corrupted file
144
- console.error(`Error reading the database in ${dbPath} after ${attempt + 1} attempts, moving it to a .bkp file to avoid parsing errors in the future`, error);
145
+ console.error(`Error reading the database after ${attempt + 1} attempts, moving it to a .bkp file to avoid parsing errors in the future`, error);
145
146
  // Log to Sentry if available
146
147
  if (process.env.SENTRY_DSN && process.env.EPICSHOP_IS_PUBLISHED) {
147
148
  try {
@@ -152,7 +153,6 @@ async function readDb() {
152
153
  retry_attempts: attempt.toString(),
153
154
  },
154
155
  extra: {
155
- filePath: dbPath,
156
156
  errorMessage: error instanceof Error ? error.message : String(error),
157
157
  retryAttempts: attempt,
158
158
  },
@@ -162,7 +162,14 @@ async function readDb() {
162
162
  console.error('Failed to log to Sentry:', sentryError);
163
163
  }
164
164
  }
165
- void fsExtra.move(dbPath, `${dbPath}.bkp`).catch(() => { });
165
+ // Try to move corrupted file to backup if we can determine the path
166
+ try {
167
+ const { path: dbPath } = await loadJSON();
168
+ if (dbPath && (await fsExtra.exists(dbPath))) {
169
+ void fsExtra.move(dbPath, `${dbPath}.bkp`).catch(() => { });
170
+ }
171
+ }
172
+ catch { }
166
173
  }
167
174
  }
168
175
  return null;
@@ -206,10 +213,9 @@ export async function requireAuthInfo({ request, redirectTo, }) {
206
213
  export async function setAuthInfo({ id, tokenSet, email = 'unknown@example.com', name, }) {
207
214
  const data = await readDb();
208
215
  const authInfo = AuthInfoSchema.parse({ id, tokenSet, email, name });
209
- await fsExtra.ensureDir(homeDir);
210
216
  const config = getWorkshopConfig();
211
217
  if (config.product.host) {
212
- await fsExtra.writeJSON(dbPath, {
218
+ await saveJSON({
213
219
  ...data,
214
220
  authInfos: {
215
221
  ...data?.authInfos,
@@ -218,7 +224,7 @@ export async function setAuthInfo({ id, tokenSet, email = 'unknown@example.com',
218
224
  });
219
225
  }
220
226
  else {
221
- await fsExtra.writeJSON(dbPath, { ...data, authInfo });
227
+ await saveJSON({ ...data, authInfo });
222
228
  }
223
229
  return authInfo;
224
230
  }
@@ -247,8 +253,7 @@ export async function setPreferences(preferences) {
247
253
  },
248
254
  },
249
255
  };
250
- await fsExtra.ensureDir(homeDir);
251
- await fsExtra.writeJSON(dbPath, updatedData);
256
+ await saveJSON(updatedData);
252
257
  return updatedData.preferences;
253
258
  }
254
259
  export async function getMutedNotifications() {
@@ -262,8 +267,7 @@ export async function muteNotification(id) {
262
267
  ...data,
263
268
  mutedNotifications,
264
269
  };
265
- await fsExtra.ensureDir(homeDir);
266
- await fsExtra.writeJSON(dbPath, updatedData);
270
+ await saveJSON(updatedData);
267
271
  return mutedNotifications;
268
272
  }
269
273
  export async function setFontSizePreference(fontSize) {
@@ -272,8 +276,7 @@ export async function setFontSizePreference(fontSize) {
272
276
  ...data,
273
277
  preferences: { ...data?.preferences, fontSize },
274
278
  };
275
- await fsExtra.ensureDir(homeDir);
276
- await fsExtra.writeJSON(dbPath, updatedData);
279
+ await saveJSON(updatedData);
277
280
  return updatedData.preferences.fontSize;
278
281
  }
279
282
  export async function getFontSizePreference() {
@@ -296,8 +299,7 @@ export async function markOnboardingVideoWatched(videoUrl) {
296
299
  ].filter(Boolean),
297
300
  },
298
301
  };
299
- await fsExtra.ensureDir(homeDir);
300
- await fsExtra.writeJSON(dbPath, updatedData);
302
+ await saveJSON(updatedData);
301
303
  return updatedData.onboarding;
302
304
  }
303
305
  //# sourceMappingURL=db.server.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"db.server.js","sourceRoot":"","sources":["../../src/db.server.ts"],"names":[],"mappings":"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","sourcesContent":["import './init-env.js'\n\nimport path from 'path'\nimport { createId as cuid } from '@paralleldrive/cuid2'\nimport fsExtra from 'fs-extra'\nimport { redirect } from 'react-router'\nimport { z } from 'zod'\nimport { getWorkshopConfig } from './config.server.js'\n\nconst TokenSetSchema = z.object({\n\taccess_token: z.string(),\n\ttoken_type: z.string(),\n\tscope: z.string(),\n})\nexport const PlayerPreferencesSchema = z\n\t.object({\n\t\tminResolution: z.number().optional(),\n\t\tmaxResolution: z.number().optional(),\n\t\tvolumeRate: z.number().optional(),\n\t\tplaybackRate: z.number().optional(),\n\t\tautoplay: z.boolean().optional(),\n\t\tsubtitle: z\n\t\t\t.object({\n\t\t\t\tid: z.string().nullable().default(null),\n\t\t\t\tmode: z\n\t\t\t\t\t.literal('disabled')\n\t\t\t\t\t.or(z.literal('hidden'))\n\t\t\t\t\t.or(z.literal('showing'))\n\t\t\t\t\t.nullable()\n\t\t\t\t\t.default('disabled'),\n\t\t\t})\n\t\t\t.optional()\n\t\t\t.default({}),\n\t\tmuted: z.boolean().optional(),\n\t\ttheater: z.boolean().optional(),\n\t\tdefaultView: z.string().optional(),\n\t\tactiveSidebarTab: z.number().optional(),\n\t})\n\t.optional()\n\t.default({})\n\nconst PresencePreferencesSchema = z\n\t.object({\n\t\toptOut: z.boolean(),\n\t})\n\t.optional()\n\t.default({ optOut: false })\n\nconst AuthInfoSchema = z.object({\n\tid: z.string(),\n\ttokenSet: TokenSetSchema,\n\temail: z.string(),\n\tname: z.string().nullable().optional(),\n})\n\nconst MutedNotificationSchema = z.array(z.string()).default([])\n\nconst DataSchema = z.object({\n\tonboarding: z\n\t\t.object({\n\t\t\ttourVideosWatched: z.array(z.string()).default([]),\n\t\t})\n\t\t.passthrough()\n\t\t.optional()\n\t\t.default({ tourVideosWatched: [] }),\n\tpreferences: z\n\t\t.object({\n\t\t\tplayer: PlayerPreferencesSchema,\n\t\t\tpresence: PresencePreferencesSchema,\n\t\t\tplayground: z\n\t\t\t\t.object({\n\t\t\t\t\tpersist: z.boolean().default(false),\n\t\t\t\t})\n\t\t\t\t.optional(),\n\t\t\tfontSize: z.number().optional(),\n\t\t\texerciseWarning: z\n\t\t\t\t.object({\n\t\t\t\t\tdismissed: z.boolean().default(false),\n\t\t\t\t})\n\t\t\t\t.optional()\n\t\t\t\t.default({ dismissed: false }),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n\t// deprecated. Probably safe to remove in May 2026:\n\tauthInfo: AuthInfoSchema.optional(),\n\t// new:\n\tauthInfos: z.record(z.string(), AuthInfoSchema).optional(),\n\tclientId: z.string().optional(),\n\tmutedNotifications: MutedNotificationSchema.optional(),\n})\n\nconst homeDir = process.env.EPICSHOP_HOME_DIR\nconst dbPath = path.join(homeDir, 'data.json')\n\nexport async function getClientId() {\n\tconst data = await readDb()\n\tif (data?.clientId) return data.clientId\n\n\tconst clientId = cuid()\n\tawait fsExtra.ensureDir(homeDir)\n\tawait fsExtra.writeJSON(dbPath, { ...data, clientId })\n\treturn clientId\n}\n\nexport async function logout() {\n\tconst config = getWorkshopConfig()\n\tconst host = config.product.host\n\tif (host) {\n\t\tconst data = await readDb()\n\t\tconst newAuthInfos = { ...data?.authInfos }\n\t\tdelete newAuthInfos[host]\n\t\tawait fsExtra.writeJSON(dbPath, {\n\t\t\t...data,\n\t\t\tauthInfos: newAuthInfos,\n\t\t})\n\t}\n}\n\nexport async function deleteDb() {\n\tif (process.env.EPICSHOP_DEPLOYED) return null\n\n\ttry {\n\t\tif (await fsExtra.exists(dbPath)) {\n\t\t\tawait fsExtra.remove(dbPath)\n\t\t}\n\t} catch (error) {\n\t\tconsole.error(`Error deleting the database in ${dbPath}`, error)\n\t}\n}\n\nasync function readDb() {\n\tif (process.env.EPICSHOP_DEPLOYED) return null\n\n\tconst maxRetries = 3\n\tconst baseDelay = 10\n\n\tfor (let attempt = 0; attempt <= maxRetries; attempt++) {\n\t\ttry {\n\t\t\tif (await fsExtra.exists(dbPath)) {\n\t\t\t\tconst db = DataSchema.parse(await fsExtra.readJSON(dbPath))\n\t\t\t\treturn db\n\t\t\t}\n\t\t\treturn null\n\t\t} catch (error) {\n\t\t\t// If this is a retry attempt, it might be a race condition\n\t\t\tif (attempt < maxRetries) {\n\t\t\t\tconst delay = baseDelay * Math.pow(2, attempt)\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`Database read error on attempt ${attempt + 1}/${maxRetries + 1}, retrying in ${delay}ms...`,\n\t\t\t\t)\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, delay))\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Final attempt failed, handle as corrupted file\n\t\t\tconsole.error(\n\t\t\t\t`Error reading the database in ${dbPath} after ${attempt + 1} attempts, moving it to a .bkp file to avoid parsing errors in the future`,\n\t\t\t\terror,\n\t\t\t)\n\n\t\t\t// Log to Sentry if available\n\t\t\tif (process.env.SENTRY_DSN && process.env.EPICSHOP_IS_PUBLISHED) {\n\t\t\t\ttry {\n\t\t\t\t\tconst Sentry = await import('@sentry/react-router')\n\t\t\t\t\tSentry.captureException(error, {\n\t\t\t\t\t\ttags: {\n\t\t\t\t\t\t\terror_type: 'corrupted_database_file',\n\t\t\t\t\t\t\tretry_attempts: attempt.toString(),\n\t\t\t\t\t\t},\n\t\t\t\t\t\textra: {\n\t\t\t\t\t\t\tfilePath: dbPath,\n\t\t\t\t\t\t\terrorMessage:\n\t\t\t\t\t\t\t\terror instanceof Error ? error.message : String(error),\n\t\t\t\t\t\t\tretryAttempts: attempt,\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t} catch (sentryError) {\n\t\t\t\t\tconsole.error('Failed to log to Sentry:', sentryError)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvoid fsExtra.move(dbPath, `${dbPath}.bkp`).catch(() => {})\n\t\t}\n\t}\n\treturn null\n}\n\nexport async function getAuthInfo() {\n\tconst config = getWorkshopConfig()\n\tconst data = await readDb()\n\tif (config.product.host && typeof data?.authInfos === 'object') {\n\t\tif (config.product.host in data.authInfos) {\n\t\t\treturn data.authInfos[config.product.host]\n\t\t}\n\t}\n\n\t// special case for non-epicweb/epicreact workshops\n\tif (\n\t\t!config.product.host ||\n\t\tconfig.product.host === 'epicweb.dev' ||\n\t\tconfig.product.host === 'epicreact.dev'\n\t) {\n\t\t// upgrade from old authInfo to new authInfos\n\t\tif (data?.authInfo && config.product.host) {\n\t\t\tawait setAuthInfo(data.authInfo)\n\t\t}\n\t\treturn data?.authInfo ?? null\n\t}\n\n\treturn null\n}\n\nexport async function requireAuthInfo({\n\trequest,\n\tredirectTo,\n}: {\n\trequest: Request\n\tredirectTo?: string | null\n}) {\n\tconst authInfo = await getAuthInfo()\n\tif (!authInfo) {\n\t\tconst requestUrl = new URL(request.url)\n\t\tredirectTo =\n\t\t\tredirectTo === null\n\t\t\t\t? null\n\t\t\t\t: (redirectTo ?? `${requestUrl.pathname}${requestUrl.search}`)\n\t\tconst loginParams = redirectTo ? new URLSearchParams({ redirectTo }) : null\n\t\tconst loginRedirect = ['/login', loginParams?.toString()]\n\t\t\t.filter(Boolean)\n\t\t\t.join('?')\n\t\tthrow redirect(loginRedirect)\n\t}\n\treturn authInfo\n}\n\nexport async function setAuthInfo({\n\tid,\n\ttokenSet,\n\temail = 'unknown@example.com',\n\tname,\n}: {\n\tid: string\n\ttokenSet: Partial<z.infer<typeof TokenSetSchema>>\n\temail?: string | null\n\tname?: string | null\n}) {\n\tconst data = await readDb()\n\tconst authInfo = AuthInfoSchema.parse({ id, tokenSet, email, name })\n\tawait fsExtra.ensureDir(homeDir)\n\tconst config = getWorkshopConfig()\n\tif (config.product.host) {\n\t\tawait fsExtra.writeJSON(dbPath, {\n\t\t\t...data,\n\t\t\tauthInfos: {\n\t\t\t\t...data?.authInfos,\n\t\t\t\t[config.product.host]: authInfo,\n\t\t\t},\n\t\t})\n\t} else {\n\t\tawait fsExtra.writeJSON(dbPath, { ...data, authInfo })\n\t}\n\treturn authInfo\n}\n\nexport async function getPreferences() {\n\tconst data = await readDb()\n\treturn data?.preferences ?? null\n}\n\nexport async function setPreferences(\n\tpreferences: z.input<typeof DataSchema>['preferences'],\n) {\n\tconst data = await readDb()\n\tconst updatedData = {\n\t\t...data,\n\t\tpreferences: {\n\t\t\t...data?.preferences,\n\t\t\t...preferences,\n\t\t\tplayer: {\n\t\t\t\t...data?.preferences?.player,\n\t\t\t\t...preferences?.player,\n\t\t\t},\n\t\t\tpresence: {\n\t\t\t\t...data?.preferences?.presence,\n\t\t\t\t...preferences?.presence,\n\t\t\t},\n\t\t\texerciseWarning: {\n\t\t\t\t...data?.preferences?.exerciseWarning,\n\t\t\t\t...preferences?.exerciseWarning,\n\t\t\t},\n\t\t},\n\t}\n\tawait fsExtra.ensureDir(homeDir)\n\tawait fsExtra.writeJSON(dbPath, updatedData)\n\treturn updatedData.preferences\n}\n\nexport async function getMutedNotifications() {\n\tconst data = await readDb()\n\treturn data?.mutedNotifications ?? []\n}\n\nexport async function muteNotification(id: string) {\n\tconst data = await readDb()\n\tconst mutedNotifications = Array.from(\n\t\tnew Set([...(data?.mutedNotifications ?? []), id]),\n\t)\n\tconst updatedData = {\n\t\t...data,\n\t\tmutedNotifications,\n\t}\n\tawait fsExtra.ensureDir(homeDir)\n\tawait fsExtra.writeJSON(dbPath, updatedData)\n\treturn mutedNotifications\n}\n\nexport async function setFontSizePreference(fontSize: number | undefined) {\n\tconst data = await readDb()\n\tconst updatedData = {\n\t\t...data,\n\t\tpreferences: { ...data?.preferences, fontSize },\n\t}\n\tawait fsExtra.ensureDir(homeDir)\n\tawait fsExtra.writeJSON(dbPath, updatedData)\n\treturn updatedData.preferences.fontSize\n}\n\nexport async function getFontSizePreference() {\n\tconst data = await readDb()\n\treturn data?.preferences?.fontSize ?? null\n}\n\nexport async function readOnboardingData() {\n\tconst data = await readDb()\n\treturn data?.onboarding ?? null\n}\n\nexport async function markOnboardingVideoWatched(videoUrl: string) {\n\tconst data = await readDb()\n\tconst updatedData = {\n\t\t...data,\n\t\tonboarding: {\n\t\t\t...data?.onboarding,\n\t\t\ttourVideosWatched: [\n\t\t\t\t...(data?.onboarding.tourVideosWatched ?? []),\n\t\t\t\tvideoUrl,\n\t\t\t].filter(Boolean),\n\t\t},\n\t}\n\tawait fsExtra.ensureDir(homeDir)\n\tawait fsExtra.writeJSON(dbPath, updatedData)\n\treturn updatedData.onboarding\n}\n"]}
1
+ {"version":3,"file":"db.server.js","sourceRoot":"","sources":["../../src/db.server.ts"],"names":[],"mappings":"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","sourcesContent":["import './init-env.js'\n\nimport { createId as cuid } from '@paralleldrive/cuid2'\nimport fsExtra from 'fs-extra'\nimport { redirect } from 'react-router'\nimport { z } from 'zod'\nimport { getWorkshopConfig } from './config.server.js'\nimport { saveJSON, loadJSON, migrateLegacyData } from './data-storage.server.js'\n\n// Attempt migration from legacy ~/.epicshop\nawait migrateLegacyData().catch(() => {})\n\nconst TokenSetSchema = z.object({\n\taccess_token: z.string(),\n\ttoken_type: z.string(),\n\tscope: z.string(),\n})\nexport const PlayerPreferencesSchema = z\n\t.object({\n\t\tminResolution: z.number().optional(),\n\t\tmaxResolution: z.number().optional(),\n\t\tvolumeRate: z.number().optional(),\n\t\tplaybackRate: z.number().optional(),\n\t\tautoplay: z.boolean().optional(),\n\t\tsubtitle: z\n\t\t\t.object({\n\t\t\t\tid: z.string().nullable().default(null),\n\t\t\t\tmode: z\n\t\t\t\t\t.literal('disabled')\n\t\t\t\t\t.or(z.literal('hidden'))\n\t\t\t\t\t.or(z.literal('showing'))\n\t\t\t\t\t.nullable()\n\t\t\t\t\t.default('disabled'),\n\t\t\t})\n\t\t\t.optional()\n\t\t\t.default({}),\n\t\tmuted: z.boolean().optional(),\n\t\ttheater: z.boolean().optional(),\n\t\tdefaultView: z.string().optional(),\n\t\tactiveSidebarTab: z.number().optional(),\n\t})\n\t.optional()\n\t.default({})\n\nconst PresencePreferencesSchema = z\n\t.object({\n\t\toptOut: z.boolean(),\n\t})\n\t.optional()\n\t.default({ optOut: false })\n\nconst AuthInfoSchema = z.object({\n\tid: z.string(),\n\ttokenSet: TokenSetSchema,\n\temail: z.string(),\n\tname: z.string().nullable().optional(),\n})\n\nconst MutedNotificationSchema = z.array(z.string()).default([])\n\nconst DataSchema = z.object({\n\tonboarding: z\n\t\t.object({\n\t\t\ttourVideosWatched: z.array(z.string()).default([]),\n\t\t})\n\t\t.passthrough()\n\t\t.optional()\n\t\t.default({ tourVideosWatched: [] }),\n\tpreferences: z\n\t\t.object({\n\t\t\tplayer: PlayerPreferencesSchema,\n\t\t\tpresence: PresencePreferencesSchema,\n\t\t\tplayground: z\n\t\t\t\t.object({\n\t\t\t\t\tpersist: z.boolean().default(false),\n\t\t\t\t})\n\t\t\t\t.optional(),\n\t\t\tfontSize: z.number().optional(),\n\t\t\texerciseWarning: z\n\t\t\t\t.object({\n\t\t\t\t\tdismissed: z.boolean().default(false),\n\t\t\t\t})\n\t\t\t\t.optional()\n\t\t\t\t.default({ dismissed: false }),\n\t\t})\n\t\t.optional()\n\t\t.default({}),\n\t// deprecated. Probably safe to remove in May 2026:\n\tauthInfo: AuthInfoSchema.optional(),\n\t// new:\n\tauthInfos: z.record(z.string(), AuthInfoSchema).optional(),\n\tclientId: z.string().optional(),\n\tmutedNotifications: MutedNotificationSchema.optional(),\n})\n\nexport async function getClientId() {\n\tconst data = await readDb()\n\tif (data?.clientId) return data.clientId\n\n\tconst clientId = cuid()\n\tawait saveJSON({ ...data, clientId })\n\treturn clientId\n}\n\nexport async function logout() {\n\tconst config = getWorkshopConfig()\n\tconst host = config.product.host\n\tif (host) {\n\t\tconst data = await readDb()\n\t\tconst newAuthInfos = { ...data?.authInfos }\n\t\tdelete newAuthInfos[host]\n\t\tawait saveJSON({\n\t\t\t...data,\n\t\t\tauthInfos: newAuthInfos,\n\t\t})\n\t}\n}\n\nexport async function deleteDb() {\n\tif (process.env.EPICSHOP_DEPLOYED) return null\n\n\ttry {\n\t\tconst { path: dbPath } = await loadJSON()\n\t\tif (dbPath && (await fsExtra.exists(dbPath))) {\n\t\t\tawait fsExtra.remove(dbPath)\n\t\t}\n\t} catch (error) {\n\t\tconsole.error(`Error deleting the database`, error)\n\t}\n}\n\nasync function readDb() {\n\tif (process.env.EPICSHOP_DEPLOYED) return null\n\n\tconst maxRetries = 3\n\tconst baseDelay = 10\n\n\tfor (let attempt = 0; attempt <= maxRetries; attempt++) {\n\t\ttry {\n\t\t\tconst { data, path: dbPath } = await loadJSON()\n\t\t\tif (data && dbPath) {\n\t\t\t\tconst db = DataSchema.parse(data)\n\t\t\t\treturn db\n\t\t\t}\n\t\t\treturn null\n\t\t} catch (error) {\n\t\t\t// If this is a retry attempt, it might be a race condition\n\t\t\tif (attempt < maxRetries) {\n\t\t\t\tconst delay = baseDelay * Math.pow(2, attempt)\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`Database read error on attempt ${attempt + 1}/${maxRetries + 1}, retrying in ${delay}ms...`,\n\t\t\t\t)\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, delay))\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Final attempt failed, handle as corrupted file\n\t\t\tconsole.error(\n\t\t\t\t`Error reading the database after ${attempt + 1} attempts, moving it to a .bkp file to avoid parsing errors in the future`,\n\t\t\t\terror,\n\t\t\t)\n\n\t\t\t// Log to Sentry if available\n\t\t\tif (process.env.SENTRY_DSN && process.env.EPICSHOP_IS_PUBLISHED) {\n\t\t\t\ttry {\n\t\t\t\t\tconst Sentry = await import('@sentry/react-router')\n\t\t\t\t\tSentry.captureException(error, {\n\t\t\t\t\t\ttags: {\n\t\t\t\t\t\t\terror_type: 'corrupted_database_file',\n\t\t\t\t\t\t\tretry_attempts: attempt.toString(),\n\t\t\t\t\t\t},\n\t\t\t\t\t\textra: {\n\t\t\t\t\t\t\terrorMessage:\n\t\t\t\t\t\t\t\terror instanceof Error ? error.message : String(error),\n\t\t\t\t\t\t\tretryAttempts: attempt,\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t} catch (sentryError) {\n\t\t\t\t\tconsole.error('Failed to log to Sentry:', sentryError)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Try to move corrupted file to backup if we can determine the path\n\t\t\ttry {\n\t\t\t\tconst { path: dbPath } = await loadJSON()\n\t\t\t\tif (dbPath && (await fsExtra.exists(dbPath))) {\n\t\t\t\t\tvoid fsExtra.move(dbPath, `${dbPath}.bkp`).catch(() => {})\n\t\t\t\t}\n\t\t\t} catch {}\n\t\t}\n\t}\n\treturn null\n}\n\nexport async function getAuthInfo() {\n\tconst config = getWorkshopConfig()\n\tconst data = await readDb()\n\tif (config.product.host && typeof data?.authInfos === 'object') {\n\t\tif (config.product.host in data.authInfos) {\n\t\t\treturn data.authInfos[config.product.host]\n\t\t}\n\t}\n\n\t// special case for non-epicweb/epicreact workshops\n\tif (\n\t\t!config.product.host ||\n\t\tconfig.product.host === 'epicweb.dev' ||\n\t\tconfig.product.host === 'epicreact.dev'\n\t) {\n\t\t// upgrade from old authInfo to new authInfos\n\t\tif (data?.authInfo && config.product.host) {\n\t\t\tawait setAuthInfo(data.authInfo)\n\t\t}\n\t\treturn data?.authInfo ?? null\n\t}\n\n\treturn null\n}\n\nexport async function requireAuthInfo({\n\trequest,\n\tredirectTo,\n}: {\n\trequest: Request\n\tredirectTo?: string | null\n}) {\n\tconst authInfo = await getAuthInfo()\n\tif (!authInfo) {\n\t\tconst requestUrl = new URL(request.url)\n\t\tredirectTo =\n\t\t\tredirectTo === null\n\t\t\t\t? null\n\t\t\t\t: (redirectTo ?? `${requestUrl.pathname}${requestUrl.search}`)\n\t\tconst loginParams = redirectTo ? new URLSearchParams({ redirectTo }) : null\n\t\tconst loginRedirect = ['/login', loginParams?.toString()]\n\t\t\t.filter(Boolean)\n\t\t\t.join('?')\n\t\tthrow redirect(loginRedirect)\n\t}\n\treturn authInfo\n}\n\nexport async function setAuthInfo({\n\tid,\n\ttokenSet,\n\temail = 'unknown@example.com',\n\tname,\n}: {\n\tid: string\n\ttokenSet: Partial<z.infer<typeof TokenSetSchema>>\n\temail?: string | null\n\tname?: string | null\n}) {\n\tconst data = await readDb()\n\tconst authInfo = AuthInfoSchema.parse({ id, tokenSet, email, name })\n\tconst config = getWorkshopConfig()\n\tif (config.product.host) {\n\t\tawait saveJSON({\n\t\t\t...data,\n\t\t\tauthInfos: {\n\t\t\t\t...data?.authInfos,\n\t\t\t\t[config.product.host]: authInfo,\n\t\t\t},\n\t\t})\n\t} else {\n\t\tawait saveJSON({ ...data, authInfo })\n\t}\n\treturn authInfo\n}\n\nexport async function getPreferences() {\n\tconst data = await readDb()\n\treturn data?.preferences ?? null\n}\n\nexport async function setPreferences(\n\tpreferences: z.input<typeof DataSchema>['preferences'],\n) {\n\tconst data = await readDb()\n\tconst updatedData = {\n\t\t...data,\n\t\tpreferences: {\n\t\t\t...data?.preferences,\n\t\t\t...preferences,\n\t\t\tplayer: {\n\t\t\t\t...data?.preferences?.player,\n\t\t\t\t...preferences?.player,\n\t\t\t},\n\t\t\tpresence: {\n\t\t\t\t...data?.preferences?.presence,\n\t\t\t\t...preferences?.presence,\n\t\t\t},\n\t\t\texerciseWarning: {\n\t\t\t\t...data?.preferences?.exerciseWarning,\n\t\t\t\t...preferences?.exerciseWarning,\n\t\t\t},\n\t\t},\n\t}\n\tawait saveJSON(updatedData)\n\treturn updatedData.preferences\n}\n\nexport async function getMutedNotifications() {\n\tconst data = await readDb()\n\treturn data?.mutedNotifications ?? []\n}\n\nexport async function muteNotification(id: string) {\n\tconst data = await readDb()\n\tconst mutedNotifications = Array.from(\n\t\tnew Set([...(data?.mutedNotifications ?? []), id]),\n\t)\n\tconst updatedData = {\n\t\t...data,\n\t\tmutedNotifications,\n\t}\n\tawait saveJSON(updatedData)\n\treturn mutedNotifications\n}\n\nexport async function setFontSizePreference(fontSize: number | undefined) {\n\tconst data = await readDb()\n\tconst updatedData = {\n\t\t...data,\n\t\tpreferences: { ...data?.preferences, fontSize },\n\t}\n\tawait saveJSON(updatedData)\n\treturn updatedData.preferences.fontSize\n}\n\nexport async function getFontSizePreference() {\n\tconst data = await readDb()\n\treturn data?.preferences?.fontSize ?? null\n}\n\nexport async function readOnboardingData() {\n\tconst data = await readDb()\n\treturn data?.onboarding ?? null\n}\n\nexport async function markOnboardingVideoWatched(videoUrl: string) {\n\tconst data = await readDb()\n\tconst updatedData = {\n\t\t...data,\n\t\tonboarding: {\n\t\t\t...data?.onboarding,\n\t\t\ttourVideosWatched: [\n\t\t\t\t...(data?.onboarding.tourVideosWatched ?? []),\n\t\t\t\tvideoUrl,\n\t\t\t].filter(Boolean),\n\t\t},\n\t}\n\tawait saveJSON(updatedData)\n\treturn updatedData.onboarding\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epic-web/workshop-utils",
3
- "version": "6.20.4",
3
+ "version": "6.20.6",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -20,6 +20,7 @@
20
20
  "./cache.server": "./src/cache.server.ts",
21
21
  "./config.server": "./src/config.server.ts",
22
22
  "./db.server": "./src/db.server.ts",
23
+ "./data-storage.server": "./src/data-storage.server.ts",
23
24
  "./timing.server": "./src/timing.server.ts",
24
25
  "./modified-time.server": "./src/modified-time.server.ts",
25
26
  "./compile-mdx.server": "./src/compile-mdx.server.ts",
@@ -85,6 +86,12 @@
85
86
  "default": "./dist/esm/db.server.js"
86
87
  }
87
88
  },
89
+ "./data-storage.server": {
90
+ "import": {
91
+ "types": "./dist/esm/data-storage.server.d.ts",
92
+ "default": "./dist/esm/data-storage.server.js"
93
+ }
94
+ },
88
95
  "./timing.server": {
89
96
  "import": {
90
97
  "types": "./dist/esm/timing.server.d.ts",