Michael McGreal

Michael McGreal

Creating a community template for EdgeDB

Status: Draft

the gitpod button

TLDR

  • The edgedb docs have great REPLs for edgeql, but not for the javascript/typscript client.

  • Creating a quick codesandbox or stackblitz is actually a little tricky because of how the edgedb CLI is installed via command line

  • So, I created a repository that uses a .gitpod.yml file to orchestrate installation, among other things

  • This way, users can quickly spin up a playground, modify schema, generate edgeqljs, and test query builders

  • To see for yourself, open the template repository, and click the gitpod button in the readme

    it looks like this the gitpod button

how it works

  • Gitpod will first execute the commands in the .gitpod.yml file.
  • the standard npm install and npm run build that installs packages specified in package.json: edgedb, @edgedb/generate, typescript, and ts-node (I might remove ts-node soon, in favor of tsc)
  • then it gets interesting! gitpod will execute a shell command (as if you typed it yourself), that installs thr edgedb cli.
    • the Edgedb CLI usually prompts the user for interactive input. Luckily, it accepts a -y flag to accept all defaults
      • we pass the flag to the script via the syntax -s -- -y (details here if you're interested)
    • note: the CLI will modify some environmental path variables. When I first tried this in Codesandbox cloud containers (which are configured via a .codesandbox/setuptasks.json, similar to gitpod's .gitpod.yml) – it did not work. Codesandbox seems to run setup tasks in a different protected directory that is not the same directory accessible to users. So after setup tasks were complete, the ededb cli was inaccessible. I haven't studied github codespaces enough, but they seem to have similar issues (and a seemingly less friendly configuration file). Then, I tested stackblitz web contianers, which were fast, but even more limiting, because they do not allow executing terminal commands, nor configuring container initialization. Luckily, gitpod executes startup command in the same directory that is provided to users. Moreover, gitpod already stores the same path as an env variable, so edgedb installer doesn't require any followup commands. i.e. Gitpod is great!
  • After the CLI is installed, another command is executed to edgedb project init --non-interactive this '--non-interactive' flag also accepts all defaults, and is perfect for automated setup like this!
  • When edgedb initialized a project, it created a folder dbschema with an empty default.esdl schema.
    • However, I wanted to also provide an example of using the edgeql-js query builder in a src/index.ts file, which would require a non-empty schema. Rather than trying to write a file via echo commands, I intstead created a file of the same name "default.esdl" in the root directory (edgedb does not look here). Then after, the project has been initialized, gitpod executes another command mv default.esdl dbschema which will move the file from the root directory into the dbschema folder overriting the blank default.esdl
  • however, simply moving the folder into dbschema does nothing. We need to edgedb migration create and edgedb migration apply. By default, these commands also require user input to confirm changes. Luckily, edgedb's cli flag --non-interactive works here too!
    • If a user were to manually run migrations though, the interactive mode is useful. So I also added a package.json script of "migrate": "edgedb migration create && edgedb migration apply && npm run generate" that will run migrations (and also generate the query builder, explained below)
  • After migrations are applied, the edgedb instance will not accept queries based on the new schema, however, the query builder will not have the updated types. We have to generate them using the @edgedb/generate package (which was installed in the first gitpod command).
  • there are 3 edgedb-js generators. The one we're interested in is npx @edgedb/generate edgeql-js. For simplicity, and to run other generators, I created a package.json script of "generate": "npx @edgedb/generate edgeql-js && npx @edgedb/generate interfaces && npx @edgedb/generate queries --file". So executing npm run generate will run all 3 generators
  • So the next .gitpod.yml command is to run these generators.
  • now, the code in src/index.ts will work!

to auto-build via nodemon or manually build?

  • nodemon and similar packages that watch for new file changes (i.e. save file) are useful for frontend development to see changes quickly (especially when paired with hot module reloading)
  • but the purpose of this template is to be able to modify schema, and edit the query builder
  • in many cases, the query builder will be inserting objects
  • running queries on each save would likely make more insert queries than desired (especially for users that format on save)
  • so, I decided to remove nodemon and instead require an explicit command to run the script

ts-node or tsc?

  • from here, there were 2 options.

  • ts-node will transpile .ts files to .js files "JIT" just in time, without building and outputting the .js files.

    • so users can execute ts-node src/index.ts directly on typescript files!
      • (actually) it requires specifying 'ts-node --esm src/index/ts` (described more in the next section)
  • using tsc will first convert the .ts files into .js files, and output them to a specified directory (the default it to output the js files next to the ts files, however, I modified the tsconfig file to output them into a top level dist/ folder). Hiding the generated .js files in /dist is probably good since template users might mistakenly edit the generated .js file, rather than the source .ts file if it were next to the .ts file.

    • then regular node commands can be run on the dist folder node dist/index.js or simple node dist
  • I haven't run benchmarks, but ts-node doesn't seem any faster (and also, as of march 1 2023, hasn't been updated in 8 months)

  • executing commands on the .ts files directly is less cognitive distance, however, this can all be hidden behind a package.json script anyway, so it likely doesn't matter.

    • especially since it's a playground and user is unlikely to add files – only overwrite the existing index.ts file
  • although, ts-node doesn't add javascript files at all – which would reduce the risk of user overwriting the js file at all (even more than outputing the tsc build to a dist folder)

    ES modules and commonJS typescript mess!

    • There were a lot of other quirks when setting this up around typescript ES and node versions. Basically, I converted it from CJS modules to ESM modules, shown in the git history. I'll add notes on this soon.
    • basically, the default tsconfig file created by tsc --init uses old ecmascript versions. here are the upgrades I made:
      • in package.json
        • add "type": "module"
      • in tsconfig
        • change "target": "es2016" to something higher, such as "target": "ES2020" or "target": "ESNext" (I chose 'ESNext')
        • change "module": "commonjs" to "module": "NodeNext"
        • add "moduleResolution": "NodeNext"
          • NodeNext is nice because it allows importing local modules, node modules, or even common js modules all into 1 file (i.e. better interop between commonJS and ESM)
          • however, it does requires specifying the file types of local modules in the import statement (not for node module imports though). For example, src/index.ts imports from:
            • a node module via import { createClient } from 'edgedb
            • a local module (which was generated using @edgedb/generate) via import e from '../dbschema/edgeql-js/index.mjs - note the .mjs file extension. This is required in NodeNext - however, NodeNext would also allow us to import a .cjs file if needed! - @edgedb/generate actually offers a --target flag that will generate the files in various formats (.mts, .mjsm, .cjs, etc.) cli docs. The CLI will attempt to analyze your environment and select the best target automatically, and in this case, it generated .mjs files, so that is the extension we use when importing.
        • add "ourDir": ./dist
        • add "include": ["src"] or "include": ["src/**/*"]
        • add "strict": true
        • add "downlevelIteration": true, which is currently required for the edgedb/generate package (specifially for the query builder)

    more gitpod config for UX

    • For users that have not used vscode in the browser before, simply things like opening a terminal, or a new tab with localhost preview can be confusing at first
    • luckily, gitpod offers some command to help, which can be also be executed via .gitpod.yml!
    • to open the useful files, gp open dbschema/default.esdl and gp open src/index.ts
    • however, by default, vscode will replace the current file with the next file, instead of opening it in a new tab or panel
    • go open does not offer a flag to open in new tab or panel, so a workaround was to add a .vscode/settings.json file to disable this feature "workbench.editor.enablePreview": false. In acutual projects, setting this to false might be anoyying, but for this playground project, it is useful to introduce users to the 2 important files.
    • also to open a terminal (without a keyboard shortcut, or the user knowing the menu icon in the top left exits:)

next actions

  • consider adding to https://www.starter.place/
  • try making in github codespaces
  • offer js only version