fix(8.0): honor top-level storage.path as a rootDirectory alias

`storage: { type: 'filesystem', path: '…' }` is a widely-used, doc-promoted
config shape, but the 8.0 storage refactor dropped top-level `path` from the
root-directory resolution chain — so it was silently ignored and every such
brain wrote to the default `./brainy-data` instead. A consumer upgrading with
`storage: { path: './my-data' }` would have had their data quietly relocated.

Restores `path` as a first-class alias for `rootDirectory` (it already worked
nested under `options.path`; now it works top-level too), adds it to the
StorageOptions / BrainyConfig.storage types, and pins every accepted spelling
(rootDirectory, path, options.rootDirectory, options.path, default) in a unit
test of the resolution chain.
This commit is contained in:
David Snelling 2026-06-16 09:46:39 -07:00
parent 0951fa1da0
commit 5096f90fbc
3 changed files with 65 additions and 0 deletions

View file

@ -42,6 +42,14 @@ export interface StorageOptions {
/** Root directory for filesystem storage. */ /** Root directory for filesystem storage. */
rootDirectory?: string rootDirectory?: string
/**
* Alias for `rootDirectory`. The `storage: { type: 'filesystem', path: '…' }`
* shape is widely used (and shown throughout the docs), so it is honored as a
* first-class top-level key not silently ignored in favour of the default
* directory.
*/
path?: string
/** /**
* Nested options block (backward-compat for `BrainyConfig.storage.options`). * Nested options block (backward-compat for `BrainyConfig.storage.options`).
* Recognized keys: `rootDirectory`, `path`. * Recognized keys: `rootDirectory`, `path`.
@ -94,6 +102,7 @@ async function pickAdapter(options: StorageOptions): Promise<StorageAdapter> {
async function createFilesystemStorage(options: StorageOptions): Promise<StorageAdapter> { async function createFilesystemStorage(options: StorageOptions): Promise<StorageAdapter> {
const rootDir = const rootDir =
options.rootDirectory ?? options.rootDirectory ??
options.path ??
options.options?.rootDirectory ?? options.options?.rootDirectory ??
options.options?.path ?? options.options?.path ??
'./brainy-data' './brainy-data'

View file

@ -1202,6 +1202,9 @@ export interface BrainyConfig {
/** Root directory for filesystem storage. Passed through to storage factories /** Root directory for filesystem storage. Passed through to storage factories
* including plugin-provided factories (e.g. native mmap providers). */ * including plugin-provided factories (e.g. native mmap providers). */
rootDirectory?: string rootDirectory?: string
/** Alias for `rootDirectory`. The `storage: { type: 'filesystem', path: '' }`
* shape is honored as a first-class key so existing configs keep working. */
path?: string
options?: any options?: any
} }
| StorageAdapter | StorageAdapter

View file

@ -0,0 +1,53 @@
/**
* Storage root-directory resolution from `StorageOptions`.
*
* `storage: { type: 'filesystem', path: '…' }` is a widely-used, doc-promoted
* config shape. A refactor once dropped the top-level `path` key from the
* resolution chain, so it was silently ignored and every brain wrote to the
* default `./brainy-data` instead a quiet data-misplacement footgun on
* upgrade. These tests pin every accepted spelling to the directory it must
* resolve to.
*/
import { describe, it, expect } from 'vitest'
import { createStorage } from '../../../src/storage/storageFactory.js'
/** Read the resolved root directory off the concrete FileSystemStorage. */
function rootDirOf(storage: unknown): string {
return (storage as { rootDir: string }).rootDir
}
describe('createStorage — filesystem root-directory resolution', () => {
it('honors top-level rootDirectory', async () => {
const storage = await createStorage({ type: 'filesystem', rootDirectory: '/tmp/brainy-rd' })
expect(rootDirOf(storage)).toBe('/tmp/brainy-rd')
})
it('honors top-level path (the documented shorthand)', async () => {
const storage = await createStorage({ type: 'filesystem', path: '/tmp/brainy-path' })
expect(rootDirOf(storage)).toBe('/tmp/brainy-path')
})
it('honors nested options.rootDirectory', async () => {
const storage = await createStorage({ type: 'filesystem', options: { rootDirectory: '/tmp/brainy-ord' } })
expect(rootDirOf(storage)).toBe('/tmp/brainy-ord')
})
it('honors nested options.path', async () => {
const storage = await createStorage({ type: 'filesystem', options: { path: '/tmp/brainy-opath' } })
expect(rootDirOf(storage)).toBe('/tmp/brainy-opath')
})
it('prefers top-level rootDirectory over a nested options.path', async () => {
const storage = await createStorage({
type: 'filesystem',
rootDirectory: '/tmp/brainy-win',
options: { path: '/tmp/brainy-lose' }
})
expect(rootDirOf(storage)).toBe('/tmp/brainy-win')
})
it('falls back to ./brainy-data only when no directory is supplied', async () => {
const storage = await createStorage({ type: 'filesystem' })
expect(rootDirOf(storage)).toBe('./brainy-data')
})
})