-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
#2 Relations and References #3
Changes from all commits
ecef4f8
ededfb7
a8ee887
57f361e
2f112e4
6f6c621
6449f2e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -56,9 +56,16 @@ export async function loadAndTransformCollections( | |
const keys = { name: collection.name }; | ||
const name = naming.replace(/%%(\w+)%%/, (_, k: keyof typeof keys) => keys[k] ?? _); | ||
const path = resolve(to, name); | ||
const relational = collection.fields?.some(field => field.widget === 'relation'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would fail on nested fields. This should be a recursive lookup. Or what might be better, is some additional property in the result payload of the transformers, so the imports would be build after transformations. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't think about or test with nested relations, my bad There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No problem. I changed the transform result to include a dependencies property, this should do the trick. |
||
const astroImports = ['z']; | ||
|
||
if (relational) { | ||
// Collections containing relational fields require `reference` | ||
astroImports.push('reference'); | ||
} | ||
|
||
// build content and prettify if possible | ||
const raw = `import { z } from 'astro:content';\n\nexport const schema = ${cptime};\n`; | ||
const raw = `import { ${astroImports.sort().join(', ')} } from 'astro:content';\n\nexport const schema = ${cptime};\n`; | ||
const pretty = await tryOrFail(() => formatCode(raw, 'typescript'), ERROR.FORMATTING_FAILED); | ||
|
||
// prepare folder if non-existent, remove existing and write file | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import type { CmsFieldBase, CmsFieldRelation } from 'decap-cms-core'; | ||
import z from 'zod'; | ||
|
||
import { transformRelationField } from './field-relation.transform.js'; | ||
|
||
describe('field-relation.transform', () => { | ||
// Constructs a base relation field configuration with overrides | ||
function buildField( | ||
partial: Partial<CmsFieldBase & CmsFieldRelation>, | ||
): CmsFieldBase & CmsFieldRelation { | ||
return { | ||
name: 'test_field', | ||
widget: 'relation', | ||
collection: 'authors', | ||
multiple: false, | ||
required: true, | ||
search_fields: ['name'], | ||
value_field: 'name', | ||
display_fields: ['name'], | ||
...partial, | ||
}; | ||
} | ||
|
||
// Normalises quotes in the cptime output for consistent comparison | ||
function normaliseCptime(cptime: string): string { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As the test data is explicitly defined and the transformers are deterministic, this should not be necessary. Instead, we must trust the resulting output to have a specific kind of quote and expect them to have always the same shape. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was only added because of a battle between eslint and prettier, so it's there to satisfy the IDE only. I couldn't get eslint and prettier rules to agree on quotes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I see. But badly configured tooling shouldn't force us to do such things. I'll align the configuration. |
||
return cptime.replace(/'/g, '"'); | ||
} | ||
|
||
it('transforms a single required relation field into a string schema', () => { | ||
const field = buildField({ multiple: false, required: true, collection: 'authors' }); | ||
const { runtime, cptime } = transformRelationField(field, z); | ||
|
||
expect(runtime._def.typeName).toBe(z.ZodFirstPartyTypeKind.ZodString); | ||
expect(normaliseCptime(cptime)).toBe('reference("authors")'); | ||
expect(() => runtime.parse('some-author-id')).not.toThrow(); | ||
expect(() => runtime.parse(['not allowed'])).toThrow(); | ||
}); | ||
|
||
it('transforms a single non-required relation field into the same string schema', () => { | ||
const field = buildField({ multiple: false, required: false, collection: 'authors' }); | ||
const { runtime, cptime } = transformRelationField(field, z); | ||
|
||
expect(runtime._def.typeName).toBe(z.ZodFirstPartyTypeKind.ZodString); | ||
expect(normaliseCptime(cptime)).toBe('reference("authors")'); | ||
expect(() => runtime.parse('some-author-id')).not.toThrow(); | ||
}); | ||
|
||
it('transforms a multiple required relation field into an array of strings schema', () => { | ||
const field = buildField({ multiple: true, required: true, collection: 'tags' }); | ||
const { runtime, cptime } = transformRelationField(field, z); | ||
|
||
expect(runtime._def.typeName).toBe(z.ZodFirstPartyTypeKind.ZodArray); | ||
expect(normaliseCptime(cptime)).toBe('z.array(reference("tags"))'); | ||
expect(() => runtime.parse(['tag1', 'tag2'])).not.toThrow(); | ||
expect(() => runtime.parse('invalid')).toThrow(); | ||
}); | ||
|
||
it('transforms a multiple non-required relation field into the same array schema', () => { | ||
const field = buildField({ multiple: true, required: false, collection: 'categories' }); | ||
const { runtime, cptime } = transformRelationField(field, z); | ||
|
||
expect(runtime._def.typeName).toBe(z.ZodFirstPartyTypeKind.ZodArray); | ||
expect(normaliseCptime(cptime)).toBe('z.array(reference("categories"))'); | ||
expect(() => runtime.parse(['cat1', 'cat2'])).not.toThrow(); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,15 @@ | ||
import type { CmsFieldBase, CmsFieldRelation } from 'decap-cms-core'; | ||
import type { ZodString } from 'zod'; | ||
import type { ZodTypeAny } from 'zod'; | ||
|
||
import type { Transformer } from '../utils/transform.utils.js'; | ||
|
||
// TODO implement transform relations | ||
// https://decapcms.org/docs/widgets/#relation | ||
export const transformRelationField: Transformer<ZodString, CmsFieldBase & CmsFieldRelation> = ( | ||
_, | ||
export const transformRelationField: Transformer<ZodTypeAny, CmsFieldBase & CmsFieldRelation> = ( | ||
{ collection, multiple = false }, | ||
z, | ||
) => ({ | ||
runtime: z.string(), | ||
cptime: 'z.string()', | ||
}); | ||
) => { | ||
const runtime = multiple ? z.array(z.string()) : z.string(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's the reason I haven't thrown it together yet... 😅 Tbh, I'm not sure if supporting the runtime types anyway, as I personally just generate the types in watch mode as static files. So it maybe makes sense to drop them entirely... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm also not really using / interested in the runtime side of things. I include adc as part of my build process and watch the content dir to trigger a new build. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll drop the runtime support, that reduces complexity for several aspects. |
||
|
||
const cptime = multiple ? `z.array(reference('${collection}'))` : `reference('${collection}')`; | ||
|
||
return { runtime, cptime }; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a relation example as well, wouldn't it make more sense to extend that instead?