Skip to content

Editor #86

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

Draft
wants to merge 35 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
e78948f
initial commit
odama626 Jul 29, 2022
4154b26
wip
odama626 Jul 29, 2022
1ffcc8c
Merge branch 'main' into editor
odama626 Jul 30, 2022
352eba4
add field.description and redesign nesting pattern
odama626 Jul 30, 2022
876f72e
target schema types with config instead of schema query list types
odama626 Jul 30, 2022
199d982
put disabled fields in collapsible region at the bottom
odama626 Jul 30, 2022
72b2401
add typings
odama626 Jul 31, 2022
ceb7697
wip
odama626 Aug 3, 2022
9339af9
refactor collection metadata
odama626 Aug 4, 2022
a3cc34a
Merge branch 'main' into mutate
odama626 Aug 5, 2022
9410310
add more metadata to collection
odama626 Aug 5, 2022
df774c0
add json assertsions
odama626 Aug 5, 2022
fdefe15
update mutations update file and memory :D, breaks ava watch
odama626 Aug 6, 2022
978b130
isolate context based on plugin, major breaking changes
odama626 Aug 6, 2022
b724de1
comments
odama626 Aug 6, 2022
2309708
added sourceMemory to allow for testing without a filesystem
odama626 Aug 6, 2022
27912b9
sourceVirtual
odama626 Aug 6, 2022
a1fd32d
disable star paths, add basic create mutations
odama626 Aug 7, 2022
22da949
handle creating new records with variable paths
odama626 Aug 8, 2022
ed214f3
Merge branch 'main' into mutate
odama626 Aug 8, 2022
f31e91f
remove nanoid-dictionary
odama626 Aug 8, 2022
e43f01c
cleanups, add required fields to resolvers, add upsert resolver
odama626 Aug 8, 2022
251ec6b
Merge branch 'main' into mutate
odama626 Aug 11, 2022
a18ee39
expose collectionResolvers to config
odama626 Aug 11, 2022
259d940
prettier
odama626 Aug 11, 2022
fe42961
test husky
odama626 Aug 11, 2022
40f4c26
test husky
odama626 Aug 11, 2022
02bc50f
remove stray arg
odama626 Aug 13, 2022
b82ddf5
Merge branch 'main' into mutate
odama626 Aug 13, 2022
4d98b05
Merge branch 'mutate' into collection-resolvers
odama626 Aug 13, 2022
4521a3a
Merge branch 'main' into editor
odama626 Aug 13, 2022
b68b569
Merge branch 'collection-resolvers' into editor
odama626 Aug 13, 2022
1dfcff6
update editor
odama626 Aug 13, 2022
c3574a1
Merge branch 'main' into editor
odama626 Aug 16, 2022
c94f3be
add header
odama626 Aug 16, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions examples/content/markdown/authors/tony.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ friend: 40s3
image: tony.svg
date_joined: 2021-02-25T16:41:59.558Z
skills:
sitting: 204
sitting: 69
breathing: 7.07
liquid_consumption: 100
liquid_consumption: 420
existence: simulation
sports: -2
---

6 changes: 3 additions & 3 deletions examples/nextjs/flatbread.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,21 @@ export default defineConfig({
content: [
{
path: 'content/markdown/posts',
collection: 'Post',
name: 'Post',
refs: {
authors: 'Author',
},
},
{
path: 'content/markdown/posts/[category]/[slug].md',
collection: 'PostCategory',
name: 'PostCategory',
refs: {
authors: 'Author',
},
},
{
path: 'content/markdown/posts/**/*.md',
collection: 'PostCategoryBlob',
name: 'PostCategoryBlob',
refs: {
authors: 'Author',
},
Expand Down
38 changes: 23 additions & 15 deletions examples/sveltekit/flatbread.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,58 +16,66 @@ const transformerConfig = {
export default defineConfig({
source: sourceFilesystem(),
transformer: [transformerMarkdown(transformerConfig), transformerYaml()],

collectionResolvers: [
function fakeResolver(schemaComposer, args) {
const { name } = args;

schemaComposer.Query.addFields({
[`fake${name}`]: {
type: 'String',
description: `fake resolver`,
resolve() {
return `fake ${name}!`;
},
},
});
},
],

content: [
{
path: 'content/markdown/posts',
collection: 'Post',
name: 'Post',
refs: {
authors: 'Author',
},
},
{
path: 'content/markdown/posts/[category]/[slug].md',
collection: 'PostCategory',
refs: {
authors: 'Author',
},
},
{
path: 'content/markdown/posts/**/*.md',
collection: 'PostCategoryBlob',
name: 'PostCategory',
refs: {
authors: 'Author',
},
},
{
path: 'content/markdown/authors',
collection: 'Author',
name: 'Author',
refs: {
friend: 'Author',
},
overrides: [
createSvImgField('image', {
inputDir: 'static/authorImages',
outputDir: 'static/g',
srcGenerator: (path) => '/g/' + path,
srcGenerator: (path) => 'http://localhost:5174/g/' + path,
}),
],
},
{
path: 'content/yaml/authors',
collection: 'YamlAuthor',
name: 'YamlAuthor',
refs: {
friend: 'YamlAuthor',
},
},
{
path: 'content/markdown/deeply-nested',
collection: 'OverrideTest',
name: 'OverrideTest',
overrides: [
{
field: 'deeply.nested',
type: 'String',
test: undefined,
test2: null,
resolve: (source) => String(source).toUpperCase(),
},
{
Expand Down
20 changes: 13 additions & 7 deletions examples/sveltekit/src/routes/index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@
const query = `
query PostCategory {
allPostCategories (sortBy: "title", order: DESC) {
_collection
_filename
_slug
_metadata {
sourceContext {
filename
slug
}
collection
}
id
title
category
Expand All @@ -18,7 +22,11 @@
timeToRead
}
authors {
_slug
_metadata {
sourceContext {
slug
}
}
id
name
entity
Expand Down Expand Up @@ -99,9 +107,7 @@

<div class="grid grid-cols-2 divide-x-2 divide-black">
<Pane label="JSON Output">
<pre
class="overflow-auto p-3"
style="height: calc(100vh - 3.5rem);">
<pre class="overflow-auto p-3" style="height: calc(100vh - 3.5rem);">
<code class="text-sm">
{JSON.stringify(data, null, 2)}
</code>
Expand Down
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"lodash-es": "4.17.21",
"lru-cache": "7.13.2",
"matcher": "5.0.0",
"nanoid": "4.0.0",
"plur": "5.1.0"
},
"devDependencies": {
Expand Down
14 changes: 3 additions & 11 deletions packages/core/src/cache/cache.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { GraphQLSchema } from 'graphql';
import LRU from 'lru-cache';
import { createHash } from 'node:crypto';
import { LoadedFlatbreadConfig } from '../types';
import { anyToString } from '../utils/stringUtils';
import createShaHash from '../utils/createShaHash';

type SchemaCacheKey = string;

Expand All @@ -29,7 +28,7 @@ export function cacheSchema(
config: LoadedFlatbreadConfig,
schema: GraphQLSchema
) {
const schemaHashKey = getSchemaHash(config);
const schemaHashKey = createShaHash(config);
cache.schema.set(schemaHashKey, schema);
}

Expand All @@ -39,13 +38,6 @@ export function cacheSchema(
export function checkCacheForSchema(
config: LoadedFlatbreadConfig
): GraphQLSchema | undefined {
const schemaHashKey = getSchemaHash(config);
const schemaHashKey = createShaHash(config);
return cache.schema.get(schemaHashKey);
}

/**
* Generates a hash key for a given Flatbread config.
*/
export function getSchemaHash(config: LoadedFlatbreadConfig) {
return createHash('md5').update(anyToString(config)).digest('hex');
}
105 changes: 105 additions & 0 deletions packages/core/src/generators/collectionMutations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { ObjectTypeComposer, SchemaComposer } from 'graphql-compose';
import { get, merge } from 'lodash-es';
import { CollectionContext, CollectionResolverArgs, EntryNode } from '../types';

export default function addCollectionMutations(
schemaComposer: SchemaComposer,
args: CollectionResolverArgs
) {
const { name, objectTypeComposer, updateCollectionRecord, collectionEntry } =
args;

async function update(
payload: Record<string, EntryNode>,
existing: EntryNode
) {
// remove _metadata to prevent injection
const { _metadata, ...update } = payload?.[name];

const targetRecord = objectTypeComposer
.getResolver('findById')
.resolve({ args: update });

// remove supplied key (might not be required)
delete update[targetRecord._metadata.referenceField];
const newRecord = merge(targetRecord, update);

await updateCollectionRecord(collectionEntry, newRecord);

return newRecord;
}

async function create(source: unknown, payload: Record<string, EntryNode>) {
collectionEntry.creationRequiredFields.forEach((field) => {
if (!payload[name].hasOwnProperty(field))
throw new Error(
`field ${field} is required when creating a new ${name}`
);
});

const record = merge(payload[name], {
_metadata: {
referenceField: collectionEntry.referenceField ?? 'id',
collection: name,
transformedBy: collectionEntry?.defaultTransformer,
sourcedBy: collectionEntry?.defaultSource,
} as CollectionContext,
});

return await updateCollectionRecord(collectionEntry, record);
}

schemaComposer.Mutation.addFields({
[`update${name}`]: {
type: objectTypeComposer,
args: {
[name]: objectTypeComposer
.getInputTypeComposer()
.makeFieldNonNull(collectionEntry.creationRequiredFields),
},
description: `Update a ${name}`,
async resolve(source: unknown, payload: Record<string, EntryNode>) {
const { _metadata, ...args } = payload?.[name];

const existingRecord = objectTypeComposer
.getResolver('findById')
.resolve({ args });

if (!existingRecord)
throw new Error(
`${name} with ${collectionEntry.referenceField} of ${get(
args,
collectionEntry.referenceField
)} not found`
);
return update(payload, existingRecord);
},
},
[`create${name}`]: {
type: objectTypeComposer,
args: {
[name]: objectTypeComposer
.getInputTypeComposer()
.clone(`${name}CreateInput`)
.removeField('id')
.makeFieldNonNull(collectionEntry.creationRequiredFields),
},
description: `Create a ${name}`,
resolve: create,
},
[`upsert${name}`]: {
type: objectTypeComposer,
args: { [name]: objectTypeComposer.getInputTypeComposer() },
async resolve(source: unknown, payload: Record<string, EntryNode>) {
const { _metadata, ...args } = payload?.[name];

const existingRecord = objectTypeComposer
.getResolver('findById')
.resolve({ args });

if (existingRecord) return update(payload, existingRecord);
create(source, payload);
},
},
});
}
82 changes: 82 additions & 0 deletions packages/core/src/generators/collectionQueries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import resolveQueryArgs from '../resolvers/arguments';

import { SchemaComposer } from 'graphql-compose';
import { cloneDeep } from 'lodash-es';
import {
generateArgsForAllItemQuery,
generateArgsForManyItemQuery,
generateArgsForSingleItemQuery,
} from '../generators/arguments';
import { CollectionResolverArgs, EntryNode } from '../types';

export default function addCollectionQueries(
schemaComposer: SchemaComposer,
args: CollectionResolverArgs & { allContentNodesJSON: Record<string, any[]> }
) {
const { name, pluralName, config, objectTypeComposer, allContentNodesJSON } =
args;

const pluralTypeQueryName = 'all' + pluralName;

objectTypeComposer.addResolver({
name: 'findById',
type: () => objectTypeComposer,
description: `Find one ${name} by its ID`,
args: generateArgsForSingleItemQuery(),
resolve: (rp: Record<string, any>) =>
cloneDeep(
allContentNodesJSON[name].find(
(node: EntryNode) => node.id === rp.args.id
)
),
});

objectTypeComposer.addResolver({
name: 'findMany',
type: () => [objectTypeComposer],
description: `Find many ${pluralName} by their IDs`,
args: generateArgsForManyItemQuery(pluralName),
resolve: (rp: Record<string, any>) => {
const idsToFind = rp.args.ids ?? [];
const matches =
cloneDeep(allContentNodesJSON[name])?.filter((node: EntryNode) =>
idsToFind?.includes(node.id)
) ?? [];
return resolveQueryArgs(matches, rp.args, config, {
type: {
name: name,
pluralName: pluralName,
pluralQueryName: pluralTypeQueryName,
},
});
},
});

objectTypeComposer.addResolver({
name: 'all',
args: generateArgsForAllItemQuery(pluralName),
type: () => [objectTypeComposer],
description: `Return a set of ${pluralName}`,
resolve: (rp: Record<string, any>) => {
const nodes = cloneDeep(allContentNodesJSON[name]);
return resolveQueryArgs(nodes, rp.args, config, {
type: {
name: name,
pluralName: pluralName,
pluralQueryName: pluralTypeQueryName,
},
});
},
});

schemaComposer.Query.addFields({
/**
* Add find by ID to each content type
*/
[name]: objectTypeComposer.getResolver('findById'),
/**
* Add find 'many' to each content type
*/
[pluralTypeQueryName]: objectTypeComposer.getResolver('all'),
});
}
Loading