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):
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:
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):
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:
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:
1{2 "server": {3 "host": "127.0.0.1",4 "port": 80805 },6 "debug": false7}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:
null→nulltrue/false→true/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:
const general: Config = config;Text Modules
Text files (markdown, CSS, HTML, plain text) import as string:
1import README from "./README.md";2README satisfies string;Binary Modules
Binary files (images, fonts, wasm, etc.) import as uint8[]:
1import icon from "./icon.png";2icon satisfies uint8[];Import Attributes
Override the default loader with import attributes:
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.