UNPKG

postgraphile

Version:

A GraphQL schema created by reflection over a PostgreSQL schema 🐘 (previously known as PostGraphQL)

574 lines (573 loc) 60.7 kB
#!/usr/bin/env node "use strict"; // tslint:disable no-console Object.defineProperty(exports, "__esModule", { value: true }); /* * IMPORTANT: the './postgraphilerc' import MUST come first! * * Reason: enables user to apply modifications to their Node.js environment * (e.g. sourcing modules that affect global state, like dotenv) before any of * our other require()s occur. */ const postgraphilerc_1 = require("./postgraphilerc"); const os = require("os"); const http_1 = require("http"); const chalk_1 = require("chalk"); const program = require("commander"); const pg_connection_string_1 = require("pg-connection-string"); const postgraphile_1 = require("./postgraphile"); const plugins_1 = require("../plugins"); const pg_1 = require("pg"); const cluster = require("cluster"); const pluginHook_1 = require("./pluginHook"); const debugFactory = require("debug"); // @ts-ignore Allow importing JSON const manifest = require("../../package.json"); // @ts-ignore Allow importing JSON const sponsors = require("../../sponsors.json"); const subscriptions_1 = require("./http/subscriptions"); const fs_1 = require("fs"); const tagsFile = process.cwd() + '/postgraphile.tags.json5'; /* * Watch mode on the tags file is non-trivial, so only load the plugin if the * file exists when PostGraphile starts. */ const smartTagsPlugin = fs_1.existsSync(tagsFile) ? plugins_1.makePgSmartTagsFromFilePlugin() : null; const isDev = process.env.POSTGRAPHILE_ENV === 'development'; function isString(str) { return typeof str === 'string'; } const sponsor = sponsors[Math.floor(sponsors.length * Math.random())]; const debugCli = debugFactory('postgraphile:cli'); // TODO: Demo Postgres database const DEMO_PG_URL = null; function extractPlugins(rawArgv) { let argv; let pluginStrings = []; if (rawArgv[2] === '--plugins') { pluginStrings = rawArgv[3].split(','); argv = [...rawArgv.slice(0, 2), ...rawArgv.slice(4)]; } else { pluginStrings = (postgraphilerc_1.default && postgraphilerc_1.default['options'] && postgraphilerc_1.default['options']['plugins']) || []; argv = rawArgv; } const plugins = pluginStrings.map((pluginString) => { debugCli('Loading plugin %s', pluginString); const rawPlugin = require(pluginString); // tslint:disable-lin no-var-requires if (rawPlugin['default'] && typeof rawPlugin['default'] === 'object') { return rawPlugin['default']; } else { return rawPlugin; } }); return { argv, plugins }; } const { argv: argvSansPlugins, plugins: extractedPlugins } = extractPlugins(process.argv); const pluginHook = pluginHook_1.makePluginHook(extractedPlugins); program.version(manifest.version).usage('[options...]').description(manifest.description); function addFlag(optionString, description, parse) { program.option(optionString, description, parse); return addFlag; } // Standard options program .option('--plugins <string>', 'a list of PostGraphile server plugins (not Graphile Engine schema plugins) to load; if present, must be the _first_ option') .option('-c, --connection <string>', "the PostgreSQL database name or connection string. If omitted, inferred from environmental variables (see https://www.postgresql.org/docs/current/static/libpq-envars.html). Examples: 'db', 'postgres:///db', 'postgres://user:password@domain:port/db?ssl=true'") .option('-C, --owner-connection <string>', 'as `--connection`, but for a privileged user (e.g. for setting up watch fixtures, logical decoding, etc); defaults to the value from `--connection`') .option('-s, --schema <string>', 'a Postgres schema to be introspected. Use commas to define multiple schemas', (option) => option.split(',')) .option('-S, --subscriptions', 'Enable GraphQL support for subscriptions (you still need a subscriptions plugin currently)') .option('--websockets <string>', "Choose which websocket transport libraries to use. Use commas to define multiple. Defaults to 'v0,v1' if `--subscriptions` or `--live` were passed, '[]' otherwise", (option) => option.split(',')) .option('--websocket-operations <operations>', "Toggle which GraphQL websocket transport operations are supported: 'subscriptions' or 'all'. Defaults to 'subscriptions'") .option('-L, --live', '[EXPERIMENTAL] Enables live-query support via GraphQL subscriptions (sends updated payload any time nested collections/records change). Implies --subscriptions') .option('-w, --watch', 'automatically updates your GraphQL schema when your database schema changes (NOTE: requires DB superuser to install `postgraphile_watch` schema)') .option('-n, --host <string>', 'the hostname to be used. Defaults to `localhost`') .option('-p, --port <number>', 'the port to be used. Defaults to 5000', parseFloat) .option('-m, --max-pool-size <number>', 'the maximum number of clients to keep in the Postgres pool. defaults to 10', parseFloat) .option('-r, --default-role <string>', 'the default Postgres role to use when a request is made. supercedes the role used to connect to the database') .option('--retry-on-init-fail', 'if an error occurs building the initial schema, this flag will cause PostGraphile to keep trying to build the schema with exponential backoff rather than exiting'); pluginHook('cli:flags:add:standard', addFlag); // Schema configuration program .option('-j, --dynamic-json', '[RECOMMENDED] enable dynamic JSON in GraphQL inputs and outputs. PostGraphile uses stringified JSON by default') .option('-N, --no-setof-functions-contain-nulls', '[RECOMMENDED] if none of your `RETURNS SETOF compound_type` functions mix NULLs with the results then you may enable this to reduce the nullables in the GraphQL schema') .option('-a, --classic-ids', 'use classic global id field name. required to support Relay 1') .option('-M, --disable-default-mutations', 'disable default mutations, mutation will only be possible through Postgres functions') .option('--simple-collections <omit|both|only>', '"omit" (default) - relay connections only, "only" - simple collections only (no Relay connections), "both" - both') .option('--no-ignore-rbac', '[RECOMMENDED] set this to exclude fields, queries and mutations that are not available to any possible user (determined from the user in connection string and any role they can become); this will be enabled by default in v5') .option('--no-ignore-indexes', '[RECOMMENDED] set this to exclude filters, orderBy, and relations that would be expensive to access due to missing indexes') .option('--include-extension-resources', 'by default, tables and functions that come from extensions are excluded; use this flag to include them (not recommended)') .option('--use-partitioned-parent', 'by default, partitioned table partitions are exposed and their parents are hidden; use this flag to hide partitions and expose their parents'); pluginHook('cli:flags:add:schema', addFlag); // Error enhancements program .option('--show-error-stack [json|string]', 'show JavaScript error stacks in the GraphQL result errors (recommended in development)') .option('--extended-errors <string>', "a comma separated list of extended Postgres error fields to display in the GraphQL result. Recommended in development: 'hint,detail,errcode'. Default: none", (option) => option.split(',').filter(_ => _)); pluginHook('cli:flags:add:errorHandling', addFlag); // Plugin-related options program .option('--append-plugins <string>', 'a comma-separated list of plugins to append to the list of Graphile Engine schema plugins') .option('--prepend-plugins <string>', 'a comma-separated list of plugins to prepend to the list of Graphile Engine schema plugins') .option('--skip-plugins <string>', 'a comma-separated list of Graphile Engine schema plugins to skip'); pluginHook('cli:flags:add:plugins', addFlag); // Things that relate to -X program .option('--read-cache <path>', '[experimental] reads cached values from local cache file to improve startup time (you may want to do this in production)') .option('--write-cache <path>', '[experimental] writes computed values to local cache file so startup can be faster (do this during the build phase)') .option('--export-schema-json <path>', 'enables exporting the detected schema, in JSON format, to the given location. The directories must exist already, if the file exists it will be overwritten.') .option('--export-schema-graphql <path>', 'enables exporting the detected schema, in GraphQL schema format, to the given location. The directories must exist already, if the file exists it will be overwritten.') .option('--sort-export', 'lexicographically (alphabetically) sort exported schema for more stable diffing.') .option('-X, --no-server', '[experimental] for when you just want to use --write-cache or --export-schema-* and not actually run a server (e.g. CI)'); pluginHook('cli:flags:add:noServer', addFlag); // Webserver configuration program .option('-q, --graphql <path>', 'the route to mount the GraphQL server on. defaults to `/graphql`') .option('-i, --graphiql <path>', 'the route to mount the GraphiQL interface on. defaults to `/graphiql`') .option('--enhance-graphiql', '[DEVELOPMENT] opt in to additional GraphiQL functionality (this may change over time - only intended for use in development; automatically enables with `subscriptions` and `live`)') .option('-b, --disable-graphiql', 'disables the GraphiQL interface. overrides the GraphiQL route option') .option('-o, --cors', 'enable generous CORS settings; disabled by default, if possible use a proxy instead') .option('-l, --body-size-limit <string>', "set the maximum size of the HTTP request body that can be parsed (default 100kB). The size can be given as a human-readable string, such as '200kB' or '5MB' (case insensitive).") .option('--timeout <number>', 'set the timeout value in milliseconds for sockets', parseFloat) .option('--cluster-workers <count>', '[experimental] spawn <count> workers to increase throughput', parseFloat) .option('--enable-query-batching', '[experimental] enable the server to process multiple GraphQL queries in one request') .option('--disable-query-log', 'disable logging queries to console (recommended in production)') .option('--allow-explain', '[EXPERIMENTAL] allows users to use the Explain button in GraphiQL to view the plan for the SQL that is executed (DO NOT USE IN PRODUCTION)'); pluginHook('cli:flags:add:webserver', addFlag); // JWT-related options program .option('-e, --jwt-secret <string>', 'the secret to be used when creating and verifying JWTs. if none is provided auth will be disabled') .option('--jwt-verify-algorithms <string>', 'a comma separated list of the names of the allowed jwt token algorithms', (option) => option.split(',')) .option('-A, --jwt-verify-audience <string>', "a comma separated list of JWT audiences that will be accepted; defaults to 'postgraphile'. To disable audience verification, set to ''.", (option) => option.split(',').filter(_ => _)) .option('--jwt-verify-clock-tolerance <number>', 'number of seconds to tolerate when checking the nbf and exp claims, to deal with small clock differences among different servers', parseFloat) .option('--jwt-verify-id <string>', 'the name of the allowed jwt token id') .option('--jwt-verify-ignore-expiration', 'if `true` do not validate the expiration of the token defaults to `false`') .option('--jwt-verify-ignore-not-before', 'if `true` do not validate the notBefore of the token defaults to `false`') .option('--jwt-verify-issuer <string>', 'a comma separated list of the names of the allowed jwt token issuer', (option) => option.split(',')) .option('--jwt-verify-subject <string>', 'the name of the allowed jwt token subject') .option('--jwt-role <string>', 'a comma seperated list of strings that create a path in the jwt from which to extract the postgres role. if none is provided it will use the key `role` on the root of the jwt.', (option) => option.split(',')) .option('-t, --jwt-token-identifier <identifier>', 'the Postgres identifier for a composite type that will be used to create JWT tokens'); pluginHook('cli:flags:add:jwt', addFlag); // Any other options pluginHook('cli:flags:add', addFlag); // Deprecated program .option('--token <identifier>', '[DEPRECATED] Use --jwt-token-identifier instead. This option will be removed in v5.') .option('--secret <string>', '[DEPRECATED] Use --jwt-secret instead. This option will be removed in v5.') .option('--jwt-audiences <string>', '[DEPRECATED] Use --jwt-verify-audience instead. This option will be removed in v5.', (option) => option.split(',')) .option('--legacy-functions-only', '[DEPRECATED] PostGraphile 4.1.0 introduced support for PostgreSQL functions than declare parameters with IN/OUT/INOUT or declare RETURNS TABLE(...); enable this flag to ignore these types of functions. This option will be removed in v5.'); pluginHook('cli:flags:add:deprecated', addFlag); // Awkward application workarounds / legacy support program .option('--legacy-relations <omit|deprecated|only>', "some one-to-one relations were previously detected as one-to-many - should we export 'only' the old relation shapes, both new and old but mark the old ones as 'deprecated', or 'omit' the old relation shapes entirely") .option('--legacy-json-uuid', `ONLY use this option if you require the v3 typenames 'Json' and 'Uuid' over 'JSON' and 'UUID'`); pluginHook('cli:flags:add:workarounds', addFlag); program.on('--help', () => { console.log(` Get started: $ postgraphile $ postgraphile -c postgres://localhost/my_db $ postgraphile --connection postgres://user:pass@localhost/my_db --schema my_schema --watch --dynamic-json `); process.exit(0); }); program.parse(argvSansPlugins); function exitWithErrorMessage(message) { console.error(message); console.error(); console.error('For help, run `postgraphile --help`'); process.exit(1); } if (program.args.length) { exitWithErrorMessage(`ERROR: some of the parameters you passed could not be processed: '${program.args.join("', '")}'`); } if (program['plugins']) { exitWithErrorMessage(`--plugins must be the first argument to postgraphile if specified`); } // Kill server on exit. process.on('SIGINT', () => { process.exit(1); }); // For `--no-*` options, `program` automatically contains the default, // overriding our options. We typically want the CLI to "win", but not // with defaults! So this code extracts those `--no-*` values and // re-overwrites the values if necessary. const configOptions = postgraphilerc_1.default['options'] || {}; const overridesFromOptions = {}; ['ignoreIndexes', 'ignoreRbac', 'setofFunctionsContainNulls'].forEach(option => { if (option in configOptions) { overridesFromOptions[option] = configOptions[option]; } }); // Destruct our configuration file and command line arguments, use defaults, and rename options to // something appropriate for JavaScript. const { demo: isDemo = false, connection: pgConnectionString, ownerConnection, subscriptions, live, websockets = subscriptions || live ? ['v0', 'v1'] : [], websocketOperations = 'subscriptions', watch: watchPg, schema: dbSchema, host: hostname = 'localhost', port = 5000, timeout: serverTimeout, maxPoolSize, defaultRole: pgDefaultRole, retryOnInitFail, graphql: graphqlRoute = '/graphql', graphiql: graphiqlRoute = '/graphiql', enhanceGraphiql = false, disableGraphiql = false, secret: deprecatedJwtSecret, jwtSecret, jwtPublicKey, jwtAudiences, jwtVerifyAlgorithms, jwtVerifyAudience, jwtVerifyClockTolerance, jwtVerifyId, jwtVerifyIgnoreExpiration, jwtVerifyIgnoreNotBefore, jwtVerifyIssuer, jwtVerifySubject, jwtSignOptions = {}, jwtVerifyOptions: rawJwtVerifyOptions, jwtRole = ['role'], token: deprecatedJwtPgTypeIdentifier, jwtTokenIdentifier: jwtPgTypeIdentifier, cors: enableCors = false, classicIds = false, dynamicJson = false, disableDefaultMutations = false, ignoreRbac = true, includeExtensionResources = false, exportSchemaJson: exportJsonSchemaPath, exportSchemaGraphql: exportGqlSchemaPath, sortExport = false, showErrorStack: rawShowErrorStack, extendedErrors = [], bodySizeLimit, appendPlugins: appendPluginNames, prependPlugins: prependPluginNames, // replaceAllPlugins is NOT exposed via the CLI skipPlugins: skipPluginNames, readCache, writeCache, legacyRelations: rawLegacyRelations = 'deprecated', server: yesServer, clusterWorkers, enableQueryBatching, setofFunctionsContainNulls = true, legacyJsonUuid, disableQueryLog, allowExplain, simpleCollections, legacyFunctionsOnly, ignoreIndexes, usePartitionedParent = false, } = { ...postgraphilerc_1.default['options'], ...program, ...overridesFromOptions }; const showErrorStack = (val => { switch (val) { case 'string': case true: return true; case null: case undefined: return undefined; case 'json': return 'json'; default: { exitWithErrorMessage(`Invalid argument for '--show-error-stack' - expected no argument, or 'string' or 'json'`); } } })(rawShowErrorStack); if (allowExplain && !disableGraphiql && !enhanceGraphiql) { exitWithErrorMessage('`--allow-explain` requires `--enhance-graphiql` or `--disable-graphiql`'); } let legacyRelations; if (!['omit', 'only', 'deprecated'].includes(rawLegacyRelations)) { exitWithErrorMessage(`Invalid argument to '--legacy-relations' - expected on of 'omit', 'deprecated', 'only'; but received '${rawLegacyRelations}'`); } else { legacyRelations = rawLegacyRelations; } // Validate websockets argument if ( // must be array !Array.isArray(websockets) || // empty array = 'none' (websockets.length && // array can only hold the versions websockets.some(ver => !['v0', 'v1'].includes(ver)))) { exitWithErrorMessage(`Invalid argument to '--websockets' - expected 'v0' and/or 'v1' (separated by comma); but received '${websockets}'`); } if (websocketOperations !== 'subscriptions' && websocketOperations !== 'all') { exitWithErrorMessage(`Invalid argument to '--websocket-operations' - expected 'subscriptions' or 'all' but received '${websocketOperations}'`); } const noServer = !yesServer; // Add custom logic for getting the schemas from our CLI. If we are in demo // mode, we want to use the `forum_example` schema. Otherwise the `public` // schema is what we want. const schemas = dbSchema || (isDemo ? ['forum_example'] : ['public']); const ownerConnectionString = ownerConnection || pgConnectionString || process.env.DATABASE_URL; // Work around type mismatches between parsePgConnectionString and PoolConfig const coerce = (o) => { const port = typeof o.port === 'number' ? o.port : typeof o.port === 'string' ? parseInt(o.port, 10) : undefined; return { ...o, application_name: o['application_name'] || undefined, ssl: o.ssl == null ? undefined : o.ssl.rejectUnauthorized == null ? !!o.ssl : o.ssl, user: typeof o.user === 'string' ? o.user : undefined, database: typeof o.database === 'string' ? o.database : undefined, password: typeof o.password === 'string' ? o.password : undefined, port: Number.isFinite(port) ? port : undefined, host: typeof o.host === 'string' ? o.host : undefined, }; }; // Create our Postgres config. const pgConfig = { // If we have a Postgres connection string, parse it and use that as our // config. If we don’t have a connection string use some environment // variables or final defaults. Other environment variables should be // detected and used by `pg`. ...(pgConnectionString || process.env.DATABASE_URL || isDemo ? coerce(pg_connection_string_1.parse(pgConnectionString || process.env.DATABASE_URL || DEMO_PG_URL)) : { host: process.env.PGHOST || process.env.PGHOSTADDR || 'localhost', port: (process.env.PGPORT ? parseInt(process.env.PGPORT, 10) : null) || 5432, database: process.env.PGDATABASE, user: process.env.PGUSER, password: process.env.PGPASSWORD, }), // Add the max pool size to our config. max: maxPoolSize, }; const loadPlugins = (rawNames) => { if (!rawNames) { return undefined; } const names = Array.isArray(rawNames) ? rawNames : String(rawNames).split(','); return names.map(rawName => { if (typeof rawName === 'function') { return rawName; } const name = String(rawName); const parts = name.split(':'); if (process.platform === 'win32' && parts[0].length === 1 && /^[a-z]$/i.test(parts[0]) && ['\\', '/'].includes(name[2])) { // Assume this is a windows path `C:/path/to/module.js` or `C:\path\to\module.js` const driveLetter = parts.shift(); // Add the drive part back onto the path parts[0] = `${driveLetter}:${parts[0]}`; } let root; try { root = require(String(parts.shift())); } catch (e) { // tslint:disable-next-line no-console console.error(`Failed to load plugin '${name}'`); throw e; } let plugin = root; let part; while ((part = parts.shift())) { plugin = plugin[part]; if (plugin == null) { throw new Error(`No plugin found matching spec '${name}' - failed at '${part}'`); } } if (typeof plugin === 'function') { return plugin; } else if (plugin === root && typeof plugin.default === 'function') { return plugin.default; // ES6 workaround } else { throw new Error(`No plugin found matching spec '${name}' - expected function, found '${typeof plugin}'`); } }); }; if (jwtAudiences != null && jwtVerifyAudience != null) { exitWithErrorMessage(`Provide either '--jwt-audiences' or '-A, --jwt-verify-audience' but not both`); } function trimNulls(obj) { return Object.keys(obj).reduce((memo, key) => { if (obj[key] != null) { memo[key] = obj[key]; } return memo; }, {}); } if (rawJwtVerifyOptions && (jwtVerifyAlgorithms || jwtVerifyAudience || jwtVerifyClockTolerance || jwtVerifyId || jwtVerifyIgnoreExpiration || jwtVerifyIgnoreNotBefore || jwtVerifyIssuer || jwtVerifySubject)) { exitWithErrorMessage('You may not mix `jwtVerifyOptions` with the legacy `jwtVerify*` settings; please only provide `jwtVerifyOptions`.'); } const jwtVerifyOptions = rawJwtVerifyOptions ? rawJwtVerifyOptions : trimNulls({ algorithms: jwtVerifyAlgorithms, audience: jwtVerifyAudience, clockTolerance: jwtVerifyClockTolerance, jwtId: jwtVerifyId, ignoreExpiration: jwtVerifyIgnoreExpiration, ignoreNotBefore: jwtVerifyIgnoreNotBefore, issuer: jwtVerifyIssuer, subject: jwtVerifySubject, }); const appendPlugins = loadPlugins(appendPluginNames); const prependPlugins = loadPlugins(prependPluginNames); const skipPlugins = loadPlugins(skipPluginNames); // The options to pass through to the schema builder, or the middleware const postgraphileOptions = pluginHook('cli:library:options', { ...postgraphilerc_1.default['options'], classicIds, dynamicJson, disableDefaultMutations, ignoreRBAC: ignoreRbac, includeExtensionResources, graphqlRoute, graphiqlRoute, graphiql: !disableGraphiql, enhanceGraphiql: enhanceGraphiql ? true : undefined, jwtPgTypeIdentifier: jwtPgTypeIdentifier || deprecatedJwtPgTypeIdentifier, jwtSecret: jwtSecret || deprecatedJwtSecret || process.env.JWT_SECRET, jwtPublicKey, jwtAudiences, jwtSignOptions, jwtRole, jwtVerifyOptions, retryOnInitFail, pgDefaultRole, subscriptions: subscriptions || live, websockets, websocketOperations, live, watchPg, showErrorStack, extendedErrors, disableQueryLog, allowExplain: allowExplain ? true : undefined, enableCors, exportJsonSchemaPath, exportGqlSchemaPath, sortExport, bodySizeLimit, appendPlugins: smartTagsPlugin ? [smartTagsPlugin, ...(appendPlugins || [])] : appendPlugins, prependPlugins, skipPlugins, readCache, writeCache, legacyRelations, setofFunctionsContainNulls, legacyJsonUuid, enableQueryBatching, pluginHook, simpleCollections, legacyFunctionsOnly, ignoreIndexes, ownerConnectionString, usePartitionedParent, }, { config: postgraphilerc_1.default, cliOptions: program }); function killAllWorkers(signal = 'SIGTERM') { for (const id in cluster.workers) { const worker = cluster.workers[id]; if (Object.prototype.hasOwnProperty.call(cluster.workers, id) && worker) { worker.kill(signal); } } } if (noServer) { // No need for a server, let's just spin up the schema builder (async () => { const pgPool = new pg_1.Pool(pgConfig); pgPool.on('error', err => { // tslint:disable-next-line no-console console.error('PostgreSQL client generated error: ', err.message); }); const { getGraphQLSchema } = postgraphile_1.getPostgraphileSchemaBuilder(pgPool, schemas, postgraphileOptions); await getGraphQLSchema(); if (!watchPg) { await pgPool.end(); } })().then(null, e => { console.error('Error occurred!'); console.error(e); process.exit(1); }); } else { if (clusterWorkers >= 2 && cluster.isMaster) { let shuttingDown = false; const shutdown = () => { if (!shuttingDown) { shuttingDown = true; process.exitCode = 1; const fallbackTimeout = setTimeout(() => { const remainingCount = Object.keys(cluster.workers).length; if (remainingCount > 0) { console.log(` [cluster] ${remainingCount} workers did not die fast enough, sending SIGKILL`); killAllWorkers('SIGKILL'); const ultraFallbackTimeout = setTimeout(() => { console.log(` [cluster] really should have exited automatically, but haven't - exiting`); process.exit(3); }, 5000); ultraFallbackTimeout.unref(); } else { console.log(` [cluster] should have exited automatically, but haven't - exiting`); process.exit(2); } }, 5000); fallbackTimeout.unref(); console.log(` [cluster] killing other workers with SIGTERM`); killAllWorkers('SIGTERM'); } }; cluster.on('exit', (worker, code, signal) => { console.log(` [cluster] worker pid=${worker.process.pid} exited (code=${code}, signal=${signal})`); shutdown(); }); for (let i = 0; i < clusterWorkers; i++) { const worker = cluster.fork({ POSTGRAPHILE_WORKER_NUMBER: String(i + 1), }); console.log(` [cluster] started worker ${i + 1} (pid=${worker.process.pid})`); } } else { // Create’s our PostGraphile server const rawMiddleware = postgraphile_1.default(pgConfig, schemas, postgraphileOptions); // You probably don't want this hook; likely you want // `postgraphile:middleware` instead. This hook will likely be removed in // future without warning. const middleware = pluginHook( /* DO NOT USE -> */ 'cli:server:middleware' /* <- DO NOT USE */, rawMiddleware, { options: postgraphileOptions, }); const server = http_1.createServer(middleware); if (serverTimeout) { server.timeout = serverTimeout; } if (websockets.length) { subscriptions_1.enhanceHttpServerWithWebSockets(server, middleware); } pluginHook('cli:server:created', server, { options: postgraphileOptions, middleware, }); // Start our server by listening to a specific port and host name. Also log // some instructions and other interesting information. server.listen(port, hostname, () => { const address = server.address(); const actualPort = typeof address === 'string' || address == null ? port : address.port; const self = cluster.isMaster ? isDev ? `server (pid=${process.pid})` : 'server' : `worker ${process.env.POSTGRAPHILE_WORKER_NUMBER} (pid=${process.pid})`; const versionString = `v${manifest.version}`; if (cluster.isMaster || process.env.POSTGRAPHILE_WORKER_NUMBER === '1') { console.log(''); console.log(`PostGraphile ${versionString} ${self} listening on port ${chalk_1.default.underline(actualPort.toString())} 🚀`); console.log(''); const { host: rawPgHost, port: rawPgPort, database: pgDatabase, user: pgUser, password: pgPassword, } = pgConfig; // Not using default because want to handle the empty string also. const pgHost = rawPgHost || 'localhost'; const pgPort = (rawPgPort && parseInt(String(rawPgPort), 10)) || 5432; const safeConnectionString = isDemo ? 'postgraphile_demo' : `postgres://${pgUser ? pgUser : ''}${pgPassword ? ':[SECRET]' : ''}${pgUser || pgPassword ? '@' : ''}${pgUser || pgPassword || pgHost !== 'localhost' || pgPort !== 5432 ? pgHost : ''}${pgPort !== 5432 ? `:${pgConfig.port || 5432}` : ''}${pgDatabase ? `/${pgDatabase}` : ''}`; const information = pluginHook('cli:greeting', [ `GraphQL API: ${chalk_1.default.underline.bold.blue(`http://${hostname}:${actualPort}${graphqlRoute}`)}` + (postgraphileOptions.subscriptions ? ` (${postgraphileOptions.live ? 'live ' : ''}subscriptions enabled)` : ''), !disableGraphiql && `GraphiQL GUI/IDE: ${chalk_1.default.underline.bold.blue(`http://${hostname}:${actualPort}${graphiqlRoute}`)}` + (postgraphileOptions.enhanceGraphiql || postgraphileOptions.live || postgraphileOptions.subscriptions ? '' : ` (${chalk_1.default.bold('RECOMMENDATION')}: add '--enhance-graphiql')`), `Postgres connection: ${chalk_1.default.underline.magenta(safeConnectionString)}${postgraphileOptions.watchPg ? ' (watching)' : ''}`, `Postgres schema(s): ${schemas.map(schema => chalk_1.default.magenta(schema)).join(', ')}`, `Documentation: ${chalk_1.default.underline(`https://graphile.org/postgraphile/introduction/`)}`, `Node.js version: ${process.version} on ${os.platform()} ${os.arch()}`, extractedPlugins.length === 0 ? `Join ${chalk_1.default.bold(sponsor)} in supporting PostGraphile development: ${chalk_1.default.underline.bold.blue(`https://graphile.org/sponsor/`)}` : null, ], { options: postgraphileOptions, middleware, port: actualPort, chalk: chalk_1.default, }).filter(isString); console.log(information.map(msg => ` ‣ ${msg}`).join('\n')); console.log(''); console.log(chalk_1.default.gray('* * *')); } else { console.log(`PostGraphile ${versionString} ${self} listening on port ${chalk_1.default.underline(actualPort.toString())} 🚀`); } console.log(''); }); } } /* eslint-enable */ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3Bvc3RncmFwaGlsZS9jbGkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFDQSw0QkFBNEI7O0FBRTVCOzs7Ozs7R0FNRztBQUNILHFEQUFzQztBQUV0Qyx5QkFBeUI7QUFDekIsK0JBQW9DO0FBQ3BDLGlDQUEwQjtBQUMxQixxQ0FBc0M7QUFFdEMsK0RBQXdFO0FBQ3hFLGlEQUE0RTtBQUM1RSx3Q0FBMkQ7QUFDM0QsMkJBQXNDO0FBQ3RDLG1DQUFvQztBQUNwQyw2Q0FBa0U7QUFDbEUsc0NBQXVDO0FBRXZDLGtDQUFrQztBQUNsQywrQ0FBK0M7QUFDL0Msa0NBQWtDO0FBQ2xDLGdEQUFpRDtBQUNqRCx3REFBdUU7QUFDdkUsMkJBQWdDO0FBRWhDLE1BQU0sUUFBUSxHQUFHLE9BQU8sQ0FBQyxHQUFHLEVBQUUsR0FBRywwQkFBMEIsQ0FBQztBQUM1RDs7O0dBR0c7QUFDSCxNQUFNLGVBQWUsR0FBRyxlQUFVLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDLHVDQUE2QixFQUFFLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQztBQUV0RixNQUFNLEtBQUssR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLGdCQUFnQixLQUFLLGFBQWEsQ0FBQztBQUU3RCxTQUFTLFFBQVEsQ0FBQyxHQUFZO0lBQzVCLE9BQU8sT0FBTyxHQUFHLEtBQUssUUFBUSxDQUFDO0FBQ2pDLENBQUM7QUFFRCxNQUFNLE9BQU8sR0FBRyxRQUFRLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxRQUFRLENBQUMsTUFBTSxHQUFHLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDLENBQUM7QUFFdEUsTUFBTSxRQUFRLEdBQUcsWUFBWSxDQUFDLGtCQUFrQixDQUFDLENBQUM7QUFFbEQsK0JBQStCO0FBQy9CLE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQztBQUV6QixTQUFTLGNBQWMsQ0FDckIsT0FBc0I7SUFLdEIsSUFBSSxJQUFJLENBQUM7SUFDVCxJQUFJLGFBQWEsR0FBRyxFQUFFLENBQUM7SUFDdkIsSUFBSSxPQUFPLENBQUMsQ0FBQyxDQUFDLEtBQUssV0FBVyxFQUFFO1FBQzlCLGFBQWEsR0FBRyxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ3RDLElBQUksR0FBRyxDQUFDLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLEVBQUUsR0FBRyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7S0FDdEQ7U0FBTTtRQUNMLGFBQWEsR0FBRyxDQUFDLHdCQUFNLElBQUksd0JBQU0sQ0FBQyxTQUFTLENBQUMsSUFBSSx3QkFBTSxDQUFDLFNBQVMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDO1FBQ3BGLElBQUksR0FBRyxPQUFPLENBQUM7S0FDaEI7SUFDRCxNQUFNLE9BQU8sR0FBRyxhQUFhLENBQUMsR0FBRyxDQUFDLENBQUMsWUFBb0IsRUFBRSxFQUFFO1FBQ3pELFFBQVEsQ0FBQyxtQkFBbUIsRUFBRSxZQUFZLENBQUMsQ0FBQztRQUM1QyxNQUFNLFNBQVMsR0FBRyxPQUFPLENBQUMsWUFBWSxDQUFDLENBQUMsQ0FBQyxxQ0FBcUM7UUFDOUUsSUFBSSxTQUFTLENBQUMsU0FBUyxDQUFDLElBQUksT0FBTyxTQUFTLENBQUMsU0FBUyxDQUFDLEtBQUssUUFBUSxFQUFFO1lBQ3BFLE9BQU8sU0FBUyxDQUFDLFNBQVMsQ0FBQyxDQUFDO1NBQzdCO2FBQU07WUFDTCxPQUFPLFNBQVMsQ0FBQztTQUNsQjtJQUNILENBQUMsQ0FBQyxDQUFDO0lBQ0gsT0FBTyxFQUFFLElBQUksRUFBRSxPQUFPLEVBQUUsQ0FBQztBQUMzQixDQUFDO0FBRUQsTUFBTSxFQUFFLElBQUksRUFBRSxlQUFlLEVBQUUsT0FBTyxFQUFFLGdCQUFnQixFQUFFLEdBQUcsY0FBYyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztBQUUxRixNQUFNLFVBQVUsR0FBRywyQkFBYyxDQUFDLGdCQUFnQixDQUFDLENBQUM7QUFFcEQsT0FBTyxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLENBQUMsS0FBSyxDQUFDLGNBQWMsQ0FBQyxDQUFDLFdBQVcsQ0FBQyxRQUFRLENBQUMsV0FBVyxDQUFDLENBQUM7QUFTMUYsU0FBUyxPQUFPLENBQ2QsWUFBb0IsRUFDcEIsV0FBbUIsRUFDbkIsS0FBaUM7SUFFakMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxZQUFZLEVBQUUsV0FBVyxFQUFFLEtBQUssQ0FBQyxDQUFDO0lBQ2pELE9BQU8sT0FBTyxDQUFDO0FBQ2pCLENBQUM7QUFFRCxtQkFBbUI7QUFDbkIsT0FBTztLQUNKLE1BQU0sQ0FDTCxvQkFBb0IsRUFDcEIsNEhBQTRILENBQzdIO0tBQ0EsTUFBTSxDQUNMLDJCQUEyQixFQUMzQixtUUFBbVEsQ0FDcFE7S0FDQSxNQUFNLENBQ0wsaUNBQWlDLEVBQ2pDLHFKQUFxSixDQUN0SjtLQUNBLE1BQU0sQ0FDTCx1QkFBdUIsRUFDdkIsNkVBQTZFLEVBQzdFLENBQUMsTUFBYyxFQUFFLEVBQUUsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUN0QztLQUNBLE1BQU0sQ0FDTCxxQkFBcUIsRUFDckIsNEZBQTRGLENBQzdGO0tBQ0EsTUFBTSxDQUNMLHVCQUF1QixFQUN2QixvS0FBb0ssRUFDcEssQ0FBQyxNQUFjLEVBQUUsRUFBRSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQ3RDO0tBQ0EsTUFBTSxDQUNMLHFDQUFxQyxFQUNyQywwSEFBMEgsQ0FDM0g7S0FDQSxNQUFNLENBQ0wsWUFBWSxFQUNaLGlLQUFpSyxDQUNsSztLQUNBLE1BQU0sQ0FDTCxhQUFhLEVBQ2Isa0pBQWtKLENBQ25KO0tBQ0EsTUFBTSxDQUFDLHFCQUFxQixFQUFFLGtEQUFrRCxDQUFDO0tBQ2pGLE1BQU0sQ0FBQyxxQkFBcUIsRUFBRSx1Q0FBdUMsRUFBRSxVQUFVLENBQUM7S0FDbEYsTUFBTSxDQUNMLDhCQUE4QixFQUM5Qiw0RUFBNEUsRUFDNUUsVUFBVSxDQUNYO0tBQ0EsTUFBTSxDQUNMLDZCQUE2QixFQUM3Qiw4R0FBOEcsQ0FDL0c7S0FDQSxNQUFNLENBQ0wsc0JBQXNCLEVBQ3RCLG1LQUFtSyxDQUNwSyxDQUFDO0FBRUosVUFBVSxDQUFDLHdCQUF3QixFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBRTlDLHVCQUF1QjtBQUN2QixPQUFPO0tBQ0osTUFBTSxDQUNMLG9CQUFvQixFQUNwQixnSEFBZ0gsQ0FDakg7S0FDQSxNQUFNLENBQ0wsd0NBQXdDLEVBQ3hDLHlLQUF5SyxDQUMxSztLQUNBLE1BQU0sQ0FBQyxtQkFBbUIsRUFBRSwrREFBK0QsQ0FBQztLQUM1RixNQUFNLENBQ0wsaUNBQWlDLEVBQ2pDLHNGQUFzRixDQUN2RjtLQUNBLE1BQU0sQ0FDTCx1Q0FBdUMsRUFDdkMsbUhBQW1ILENBQ3BIO0tBQ0EsTUFBTSxDQUNMLGtCQUFrQixFQUNsQixpT0FBaU8sQ0FDbE87S0FDQSxNQUFNLENBQ0wscUJBQXFCLEVBQ3JCLDRIQUE0SCxDQUM3SDtLQUNBLE1BQU0sQ0FDTCwrQkFBK0IsRUFDL0IsMEhBQTBILENBQzNIO0tBQ0EsTUFBTSxDQUNMLDBCQUEwQixFQUMxQiw4SUFBOEksQ0FDL0ksQ0FBQztBQUVKLFVBQVUsQ0FBQyxzQkFBc0IsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUU1QyxxQkFBcUI7QUFDckIsT0FBTztLQUNKLE1BQU0sQ0FDTCxrQ0FBa0MsRUFDbEMsd0ZBQXdGLENBQ3pGO0tBQ0EsTUFBTSxDQUNMLDRCQUE0QixFQUM1Qiw2SkFBNkosRUFDN0osQ0FBQyxNQUFjLEVBQUUsRUFBRSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQ3JELENBQUM7QUFFSixVQUFVLENBQUMsNkJBQTZCLEVBQUUsT0FBTyxDQUFDLENBQUM7QUFFbkQseUJBQXlCO0FBQ3pCLE9BQU87S0FDSixNQUFNLENBQ0wsMkJBQTJCLEVBQzNCLDJGQUEyRixDQUM1RjtLQUNBLE1BQU0sQ0FDTCw0QkFBNEIsRUFDNUIsNEZBQTRGLENBQzdGO0tBQ0EsTUFBTSxDQUNMLHlCQUF5QixFQUN6QixrRUFBa0UsQ0FDbkUsQ0FBQztBQUVKLFVBQVUsQ0FBQyx1QkFBdUIsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUU3QywyQkFBMkI7QUFDM0IsT0FBTztLQUNKLE1BQU0sQ0FDTCxxQkFBcUIsRUFDckIsMEhBQTBILENBQzNIO0tBQ0EsTUFBTSxDQUNMLHNCQUFzQixFQUN0QixxSEFBcUgsQ0FDdEg7S0FDQSxNQUFNLENBQ0wsNkJBQTZCLEVBQzdCLDhKQUE4SixDQUMvSjtLQUNBLE1BQU0sQ0FDTCxnQ0FBZ0MsRUFDaEMsd0tBQXdLLENBQ3pLO0tBQ0EsTUFBTSxDQUNMLGVBQWUsRUFDZixrRkFBa0YsQ0FDbkY7S0FDQSxNQUFNLENBQ0wsaUJBQWlCLEVBQ2pCLHlIQUF5SCxDQUMxSCxDQUFDO0FBRUosVUFBVSxDQUFDLHdCQUF3QixFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBRTlDLDBCQUEwQjtBQUMxQixPQUFPO0tBQ0osTUFBTSxDQUNMLHNCQUFzQixFQUN0QixrRUFBa0UsQ0FDbkU7S0FDQSxNQUFNLENBQ0wsdUJBQXVCLEVBQ3ZCLHVFQUF1RSxDQUN4RTtLQUNBLE1BQU0sQ0FDTCxvQkFBb0IsRUFDcEIscUxBQXFMLENBQ3RMO0tBQ0EsTUFBTSxDQUNMLHdCQUF3QixFQUN4QixzRUFBc0UsQ0FDdkU7S0FDQSxNQUFNLENBQ0wsWUFBWSxFQUNaLHFGQUFxRixDQUN0RjtLQUNBLE1BQU0sQ0FDTCxnQ0FBZ0MsRUFDaEMsa0xBQWtMLENBQ25MO0tBQ0EsTUFBTSxDQUFDLG9CQUFvQixFQUFFLG1EQUFtRCxFQUFFLFVBQVUsQ0FBQztLQUM3RixNQUFNLENBQ0wsMkJBQTJCLEVBQzNCLDZEQUE2RCxFQUM3RCxVQUFVLENBQ1g7S0FDQSxNQUFNLENBQ0wseUJBQXlCLEVBQ3pCLHFGQUFxRixDQUN0RjtLQUNBLE1BQU0sQ0FBQyxxQkFBcUIsRUFBRSxnRUFBZ0UsQ0FBQztLQUMvRixNQUFNLENBQ0wsaUJBQWlCLEVBQ2pCLDRJQUE0SSxDQUM3SSxDQUFDO0FBRUosVUFBVSxDQUFDLHlCQUF5QixFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBRS9DLHNCQUFzQjtBQUN0QixPQUFPO0tBQ0osTUFBTSxDQUNMLDJCQUEyQixFQUMzQixtR0FBbUcsQ0FDcEc7S0FDQSxNQUFNLENBQ0wsa0NBQWtDLEVBQ2xDLHlFQUF5RSxFQUN6RSxDQUFDLE1BQWMsRUFBRSxFQUFFLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FDdEM7S0FDQSxNQUFNLENBQ0wsb0NBQW9DLEVBQ3BDLHlJQUF5SSxFQUN6SSxDQUFDLE1BQWMsRUFBRSxFQUFFLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FDckQ7S0FDQSxNQUFNLENBQ0wsdUNBQXVDLEVBQ3ZDLGtJQUFrSSxFQUNsSSxVQUFVLENBQ1g7S0FDQSxNQUFNLENBQUMsMEJBQTBCLEVBQUUsc0NBQXNDLENBQUM7S0FDMUUsTUFBTSxDQUNMLGdDQUFnQyxFQUNoQywyRUFBMkUsQ0FDNUU7S0FDQSxNQUFNLENBQ0wsZ0NBQWdDLEVBQ2hDLDBFQUEwRSxDQUMzRTtLQUNBLE1BQU0sQ0FDTCw4QkFBOEIsRUFDOUIscUVBQXFFLEVBQ3JFLENBQUMsTUFBYyxFQUFFLEVBQUUsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUN0QztLQUNBLE1BQU0sQ0FBQywrQkFBK0IsRUFBRSwyQ0FBMkMsQ0FBQztLQUNwRixNQUFNLENBQ0wscUJBQXFCLEVBQ3JCLGlMQUFpTCxFQUNqTCxDQUFDLE1BQWMsRUFBRSxFQUFFLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FDdEM7S0FDQSxNQUFNLENBQ0wseUNBQXlDLEVBQ3pDLHFGQUFxRixDQUN0RixDQUFDO0FBRUosVUFBVSxDQUFDLG1CQUFtQixFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBRXpDLG9CQUFvQjtBQUNwQixVQUFVLENBQUMsZUFBZSxFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBRXJDLGFBQWE7QUFDYixPQUFPO0tBQ0osTUFBTSxDQUNMLHNCQUFzQixFQUN0QixxRkFBcUYsQ0FDdEY7S0FDQSxNQUFNLENBQ0wsbUJBQW1CLEVBQ25CLDJFQUEyRSxDQUM1RTtLQUNBLE1BQU0sQ0FDTCwwQkFBMEIsRUFDMUIsb0ZBQW9GLEVBQ3BGLENBQUMsTUFBYyxFQUFFLEVBQUUsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUN0QztLQUNBLE1BQU0sQ0FDTCx5QkFBeUIsRUFDekIsOE9BQThPLENBQy9PLENBQUM7QUFFSixVQUFVLENBQUMsMEJBQTBCLEVBQUUsT0FBTyxDQUFDLENBQUM7QUFFaEQsbURBQW1EO0FBQ25ELE9BQU87S0FDSixNQUFNLENBQ0wsMkNBQTJDLEVBQzNDLHlOQUF5TixDQUMxTjtLQUNBLE1BQU0sQ0FDTCxvQkFBb0IsRUFDcEIsK0ZBQStGLENBQ2hHLENBQUM7QUFFSixVQUFVLENBQUMsMkJBQTJCLEVBQUUsT0FBTyxDQUFDLENBQUM7QUFFakQsT0FBTyxDQUFDLEVBQUUsQ0FBQyxRQUFRLEVBQUUsR0FBRyxFQUFFO0lBQ3hCLE9BQU8sQ0FBQyxHQUFHLENBQUM7Ozs7OztDQU1iLENBQUMsQ0FBQztJQUNELE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDbEIsQ0FBQyxDQUFDLENBQUM7QUFFSCxPQUFPLENBQUMsS0FBSyxDQUFDLGVBQWUsQ0FBQyxDQUFDO0FBRS9CLFNBQVMsb0JBQW9CLENBQUMsT0FBZTtJQUMzQyxPQUFPLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQ3ZCLE9BQU8sQ0FBQyxLQUFLLEVBQUUsQ0FBQztJQUNoQixPQUFPLENBQUMsS0FBSyxDQUFDLHFDQUFxQyxDQUFDLENBQUM7SUFDckQsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNsQixDQUFDO0FBRUQsSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRTtJQUN2QixvQkFBb0IsQ0FDbEIscUVBQXFFLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUNwRixNQUFNLENBQ1AsR0FBRyxDQUNMLENBQUM7Q0FDSDtBQUVELElBQUksT0FBTyxDQUFDLFNBQVMsQ0FBQyxFQUFFO0lBQ3RCLG9CQUFvQixDQUFDLG1FQUFtRSxDQUFDLENBQUM7Q0FDM0Y7QUFFRCx1QkFBdUI7QUFDdkIsT0FBTyxDQUFDLEVBQUUsQ0FBQyxRQUFRLEVBQUUsR0FBRyxFQUFFO0lBQ3hCLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDbEIsQ0FBQyxDQUFDLENBQUM7QUFFSCxzRUFBc0U7QUFDdEUsc0VBQXNFO0FBQ3RFLGlFQUFpRTtBQUNqRSx5Q0FBeUM7QUFDekMsTUFBTSxhQUFhLEdBQUcsd0JBQU0sQ0FBQyxTQUFTLENBQUMsSUFBSSxFQUFFLENBQUM7QUFDOUMsTUFBTSxvQkFBb0IsR0FBRyxFQUFFLENBQUM7QUFDaEMsQ0FBQyxlQUFlLEVBQUUsWUFBWSxFQUFFLDRCQUE0QixDQUFDLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxFQUFFO0lBQzdFLElBQUksTUFBTSxJQUFJLGFBQWEsRUFBRTtRQUMzQixvQkFBb0IsQ0FBQyxNQUFNLENBQUMsR0FBRyxhQUFhLENBQUMsTUFBTSxDQUFDLENBQUM7S0FDdEQ7QUFDSCxDQUFDLENBQUMsQ0FBQztBQUVILGtHQUFrRztBQUNsRyx3Q0FBd0M7QUFDeEMsTUFBTSxFQUNKLElBQUksRUFBRSxNQUFNLEdBQUcsS0FBSyxFQUNwQixVQUFVLEVBQUUsa0JBQWtCLEVBQzlCLGVBQWUsRUFDZixhQUFhLEVBQ2IsSUFBSSxFQUNKLFVBQVUsR0FBRyxhQUFhLElBQUksSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksRUFBRSxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUN0RCxtQkFBbUIsR0FBRyxlQUFlLEVBQ3JDLEtBQUssRUFBRSxPQUFPLEVBQ2QsTUFBTSxFQUFFLFFBQVEsRUFDaEIsSUFBSSxFQUFFLFFBQVEsR0FBRyxXQUFXLEVBQzVCLElBQUksR0FBRyxJQUFJLEVBQ1gsT0FBTyxFQUFFLGFBQWEsRUFDdEIsV0FBVyxFQUNYLFdBQVcsRUFBRSxhQUFhLEVBQzFCLGVBQWUsRUFDZixPQUFPLEVBQUUsWUFBWSxHQUFHLFVBQVUsRUFDbEMsUUFBUSxFQUFFLGFBQWEsR0FBRyxXQUFXLEVBQ3JDLGVBQWUsR0FBRyxLQUFLLEVBQ3ZCLGVBQWUsR0FBRyxLQUFLLEVBQ3ZCLE1BQU0sRUFBRSxtQkFBbUIsRUFDM0IsU0FBUyxFQUNULFlBQVksRUFDWixZQUFZLEVBQ1osbUJBQW1CLEVBQ25CLGlCQUFpQixFQUNqQix1QkFBdUIsRUFDdkIsV0FBVyxFQUNYLHlCQUF5QixFQUN6Qix3QkFBd0IsRUFDeEIsZUFBZSxFQUNmLGdCQUFnQixFQUNoQixjQUFjLEdBQUcsRUFBRSxFQUNuQixnQkFBZ0IsRUFBRSxtQkFBbUIsRUFDckMsT0FBTyxHQUFHLENBQUMsTUFBTSxDQUFDLEVBQ2xCLEtBQUssRUFBRSw2QkFBNkIsRUFDcEMsa0JBQWtCLEVBQUUsbUJBQW1CLEVBQ3ZDLElBQUksRUFBRSxVQUFVLEdBQUcsS0FBSyxFQUN4QixVQUFVLEdBQUcsS0FBSyxFQUNsQixXQUFXLEdBQUcsS0FBSyxFQUNuQix1QkFBdUIsR0FBRyxLQUFLLEVBQy9CLFVBQVUsR0FBRyxJQUFJLEVBQ2pCLHlCQUF5QixHQUFHLEtBQUssRUFDakMsZ0JBQWdCLEVBQUUsb0JBQW9CLEVBQ3RDLG1CQUFtQixFQUFFLG1CQUFtQixFQUN4QyxVQUFVLEdBQUcsS0FBSyxFQUNsQixjQUFjLEVBQUUsaUJBQWlCLEVBQ2pDLGNBQWMsR0FBRyxFQUFFLEVBQ25CLGFBQWEsRUFDYixhQUFhLEVBQUUsaUJBQWlCLEVBQ2hDLGNBQWMsRUFBRSxrQkFBa0I7QUFDbEMsK0NBQStDO0FBQy9DLFdBQVcsRUFBRSxlQUFlLEVBQzVCLFNBQVMsRUFDVCxVQUFVLEVBQ1YsZUFBZSxFQUFFLGtCQUFrQixHQUFHLFlBQVksRUFDbEQsTUFBTSxFQUFFLFNBQVMsRUFDakIsY0FBYyxFQUNkLG1CQUFtQixFQUNuQiwwQkFBMEIsR0FBRyxJQUFJLEVBQ2pDLGNBQWMsRUFDZCxlQUFlLEVBQ2YsWUFBWSxFQUNaLGlCQUFpQixFQUNqQixtQkFBbUIsRUFDbkIsYUFBYSxFQUNiLG9CQUFvQixHQUFHLEtBQUssR0FFN0IsR0FBRyxFQUFFLEdBQUcsd0JBQU0sQ0FBQyxTQUFTLENBQUMsRUFBRSxHQUFHLE9BQU8sRUFBRSxHQUFHLG9CQUFvQixFQUFvQixDQUFDO0FBRXBGLE1BQU0sY0FBYyxHQUFHLENBQUMsR0FBRyxDQUFDLEVBQUU7SUFDNUIsUUFBUSxHQUFHLEVBQUU7UUFDWCxLQUFLLFFBQVEsQ0FBQztRQUNkLEtBQUssSUFBSTtZQUNQLE9BQU8sSUFBSSxDQUFDO1FBQ2QsS0FBSyxJQUFJLENBQUM7UUFDVixLQUFLLFNBQVM7WUFDWixPQUFPLFNBQVMsQ0FBQztRQUNuQixLQUFLLE1BQU07WUFDVCxPQUFPLE1BQU0sQ0FBQztRQUNoQixPQUFPLENBQUMsQ0FBQztZQUNQLG9CQUFvQixDQUNsQix5RkFBeUYsQ0FDMUYsQ0FBQztTQUNIO0tBQ0Y7QUFDSCxDQUFDLENBQUMsQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDO0FBRXRCLElBQUksWUFBWSxJQUFJLENBQUMsZUFBZSxJQUFJLENBQUMsZUFBZSxFQUFFO0lBQ3hELG9CQUFvQixDQUFDLHlFQUF5RSxDQUFDLENBQUM7Q0FDakc7QUFFRCxJQUFJLGVBQStDLENBQUM7QUFDcEQsSUFBSSxDQUFDLENBQUMsTUFBTSxFQUFFLE1BQU0sRUFBRSxZQUFZLENBQUMsQ0FBQyxRQUFRLENBQUMsa0JBQWtCLENBQUMsRUFBRTtJQUNoRSxvQkFBb0IsQ0FDbEIseUdBQXlHLGtCQUFrQixHQUFHLENBQy9ILENBQUM7Q0FDSDtLQUFNO0lBQ0wsZUFBZSxHQUFHLGtCQUFrQixDQUFDO0NBQ3RDO0FBRUQsK0JBQStCO0FBQy9CO0FBQ0UsZ0JBQWdCO0FBQ2hCLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxVQUFVLENBQUM7SUFDMUIsdUJBQXVCO0lBQ3ZCLENBQUMsVUFBVSxDQUFDLE1BQU07UUFDaEIsbUNBQW1DO1FBQ25DLFVBQVUsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxFQUFFLElBQUksQ0FBQyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQ3REO0lBQ0Esb0JBQW9CLENBQ2xCLHNHQUFzRyxVQUFVLEdBQUcsQ0FDcEgsQ0FBQztDQUNIO0FBRUQsSUFBSSxtQkFBbUIsS0FBSyxlQUFlLElBQUksbUJBQW1CLEtBQUssS0FBSyxFQUFFO0lBQzVFLG9CQUFvQixDQUNsQixrR0FBa0csbUJBQW1CLEdBQUcsQ0FDekgsQ0FBQztDQUNIO0FBRUQsTUFBTSxRQUFRLEdBQUcsQ0FBQyxTQUFTLENBQUM7QUFFNUIsMkVBQTJFO0FBQzNFLDBFQUEwRTtBQUMxRSwwQkFBMEI7QUFDMUIsTUFBTSxPQUFPLEdBQWtCLFFBQVEsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxlQUFlLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDO0FBRXJGLE1BQU0scUJBQXFCLEdBQUcsZUFBZSxJQUFJLGtCQUFrQixJQUFJLE9BQU8sQ0FBQyxHQUFHLENBQUMsWUFBWSxDQUFDO0FBRWhHLDZFQUE2RTtBQUM3RSxNQUFNLE1BQU0sR0FBRyxDQUFDLENBQTZDLEVBQWMsRUFBRTtJQUMzRSxNQUFNLElBQUksR0FDUixPQUFPLENBQUMsQ0FBQyxJQUFJLEtBQUssUUFBUTtRQUN4QixDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUk7UUFDUixDQUFDLENBQUMsT0FBTyxDQUFDLENBQUMsSUFBSSxLQUFLLFFBQVE7WUFDNUIsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsSUFBSSxFQUFFLEVBQUUsQ0FBQztZQUN0QixDQUFDLENBQUMsU0FBUyxDQUFDO0lBQ2hCLE9BQU87UUFDTCxHQUFHLENBQUM7UUFDSixnQkFBZ0IsRUFBRSxDQUFDLENBQUMsa0JBQWtCLENBQUMsSUFBSSxTQUFTO1FBQ3BELEdBQUcsRUFDRCxDQUFDLENBQUMsR0FBRyxJQUFJLElBQUk7WUFDWCxDQUFDLENBQUMsU0FBUztZQUNYLENBQUMsQ0FBRSxDQUFDLENBQUMsR0FBVyxDQUFDLGtCQUFrQixJQUFJLElBQUk7Z0JBQzNDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUc7Z0JBQ1QsQ0FBQyxDQUFFLENBQUMsQ0FBQyxHQUFXO1FBQ3BCLElBQUksRUFBRSxPQUFPLENBQUMsQ0FBQyxJQUFJLEtBQUssUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxTQUFTO1FBQ3JELFFBQVEsRUFBRSxPQUFPLENBQUMsQ0FBQyxRQUFRLEtBQUssUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxTQUFTO1FBQ2pFLFFBQVEsRUFBRSxPQUFPLENBQUMsQ0FBQyxRQUFRLEtBQUssUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxTQUFTO1FBQ2pFLElBQUksRUFBRSxNQUFNLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLFNBQVM7UUFDOUMsSUFBSSxFQUFFLE9BQU8sQ0FBQyxDQUFDLElBQUksS0FBSyxRQUFRLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLFNBQVM7S0FDdEQsQ0FBQztBQUNKLENBQUMsQ0FBQztBQUVGLDhCQUE4QjtBQUM5QixNQUFNLFFBQVEsR0FBZTtJQUMzQix3RUFBd0U7SUFDeEUsb0VBQW9FO0lBQ3BFLHFFQUFxRTtJQUNyRSw2QkFBNkI7SUFDN0IsR0FBRyxDQUFDLGtCQUFrQixJQUFJLE9BQU8sQ0FBQyxHQUFHLENBQUMsWUFBWSxJQUFJLE1BQU07UUFDMUQsQ0FBQyxDQUFDLE1BQU0sQ0FBQyw0QkFBdUIsQ0FBQyxrQkFBa0IsSUFBSSxPQUFPLENBQUMsR0FBRyxDQUFDLFlBQVksSUFBSSxXQUFXLENBQUMsQ0FBQztRQUNoRyxDQUFDLENBQUM7WUFDRSxJQUFJLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxNQUFNLElBQUksT0FBTyxDQUFDLEdBQUcsQ0FBQyxVQUFVLElBQUksV0FBVztZQUNqRSxJQUFJLEVBQUUsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxJQUFJO1lBQzVFLFFBQVEsRUFBRSxPQUFPLENBQUMsR0FBRyxDQUFDLFVBQVU7WUFDaEMsSUFBSSxFQUFFLE9BQU8sQ0FBQyxHQUFHLENBQUMsTUFBTTtZQUN4QixRQUFRLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxVQUFVO1NBQ2pDLENBQUM7SUFDTix1Q0FBdUM7SUFDdkMsR0FBRyxFQUFFLFdBQVc7Q0FDakIsQ0FBQztBQUVGLE1BQU0sV0FBVyxHQUFHLENBQUMsUUFBZSxFQUFFLEVBQUU7SUFDdEMsSUFBSSxDQUFDLFFBQVEsRUFBRTtRQUNiLE9BQU8sU0FBUyxDQUFDO0tBQ2xCO0lBQ0QsTUFBTSxLQUFLLEdBQUcsS0FBSyxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBQy9FLE9BQU8sS0FBSyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsRUFBRTtRQUN6QixJQUFJLE9BQU8sT0FBTyxLQUFLLFVBQVUsRUFBRTtZQUNqQyxPQUFPLE9BQU8sQ0FBQztTQUNoQjtRQUNELE1BQU0sSUFBSSxHQUFHLE1BQU0sQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUM3QixNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQzlCLElBQ0UsT0FBTyxDQUFDLFFBQVEsS0FBSyxPQUFPO1lBQzVCLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLEtBQUssQ0FBQztZQUNyQixVQUFVLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUN6QixDQUFDLElBQUksRUFBRSxHQUFHLENBQUMsQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQzdCO1lBQ0EsaUZBQWlGO1lBQ2pGLE1BQU0sV0FBVyxHQUFHLEtBQUssQ0FBQyxLQUFLLEVBQUUsQ0FBQztZQUNsQyx3Q0FBd0M7WUFDeEMsS0FBSyxDQUFDLENBQUMsQ0FBQyxHQUFHLEdBQUcsV0FBVyxJQUFJLEtBQUssQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1NBQ3pDO1FBQ0QsSUFBSSxJQUFJLENBQUM7UUFDVCxJQUFJO1lBQ0YsSUFBSSxHQUFHLE9BQU8sQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLEtBQUssRUFBRSxDQUFDLENBQUMsQ0FBQztTQUN2QztRQUFDLE9BQU8sQ0FBQyxFQUFFO1lBQ1Ysc0NBQXNDO1lBQ3RDLE9BQU8sQ0FBQyxLQUFLLENBQUMsMEJBQTBCLElBQUksR0FBRyxDQUFDLENBQUM7WUFDakQsTUFBTSxDQUFDLENBQUM7U0FDVDtRQUNELElBQUksTUFBTSxHQUFHLElBQUksQ0FBQztRQUNsQixJQUFJLElBQW1CLENBQUM7UUFDeEIsT0FBTyxDQUFDLElBQUksR0FBRyxLQUFLLENBQUMsS0FBSyxFQUFFLENBQUMsRUFBRTtZQUM3QixNQUFNLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQ3RCLElBQUksTUFBTSxJQUFJLElBQUksRUFBRTtnQkFDbEIsTUFBTSxJQUFJLEtBQUssQ0FBQyxrQ0FBa0MsSUFBSSxrQkFBa0IsSUFBSSxHQUFHLENBQUMsQ0FBQzthQUNsRjtTQUNGO1FBQ0QsSUFBSSxPQUFPLE1BQU0sS0FBSyxVQUFVLEVBQUU7WUFDaEMsT0FBTyxNQUFNLENBQUM7U0FDZjthQUFNLElBQUksTUFBTSxLQUFLLElBQUksSUFBSSxPQUFPLE1BQU0sQ0FBQyxPQUFPLEtBQUssVUFBVSxFQUFFO1lBQ2xFLE9BQU8sTUFBTSxDQUFDLE9BQU8sQ0FBQyxDQUFDLGlCQUFpQjtTQUN6QzthQUFNO1lBQ0wsTUFBTSxJQUFJLEtBQUssQ0FDYixrQ0FBa0MsSUFBSSxpQ0FBaUMsT0FBTyxNQUFNLEdBQUcsQ0FDeEYsQ0FBQztTQUNIO0lBQ0gsQ0FBQyxDQUFDLENBQUM7QUFDTCxDQUFDLENBQUM7QUFFRixJQUFJLFlBQVksSUFBSSxJ