Authoritative reference for runtime code conventions. Keep this document updated as patterns evolve so new code remains consistent.
- StandardJS tooling drives formatting for
*.js,*.mjs, and*.cjs: two-space indentation, no semicolons, single quotes, and trailing commas only where ECMAScript permits. Runnpm run lintafter edits. - Parenthesis spacing matches StandardJS: keep a space before the parameter list in named functions (
function openFile (path)), but none inside the parentheses ((path, options)), and include spaces after keywords (if (condition)). - Prettier is reserved for non-JavaScript text formats such as Markdown, JSON, YAML, and CSS. Generated API declarations under
api/**/*.d.tsare validated bynpm run gen:tscrather than hand-formatted. - Named functions first. Declare exports with the
functionkeyword so stack traces and docs stay descriptive. Reserve arrow functions for cases that require lexicalthis/arguments, or for compact inline callbacks. - Prefer
const. Declare bindings withconstand switch toletonly when reassignment is inevitable (e.g. loop counters, incremental accumulators).varis never used. - Descriptive names. Avoid single-letter identifiers outside of tight scopes; spell out intent (
value,handle,result) so JSDoc and readers align. - Quotes. Use single quotes for strings; switch to double quotes only when embedding an apostrophe or matching external formats (e.g. JSON payloads).
- Private fields. Use native
#privateFielddeclarations instead of pseudo-private_fields; expose data through getters/setters so callers trigger validation or coercion hooks. - Events. Prefer DOM-style
EventTargetoverEventEmitterfromoro:events; dispatch errors with the globalErrorEvent(extend it when custom fields or names are needed).
/**
* @param {string} path
* @returns {Promise<Stats>}
*/
export async function readStats(path) {
return await stats(path)
}
const timers = new Map()
const scheduleCleanup = () => timers.size === 0 && queueMicrotask(disposeTimers)- Arrow exception example. When lexical scope is required (e.g. preserving
thisinside a class), use an arrow while keeping outer APIs as named functions.
export class FileWatcher {
constructor(path) {
this.path = path
this.onTick = () => checkForUpdates(this.path)
}
#intervalId = null
get intervalId() {
return this.#intervalId
}
set intervalId(value) {
if (value !== null && typeof value !== 'number') {
throw new TypeError('intervalId must be a timer handle')
}
this.#intervalId = value
}
}- Whitespace mirrors existing modules: keep
catch/elseon the closing brace line per C++ guidelines reused in JS. - Error handling. Always propagate actionable errors; catch only to perform cleanup or throw a more specific error.
export async function removeFile(path) {
try {
await FileHandle.unlink(path)
} catch (err) {
throw err
}
}- Error causes. When wrapping an error, use the
causeoption so the original stack remains traceable.
export async function ensureDirectory(path) {
try {
await FileHandle.mkdir(path, { recursive: true })
} catch (err) {
throw new Error(`Failed to create directory ${path}`, { cause: err })
}
}- Document every exported symbol with TypeScript-flavoured tags so
npm run genproduces accurate declaration files. - Import complex types inline via
import()expressions to avoid circular imports.
/**
* Write bytes to disk.
* @param {string|URL} path
* @param {import('../buffer.js').Buffer|Uint8Array} data
* @param {{ flag?: string, mode?: number }} [options]
* @returns {Promise<void>}
*/
export async function writeFile(path, data, options = {}) {
const handle = await FileHandle.open(
path,
options.flag,
options.mode,
options
)
await handle.write(data)
await handle.close()
}- Prefer
@throws {Error}when rethrowing and@seefor external references (e.g. Node.js docs). Use plural form (@returns) for readability, matching generated d.ts output.
- Modules that expose a namespaced surface (e.g.
oro:fs) provide a thin entry point that re-exports the real implementation directory. This keeps the public path short and enables both named and default imports.
// api/fs.js
import * as exports from './fs/index.js'
export * from './fs/index.js'
export default exports- The
import * as exportstrick captures every named export for the default export. Always place aggregate logic inapi/<module>/index.js(and submodules likepromises.js) so tree-shaking stays predictable.
- Mirror existing structure under
api/fs/: group helpers (e.g.dir.js,handle.js,promises.js) and keep shared utilities (normalizePath,visit) near the top ofindex.jsto aid documentation parsing. - When adding new filesystem capabilities, document them with the patterns above and ensure both callback-style and
fs.promisessurfaces stay aligned.
- Headers first. Include local headers with quotes and keep the order broad-to-specific. See
src/runtime/ipc/router.ccandsrc/runtime/url/url.cc. - Namespaces. Open namespaces once and indent members with two spaces. Close with a trailing comment only when clarity requires it.
- Function signatures. Place a space before the parameter list (
void Router::init ()) and align constructor initializer lists vertically:
Router::Router (bridge::Bridge& bridge)
: dispatcher(bridge.dispatcher),
bridge(bridge) {}- Brace style. Follow K&R: braces on the same line for
if/else/catch, withelsesharing the closing brace line (if (...) { ... } else { ... }). - Const correctness. Use
constfor immutable values and references; preferconst autowhen type deduction keeps code readable (const auto key = toLowerCase(name);). - Moves and ownership. Use
std::movewhen transferring ownership (context = std::move(context)), and default to range-basedforloops; fall back to indexed loops only when the index is required. - Early returns. Guard failure conditions immediately (
if (!this->bridge.active()) { return false; }) to keep control flow shallow. - Lambdas. Format multi-line captures vertically and place the parameter list on the same line as the capture body:
return dispatcher.dispatch([
this,
callback = std::move(callback)
]() mutable {
callback(result);
});- Error handling. Prefer RAII over manual cleanup. When propagating exceptions, let them bubble (
throw;) or wrap with a richer exception type as needed.
- Headers. Include
<stdbool.h>,<string.h>, and other standard headers before project headers (seeinclude/oro/extension.h). - Extern guards. Wrap exported APIs in
ORO_RUNTIME_EXTENSION_EXTERN_BEGIN/ENDso C++ consumers getextern "C"automatically. - Macros. Use uppercase names with parentheses around arguments and align continuations with trailing backslashes:
#define ORO_RUNTIME_EXTENSION_ABI_VERSION ((int) ( \
ORO_RUNTIME_EXTENSION_ABI_VERSION_MAJOR << 16 | \
ORO_RUNTIME_EXTENSION_ABI_VERSION_MINOR << 8 | \
ORO_RUNTIME_EXTENSION_ABI_VERSION_PATCH << 0 \
))- Typedefs and structs. Declare opaque structs via forward typedefs (
typedef struct oapi_context oapi_context_t;) and document fields with/** ... */blocks. - Function prototypes. When parameter lists exceed ~80 chars, place one argument per line and align closing parentheses:
ORO_RUNTIME_EXTENSION_EXPORT
oapi_context_t* oapi_context_create (
oapi_context_t* parent,
bool retained
);- Callbacks. Alias function pointers with
typedeffor readability (typedef void (*oapi_context_dispatch_callback)(oapi_context_t*, const void*);). - Documentation. Prefer block comments using the existing JSDoc-like style to describe params, return values, and ownership expectations.
- Always include
oro/platform.hwhen branching on targets; avoid raw compiler defines like__linux__or_WIN32. - Use
ORO_RUNTIME_PLATFORM_IS(WINDOWS)/ORO_RUNTIME_PLATFORM_IS(LINUX)helpers or the specific constants (ORO_RUNTIME_PLATFORM_ANDROID,ORO_RUNTIME_PLATFORM_IOS, etc.); prefer the Oro-prefixed forms in new code. - Prefer the semantic aliases:
ORO_RUNTIME_PLATFORM_UNIXfor POSIX code paths,ORO_RUNTIME_PLATFORM_BSD(and itsFREEBSD/OPENBSDvariants) for BSD-specific behavior, andORO_RUNTIME_PLATFORM_MOBILEwhen gating shared mobile logic. - Defaults resolve to
"unknown"and0, so code should not guard against undefined macros.