diff --git a/src/adapters/normalized-adapter.test.ts b/src/adapters/normalized-adapter.test.ts index 67f85a3ec..18fe363a6 100644 --- a/src/adapters/normalized-adapter.test.ts +++ b/src/adapters/normalized-adapter.test.ts @@ -51,6 +51,16 @@ await test('read removes $schema and normalizes ids', async () => { assert.notEqual(posts[1]?.['id'], '') }) +await test('read preserves primitive array items', async () => { + const adapter = new StubAdapter({ + roles: ['admin', 'user'], + }) + + const normalized = await new NormalizedAdapter(adapter).read() + + assert.deepEqual(normalized?.['roles'], ['admin', 'user']) +}) + await test('write always overwrites $schema', async () => { const adapter = new StubAdapter(null) const normalizedAdapter = new NormalizedAdapter(adapter) diff --git a/src/adapters/normalized-adapter.ts b/src/adapters/normalized-adapter.ts index e2d9e7284..ba01a1a67 100644 --- a/src/adapters/normalized-adapter.ts +++ b/src/adapters/normalized-adapter.ts @@ -1,10 +1,10 @@ import type { Adapter } from 'lowdb' import { randomId } from '../random-id.ts' -import type { Data, Item } from '../service.ts' +import { isItem, type Data, type Item } from '../service.ts' export const DEFAULT_SCHEMA_PATH = './node_modules/json-server/schema.json' -export type RawData = Record & { +export type RawData = Record & { $schema?: string } @@ -27,6 +27,10 @@ export class NormalizedAdapter implements Adapter { for (const value of Object.values(data)) { if (Array.isArray(value)) { for (const item of value) { + if (!isItem(item)) { + continue + } + if (typeof item['id'] === 'number') { item['id'] = item['id'].toString() } diff --git a/src/bin.ts b/src/bin.ts index 3cc39d83b..7a10c68f4 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -16,6 +16,7 @@ import { NormalizedAdapter } from "./adapters/normalized-adapter.ts"; import type { RawData } from "./adapters/normalized-adapter.ts"; import { Observer } from "./adapters/observer.ts"; import { createApp } from "./app.ts"; +import { formatPrimitiveArrayWarning, primitiveArrayResourceNames } from "./primitive-array-warning.ts"; import type { Data } from "./service.ts"; function help() { @@ -139,6 +140,10 @@ const observer = new Observer(new NormalizedAdapter(adapter)); const db = new Low(observer, {}); await db.read(); +for (const name of primitiveArrayResourceNames(db.data)) { + console.log(chalk.yellow(formatPrimitiveArrayWarning(name))); +} + // Create app const app = createApp(db, { logger: false, static: staticArr }); diff --git a/src/primitive-array-warning.test.ts b/src/primitive-array-warning.test.ts new file mode 100644 index 000000000..c2fee7857 --- /dev/null +++ b/src/primitive-array-warning.test.ts @@ -0,0 +1,26 @@ +import assert from 'node:assert/strict' +import test from 'node:test' + +import type { Data } from './service.ts' +import { + formatPrimitiveArrayWarning, + primitiveArrayResourceNames, +} from './primitive-array-warning.ts' + +await test('primitiveArrayResourceNames returns resources with primitive array items', () => { + const data = { + roles: ['admin', 'user'], + posts: [{ id: '1', title: 'post' }], + mixed: [{ id: '1' }, null], + profile: { name: 'Ada' }, + } as unknown as Data + + assert.deepEqual(primitiveArrayResourceNames(data), ['roles', 'mixed']) +}) + +await test('formatPrimitiveArrayWarning describes the resource problem', () => { + assert.equal( + formatPrimitiveArrayWarning('roles'), + 'Warning: "roles" contains primitive values. Resources should usually be arrays of objects with id fields.', + ) +}) diff --git a/src/primitive-array-warning.ts b/src/primitive-array-warning.ts new file mode 100644 index 000000000..d518c38b3 --- /dev/null +++ b/src/primitive-array-warning.ts @@ -0,0 +1,15 @@ +import { isItem, type Data } from './service.ts' + +export function primitiveArrayResourceNames(data: Data): string[] { + return Object.entries(data).flatMap(([name, value]) => { + if (Array.isArray(value) && value.some((item: unknown) => !isItem(item))) { + return [name] + } + + return [] + }) +} + +export function formatPrimitiveArrayWarning(name: string): string { + return `Warning: "${name}" contains primitive values. Resources should usually be arrays of objects with id fields.` +}