{"version":3,"file":"storage-host.mjs","names":[],"sources":["../../src/hosts/storage-host.ts"],"sourcesContent":["/* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD */\n/* SPDX-License-Identifier: Apache-2.0 */\n\n/**\n * In-memory {@link BrookesiaHost} for Storage RPC — Node/vitest only (no device runtime).\n * Import: `@brookesia/service/hosts/storage`\n */\n\nimport type { BrookesiaHost } from '../common/brookesia-host.js';\nimport { serializeRpcError, serializeRpcResult } from '../common/rpc-json.js';\nimport {\n  STORAGE_DEFAULT_NAMESPACE,\n  STORAGE_FUNCTIONS,\n  STORAGE_SERVICE_NAME,\n} from '../storage/storage-client.js';\nimport type {\n  FileSystemCapacity,\n  FileSystemInfo,\n  FileSystemType,\n  KvNameResult,\n  MediumType,\n  StorageEntryInfo,\n  StorageKeyValueMap,\n  StorageValue,\n  StorageValueType,\n} from '../storage/storage-types.js';\n\ntype Store = Map<string, Map<string, StorageValue>>;\n\nlet nextHandle = 1;\nconst handles = new Map<number, Store>();\n\n/** Default file systems exposed by the mock host. */\nconst DEFAULT_FILE_SYSTEMS: FileSystemInfo[] = [\n  {\n    fs_type: 'LittleFS' as FileSystemType,\n    medium_type: 'Flash' as MediumType,\n    mount_point: '/littlefs',\n    supports_directories: true,\n  },\n];\n\nfunction namespaceKey(nspace: string): string {\n  return nspace || STORAGE_DEFAULT_NAMESPACE;\n}\n\nfunction valueType(value: StorageValue): StorageValueType {\n  if (typeof value === 'boolean') {\n    return 'Bool';\n  }\n  if (typeof value === 'number') {\n    return 'Int';\n  }\n  return 'String';\n}\n\nfunction getStore(handle: number): Store {\n  const store = handles.get(handle);\n  if (!store) {\n    throw new Error(`Storage memory host: invalid handle ${handle}`);\n  }\n  return store;\n}\n\nexport type StorageHostOptions = {\n  /** Seed data for KV namespaces: `{ nspace: { key: value } }` */\n  initialKvData?: Record<string, StorageKeyValueMap>;\n  /** File systems to report via GetFileSystems (default: 1 LittleFS entry). */\n  fileSystems?: FileSystemInfo[];\n  /** Capacity to report for GetFileSystemCapacity calls (default: 1 MiB total, 256 KiB used). */\n  capacity?: FileSystemCapacity;\n};\n\nexport function createStorageHost(options?: StorageHostOptions): BrookesiaHost {\n  const seedKv: Store = new Map();\n  for (const [nspace, pairs] of Object.entries(options?.initialKvData ?? {})) {\n    seedKv.set(namespaceKey(nspace), new Map(Object.entries(pairs)));\n  }\n  const fileSystems = options?.fileSystems ?? DEFAULT_FILE_SYSTEMS;\n  const capacity = options?.capacity ?? {\n    total_bytes: 1024 * 1024,\n    used_bytes: 256 * 1024,\n    free_bytes: 768 * 1024,\n  };\n\n  return {\n    service_available(name: string): boolean {\n      return name === STORAGE_SERVICE_NAME;\n    },\n\n    start_service(name: string): number {\n      if (name !== STORAGE_SERVICE_NAME) {\n        throw new Error(`Storage memory host: unknown service ${name}`);\n      }\n      const handle = nextHandle++;\n      const clone: Store = new Map();\n      for (const [ns, entries] of seedKv) {\n        clone.set(ns, new Map(entries));\n      }\n      handles.set(handle, clone);\n      return handle;\n    },\n\n    stop_service(handle: number): void {\n      handles.delete(handle);\n    },\n\n    call_function(handle: number, functionName: string, paramsJson: string): string {\n      const store = getStore(handle);\n      const params = JSON.parse(paramsJson) as Record<string, unknown>;\n      const nspace = namespaceKey(String(params.Nspace ?? STORAGE_DEFAULT_NAMESPACE));\n\n      try {\n        // ── KV operations ──────────────────────────────\n        if (functionName === STORAGE_FUNCTIONS.KVSet) {\n          const pairs = params.KeyValuePairs as StorageKeyValueMap;\n          if (!pairs || typeof pairs !== 'object') {\n            return serializeRpcError('KeyValuePairs required');\n          }\n          let ns = store.get(nspace);\n          if (!ns) {\n            ns = new Map();\n            store.set(nspace, ns);\n          }\n          for (const [key, value] of Object.entries(pairs)) {\n            ns.set(key, value);\n          }\n          return serializeRpcResult();\n        }\n\n        if (functionName === STORAGE_FUNCTIONS.KVGet) {\n          const ns = store.get(nspace) ?? new Map();\n          const keys = params.Keys as string[] | undefined;\n          const out: StorageKeyValueMap = {};\n          const keyList = keys?.length ? keys : [...ns.keys()];\n          for (const key of keyList) {\n            const value = ns.get(key);\n            if (value !== undefined) {\n              out[key] = value;\n            }\n          }\n          return serializeRpcResult(out);\n        }\n\n        if (functionName === STORAGE_FUNCTIONS.KVList) {\n          const ns = store.get(nspace) ?? new Map();\n          const entries: StorageEntryInfo[] = [];\n          for (const [key, value] of ns) {\n            entries.push({ nspace, key, type: valueType(value) });\n          }\n          return serializeRpcResult(entries);\n        }\n\n        if (functionName === STORAGE_FUNCTIONS.KVErase) {\n          const keys = params.Keys as string[] | undefined;\n          const ns = store.get(nspace);\n          if (!ns) {\n            return serializeRpcResult();\n          }\n          if (!keys?.length) {\n            store.delete(nspace);\n          } else {\n            for (const key of keys) {\n              ns.delete(key);\n            }\n          }\n          return serializeRpcResult();\n        }\n\n        // ── File system operations ─────────────────────\n        if (functionName === STORAGE_FUNCTIONS.GetFileSystems) {\n          return serializeRpcResult([...fileSystems]);\n        }\n\n        if (functionName === STORAGE_FUNCTIONS.GetFileSystemCapacity) {\n          return serializeRpcResult({ ...capacity });\n        }\n\n        // ── KV name utilities ──────────────────────────\n        if (functionName === STORAGE_FUNCTIONS.MakeKVKey) {\n          const parts = params.Parts as string[];\n          const separator = String(params.Separator ?? '.');\n          const originalName = parts.join(separator);\n          const result: KvNameResult = {\n            name: originalName,\n            original_name: originalName,\n            hashed: false,\n            warning: '',\n          };\n          return serializeRpcResult(result);\n        }\n\n        if (functionName === STORAGE_FUNCTIONS.MakeKVNamespace) {\n          const parts = params.Parts as string[];\n          const separator = String(params.Separator ?? '.');\n          const originalName = parts.join(separator);\n          const result: KvNameResult = {\n            name: originalName,\n            original_name: originalName,\n            hashed: false,\n            warning: '',\n          };\n          return serializeRpcResult(result);\n        }\n\n        return serializeRpcError(`unknown function ${functionName}`);\n      } catch (err) {\n        const message = err instanceof Error ? err.message : String(err);\n        return serializeRpcError(message);\n      }\n    },\n  };\n}\n\nexport function resetStorageHostState(): void {\n  handles.clear();\n  nextHandle = 1;\n}\n"],"mappings":"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"}