graphile-build
Advanced tools
Comparing version 5.0.0-0.19 to 5.0.0-0.20
# graphile-build | ||
## 5.0.0-0.20 | ||
### Patch Changes | ||
- [#210](https://github.com/benjie/postgraphile-private/pull/210) | ||
[`2fb5001b4`](https://github.com/benjie/postgraphile-private/commit/2fb5001b4aaac07942b2e9b0398a996f9aa8b15d) | ||
Thanks [@benjie](https://github.com/benjie)! - retryOnInitFail implemented, | ||
and bug in introspection cache on error resolved. | ||
- [#210](https://github.com/benjie/postgraphile-private/pull/210) | ||
[`b523118fe`](https://github.com/benjie/postgraphile-private/commit/b523118fe6217c027363fea91252a3a1764e17df) | ||
Thanks [@benjie](https://github.com/benjie)! - Replace BaseGraphQLContext with | ||
Grafast.Context throughout. | ||
- Updated dependencies | ||
[[`b523118fe`](https://github.com/benjie/postgraphile-private/commit/b523118fe6217c027363fea91252a3a1764e17df)]: | ||
- grafast@0.0.1-0.15 | ||
## 5.0.0-0.19 | ||
@@ -4,0 +22,0 @@ |
@@ -1,2 +0,2 @@ | ||
import type { BaseGraphQLArguments, BaseGraphQLContext, ExecutableStep, GraphileFieldConfig, GraphileFieldConfigArgumentMap, GraphileInputFieldConfig, GraphileInputFieldConfigMap, OutputPlanForType } from "grafast"; | ||
import type { BaseGraphQLArguments, ExecutableStep, GraphileFieldConfig, GraphileFieldConfigArgumentMap, GraphileInputFieldConfig, GraphileInputFieldConfigMap, OutputPlanForType } from "grafast"; | ||
import type { GraphQLEnumType, GraphQLEnumTypeConfig, GraphQLEnumValueConfig, GraphQLEnumValueConfigMap, GraphQLFieldConfig, GraphQLFieldConfigArgumentMap, GraphQLFieldConfigMap, GraphQLInputFieldConfigMap, GraphQLInputObjectType, GraphQLInputObjectTypeConfig, GraphQLInputType, GraphQLInterfaceType, GraphQLInterfaceTypeConfig, GraphQLNamedType, GraphQLNonNull, GraphQLObjectType, GraphQLObjectTypeConfig, GraphQLOutputType, GraphQLScalarType, GraphQLScalarTypeConfig, GraphQLSchema, GraphQLSchemaConfig, GraphQLType, GraphQLUnionType, GraphQLUnionTypeConfig } from "graphql"; | ||
@@ -63,2 +63,13 @@ import type { Behavior } from "./behavior.js"; | ||
simpleCollections?: "only" | "both" | "omit"; | ||
/** | ||
* When false (default), Grafserv will exit if it fails to build the | ||
* initial schema (for example if it cannot connect to the database, or if | ||
* there are fatal naming conflicts in the schema). When true, PostGraphile | ||
* will keep trying to rebuild the schema indefinitely, using an exponential | ||
* backoff between attempts, starting at 100ms and increasing up to 30s delay | ||
* between retries. When a function, the function will be called passing the | ||
* error and the number of attempts, and it should return true to retry, | ||
* false to permanently abort trying. | ||
*/ | ||
retryOnInitFail?: boolean | ((error: Error, attempts: number, delay: number) => boolean | Promise<boolean>); | ||
} | ||
@@ -77,7 +88,7 @@ /** | ||
/** Our take on GraphQLFieldConfigMap that allows for plans */ | ||
type GraphileFieldConfigMap<TParentStep extends ExecutableStep<any> | null, TContext extends BaseGraphQLContext> = { | ||
type GraphileFieldConfigMap<TParentStep extends ExecutableStep<any> | null, TContext extends Grafast.Context> = { | ||
[fieldName: string]: GraphileFieldConfig<any, TContext, TParentStep, any, any>; | ||
}; | ||
/** Our take on GraphQLObjectTypeConfig that allows for plans */ | ||
interface GraphileObjectTypeConfig<TParentStep extends ExecutableStep<any> | null, TContext extends BaseGraphQLContext> extends Omit<GraphQLObjectTypeConfig<unknown, TContext>, "fields" | "interfaces"> { | ||
interface GraphileObjectTypeConfig<TParentStep extends ExecutableStep<any> | null, TContext extends Grafast.Context> extends Omit<GraphQLObjectTypeConfig<unknown, TContext>, "fields" | "interfaces"> { | ||
fields?: GraphileFieldConfigMap<TParentStep, TContext> | ((context: ContextObjectFields) => GraphileFieldConfigMap<TParentStep, TContext>); | ||
@@ -471,3 +482,3 @@ interfaces?: GraphQLInterfaceType[] | ((context: ContextObjectInterfaces) => GraphQLInterfaceType[]); | ||
*/ | ||
type FieldWithHooksFunction = <TType extends GraphQLOutputType, TContext extends BaseGraphQLContext, TParentStep extends ExecutableStep<any>, TFieldStep extends OutputPlanForType<TType>, TArgs extends BaseGraphQLArguments>(fieldScope: ScopeObjectFieldsField, spec: GraphileFieldConfig<TType, TContext, TParentStep, TFieldStep, TArgs> | ((context: ContextObjectFieldsField) => GraphileFieldConfig<TType, TContext, TParentStep, TFieldStep, TArgs>)) => GraphileFieldConfig<TType, TContext, TParentStep, TFieldStep, TArgs>; | ||
type FieldWithHooksFunction = <TType extends GraphQLOutputType, TContext extends Grafast.Context, TParentStep extends ExecutableStep<any>, TFieldStep extends OutputPlanForType<TType>, TArgs extends BaseGraphQLArguments>(fieldScope: ScopeObjectFieldsField, spec: GraphileFieldConfig<TType, TContext, TParentStep, TFieldStep, TArgs> | ((context: ContextObjectFieldsField) => GraphileFieldConfig<TType, TContext, TParentStep, TFieldStep, TArgs>)) => GraphileFieldConfig<TType, TContext, TParentStep, TFieldStep, TArgs>; | ||
type InterfaceFieldWithHooksFunction = (fieldScope: ScopeInterfaceFieldsField, spec: GraphQLFieldConfig<any, any> | ((context: ContextInterfaceFieldsField) => GraphQLFieldConfig<any, any>)) => GraphQLFieldConfig<any, any>; | ||
@@ -474,0 +485,0 @@ type InputFieldWithHooksFunction = (fieldScope: ScopeInputObjectFieldsField, spec: GraphileInputFieldConfig<any, any, any, any, any> | ((context: ContextInputObjectFieldsField) => GraphileInputFieldConfig<any, any, any, any, any>)) => GraphileInputFieldConfig<any, any, any, any, any>; |
@@ -30,3 +30,3 @@ import "./global.js"; | ||
inflection: GraphileBuild.Inflection; | ||
} | undefined, callback: (gather: GraphileBuild.BuildInput | null, error?: Error) => void) => Promise<() => void>; | ||
} | undefined, callback: (gather: GraphileBuild.BuildInput | null, error: Error | undefined, retry: () => void) => void) => Promise<() => void>; | ||
/** | ||
@@ -46,2 +46,25 @@ * Gets a SchemaBuilder object for the given preset and inflection. It's rare | ||
export { defaultPreset } from "./preset.js"; | ||
export interface SchemaResult { | ||
schema: GraphQLSchema; | ||
resolvedPreset: GraphileConfig.ResolvedPreset; | ||
} | ||
/** | ||
* Builds the GraphQL schema by resolving the preset, running inflection then | ||
* gather and building the schema. Returns the results. | ||
* | ||
* @experimental | ||
*/ | ||
export declare function makeSchema(preset: GraphileConfig.Preset): Promise<SchemaResult>; | ||
/** | ||
* Runs the "gather" phase in watch mode and calls 'callback' with the | ||
* generated SchemaResult each time a new schema is generated. | ||
* | ||
* It is guaranteed that `callback` will be called at least once before the | ||
* promise resolves. | ||
* | ||
* Returns a function that can be called to stop watching. | ||
* | ||
* @experimental | ||
*/ | ||
export declare function watchSchema(preset: GraphileConfig.Preset, callback: (fatalError: Error | null, params?: SchemaResult) => void): Promise<() => void>; | ||
//# sourceMappingURL=index.d.ts.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.defaultPreset = exports.TrimEmptyDescriptionsPlugin = exports.SwallowErrorsPlugin = exports.SubscriptionPlugin = exports.StreamDeferPlugin = exports.RegisterQueryNodePlugin = exports.QueryQueryPlugin = exports.QueryPlugin = exports.PageInfoStartEndCursorPlugin = exports.NodePlugin = exports.NodeIdCodecPipeStringPlugin = exports.NodeIdCodecBase64JSONPlugin = exports.NodeAccessorPlugin = exports.MutationPlugin = exports.MutationPayloadQueryPlugin = exports.CursorTypePlugin = exports.CommonTypesPlugin = exports.ClientMutationIdDescriptionPlugin = exports.BuiltinScalarConnectionsPlugin = exports.AddNodeInterfaceToSuitableTypesPlugin = exports.buildSchema = exports.getBuilder = exports.watchGather = exports.gather = exports.buildInflection = exports.SchemaBuilder = exports.upperFirst = exports.upperCamelCase = exports.singularize = exports.pluralize = exports.formatInsideUnderscores = exports.constantCaseAll = exports.constantCase = exports.camelCase = void 0; | ||
exports.watchSchema = exports.makeSchema = exports.defaultPreset = exports.TrimEmptyDescriptionsPlugin = exports.SwallowErrorsPlugin = exports.SubscriptionPlugin = exports.StreamDeferPlugin = exports.RegisterQueryNodePlugin = exports.QueryQueryPlugin = exports.QueryPlugin = exports.PageInfoStartEndCursorPlugin = exports.NodePlugin = exports.NodeIdCodecPipeStringPlugin = exports.NodeIdCodecBase64JSONPlugin = exports.NodeAccessorPlugin = exports.MutationPlugin = exports.MutationPayloadQueryPlugin = exports.CursorTypePlugin = exports.CommonTypesPlugin = exports.ClientMutationIdDescriptionPlugin = exports.BuiltinScalarConnectionsPlugin = exports.AddNodeInterfaceToSuitableTypesPlugin = exports.buildSchema = exports.getBuilder = exports.watchGather = exports.gather = exports.buildInflection = exports.SchemaBuilder = exports.upperFirst = exports.upperCamelCase = exports.singularize = exports.pluralize = exports.formatInsideUnderscores = exports.constantCaseAll = exports.constantCase = exports.camelCase = void 0; | ||
const tslib_1 = require("tslib"); | ||
@@ -41,3 +41,5 @@ require("./global.js"); | ||
Object.defineProperty(exports, "upperFirst", { enumerable: true, get: function () { return utils_js_1.upperFirst; } }); | ||
const grafast_1 = require("grafast"); | ||
const EMPTY_OBJECT = Object.freeze(Object.create(null)); | ||
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); | ||
const getSchemaHooks = (plugin) => plugin.schema?.hooks; | ||
@@ -170,5 +172,7 @@ /** | ||
let runInProgress = true; | ||
let counter = 0; | ||
const handleChange = () => { | ||
if (stopped) | ||
if (stopped) { | ||
return; | ||
} | ||
if (runInProgress) { | ||
@@ -178,9 +182,11 @@ runAgain = true; | ||
} | ||
++counter; | ||
runAgain = false; | ||
runInProgress = true; | ||
run().then((v) => { | ||
runInProgress = false; | ||
if (stopped) | ||
return; | ||
try { | ||
callback(v); | ||
callback(v, undefined, makeRetry(counter)); | ||
} | ||
@@ -191,10 +197,10 @@ catch { | ||
} | ||
runInProgress = false; | ||
if (runAgain) | ||
handleChange(); | ||
}, (e) => { | ||
runInProgress = false; | ||
if (stopped) | ||
return; | ||
try { | ||
callback(null, e); | ||
callback(null, e, makeRetry(counter)); | ||
} | ||
@@ -205,3 +211,2 @@ catch { | ||
} | ||
runInProgress = false; | ||
if (runAgain) | ||
@@ -211,2 +216,12 @@ handleChange(); | ||
}; | ||
const makeRetry = (currentCounter) => { | ||
return () => { | ||
if (currentCounter === counter) { | ||
handleChange(); | ||
} | ||
else { | ||
// Another change was already registered; ignore | ||
} | ||
}; | ||
}; | ||
if (gatherPlugins) { | ||
@@ -227,6 +242,6 @@ // Put all the plugins into watch mode. | ||
const firstResult = await run(); | ||
callback(firstResult); | ||
callback(firstResult, undefined, makeRetry(counter)); | ||
} | ||
catch (e) { | ||
callback(null, e); | ||
callback(null, e, makeRetry(counter)); | ||
} | ||
@@ -297,2 +312,133 @@ runInProgress = false; | ||
Object.defineProperty(exports, "defaultPreset", { enumerable: true, get: function () { return preset_js_1.defaultPreset; } }); | ||
/** | ||
* Builds the GraphQL schema by resolving the preset, running inflection then | ||
* gather and building the schema. Returns the results. | ||
* | ||
* @experimental | ||
*/ | ||
async function makeSchema(preset) { | ||
const resolvedPreset = (0, graphile_config_1.resolvePresets)([preset]); | ||
// An error caused here cannot be solved by retrying, so don't catch it. | ||
const inflection = (0, exports.buildInflection)(resolvedPreset); | ||
const shared = { inflection }; | ||
const retryOnInitFail = resolvedPreset.schema?.retryOnInitFail; | ||
let phase = "UNKNOWN"; | ||
const make = async () => { | ||
phase = "GATHER"; | ||
const input = await (0, exports.gather)(resolvedPreset, shared); | ||
phase = "SCHEMA"; | ||
const schema = (0, exports.buildSchema)(resolvedPreset, input, shared); | ||
return { schema, resolvedPreset }; | ||
}; | ||
if (retryOnInitFail) { | ||
// eslint-disable-next-line no-constant-condition | ||
for (let attempts = 1; true; attempts++) { | ||
try { | ||
const result = await make(); | ||
if (attempts > 1) { | ||
console.warn(`Schema constructed successfully on attempt ${attempts}.`); | ||
} | ||
return result; | ||
} | ||
catch (error) { | ||
await sleepFromRetryOnInitFail(retryOnInitFail, phase, attempts, error); | ||
} | ||
} | ||
} | ||
else { | ||
return make(); | ||
} | ||
} | ||
exports.makeSchema = makeSchema; | ||
async function sleepFromRetryOnInitFail(retryOnInitFail, phase, attempts, error) { | ||
const delay = Math.min(100 * Math.pow(attempts, 2), 30000); | ||
const start = process.hrtime(); | ||
const retryOrPromise = typeof retryOnInitFail === "function" | ||
? retryOnInitFail(error, attempts, delay) | ||
: retryOnInitFail; | ||
if (retryOrPromise === false) { | ||
throw error; | ||
} | ||
console.warn(`Error occurred whilst building the schema (phase = ${phase}; attempt ${attempts}). We'll try again ${retryOrPromise === true ? `in ${delay}ms` : `shortly`}.\n ${String(error).replace(/\n/g, "\n ")}`); | ||
if (retryOrPromise === true) { | ||
await sleep(delay); | ||
} | ||
else if (!(0, grafast_1.isPromiseLike)(retryOrPromise)) { | ||
throw new Error(`Invalid retryOnInitFail setting; must be true, false, or an optionally async function that resolves to true/false`); | ||
} | ||
else { | ||
const retry = await retryOrPromise; | ||
const diff = process.hrtime(start); | ||
const dur = diff[0] * 1e3 + diff[1] * 1e-6; | ||
if (!retry) { | ||
throw error; | ||
} | ||
else if (dur < 50) { | ||
// retryOnInitFail didn't wait long enough; use default wait. | ||
console.error(`Your retryOnInitFail function should include a delay of at least 50ms before resolving; falling back to a ${delay}ms wait (attempts = ${attempts}) to avoid overwhelming the database.`); | ||
await sleep(delay); | ||
} | ||
else { | ||
// The promise already waited long enough, continue | ||
} | ||
} | ||
} | ||
/** | ||
* Runs the "gather" phase in watch mode and calls 'callback' with the | ||
* generated SchemaResult each time a new schema is generated. | ||
* | ||
* It is guaranteed that `callback` will be called at least once before the | ||
* promise resolves. | ||
* | ||
* Returns a function that can be called to stop watching. | ||
* | ||
* @experimental | ||
*/ | ||
async function watchSchema(preset, callback) { | ||
const resolvedPreset = (0, graphile_config_1.resolvePresets)([preset]); | ||
const shared = { inflection: (0, exports.buildInflection)(resolvedPreset) }; | ||
const retryOnInitFail = resolvedPreset.schema?.retryOnInitFail; | ||
let attempts = 0; | ||
let haveHadSuccess = false; | ||
const handleErrorWithRetry = (error, retry) => { | ||
if (retryOnInitFail) { | ||
sleepFromRetryOnInitFail(retryOnInitFail, "GATHER", attempts, error).then(retry, callback); | ||
} | ||
else { | ||
if (!haveHadSuccess) { | ||
// Inability to gather is fatal - database connection issue? | ||
callback(error); | ||
} | ||
else { | ||
console.error(`Error occurred during watch gather: ${error}`); | ||
} | ||
} | ||
}; | ||
const stopWatching = await (0, exports.watchGather)(resolvedPreset, shared, (input, error, retry) => { | ||
++attempts; | ||
if (error) { | ||
// An error here could be a database connectivity issue or similar | ||
// issue, if retryOnInitFail is set we should automatically retry. | ||
handleErrorWithRetry(error, retry); | ||
} | ||
else { | ||
if (attempts > 1) { | ||
console.warn(`Gather completed successfully on attempt ${attempts}.`); | ||
} | ||
attempts = 0; | ||
haveHadSuccess = true; | ||
try { | ||
const schema = (0, exports.buildSchema)(resolvedPreset, input, shared); | ||
callback(null, { schema, resolvedPreset }); | ||
} | ||
catch (e) { | ||
// Retrying this on its own is pointless, we need the gather phase to | ||
// give us more data so we can just await regular watch for that. | ||
console.error(`Error occurred during watch schema generation:\n ${String(e).replace(/\n/g, "\n ")}`); | ||
} | ||
} | ||
}); | ||
return stopWatching; | ||
} | ||
exports.watchSchema = watchSchema; | ||
//# sourceMappingURL=index.js.map |
@@ -35,3 +35,3 @@ import type { GraphQLNamedType, GraphQLScalarTypeConfig } from "graphql"; | ||
*/ | ||
export declare const stringScalarSpec: Readonly<Omit<GraphQLScalarTypeConfig<unknown, unknown>, "name" | "description">>; | ||
export declare const stringScalarSpec: Readonly<Omit<GraphQLScalarTypeConfig<unknown, unknown>, "description" | "name">>; | ||
/** | ||
@@ -38,0 +38,0 @@ * Only use this on descriptions that are plain text, or that we create |
{ | ||
"name": "graphile-build", | ||
"version": "5.0.0-0.19", | ||
"version": "5.0.0-0.20", | ||
"description": "Build a GraphQL schema from plugins", | ||
@@ -44,3 +44,3 @@ "type": "commonjs", | ||
"debug": "^4.3.3", | ||
"grafast": "^0.0.1-0.14", | ||
"grafast": "^0.0.1-0.15", | ||
"graphile-config": "^0.0.1-0.5", | ||
@@ -47,0 +47,0 @@ "graphile-export": "^0.0.2-0.4", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
367071
4529
Updatedgrafast@^0.0.1-0.15