# JSOC JSOC is the configuration language. It is JSON with comments, unquoted keys, trailing commas, and one function, `env()`, which reads an environment variable while the file compiles. A `.jsoc` file is a module: import it like any other file and the compiler evaluates it at build time, type checks every read of it, and emits plain JSON. `config.jsoc` at the project root is one such file, the one Elements itself reads; `elements man config` covers what goes in it. This page is the language. ## Syntax ```jsoc { // line comment /* block comment */ name: "my_app", // unquoted key "retry-count": 3, // quoted key when the name is not an identifier tags: ["a", "b",], // trailing commas anywhere debug: false, nothing: null, port: env("PORT", 4000), } ``` A value is a string, a number, `true`, `false`, `null`, an object, an array, or an `env()` call. Strings take single or double quotes. Nothing else parses: there are no expressions, no arithmetic, no string concatenation, no references from one key to another, and no spreads. A file that needs to compute a value computes it in TypeScript from the imported values instead. A key that appears twice takes the last value, as JSON parsers do. The one exception is the target rule below, where a server entry and a `@browser` entry for the same key are two different values on purpose. ## The env() Function `env()` is the only call. It reads one named variable when the file is evaluated and folds the value into the emitted JSON. Nothing reads the environment at runtime. ```jsoc env("KEY") // string | undefined: unset leaves it empty env("KEY", "fallback") // string: the fallback when unset env!("KEY") // string: a build error when unset env("PORT", 4000) // number: parsed, a build error when it does not parse env!("LIVE") // boolean, required ``` The type argument is `string`, `number`, or `boolean`, and nothing else. A default must have that type. Without a type argument the default's type decides, and with neither the value is a string. Numbers parse as decimals. Booleans read `false`, `no`, `n`, `0`, and `f` as false, in any case, and everything else as true, including an empty value, so `LIVE=` in an env file turns `env("LIVE", false)` on. A value that does not parse as the requested type is a build error at the call, with the variable name and the text it held. Where the values come from is `elements man config`: the process environment, then `config/env/.env` on top of it. ## Types An import of a `.jsoc` file is typed from its contents. Literals widen to their primitive: `"dark"` is `string`, `3` is `number`, `true` is `boolean`. Arrays are arrays of their element type, objects are object types with exactly the keys written, and `env()` calls have the types listed above. Reading a key the file does not have is a compile error, and so is using a value as the wrong type. ```jsoc // app/shared/settings.jsoc { theme: "dark", retries: env("RETRIES", 3), label: env("LABEL"), } ``` ```ts import settings from "#app/shared/settings.jsoc"; let theme: string = settings.theme; let retries: number = settings.retries; let label: string = settings.label ?? "untitled"; // string | undefined without the ?? settings.colour; // error: no such key ``` `config.jsoc` is also checked against the shape Elements expects: an unknown compiler option, a bad `session.expires`, or a deploy block with a missing field is a build diagnostic on the line that has it. ## Browser and Server A `.jsoc` file is evaluated once for each target. Every key is server-only unless it is tagged, so a secret written next to a public value stays on the server without any arrangement on your part. A `/** @browser */` tag on a property makes that property and everything under it visible to the browser as well. ```jsoc { /** @browser */ public: { appName: "Acme", features: { signups: true }, }, databaseUrl: env!("DATABASE_URL"), } ``` The server sees all of it. The browser sees `public` and nothing else: a container is present in the browser file when any key under it is, and an untagged sibling is not. The same key may appear twice, once plain and once tagged; the server takes the plain entry and the browser takes the tagged one, whichever order they are written in. The tag is what decides what ships. Reading an untagged key from browser-reachable code is not a build error today: the browser module does not contain the key, so the read is `undefined` in the browser. When the browser needs a value, tag it. ## Importing ```ts import config from "#config"; // config.jsoc, through the import map import settings from "#app/shared/settings.jsoc"; // any other .jsoc file ``` The `.jsoc` extension is written out; it is not searched for the way `.ts` is. The same import works in a `.ts` file, in an `.html` template, on the server, and in the browser, and each target loads its own projection. What the module holds is the evaluated result. There is no parsing at runtime and no environment read; `config.database.host` is a string that was decided when the build ran. ## Hot Reload A `.jsoc` file is a source like any other. Save it, or save an env file whose variable it reads, and the project server evaluates it again, emits the module again, rebuilds everything that imports it, and hot reloads the running program and the open browsers. That is the difference from `process.env`: a value read through `env()` in a `.jsoc` file follows an env file edit into the running app, and a `process.env` read is fixed when the process starts. Keep configuration in `.jsoc`; reach for `process.env` only for a value that has to stay out of the build. ## Related - `config`: what `config.jsoc` holds, the environments, and the env files. - `typescript`: the compiler that checks `.jsoc` alongside `.ts` and `.html`. - `build`: the build loop that evaluates and emits `.jsoc` modules on save.