diff --git a/src/module.ts b/src/module.ts index 53ccff199..d47a4fa1c 100644 --- a/src/module.ts +++ b/src/module.ts @@ -215,7 +215,9 @@ async function processCollectionItems(nuxt: Nuxt, collections: ResolvedCollectio } collectionDump[collection.name] = [] // Collection table definition - collectionDump[collection.name].push(...collection.tableDefinition.split('\n')) + collectionDump[collection.name].push( + ...generateCollectionTableDefinition(collection, { drop: true }).split('\n'), + ) if (!collection.source) { continue @@ -262,7 +264,7 @@ async function processCollectionItems(nuxt: Nuxt, collections: ResolvedCollectio collectionChecksum[collection.name] = hash(collectionDump[collection.name]) collectionDump[collection.name].push( - generateCollectionTableDefinition(infoCollection.name, infoCollection, { drop: false }), + generateCollectionTableDefinition(infoCollection, { drop: false }), `DELETE FROM ${infoCollection.tableName} WHERE _id = 'checksum_${collection.name}'`, generateCollectionInsert(infoCollection, { _id: `checksum_${collection.name}`, version: collectionChecksum[collection.name] }), ) diff --git a/src/runtime/adapters/sqlite.ts b/src/runtime/adapters/sqlite.ts index bca19dbab..36ff09411 100644 --- a/src/runtime/adapters/sqlite.ts +++ b/src/runtime/adapters/sqlite.ts @@ -7,6 +7,7 @@ export default createDatabaseAdapter<{ filename: string }>((opts) => { if (!db) { const filename = !opts || isAbsolute(opts?.filename || '') ? opts?.filename + // @ts-expect-error - `_importMeta_` is defined in nitro runtime : new URL(opts.filename, globalThis._importMeta_.url).pathname db = new Database(filename) } diff --git a/src/types/collection.ts b/src/types/collection.ts index 04e81b7ce..ce73e71f8 100644 --- a/src/types/collection.ts +++ b/src/types/collection.ts @@ -41,22 +41,16 @@ export interface DefinedCollection { source: ResolvedCollectionSource | undefined schema: ZodObject extendedSchema: ZodObject + jsonFields: string[] } export interface ResolvedCollection { name: string - pascalName: string + tableName: string type: CollectionType source: ResolvedCollectionSource | undefined schema: ZodObject extendedSchema: ZodObject - tableName: string - tableDefinition: string - generatedFields: { - raw: boolean - body: boolean - path: boolean - } jsonFields: string[] /** * Whether the collection is private or not. diff --git a/src/utils/collection.ts b/src/utils/collection.ts index c3a869b79..2d0fe5f5b 100644 --- a/src/utils/collection.ts +++ b/src/utils/collection.ts @@ -1,15 +1,16 @@ -import { pascalCase } from 'scule' import type { ZodObject, ZodOptionalDef, ZodRawShape, ZodStringDef, ZodType } from 'zod' import type { Collection, ResolvedCollection, CollectionSource, DefinedCollection, ResolvedCollectionSource } from '../types/collection' import { defineLocalSource, defineGitHubSource } from './source' import { metaSchema, pageSchema } from './schema' import type { ZodFieldType } from './zod' -import { getUnderlyingType, ZodToSqlFieldTypes, z } from './zod' +import { getUnderlyingType, ZodToSqlFieldTypes, z, getUnderlyingTypeName } from './zod' import { logger } from './dev' const JSON_FIELDS_TYPES = ['ZodObject', 'ZodArray', 'ZodRecord', 'ZodIntersection', 'ZodUnion', 'ZodAny'] -const getTableName = (name: string) => `content_${name}` +function getTableName(name: string) { + return `content_${name}` +} export function defineCollection(collection: Collection): DefinedCollection { let schema = collection.schema || z.object({}) @@ -24,6 +25,9 @@ export function defineCollection(collection: Collection JSON_FIELDS_TYPES + .includes(getUnderlyingTypeName(schema.shape[key as keyof typeof schema.shape]))), } } @@ -40,19 +44,7 @@ export function resolveCollection(name: string, collection: DefinedCollection): ...collection, name, type: collection.type || 'page', - pascalName: pascalCase(name), - source: collection.source, tableName: getTableName(name), - tableDefinition: generateCollectionTableDefinition(name, collection, { drop: true }), - generatedFields: { - raw: typeof collection.schema.shape.raw !== 'undefined', - body: typeof collection.schema.shape.body !== 'undefined', - path: typeof collection.schema.shape.path !== 'undefined', - }, - jsonFields: Object.keys(collection.extendedSchema.shape || {}) - .filter(key => JSON_FIELDS_TYPES - .includes(getUnderlyingType(collection.extendedSchema.shape[key]).constructor.name)), - private: name === '_info', } } @@ -139,7 +131,7 @@ export function generateCollectionInsert(collection: ResolvedCollection, data: R } // Convert a collection with Zod schema to SQL table definition -export function generateCollectionTableDefinition(name: string, collection: DefinedCollection, opts: { drop?: boolean } = {}) { +export function generateCollectionTableDefinition(collection: ResolvedCollection, opts: { drop?: boolean } = {}) { const sortedKeys = Object.keys((collection.extendedSchema).shape).sort() const sqlFields = sortedKeys.map((key) => { const type = (collection.extendedSchema).shape[key] @@ -180,10 +172,10 @@ export function generateCollectionTableDefinition(name: string, collection: Defi return `"${key}" ${sqlType}${constraints.join(' ')}` }) - let definition = `CREATE TABLE IF NOT EXISTS ${getTableName(name)} (${sqlFields.join(', ')});` + let definition = `CREATE TABLE IF NOT EXISTS ${collection.tableName} (${sqlFields.join(', ')});` if (opts.drop) { - definition = `DROP TABLE IF EXISTS ${getTableName(name)};\n${definition}` + definition = `DROP TABLE IF EXISTS ${collection.tableName};\n${definition}` } return definition diff --git a/src/utils/content/index.ts b/src/utils/content/index.ts index 186a01c17..a52f8e659 100644 --- a/src/utils/content/index.ts +++ b/src/utils/content/index.ts @@ -82,7 +82,7 @@ export async function parseContent(key: string, content: string, collection: Res }) const { id: _id, ...parsedContentFields } = parsedContent - const result = { _id } as typeof collection.schema._type + const result = { _id } as typeof collection.extendedSchema._type const meta = {} as Record const collectionKeys = Object.keys(collection.extendedSchema.shape) @@ -97,5 +97,9 @@ export async function parseContent(key: string, content: string, collection: Res result.meta = meta + // Storing `content` into `rawbody` field + // This allow users to define `rowbody` field in collection schema and access to raw content + result.rawbody = content + return result } diff --git a/src/utils/templates.ts b/src/utils/templates.ts index bf0562926..0ce250cff 100644 --- a/src/utils/templates.ts +++ b/src/utils/templates.ts @@ -4,6 +4,7 @@ import type { NuxtTemplate } from '@nuxt/schema' import { isAbsolute, join, relative } from 'pathe' import { genDynamicImport } from 'knitwork' import { deflate } from 'pako' +import { pascalCase } from 'scule' import type { ResolvedCollection } from '../types/collection' import type { Manifest } from '../types/manifest' @@ -36,15 +37,15 @@ export const contentTypesTemplate = (collections: ResolvedCollection[]) => ({ '', 'declare module \'@nuxt/content\' {', ...publicCollections.map(c => - indentLines(`interface ${c.pascalName}CollectionItem extends ${parentInterface(c)} ${printNode(zodToTs(c.schema, c.pascalName).node)}`), + indentLines(`interface ${pascalCase(c.name)}CollectionItem extends ${parentInterface(c)} ${printNode(zodToTs(c.schema, pascalCase(c.name)).node)}`), ), '', ' interface PageCollections {', - ...pagesCollections.map(c => indentLines(`${c.name}: ${c.pascalName}CollectionItem`, 4)), + ...pagesCollections.map(c => indentLines(`${c.name}: ${pascalCase(c.name)}CollectionItem`, 4)), ' }', '', ' interface Collections {', - ...publicCollections.map(c => indentLines(`${c.name}: ${c.pascalName}CollectionItem`, 4)), + ...publicCollections.map(c => indentLines(`${c.name}: ${pascalCase(c.name)}CollectionItem`, 4)), ' }', '}', '', @@ -61,7 +62,6 @@ export const collectionsTemplate = (collections: ResolvedCollection[]) => ({ const collectionsMeta = options.collections.reduce((acc, collection) => { acc[collection.name] = { name: collection.name, - pascalName: collection.pascalName, tableName: collection.tableName, // Remove source from collection meta if it's a remote collection source: collection.source?.repository ? undefined : collection.source, diff --git a/src/utils/zod.ts b/src/utils/zod.ts index c2d831e32..139440b3a 100644 --- a/src/utils/zod.ts +++ b/src/utils/zod.ts @@ -25,3 +25,7 @@ export function getUnderlyingType(zodType: ZodType): ZodType { } return zodType } + +export function getUnderlyingTypeName(zodType: ZodType): string { + return getUnderlyingType(zodType).constructor.name +} diff --git a/test/unit/generateCollectionTableDefinition.test.ts b/test/unit/generateCollectionTableDefinition.test.ts index 93635312a..c0fb660b1 100644 --- a/test/unit/generateCollectionTableDefinition.test.ts +++ b/test/unit/generateCollectionTableDefinition.test.ts @@ -1,15 +1,15 @@ import { describe, expect, test } from 'vitest' import { z } from 'zod' -import { generateCollectionTableDefinition, defineCollection } from '../../src/utils/collection' +import { generateCollectionTableDefinition, defineCollection, resolveCollection } from '../../src/utils/collection' import { getTableName } from '../utils/database' describe('generateCollectionTableDefinition', () => { test('Page without custom schema', () => { - const collection = defineCollection({ + const collection = resolveCollection('content', defineCollection({ type: 'page', source: 'pages/**', - }) - const sql = generateCollectionTableDefinition('content', collection) + }))! + const sql = generateCollectionTableDefinition(collection) expect(sql).toBe([ `CREATE TABLE IF NOT EXISTS ${getTableName('content')} (`, @@ -28,14 +28,14 @@ describe('generateCollectionTableDefinition', () => { }) test('Page with custom schema', () => { - const collection = defineCollection({ + const collection = resolveCollection('content', defineCollection({ type: 'page', source: 'pages/**', schema: z.object({ customField: z.string(), }), - }) - const sql = generateCollectionTableDefinition('content', collection) + }))! + const sql = generateCollectionTableDefinition(collection) expect(sql).toBe([ `CREATE TABLE IF NOT EXISTS ${getTableName('content')} (`, @@ -55,14 +55,14 @@ describe('generateCollectionTableDefinition', () => { }) test('Data with schema', () => { - const collection = defineCollection({ + const collection = resolveCollection('content', defineCollection({ type: 'data', source: 'data/**', schema: z.object({ customField: z.string(), }), - }) - const sql = generateCollectionTableDefinition('content', collection) + }))! + const sql = generateCollectionTableDefinition(collection) expect(sql).toBe([ `CREATE TABLE IF NOT EXISTS ${getTableName('content')} (`, @@ -77,14 +77,14 @@ describe('generateCollectionTableDefinition', () => { // Columns test('String with max length', () => { - const collection = defineCollection({ + const collection = resolveCollection('content', defineCollection({ type: 'data', source: 'data/**', schema: z.object({ customField: z.string().max(64).default('foo'), }), - }) - const sql = generateCollectionTableDefinition('content', collection) + }))! + const sql = generateCollectionTableDefinition(collection) expect(sql).toBe([ `CREATE TABLE IF NOT EXISTS ${getTableName('content')} (`, @@ -98,14 +98,14 @@ describe('generateCollectionTableDefinition', () => { }) test('Number', () => { - const collection = defineCollection({ + const collection = resolveCollection('content', defineCollection({ type: 'data', source: 'data/**', schema: z.object({ customField: z.number().default(13), }), - }) - const sql = generateCollectionTableDefinition('content', collection) + }))! + const sql = generateCollectionTableDefinition(collection) expect(sql).toBe([ `CREATE TABLE IF NOT EXISTS ${getTableName('content')} (`, @@ -119,14 +119,14 @@ describe('generateCollectionTableDefinition', () => { }) test('Boolean', () => { - const collection = defineCollection({ + const collection = resolveCollection('content', defineCollection({ type: 'data', source: 'data/**', schema: z.object({ customField: z.boolean().default(false), }), - }) - const sql = generateCollectionTableDefinition('content', collection) + }))! + const sql = generateCollectionTableDefinition(collection) expect(sql).toBe([ `CREATE TABLE IF NOT EXISTS ${getTableName('content')} (`, @@ -140,14 +140,14 @@ describe('generateCollectionTableDefinition', () => { }) test('Date', () => { - const collection = defineCollection({ + const collection = resolveCollection('content', defineCollection({ type: 'data', source: 'data/**', schema: z.object({ customField: z.date(), }), - }) - const sql = generateCollectionTableDefinition('content', collection) + }))! + const sql = generateCollectionTableDefinition(collection) expect(sql).toBe([ `CREATE TABLE IF NOT EXISTS ${getTableName('content')} (`, @@ -161,7 +161,7 @@ describe('generateCollectionTableDefinition', () => { }) test('Object', () => { - const collection = defineCollection({ + const collection = resolveCollection('content', defineCollection({ type: 'data', source: 'data/**', schema: z.object({ @@ -170,8 +170,8 @@ describe('generateCollectionTableDefinition', () => { f2: z.string(), }), }), - }) - const sql = generateCollectionTableDefinition('content', collection) + }))! + const sql = generateCollectionTableDefinition(collection) expect(sql).toBe([ `CREATE TABLE IF NOT EXISTS ${getTableName('content')} (`, @@ -185,7 +185,7 @@ describe('generateCollectionTableDefinition', () => { }) test('Array', () => { - const collection = defineCollection({ + const collection = resolveCollection('content', defineCollection({ type: 'data', source: 'data/**', schema: z.object({ @@ -194,8 +194,8 @@ describe('generateCollectionTableDefinition', () => { f2: z.string(), })), }), - }) - const sql = generateCollectionTableDefinition('content', collection) + }))! + const sql = generateCollectionTableDefinition(collection) expect(sql).toBe([ `CREATE TABLE IF NOT EXISTS ${getTableName('content')} (`, @@ -209,7 +209,7 @@ describe('generateCollectionTableDefinition', () => { }) test('Nullable', () => { - const collection = defineCollection({ + const collection = resolveCollection('content', defineCollection({ type: 'data', source: 'data/**', schema: z.object({ @@ -225,8 +225,8 @@ describe('generateCollectionTableDefinition', () => { }).nullable(), f6: z.array(z.any()).nullable(), }), - }) - const sql = generateCollectionTableDefinition('content', collection) + }))! + const sql = generateCollectionTableDefinition(collection) expect(sql).toBe([ `CREATE TABLE IF NOT EXISTS ${getTableName('content')} (`,