Skip to content
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

feat(gatsby): Schema rebuilding #19092

Merged
merged 54 commits into from
Nov 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
c795e47
Refactor example-value to support fast incremental rebuilding
vladar Oct 28, 2019
cefa829
Detect node structure changes incrementally (to avoid expensive check…
vladar Oct 28, 2019
62687b8
Track metadata for inference in the redux
vladar Oct 28, 2019
bfde835
New `rebuildWithTypes` API for incremental schema rebuilding
vladar Oct 28, 2019
6fa5089
Schema hot reloading for `develop`
vladar Oct 28, 2019
5d17926
Move things around for better cohesion
vladar Oct 30, 2019
b703768
Replace old `getExampleValue` with new APIs based on inference metadata
vladar Oct 30, 2019
2194c98
Cleanup / rename things for consistency
vladar Oct 30, 2019
d0de33b
Proper handling of ADD_FIELD_TO_NODE action
vladar Oct 30, 2019
6df4358
Make sure stale inferred fields are removed from the schema
vladar Oct 30, 2019
1c697a5
More tests to and related fixes to conflict reporting
vladar Oct 31, 2019
066cca3
Clear derived TypeComposers and InputTypeComposers on type rebuild
vladar Nov 1, 2019
2df65cd
More tests for schema rebuilding
vladar Nov 1, 2019
def3963
Delete empty inferred type
vladar Nov 4, 2019
c731ba2
Refactor: use functions for field names generated out of a type name
vladar Nov 4, 2019
896a28c
More tests + switched to inline snapshots for tests readability
vladar Nov 4, 2019
c8eaa56
Support adding / deleting child convenience fields on parent type
vladar Nov 5, 2019
7f50feb
Added nested extension test
freiksenet Nov 5, 2019
c52a0a6
Apply extensions and other special logic to all nested types
vladar Nov 5, 2019
58d6da0
Make sure all derived types are processed on rebuild (including recur…
vladar Nov 5, 2019
7cf7898
Re-using common method in schema-hot-reloader
vladar Nov 6, 2019
565caa6
Test conflicts during rebuild-schema
vladar Nov 7, 2019
28c3ce6
Test incremental example value building
vladar Nov 7, 2019
69b2a1e
Tests for compatibility with schema customization
vladar Nov 7, 2019
586e4df
Parent type processing should not mess with child structure
vladar Nov 7, 2019
f163551
Fix typo in comments
vladar Nov 8, 2019
ee8b840
Moved `createSchemaCustomization` API call before node sourcing
vladar Nov 8, 2019
bfe9b20
Do not collect inference metadata for types with @dontInfer directive…
vladar Nov 11, 2019
5c41164
Use constants vs literal strings for extension names when analyzing t…
vladar Nov 11, 2019
3f54f2c
Develop: reload eslint config on schema rebuild
vladar Nov 11, 2019
9e7eea0
Re-run queries on schema rebuild
vladar Nov 11, 2019
5dfb0f0
Fix loki tests
freiksenet Nov 11, 2019
609c059
Fix eslint error
vladar Nov 11, 2019
bff333b
Example value: do not return empty object
vladar Nov 11, 2019
178b269
Updating tests structure
vladar Nov 12, 2019
8ca59e4
Split tests for sitepage and schema rebuild to separate files
vladar Nov 12, 2019
99df433
Tests for rebuilding types with existing relations
vladar Nov 12, 2019
d2806c4
Step back and use full schema rebuild (which is relatively fast with …
vladar Nov 12, 2019
74d0bd6
Fix eslint errors
vladar Nov 12, 2019
305f2f7
Fix invalid test
vladar Nov 12, 2019
cf70e81
Take a list of types for inference from metadata vs node store
vladar Nov 13, 2019
68cc607
Handle DELETE_NODES action
vladar Nov 13, 2019
4a72c53
Cleaning up a little bit
vladar Nov 13, 2019
dc66d60
Fix loki reducer for DELETE_NODES action
vladar Nov 13, 2019
6ab53ed
More descriptive naming for eslint graphql schema reload
vladar Nov 14, 2019
ef7cda7
Fix invalid webpack compiler hook argument
vladar Nov 14, 2019
207f66b
Better detection of changed types to avoid unnecessary rebuilds
vladar Nov 14, 2019
e56380b
Add missing snapshot
vladar Nov 14, 2019
55ca80e
Add new tests to clarify updating of ___NODE fields
vladar Nov 14, 2019
b66bbe7
Be a bit more defensive with haveEqualFields args
vladar Nov 14, 2019
8801594
Re-run schema customization on __refresh
vladar Nov 15, 2019
fa8b8f6
Rebuild schema on schema customization in develop (i.e. called via __…
vladar Nov 15, 2019
ed356ce
Add support for node update
vladar Nov 15, 2019
f756d9e
Fix rebuildWithSitePage extensions
vladar Nov 15, 2019
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
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,19 @@ async function queryResult(
addInferredFields,
} = require(`../../../gatsby/src/schema/infer/add-inferred-fields`)
const {
getExampleValue,
} = require(`../../../gatsby/src/schema/infer/example-value`)
addNodes,
getExampleObject,
} = require(`../../../gatsby/src/schema/infer/inference-metadata`)

const typeName = `MarkdownRemark`
const sc = createSchemaComposer()
const tc = sc.createObjectTC(typeName)
sc.addTypeDefs(typeDefs)
const inferenceMetadata = addNodes({ typeName }, nodes)
addInferredFields({
schemaComposer: sc,
typeComposer: tc,
exampleValue: getExampleValue({ nodes, typeName }),
exampleValue: getExampleObject(inferenceMetadata),
})
tc.addFields(extendNodeTypeFields)
sc.Query.addFields({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,16 +127,18 @@ yadda yadda
addInferredFields,
} = require(`../../../gatsby/src/schema/infer/add-inferred-fields`)
const {
getExampleValue,
} = require(`../../../gatsby/src/schema/infer/example-value`)
addNodes,
getExampleObject,
} = require(`../../../gatsby/src/schema/infer/inference-metadata`)

const sc = createSchemaComposer()
const typeName = `MarkdownRemark`
const tc = sc.createObjectTC(typeName)
const inferenceMetadata = addNodes({ typeName }, nodes)
addInferredFields({
schemaComposer: sc,
typeComposer: tc,
exampleValue: getExampleValue({ nodes, typeName }),
exampleValue: getExampleObject(inferenceMetadata),
})
sc.Query.addFields({
listNode: { type: [tc], resolve: () => nodes },
Expand Down
10 changes: 10 additions & 0 deletions packages/gatsby/src/bootstrap/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,16 @@ module.exports = async (args: BootstrapArgs) => {
})
activity.end()

// Prepare static schema types
activity = report.activityTimer(`createSchemaCustomization`, {
parentSpan: bootstrapSpan,
})
activity.start()
await require(`../utils/create-schema-customization`)({
parentSpan: bootstrapSpan,
})
activity.end()

// Source nodes
activity = report.activityTimer(`source and transform nodes`, {
parentSpan: bootstrapSpan,
Expand Down
49 changes: 49 additions & 0 deletions packages/gatsby/src/bootstrap/schema-hot-reloader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
const { debounce, cloneDeep } = require(`lodash`)
const { emitter, store } = require(`../redux`)
const { rebuild } = require(`../schema`)
const { haveEqualFields } = require(`../schema/infer/inference-metadata`)
const { updateStateAndRunQueries } = require(`../query/query-watcher`)
const report = require(`gatsby-cli/lib/reporter`)

const inferredTypesChanged = (inferenceMetadata, prevInferenceMetadata) =>
Object.keys(inferenceMetadata).filter(
type =>
inferenceMetadata[type].dirty &&
!haveEqualFields(inferenceMetadata[type], prevInferenceMetadata[type])
).length > 0

const schemaChanged = (schemaCustomization, lastSchemaCustomization) =>
[`fieldExtensions`, `printConfig`, `thirdPartySchemas`, `types`].some(
key => schemaCustomization[key] !== lastSchemaCustomization[key]
)

let lastMetadata
let lastSchemaCustomization

// API_RUNNING_QUEUE_EMPTY could be emitted multiple types
// in a short period of time, so debounce seems reasonable
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is true and probably something we should just look into to debounce API_RUNNING_QUEUE_EMPTY event elsewhere (most systems that listen to that are also potentially expensive operations, so having global debounce would help)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, missed this comment, sorry. It makes sense. But I suggest doing this in a separate PR

const maybeRebuildSchema = debounce(async () => {
const { inferenceMetadata, schemaCustomization } = store.getState()

if (
!inferredTypesChanged(inferenceMetadata, lastMetadata) &&
!schemaChanged(schemaCustomization, lastSchemaCustomization)
) {
return
}

const activity = report.activityTimer(`rebuild schema`)
activity.start()
lastMetadata = cloneDeep(inferenceMetadata)
lastSchemaCustomization = schemaCustomization
await rebuild({ parentSpan: activity })
await updateStateAndRunQueries(false, { parentSpan: activity })
activity.end()
}, 1000)

module.exports = () => {
const { inferenceMetadata, schemaCustomization } = store.getState()
lastMetadata = cloneDeep(inferenceMetadata)
lastSchemaCustomization = schemaCustomization
emitter.on(`API_RUNNING_QUEUE_EMPTY`, maybeRebuildSchema)
}
26 changes: 19 additions & 7 deletions packages/gatsby/src/commands/develop.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const WorkerPool = require(`../utils/worker/pool`)

const withResolverContext = require(`../schema/context`)
const sourceNodes = require(`../utils/source-nodes`)
const createSchemaCustomization = require(`../utils/create-schema-customization`)
const websocketManager = require(`../utils/websocket-manager`)
const getSslCert = require(`../utils/get-ssl-cert`)
const slash = require(`slash`)
Expand Down Expand Up @@ -187,6 +188,20 @@ async function startServer(program) {
* If no GATSBY_REFRESH_TOKEN env var is available, then no Authorization header is required
**/
const REFRESH_ENDPOINT = `/__refresh`
const refresh = async req => {
let activity = report.activityTimer(`createSchemaCustomization`, {})
activity.start()
await createSchemaCustomization({
refresh: true,
})
activity.end()
activity = report.activityTimer(`Refreshing source data`, {})
activity.start()
await sourceNodes({
webhookBody: req.body,
})
activity.end()
}
app.use(REFRESH_ENDPOINT, express.json())
app.post(REFRESH_ENDPOINT, (req, res) => {
const enableRefresh = process.env.ENABLE_GATSBY_REFRESH_ENDPOINT
Expand All @@ -195,13 +210,7 @@ async function startServer(program) {
!refreshToken || req.headers.authorization === refreshToken

if (enableRefresh && authorizedRefresh) {
const activity = report.activityTimer(`Refreshing source data`, {})
activity.start()
sourceNodes({
webhookBody: req.body,
}).then(() => {
activity.end()
})
refresh(req)
}
res.end()
})
Expand Down Expand Up @@ -374,6 +383,9 @@ module.exports = async (program: any) => {
// Start the createPages hot reloader.
require(`../bootstrap/page-hot-reloader`)(graphqlRunner)

// Start the schema hot reloader.
require(`../bootstrap/schema-hot-reloader`)()

await queryUtil.initialProcessQueries()

require(`../redux/actions`).boundActionCreators.setProgramStatus(
Expand Down
42 changes: 20 additions & 22 deletions packages/gatsby/src/db/loki/nodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ const _ = require(`lodash`)
const invariant = require(`invariant`)
const { getDb, colls } = require(`./index`)

/////////////////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////////////////
// Node collection metadata
/////////////////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////////////////

function makeTypeCollName(type) {
return `gatsby:nodeType:${type}`
Expand Down Expand Up @@ -39,7 +39,7 @@ function createNodeTypeCollection(type) {
function getTypeCollName(type) {
const nodeTypesColl = getDb().getCollection(colls.nodeTypes.name)
invariant(nodeTypesColl, `Collection ${colls.nodeTypes.name} should exist`)
let nodeTypeInfo = nodeTypesColl.by(`type`, type)
const nodeTypeInfo = nodeTypesColl.by(`type`, type)
return nodeTypeInfo ? nodeTypeInfo.collName : undefined
}

Expand Down Expand Up @@ -72,7 +72,7 @@ function deleteNodeTypeCollections(force = false) {
// find() returns all objects in collection
const nodeTypes = nodeTypesColl.find()
for (const nodeType of nodeTypes) {
let coll = getDb().getCollection(nodeType.collName)
const coll = getDb().getCollection(nodeType.collName)
if (coll.count() === 0 || force) {
getDb().removeCollection(coll.name)
nodeTypesColl.remove(nodeType)
Expand All @@ -93,9 +93,9 @@ function deleteAll() {
}
}

/////////////////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////////////////
// Queries
/////////////////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////////////////

/**
* Returns the node with `id` == id, or null if not found
Expand Down Expand Up @@ -191,9 +191,9 @@ function hasNodeChanged(id, digest) {
}
}

/////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////
// Create/Update/Delete
/////////////////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////////////////

/**
* Creates a node in the DB. Will create a collection for the node
Expand Down Expand Up @@ -242,11 +242,8 @@ function updateNode(node) {
invariant(node.internal.type, `node has no "internal.type" field`)
invariant(node.id, `node has no "id" field`)

const type = node.internal.type

let coll = getNodeTypeCollection(type)
invariant(coll, `${type} collection doesn't exist. When trying to update`)
coll.update(node)
const oldNode = getNode(node.id)
return createNode(node, oldNode)
}

/**
Expand All @@ -264,22 +261,23 @@ function deleteNode(node) {

const type = node.internal.type

let nodeTypeColl = getNodeTypeCollection(type)
const nodeTypeColl = getNodeTypeCollection(type)
if (!nodeTypeColl) {
invariant(
nodeTypeColl,
`${type} collection doesn't exist. When trying to delete`
)
}

if (nodeTypeColl.by(`id`, node.id)) {
const obj = nodeTypeColl.by(`id`, node.id)
if (obj) {
const nodeMetaColl = getDb().getCollection(colls.nodeMeta.name)
invariant(nodeMetaColl, `Collection ${colls.nodeMeta.name} should exist`)
nodeMetaColl.findAndRemove({ id: node.id })
// TODO What if this `remove()` fails? We will have removed the id
// -> collName mapping, but not the actual node in the
// collection. Need to make this into a transaction
nodeTypeColl.remove(node)
nodeTypeColl.remove(obj)
}
// idempotent. Do nothing if node wasn't already in DB
}
Expand Down Expand Up @@ -326,7 +324,7 @@ function ensureFieldIndexes(typeName, lokiArgs, sortArgs) {
const { emitter } = require(`../../redux`)

emitter.on(`DELETE_CACHE`, () => {
for (var field in fieldUsages) {
for (const field in fieldUsages) {
delete fieldUsages[field]
}
})
Expand All @@ -350,9 +348,9 @@ function ensureFieldIndexes(typeName, lokiArgs, sortArgs) {
})
}

/////////////////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////////////////
// Reducer
/////////////////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////////////////

function reducer(state = new Map(), action) {
switch (action.type) {
Expand All @@ -378,7 +376,7 @@ function reducer(state = new Map(), action) {
}

case `DELETE_NODES`: {
deleteNodes(action.payload)
deleteNodes(action.fullNodes)
return null
}

Expand All @@ -387,9 +385,9 @@ function reducer(state = new Map(), action) {
}
}

/////////////////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////////////////
// Exports
/////////////////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////////////////

module.exports = {
getNodeTypeCollection,
Expand Down
2 changes: 2 additions & 0 deletions packages/gatsby/src/query/query-watcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,3 +265,5 @@ exports.startWatchDeletePage = () => {
}
})
}

exports.updateStateAndRunQueries = updateStateAndRunQueries
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Object {
"query": "",
},
},
"inferenceMetadata": Object {},
"staticQueryComponents": Map {},
"status": Object {
"plugins": Object {},
Expand Down
8 changes: 7 additions & 1 deletion packages/gatsby/src/redux/actions/public.js
Original file line number Diff line number Diff line change
Expand Up @@ -530,10 +530,15 @@ actions.deleteNodes = (nodes: any[], plugin: Plugin) => {
nodes.map(n => findChildren(getNode(n).children))
)

const nodeIds = [...nodes, ...descendantNodes]

const deleteNodesAction = {
type: `DELETE_NODES`,
plugin,
payload: [...nodes, ...descendantNodes],
// Payload contains node IDs but inference-metadata and loki reducers require
// full node instances
payload: nodeIds,
fullNodes: nodeIds.map(getNode),
}
return deleteNodesAction
}
Expand Down Expand Up @@ -960,6 +965,7 @@ actions.createNodeField = (
type: `ADD_FIELD_TO_NODE`,
plugin,
payload: node,
addedField: name,
}
}

Expand Down
5 changes: 4 additions & 1 deletion packages/gatsby/src/redux/actions/restricted.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @flow
const { camelCase } = require(`lodash`)
const report = require(`gatsby-cli/lib/reporter`)
const { parseTypeDef } = require(`../../schema/types/type-defs`)

import type { Plugin } from "./types"

Expand Down Expand Up @@ -187,7 +188,9 @@ actions.createTypes = (
type: `CREATE_TYPES`,
plugin,
traceId,
payload: types,
payload: Array.isArray(types)
? types.map(parseTypeDef)
: parseTypeDef(types),
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/gatsby/src/redux/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ const saveState = () => {
`components`,
`staticQueryComponents`,
`webpackCompilationHash`,
`inferenceMetadata`,
])

return writeToCache(pickedState)
Expand Down
1 change: 1 addition & 0 deletions packages/gatsby/src/redux/reducers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,5 @@ module.exports = {
schemaCustomization: require(`./schema-customization`),
themes: require(`./themes`),
logs: require(`gatsby-cli/lib/reporter/redux/reducer`),
inferenceMetadata: require(`./inference-metadata`),
}
Loading