Star Graphile Documentation

website·Indexed 35 days ago

https://star.graphile.org

Searchable website documentation for Star Graphile from https://star.graphile.org.

Tokens
17.3K
Snippets
83
Records
108
Agent score
61%

What's inside Star Graphile Documentation

  1. Overview of Graphile Config for Node.js libraries

    Graphile Config is a utility for Node.js library authors to create configurable and extensible libraries. It provides a standardized interface for configuration and plugins, allowing authors to define strongly typed options and enable users to extend functionality via plugins. Key capabilities include:

    • Defining and documenting strongly typed configuration options.
    • Supporting plugins that can add their own configuration options.
    • Bundling options and plugins into default presets.
    • Composing presets through preset extension.
    • Sharing configuration across different modes (e.g., CLI and library).
    • Implementing a middleware system for extensibility.
    • Managing plugin execution order via a built-in ordering system.
    • Inspecting resolved values of a preset using the graphile CLI.
  2. Overview of tamedevil for safe dynamic code evaluation

    tamedevil provides a safer alternative to eval and new Function for building performant dynamic functions. It addresses three primary concerns:

    1. Code Injection: Uses tagged template literals and symbols to ensure that evaluated strings are either author-written or suitably escaped.
    2. Garbage Collection: Uses new Function instead of eval and ensures no ephemeral data exists in the evaluation closure, preventing memory leaks.
    3. Performance: Allows developers to generate dynamic functions that run at native JS speed, avoiding the overhead of a custom interpreter.
  3. Overview of Graphile Export

    Graphile Export allows developers to export a GraphQL schema (or other code) as executable JavaScript. This is particularly useful for schemas automatically generated in memory by frameworks. While it is compatible with any JavaScript-based GraphQL schema that follows exportable/importable patterns, it is not limited to Graphile-produced software like PostGraphile, Graphile Build, or Gra_fast_.

    By moving intensive schema build processes (introspection, looping, computation, and transformation) from startup time to build time, developers can achieve:

    • Faster startup times and quicker cold starts in serverless environments (e.g., AWS Lambda).
    • Reduced 'thundering herd' effect during mass server restarts by removing the need to consult external data sources on startup.
    • Lower CPU and memory usage in production, as the code used to compute the schema does not need to be loaded.
    • Fewer runtime dependencies, as build systems can be omitted from the production bundle.
  4. Use EXPORTABLE to mark dependencies for graphile-export

    To mark a value expression for export, wrap it in EXPORTABLE(() => ...). When running eslint --fix, the plugin will automatically analyze the function to identify dependencies and convert the syntax to include the dependency array.

    Example: EXPORTABLE(() => function add(b) { return a + b; }) will be autofixed to EXPORTABLE((a) => function add(b) { return a + b; }, [a]).

    // Before eslint --fix
    const add = EXPORTABLE(
      () =>
        function add(b) {
          return a + b;
        },
    );
    
    // After eslint --fix
    const add = EXPORTABLE(
      (a) =>
        function add(b) {
          return a + b;
        },
      [a],
    );
  5. Indent SQL fragments for better readability with sql.indent

    Use sql.indent to mark SQL fragments that should be indented when the query is compiled with pretty-printing enabled. This is primarily a cosmetic feature to improve the readability of complex, nested SQL queries during debugging and logging.

    Important Constraint: Indentation is ONLY applied when the GRAPHILE_ENV environment variable is set to development or test. It is removed in production environments to avoid minimal overhead in the PostgreSQL parser.

    import { sql } from "pg-sql2";
    
    // Example: Indenting a subquery
    const innerSelect = sql`SELECT user_id FROM orders`;
    const query = sql`WITH recent_orders AS (${sql.indent(innerSelect)}) SELECT * FROM users`;
    
    console.log(sql.compile(query).text);
  6. Import the tamedevil tagged template function

    The te function is the primary entry point for tamedevil. It is a tagged template literal function that also provides various helper methods as properties. Depending on your module system, use the following import methods:
    // ESM
    import { te } from "tamedevil";
    
    // CommonJS
    const { te } = require("tamedevil");
  7. Perform strongly-typed PostgreSQL introspection

    To retrieve strongly-typed introspection results from a PostgreSQL database, use makeIntrospectionQuery() to generate the necessary SQL query and parseIntrospectionResults() to parse the resulting data from the database response. The parseIntrospectionResults function should be called with the introspection field from the first row of the query results.
    import {
      makeIntrospectionQuery,
      parseIntrospectionResults,
    } from "pg-introspection";
    import { Pool } from "pg";
    
    const pool = new Pool({
      connectionString: "postgres://localhost:5432/my_database",
    });
    
    async function main() {
      // 1. Generate the introspection SQL query
      const sql = makeIntrospectionQuery();
      
      // 2. Execute the query against the database
      const { rows } = await pool.query(sql);
      
      // 3. Parse the 'introspection' field from the first row
      const introspection = parseIntrospectionResults(rows[0].introspection);
    
      console.log(
        `The ${introspection.database.datname} DBA is ${
          introspection.database.getDba()?.rolname ?? "-"
        }`,
      );
    }
    
    main();
  8. Embed non-SQL values using sql.withTransformer

    By default, pg-sql2 throws an error when non-SQL values are embedded in SQL fragments. Use sql.withTransformer to define a custom transformer that dictates how unknown values should be coerced into SQL. This function also updates the TypeScript types of the sql tag within the callback to allow the specified types to be embedded.
    import { sql, type Transformer, type SQL } from "pg-sql2";
    
    // Define a transformer that converts numbers to sql.value()
    const numberToValue: Transformer<number> = (sql, value) => {
      if (typeof value === "number") {
        if (!Number.isFinite(value)) {
          throw new Error(`${value} is not a finite number...`);
        }
        return sql.value(value);
      }
      return value;
    };
    
    // Use the transformer to allow numbers in the template literal
    const query: SQL = sql.withTransformer(
      numberToValue,
      (sql) => sql`select * from users where id = ${42}`
    );
    
    console.log(sql.compile(query).text);
    // select * from users where id = $1
  9. Create qualified SQL identifiers (schema.table.column)

    To create qualified identifiers, pass multiple strings or symbols as arguments to sql.identifier(). Each argument is escaped individually and joined with dots.
    import { sql } from "pg-sql2";
    
    const schema = "public";
    const table = "users";
    const column = "name";
    
    // schema.table
    const query1 = sql`SELECT * FROM ${sql.identifier(schema, table)}`;
    // SELECT * FROM "public"."users"
    
    // table.column
    const query2 = sql`SELECT ${sql.identifier(table, column)} FROM users`;
    // SELECT "users"."name" FROM users
    
    // schema.table.column
    const query3 = sql`COMMENT ON COLUMN ${sql.identifier(schema, table, column)} IS ''`;
    // COMMENT ON COLUMN "public"."users"."name" IS '';
  10. Build dynamic SQL queries with pg-sql2

    The pg-sql2 library allows for the creation of highly dynamic SQL statements while preventing SQL injection. It uses tagged template literals and helper methods to manage identifiers and values. The final query is compiled into a parameterized SQL string and an array of values compatible with the pg module.
    const { default: sql } = require("pg-sql2");
    // or import sql from 'pg-sql2';
    
    const tableName = "user";
    const fields = ["name", "age", "height"];
    
    // Safely escape and join identifiers
    const sqlFields = sql.join(
      fields.map((fieldName) => sql.identifier(tableName, fieldName)),
      ", ",
    );
    
    // Use sql.value to create placeholders for user-provided data
    const sqlConditions = sql`created_at > NOW() - interval '3 years' and age > ${sql.value(22)}`;
    
    // Nest queries safely
    const innerQuery = sql`select ${sqlFields} from ${sql.identifier(tableName)} where ${sqlConditions}`;
    
    // Use Symbols for unique automatic identifiers (aliases)
    const sqlAlias = sql.identifier(Symbol());
    
    const query = sql`
    with ${sqlAlias} as (${innerQuery})
    select
      (select json_agg(row_to_json(${sqlAlias})) from ${sqlAlias}) as all_data,
      (select max(age) from ${sqlAlias}) as max_age
    `;
    
    // Compile to { text, values }
    const { text, values } = sql.compile(query);
    
    console.log(text);
    // Output: with __local_0__ as (select "user"."name", "user"."age", "user"."height" from "user" where created_at > NOW() - interval '3 years' and age > $1)
    // select (select json_agg(row_to_json(__local_0__)) from __local_0__) as all_data, (select max(age) from __local_0__) as max_age
    
    console.log(values); // [ 22 ]
    
    // Execution example using 'pg' module:
    // const { rows } = await pg.query(text, values);
  11. Control plugin loading order with before and after properties

    Plugin loading order is not guaranteed by the preset resolution algorithm alone. To ensure a specific order, use the before and after properties. These properties reference either the name of another plugin or a feature label defined in a plugin's provides array.

    If multiple plugins provide the same feature (e.g., 'subscriptions'), other plugins can use before: ['subscriptions'] to ensure they load before any plugin implementing that feature.

    import type {} from "graphile-config";
    
    const PluginA: GraphileConfig.Plugin = { name: "PluginA", after: ["PluginC"] };
    const PluginB: GraphileConfig.Plugin = { name: "PluginB", before: ["PluginA"] };
    const PluginC: GraphileConfig.Plugin = { name: "PluginC", after: ["PluginB"] };
    
    const Preset1: GraphileConfig.Preset = {
      plugins: [PluginA],
    };
    
    const Preset2: GraphileConfig.Preset = {
      extends: [Preset1],
      plugins: [PluginB, PluginC],
    };
    
    // Resulting order: PluginB, PluginC, PluginA
  12. Avoid SQL injection by using safer alternatives to sql.raw()

    Instead of using sql.raw(), use the following safer alternatives to handle dynamic SQL components:

    • Use sql template tags for general SQL text.
    • Use sql.identifier() for table or column names.
    • Use sql.value() for values.
    • Use sql.literal() for constants.

    For complex dynamic queries, use composition by mapping user input to predefined safe fragments using objects with null prototypes to prevent exploitation of built-in properties.

    // ❌ sql.raw(userInput) -> SQL injection!
    // ✅ sql`ASC` for SQL text
    // ✅ sql.identifier(tableName) for table/column names
    // ✅ sql.value(userInput) for values
    // ✅ sql.literal(50) for constants
    
    // Example of safe composition using maps with null prototypes
    const COLUMNS = {
      __proto__: null,
      name: sql.identifier("users", "name"),
      email: sql.identifier("users", "email"),
      created_at: sql.identifier("users", "created_at"),
    };
    
    const DIRECTIONS = {
      __proto__: null,
      asc: sql.literal("ASC"),
      desc: sql.literal("DESC"),
    };
    
    const column = COLUMNS[userInputColumn] || COLUMNS["created_at"];
    const direction = DIRECTIONS[userInputDirection] || DIRECTIONS["asc"];
    
    const query = sql`SELECT * FROM users ORDER BY ${column} ${direction}`;