destack.sh

Modules

Destack's module system goes a little further than plain code imports: sources can be included conditionally, additional file types import as typed modules, and modules carry metadata.

Conditions

Conditions generalize the idea behind module.tests.ds and [cfg(attr)]-style feature gating into a flexible "condition system" for, well, conditionally including sources and parts of sources into some builds (but not others). We recognize conditions of mode, feature, role, stage, and a general tag, in addition to all the usual target gates (e.g. host, runtime, target, platform):

destack.json.json
1{2    "stage": "alpha",3    "conditions": {4        "modes": {5            "test": {},6            "dev": {},7            "prod": {},8            "preview": { "extends": "dev" }9        },10        "features": {11            "rendererv2": {}12        },13        "roles": {14            "server": {},15            "client": {}16        },17        "aliases": {18            "alpha": { "stage": "alpha" },19            "browser": { "host": "browser" }20        }21    },22    "compiler": {23        "modes": ["preview"],24        "features": ["rendererv2"]25    }26}

One stage is active at a time - and profiles, products, and targets may override it for the resolved profile. The active conditions are available within code via import.meta.<condition> (like globalThis._importMeta_.roles) as usual for in-code dynamic gating:

Conditions.ds
1function getRenderer(): Renderer {2    if (import.meta.features.includes("rendererv2")) {3        return new RendererV2();4    } else {5        return new RendererV1();6    }7}

On the import side, Destack also generalizes <module>.<alias>.ds to support conditional inclusion of files (appended to the module.ds file itself) based on the active conditions via aliases. Every named condition is automatically available as an alias, and additional aliases can be declared explicitly in "conditions" based on target-shaped gates. For example, when importing ./user with test mode active, both user.ds and user.test.ds are included (as if):

Conditions.ds
1// user.ds2export function loadUser(id: UserId): Result<User, UserError> {3    return database.load(id);4}5 6// user.test.ds7test("loadUser", () => {8    loadUser(UserId(1)) satisfies Result<User, UserError>;9});

Chained names like user.test.browser.ds behave as "and" gates on all conditions, that is, user.test.browser.ds is included only when both test mode and the browser alias match. Package dependencies can also be gated by the same condition system. Top-level dependencies are always part of the source graph, while conditionalDependencies are included when their when predicate matches:

destack.json.json
1{2    "dependencies": {3        "@destack/http": { "source": "registry", "version": "^1.0.0" }4    },5    "conditionalDependencies": [6        {7            "when": "test",8            "dependencies": {9                "@destack/test": { "source": "registry", "version": "^1.0.0" }10            }11        },12        {13            "when": { "feature": "sqlite", "host": "native" },14            "dependencies": {15                "@destack/sqlite": { "source": "registry", "version": "^1.0.0" }16            }17        }18    ]19}

Import Meta

import.meta exposes profile metadata and current module metadata during static and comptime evaluation.

Field Description Type Examples
globalThis._importMeta_.url current module URL string "file:///app/src/main.ds", "https://example.com/mod.ds"
globalThis._importMeta_.path current local file path, when available `string undefined`
globalThis._importMeta_.dir current local directory, when available `string undefined`
globalThis._importMeta_.output output artifact format Output "js", "wasm", "native"
globalThis._importMeta_.platform target operating system Platform "linux", "windows", "none"
globalThis._importMeta_.host target host environment Host "browser", "native", "wasi"
globalThis._importMeta_.target target family and ABI Target { family: "unix", arch: "x64", abi: "gnu" }
globalThis._importMeta_.targetName active build target name `string undefined`
globalThis._importMeta_.product active deliverable product name `Product undefined`
globalThis._importMeta_.version active package version `string undefined`
globalThis._importMeta_.stage active package release stage `Stage undefined`
globalThis._importMeta_.runtime semantic runtime Runtime "destack", "js"
globalThis._importMeta_.modes active source graph modes readonly Mode[] ["test"], ["dev", "lint"]
globalThis._importMeta_.roles active source graph roles readonly Role[] ["server"], ["client"]
globalThis._importMeta_.features active source graph features readonly Feature[] ["checkout"], ["renderer"]
globalThis._importMeta_.tags active source graph tags readonly Tag[] ["preview"], ["internal"]
import.meta.<mode> mode shorthands for debug, dev, prod, test, bench, lint boolean globalThis._importMeta_.test, globalThis._importMeta_.prod
globalThis._importMeta_.env configured build environment `{ readonly [key: string]: string boolean
globalThis._importMeta_.tree current module tree tag builder `TreeTagBuilder undefined`
globalThis._importMeta_.derive current module auto derives readonly Derive[] ["Clone", "Debug"]
globalThis._importMeta_.labels current module labels { readonly [key: string]: unknown } { feature: ["checkout"] }

Data Modules

Data files are parsed at compile time and typed as exact readonly literals by default:

config.json.json
1{2    "server": {3        "host": "127.0.0.1",4        "port": 80805    },6    "debug": false7}
main.ds.ds
1import config from "./config.json";2 3config.server.host satisfies "127.0.0.1";4config.server.port satisfies 8080;5config.debug satisfies false;

Types are inferred from the data:

  • nullnull
  • true/falsetrue / false
  • Numbers → numeric literal types
  • Strings → string literal types
  • Arrays → readonly tuples
  • Objects → readonly object literals

Consumers can widen explicitly with an annotation or conversion:

Data Modules.ds
const general: Config = config;

Text Modules

Text files (markdown, CSS, HTML, plain text) import as string:

Text Modules.ds
1import README from "./README.md";2README satisfies string;

Binary Modules

Binary files (images, fonts, wasm, etc.) import as uint8[]:

Binary Modules.ds
1import icon from "./icon.png";2icon satisfies uint8[];

Import Attributes

Override the default loader with import attributes:

Import Attributes.ds
1import dataJson from "./data.toml" with { type: "json" };  // parse as JSON2import dataRaw from "./data.json" with { type: "text" };     // import as string3import dataBytes from "./file.txt" with { type: "binary" };  // import as uint8[]

Supported type loaders are json, toml, yaml, text, binary, and base64.