Skip to content

Commit

Permalink
refactor schema to withSchema (#223)
Browse files Browse the repository at this point in the history
  • Loading branch information
souporserious authored Nov 24, 2024
1 parent c4d274c commit 87ce75d
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 67 deletions.
17 changes: 17 additions & 0 deletions .changeset/khaki-donkeys-sit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
'renoun': minor
---

Moves the `Directory` `schema` option to `<Directory>.withSchema`. This aligns with the recent refactor of the `getImport` option to `<Directory>.withModule`.

### Breaking Changes

Update the `schema` option to `withSchema`:

```diff
export const posts = new Directory<{ mdx: PostType }>({
path: 'posts',
-- schema: { mdx: { frontmatter: frontmatterSchema.parse } },
})
++ .withSchema('mdx', { frontmatter: frontmatterSchema.parse })
```
2 changes: 1 addition & 1 deletion examples/blog/collections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ interface PostType {

export const posts = new Directory<{ mdx: PostType }>({
path: 'posts',
schema: { mdx: { frontmatter: frontmatterSchema.parse } },
})
.withSchema('mdx', { frontmatter: frontmatterSchema.parse })
.withModule((path) => import(`./posts/${path}`))
.withFilter((entry) => isFile(entry, 'mdx'))
.withSort(async (a, b) => {
Expand Down
62 changes: 30 additions & 32 deletions packages/renoun/src/file-system/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,27 +370,26 @@ describe('file system', () => {
ts: { metadata: { title: string } }
}>({
fileSystem,
schema: {
ts: {
metadata: (value) => {
if (typeof value.title === 'string') {
return value
}
throw new Error('Expected a title')
},
})
.withSchema('ts', {
metadata: (value) => {
if (typeof value.title === 'string') {
return value
}
throw new Error('Expected a title')
},
},
}).withModule(async (path) => {
const transpiledCode = await fileSystem.transpileFile(path)
const module = { exports: {} }
})
.withModule(async (path) => {
const transpiledCode = await fileSystem.transpileFile(path)
const module = { exports: {} }

runInNewContext(
`(function(module, exports) { ${transpiledCode} })(module, module.exports);`,
{ module }
)
runInNewContext(
`(function(module, exports) { ${transpiledCode} })(module, module.exports);`,
{ module }
)

return module.exports
})
return module.exports
})
const file = await directory.getFileOrThrow('index', 'ts')
const fileExport = file.getExport('metadata')

Expand All @@ -415,22 +414,21 @@ describe('file system', () => {
}
}>({
fileSystem,
schema: {
ts: {
metadata: metadataSchema.parse,
},
},
}).withModule(async (path) => {
const transpiledCode = await fileSystem.transpileFile(path)
const module = { exports: {} }
})
.withSchema('ts', {
metadata: metadataSchema.parse,
})
.withModule(async (path) => {
const transpiledCode = await fileSystem.transpileFile(path)
const module = { exports: {} }

runInNewContext(
`(function(module, exports) { ${transpiledCode} })(module, module.exports);`,
{ module }
)
runInNewContext(
`(function(module, exports) { ${transpiledCode} })(module, module.exports);`,
{ module }
)

return module.exports
})
return module.exports
})
const file = await directory.getFileOrThrow('hello-world', 'ts')
const metadata = await file.getExport('metadata').getRuntimeValue()

Expand Down
116 changes: 82 additions & 34 deletions packages/renoun/src/file-system/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -372,8 +372,9 @@ export class JavaScriptFileExportWithRuntime<
}

/** Options for a JavaScript file in the file system. */
export interface JavaScriptFileOptions extends FileOptions {
schema?: DirectoryOptions<any>['schema']
export interface JavaScriptFileOptions<Exports extends ExtensionType>
extends FileOptions {
schema?: ExtensionSchema<Exports>
tsConfigFilePath?: string
isVirtualFileSystem?: boolean
getModule?: (path: string) => Promise<any>
Expand All @@ -386,15 +387,15 @@ export class JavaScriptFile<
> extends File {
#exports = new Map<string, JavaScriptFileExport<Exports>>()
#getModule?: (path: string) => Promise<any>
#schema?: DirectoryOptions<any>['schema']
#schema?: ExtensionSchema<Exports>

constructor({
getModule,
schema,
tsConfigFilePath,
isVirtualFileSystem = false,
...fileOptions
}: JavaScriptFileOptions) {
}: JavaScriptFileOptions<Exports>) {
super(fileOptions)
this.#getModule = getModule
this.#schema = schema
Expand All @@ -406,20 +407,16 @@ export class JavaScriptFile<
return value
}

const extensionSchema = this.#schema[this.getExtension()]
const parseExportValue = this.#schema[name]

if (extensionSchema) {
const parseExportValue = extensionSchema[name]

if (parseExportValue) {
try {
value = parseExportValue(value)
} catch (error) {
if (error instanceof Error) {
throw new Error(
`[renoun] Schema validation failed to parse export "${name}" at file path "${this.getRelativePath()}" errored with: ${error.message}`
)
}
if (parseExportValue) {
try {
value = parseExportValue(value)
} catch (error) {
if (error instanceof Error) {
throw new Error(
`[renoun] Schema validation failed to parse export "${name}" at file path "${this.getRelativePath()}" errored with: ${error.message}`
)
}
}
}
Expand Down Expand Up @@ -493,13 +490,16 @@ export interface ExtensionTypes {
/** A function that validates and transforms export values. */
export type SchemaFunction<Value> = (value: Value) => any

/** A map of file export names to their respective schema function. */
export type ExtensionSchema<Exports extends ExtensionTypes> = {
[ExportName in keyof Exports[string]]?: SchemaFunction<
Exports[string][ExportName]
>
}

/** Functions that validate and transform export values for specific extensions. */
export type ExtensionSchemas<Types extends ExtensionTypes> = {
[Extension in keyof Types]?: {
[ExportName in keyof Types[Extension]]?: SchemaFunction<
Types[Extension][ExportName]
>
}
[Extension in keyof Types]?: ExtensionSchema<Types[Extension]>
}

/** The options for a `Directory`. */
Expand All @@ -510,9 +510,6 @@ interface DirectoryOptions<Types extends ExtensionTypes = ExtensionTypes> {
/** The base path used for all entry `getPath` methods. */
basePath?: string

/** The schemas to validate and transform export values across different file extensions. */
schema?: ExtensionSchemas<Types>

/** The file system to use for reading directory entries. */
fileSystem?: FileSystem

Expand All @@ -531,7 +528,7 @@ export class Directory<
#fileSystem: FileSystem | undefined
#entryGroup?: EntryGroup<FileSystemEntry<Types>[]> | undefined
#directory?: Directory<any, any>
#schema?: ExtensionSchemas<Types>
#schemas: ExtensionSchemas<Types> = {}
#getModule?: (path: string) => Promise<any>
#sortCallback?: (a: Entry, b: Entry) => Promise<number> | number
#filterCallback?:
Expand All @@ -545,7 +542,6 @@ export class Directory<
: join('.', options.path)
: '.'
this.#basePath = options.basePath
this.#schema = options.schema
this.#fileSystem = options.fileSystem
this.#entryGroup = options.entryGroup
}
Expand All @@ -558,10 +554,20 @@ export class Directory<
path: this.#path,
basePath: this.#basePath,
fileSystem: this.#fileSystem,
schema: this.#schema,
...options,
})

directory.#schemas = this.#schemas
if (this.#getModule) {
directory.setModuleGetter(this.#getModule)
}
if (this.#filterCallback) {
directory.setFilterCallback(this.#filterCallback)
}
if (this.#sortCallback) {
directory.setSortCallback(this.#sortCallback)
}

return directory
}

Expand Down Expand Up @@ -590,11 +596,11 @@ export class Directory<
path: this.#path,
basePath: this.#basePath,
fileSystem: this.#fileSystem,
schema: this.#schema,
})

directory.setDirectory(this)
directory.setModuleGetter(getModule)
directory.#schemas = this.#schemas
if (this.#filterCallback) {
directory.setFilterCallback(this.#filterCallback)
}
Expand Down Expand Up @@ -632,11 +638,11 @@ export class Directory<
path: this.#path,
basePath: this.#basePath,
fileSystem: this.#fileSystem,
schema: this.#schema,
})

directory.setDirectory(this)
directory.setFilterCallback(filter)
directory.#schemas = this.#schemas
if (this.#getModule) {
directory.setModuleGetter(this.#getModule)
}
Expand Down Expand Up @@ -665,17 +671,57 @@ export class Directory<
path: this.#path,
basePath: this.#basePath,
fileSystem: this.#fileSystem,
schema: this.#schema,
})

directory.setDirectory(this)
directory.setSortCallback(sort)
directory.#schemas = this.#schemas
if (this.#getModule) {
directory.setModuleGetter(this.#getModule)
}
if (this.#filterCallback) {
directory.setFilterCallback(this.#filterCallback)
}

return directory
}

/** Set an extension schema for all JavaScript files in the directory. */
protected setSchema<Extension extends keyof Types>(
extension: Extension,
schema: ExtensionSchemas<Types>[Extension]
) {
if (this.#schemas[extension]) {
throw new Error(
`[renoun] Schema for extension "${String(extension)}" is already defined in the directory "${this.#path}".`
)
}

this.#schemas[extension] = schema
}

/** Configure schema for a specific extension. */
withSchema<Extension extends keyof Types>(
extension: Extension,
schema: ExtensionSchemas<Types>[Extension]
): Directory<Types, HasModule, Entry> {
const directory = new Directory<Types, HasModule, Entry>({
path: this.#path,
basePath: this.#basePath,
fileSystem: this.#fileSystem,
})

directory.setDirectory(this)
directory.setSchema(extension, schema)
if (this.#getModule) {
directory.setModuleGetter(this.#getModule)
}
if (this.#filterCallback) {
directory.setFilterCallback(this.#filterCallback)
}
if (this.#sortCallback) {
directory.setSortCallback(this.#sortCallback)
}

return directory
}
Expand Down Expand Up @@ -920,9 +966,10 @@ export class Directory<
fileSystem,
path: entry.path,
entryGroup: this.#entryGroup,
schema: this.#schema,
})

directory.#schemas = this.#schemas

if (this.#getModule) {
directory.setModuleGetter(this.#getModule)
}
Expand Down Expand Up @@ -953,12 +1000,13 @@ export class Directory<
}
} else if (entry.isFile) {
const extension = extensionName(entry.name).slice(1)
const schema = this.#schemas[extension]
const file = isJavaScriptLikeExtension(extension)
? new JavaScriptFile({
? new JavaScriptFile<Types, HasModule>({
path: entry.path,
directory: this as Directory<Types>,
entryGroup: this.#entryGroup,
schema: this.#schema,
schema,
getModule: this.#getModule,
isVirtualFileSystem: fileSystem instanceof VirtualFileSystem,
})
Expand Down

0 comments on commit 87ce75d

Please sign in to comment.