Skip to content

Commit

Permalink
refactor: simplify collection definition
Browse files Browse the repository at this point in the history
  • Loading branch information
farnabaz committed Nov 3, 2024
1 parent 8f06edd commit 99f25df
Show file tree
Hide file tree
Showing 8 changed files with 61 additions and 64 deletions.
6 changes: 4 additions & 2 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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] }),
)
Expand Down
1 change: 1 addition & 0 deletions src/runtime/adapters/sqlite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
10 changes: 2 additions & 8 deletions src/types/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,22 +41,16 @@ export interface DefinedCollection<T extends ZodRawShape = ZodRawShape> {
source: ResolvedCollectionSource | undefined
schema: ZodObject<T>
extendedSchema: ZodObject<T>
jsonFields: string[]
}

export interface ResolvedCollection<T extends ZodRawShape = ZodRawShape> {
name: string
pascalName: string
tableName: string
type: CollectionType
source: ResolvedCollectionSource | undefined
schema: ZodObject<T>
extendedSchema: ZodObject<T>
tableName: string
tableDefinition: string
generatedFields: {
raw: boolean
body: boolean
path: boolean
}
jsonFields: string[]
/**
* Whether the collection is private or not.
Expand Down
28 changes: 10 additions & 18 deletions src/utils/collection.ts
Original file line number Diff line number Diff line change
@@ -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<T extends ZodRawShape>(collection: Collection<T>): DefinedCollection {
let schema = collection.schema || z.object({})
Expand All @@ -24,6 +25,9 @@ export function defineCollection<T extends ZodRawShape>(collection: Collection<T
source: resolveSource(collection.source),
schema: collection.schema || z.object({}),
extendedSchema: schema,
jsonFields: Object.keys(schema.shape)
.filter(key => JSON_FIELDS_TYPES
.includes(getUnderlyingTypeName(schema.shape[key as keyof typeof schema.shape]))),
}
}

Expand All @@ -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',
}
}
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion src/utils/content/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, unknown>

const collectionKeys = Object.keys(collection.extendedSchema.shape)
Expand All @@ -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
}
8 changes: 4 additions & 4 deletions src/utils/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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)),
' }',
'}',
'',
Expand All @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions src/utils/zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,7 @@ export function getUnderlyingType(zodType: ZodType): ZodType {
}
return zodType
}

export function getUnderlyingTypeName(zodType: ZodType): string {
return getUnderlyingType(zodType).constructor.name
}
62 changes: 31 additions & 31 deletions test/unit/generateCollectionTableDefinition.test.ts
Original file line number Diff line number Diff line change
@@ -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')} (`,
Expand All @@ -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')} (`,
Expand All @@ -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')} (`,
Expand All @@ -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')} (`,
Expand All @@ -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')} (`,
Expand All @@ -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')} (`,
Expand All @@ -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')} (`,
Expand All @@ -161,7 +161,7 @@ describe('generateCollectionTableDefinition', () => {
})

test('Object', () => {
const collection = defineCollection({
const collection = resolveCollection('content', defineCollection({
type: 'data',
source: 'data/**',
schema: z.object({
Expand All @@ -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')} (`,
Expand All @@ -185,7 +185,7 @@ describe('generateCollectionTableDefinition', () => {
})

test('Array', () => {
const collection = defineCollection({
const collection = resolveCollection('content', defineCollection({
type: 'data',
source: 'data/**',
schema: z.object({
Expand All @@ -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')} (`,
Expand All @@ -209,7 +209,7 @@ describe('generateCollectionTableDefinition', () => {
})

test('Nullable', () => {
const collection = defineCollection({
const collection = resolveCollection('content', defineCollection({
type: 'data',
source: 'data/**',
schema: z.object({
Expand All @@ -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')} (`,
Expand Down

0 comments on commit 99f25df

Please sign in to comment.