The lucent:* modules
The declarations of lucent:core, lucent:platform, lucent:thread, lucent:ios and lucent:android.
lucent:core
Section titled “lucent:core”Helpers for any module: delays, errors with codes, UTF-8, a monotonic clock, and promises and subscriptions over callback APIs such as native listeners. compute runs work on worker threads (Run heavy work in parallel). Its JavaScript version, @lucent-lang/lucent/core, lets tests run modules as TypeScript (Test a module).
/** Resolves after `ms` milliseconds; rejects with the signal's reason if it aborts first. */export declare function delay(ms: number, signal?: AbortSignal): Promise<void>;
/** An Error with a machine-readable `code`, visible to JavaScript as `error.code`. */export declare function error(code: string, message: string): Error;
/** The `code` of an error created with `error()`, or undefined. */export declare function errorCode(e: Error): string | undefined;
/** UTF-8 encoding of a string (like `new TextEncoder().encode(s)`). */export declare function utf8Encode(s: string): Uint8Array;
/** Decodes UTF-8 bytes (like `new TextDecoder().decode(b)`), replacing invalid sequences. */export declare function utf8Decode(bytes: Uint8Array): string;
/** Milliseconds from a monotonic clock, for measuring durations. */export declare function now(): number;
/** * A promise of what a callback API reports once, such as a native listener. * `register` starts listening, at once, and may return the cleanup that * stops it: * * ```ts * fromCallback<string>((resolve, reject) => { * const subscription = source.listen(resolve, reject); * return () => subscription.cancel(); * }, signal); * ``` * * The first of `resolve`, `reject` and the signal aborting settles the * promise; later calls do nothing. The cleanup runs exactly once, as soon as * the promise settles, or right after `register` returns if it settled * during registration. A throw from `register` rejects the promise. With an * aborted signal, the promise rejects with the signal's reason and * `register` is not called. A cleanup that throws is reported as uncaught; * the promise keeps its outcome. `resolve` takes a value, not a promise. * * Called from another thread (a `main` block, say), `resolve` and `reject` * take effect on the thread `fromCallback` was called on, in the order * they were called. */export declare function fromCallback<T>( register: (resolve: (value: T) => void, reject: (reason: Error) => void) => (() => void) | void, signal?: AbortSignal,): Promise<T>;
/** * Passes what a listener reports to `onValue` until the subscription ends, * and resolves when it does. `register` starts listening, at once, and may * return the cleanup that stops it. * * `next(value)` calls `onValue(value)` while the subscription is open, and * does nothing after. The first of `end()`, `fail(error)`, `onValue` * throwing and the signal aborting ends it: the promise resolves (`end`) or * rejects (with the error, or the signal's reason), and the cleanup runs * exactly once. Registration, an aborted signal, a throwing cleanup and * calls from other threads behave as in `fromCallback`. */export declare function subscribe<T>( register: ( next: (value: T) => void, end: () => void, fail: (error: Error) => void, ) => (() => void) | void, onValue: (value: T) => void, signal?: AbortSignal,): Promise<void>;
/** How a compute task runs. */export interface ComputeOptions { /** Aborting it cancels the task: the promise rejects with its reason at once. */ signal?: AbortSignal;}
/** * Runs `task(input)` on a pool of worker threads and resolves with its * result, on the thread that called `compute`. `task` is a function declared * at the top level of a module, taking one parameter: the compiler checks * everything it runs can run on a worker (no module state, no main-thread * or unknown-thread native code, nothing asynchronous) and that `input` and * its result are data. * * `input` is copied when `compute` is called: later changes by the caller * do not reach the task. Objects reached twice are copied once, and cycles * survive. The result comes back as it is. * * A task that throws rejects the promise with its error. With `signal`, * aborting it rejects the promise at once with the signal's reason: a * queued task never starts, and a running one stops at the next iteration * of a loop in the task or in a module function it calls. Loops in * closures, methods and generic functions, and native calls, run to their * end first; the result is then dropped. The pool * holds a bounded number of waiting tasks; beyond that, `compute` rejects * with a QuotaExceededError. * * ```ts * function edgePositions(bytes: Uint8Array): number[] { * const positions: number[] = []; * for (let i = 1; i < bytes.length; i++) * if (Math.abs(bytes[i]! - bytes[i - 1]!) > 40) positions.push(i); * return positions; * } * * export async function edges(bytes: Uint8Array, signal: AbortSignal): Promise<number[]> { * return await compute(edgePositions, bytes, { signal }); * } * ``` */export declare function compute<T, R>( task: (input: T) => R, input: T, options?: ComputeOptions,): Promise<Awaited<R>>;
/** * Bytes native code owns, handed to compute tasks and JavaScript without * copying them. A `Uint8Array` is copied whenever it crosses; a * NativeBuffer moves. * * The bytes are reached through a borrow, for the length of one call: * `withRead` lends them to its callback as a `ByteSpan`, `withWrite` as a * `MutableByteSpan`. Reads share the buffer; a write needs it to itself. * A borrow that would conflict with one in progress (a write inside a * read, a close or transfer inside either) throws an InvalidStateError at * once. The compiler keeps a span inside its callback: it cannot be * returned, stored, captured by a closure that outlives the call, passed * to code that keeps it, or held across `await`. * * `transfer()` moves the bytes to a new buffer, uncopied: every reference * to the old one then refuses them. Passing a buffer to `compute`, alone * or inside the input, moves it the same way, and a task's buffer comes * back as it is. Copies are explicit: `NativeBuffer.from(bytes)` and * `toUint8Array()`, which `NativeBuffer.stats()` counts. * * ```ts * function scan(buffer: NativeBuffer): number { * using owned = buffer; * return owned.withRead((bytes) => { * let total = 0; * for (let i = 0; i < bytes.length; i++) total += bytes[i]!; * return total; * }); * } * * export async function sample(): Promise<number> { * const buffer = NativeBuffer.allocate(4096); * buffer.withWrite((bytes) => bytes.fill(7)); * return await compute(scan, buffer.transfer()); * } * ``` * * JavaScript sees a buffer as an opaque object with the same methods; its * borrows lend it a copy of the bytes (copied back after `withWrite`), so * JavaScript never holds memory a worker may be writing. */export declare class NativeBuffer { private constructor();
/** A buffer of `size` zeroed bytes: a RangeError unless `size` is a whole number from 0. */ static allocate(size: number): NativeBuffer;
/** A new buffer holding a copy of `bytes`. */ static from(bytes: Uint8Array): NativeBuffer;
/** What every buffer has done since the app started. */ static stats(): NativeBufferStats;
/** How many bytes it holds: 0 once closed or transferred. */ readonly byteLength: number;
/** Calls `read` with the bytes, while no one writes them, and returns what it returns. */ withRead<R>(read: (bytes: ByteSpan) => R): R;
/** Calls `write` with the bytes, while nothing else borrows them, and returns what it returns. */ withWrite<R>(write: (bytes: MutableByteSpan) => R): R;
/** A copy of the bytes, independent of the buffer from then on. */ toUint8Array(): Uint8Array;
/** Moves the bytes, uncopied, to a new buffer: every reference to this one refuses them from now on. */ transfer(): NativeBuffer;
/** Releases the bytes. Closing a closed or transferred buffer does nothing. */ close(): void;
[Symbol.dispose](): void;}
/** The bytes `withRead` lends, read like a `Uint8Array`'s: `bytes[i]` is undefined out of range. */export interface ByteSpan { readonly length: number; readonly [index: number]: number;}
/** The bytes `withWrite` lends, written like a `Uint8Array`'s: values wrap to 0–255, writes out of range do nothing. */export interface MutableByteSpan extends ByteSpan { [index: number]: number;
/** Sets the bytes from `start` to `end` (counted from the end when negative) to `value`. */ fill(value: number, start?: number, end?: number): void;
/** Copies `source` in at `offset`: a RangeError if it does not fit. */ set(source: Uint8Array, offset?: number): void;}
/** Counts of what native buffers have done, for checking that a path does not copy. */export interface NativeBufferStats { /** Buffers allocated, including those `from` made. */ readonly allocated: number; /** Buffers native code handed over with its own memory. */ readonly adopted: number; /** Moves of a buffer's bytes to a new buffer: `transfer()` and handoffs to tasks. */ readonly transfers: number; /** Copies in and out (`from`, `toUint8Array`, and JavaScript's borrows), and their bytes. */ readonly copies: number; readonly bytesCopied: number;}lucent:platform
Section titled “lucent:platform”Which platform the code runs on, for platform branches.
/** * The platform: `if (PLATFORM === "ios") { … } else { … }` and * `PLATFORM === "ios" ? … : …` compile each platform's branch only, so a * shared module can use `lucent:ios/…` and `lucent:android/…` in them. */export declare const PLATFORM: "ios" | "android";lucent:thread
Section titled “lucent:thread”The main thread, for platform code.
/** * Runs `f` on the platform's main thread (the main queue on iOS, the main * Looper on Android) and resolves with its result. Main-thread-only SDK * APIs may only be used inside `f`. */export declare function main<T>(f: () => T): Promise<T>;lucent:ios
Section titled “lucent:ios”iOS helpers for platform code. The SDK itself is lucent:ios/<Framework>.
import type { UIViewController } from "lucent:ios/UIKit";
/** Whether the OS is at least `major.minor` (Swift's `#available`). */export declare function available(platform: "ios", major: number, minor?: number): boolean;
/** The root of Objective-C objects. Values of type `Any` (`id`) arrive as NSObjects. */export declare class NSObject { private readonly __lucent_NSObject: never; protected constructor();}
/** What can go where Objective-C takes `Any` (`id`), CoreFoundation values included. */export type ObjCValue = string | number | boolean | Uint8Array | Date | NSObject | null;
/** The main dispatch queue (dispatch_get_main_queue()), for APIs that take a queue. */export declare function mainQueue(): NSObject;
/** Swift's `as? String`: the string an `Any` holds, or null. */export declare function asString(value: NSObject | null): string | null;/** Swift's `as? Double` (an NSNumber). */export declare function asNumber(value: NSObject | null): number | null;/** Swift's `as? Bool` (an NSNumber). */export declare function asBoolean(value: NSObject | null): boolean | null;/** Swift's `as? Data`. */export declare function asData(value: NSObject | null): Uint8Array | null;/** Swift's `as? Date`. */export declare function asDate(value: NSObject | null): Date | null;
/** * What a method writes through a pointer (`CGFloat *`, `NSRange *`, * `NSDate **`, `CFTypeRef *`): pass it, then read `value`. For a pointer the * method also reads (a number or a struct), set `value` first. */export declare class Out<T> { constructor(); value: T | null;}
/** * UIApplication's lifecycle notifications, by name: `"didBecomeActive"` is * `UIApplication.didBecomeActiveNotification`. */export type AppEvent = | "didBecomeActive" | "willResignActive" | "didEnterBackground" | "willEnterForeground" | "didReceiveMemoryWarning" | "willTerminate";
/** * UIScene's lifecycle notifications, by name: `"willConnect"` is * `UIScene.willConnectNotification`. */export type SceneEvent = | "willConnect" | "didDisconnect" | "didActivate" | "willDeactivate" | "willEnterForeground" | "didEnterBackground";
/** * Calls `listener` each time the app posts `event`, until the returned * function is called or `signal` aborts. It runs on the main thread, as * UIKit posts it, so main-thread APIs work there without `main()`. Lucent * observes the notifications: the app's delegate, and other modules', stay * as they are. What `listener` throws is logged; the app goes on. */export declare function onAppEvent( event: AppEvent, listener: () => void, signal?: AbortSignal,): () => void;
/** * Like `onAppEvent`, for each scene's `event`: `listener` gets the scene's * session's `persistentIdentifier`. */export declare function onSceneEvent( event: SceneEvent, listener: (scene: string) => void, signal?: AbortSignal,): () => void;
/** * Presents the view controller `build` returns from the scene the person is * using (the top view controller of its key window), and resolves with the * value given to `resolve`, or rejects with the error given to `reject`. * `build` runs on the main thread, like `main()`'s function: make the view * controller there, and call `resolve` or `reject` from its delegate or * completion handler. * * It settles once, and whatever settles it dismisses the view controller if * it is still shown. It rejects with an `AbortError` when `signal` aborts, * when the person dismisses the view controller (swiping a sheet down), when * the view controller goes before settling, or when its scene disconnects; * with an `InvalidStateError` when no scene is in the foreground or UIKit * does not present it. */export declare function present<T>( build: (resolve: (value: T) => void, reject: (reason: Error) => void) => UIViewController, signal?: AbortSignal,): Promise<T>;lucent:android
Section titled “lucent:android”Android helpers for platform code. The SDK itself is lucent:android/<package>.
import type { Activity, Instrumentation_ActivityResult } from "lucent:android/android.app";import type { Context, Intent } from "lucent:android/android.content";import type { Throwable } from "lucent:android/java.lang";
/** The application Context. */export declare function appContext(): Context;
/** * The Error Lucent makes of `throwable` when a call throws it: its message * the exception's (or its class name, without one) and its `code` the class * name (`java.lang.IllegalStateException`). An exception carrying a Lucent * error (one a Lucent suspend function ended a Kotlin call with) gives * that error. * For an adapter whose callback API reports failure with a Throwable: * `reject(errorOf(e))` rejects as the call would have thrown. */export declare function errorOf(throwable: Throwable): Error;
/** Whether the device runs at least API level `api` (`Build.VERSION.SDK_INT >= api`). */export declare function available(platform: "android", api: number): boolean;
/** * The Activity in front: the one last created, started or resumed and not * destroyed, or null (before the first, between two, after the last). * Lucent holds Activities weakly: use this one now, on the main thread * (inside `main()`), and ask again later rather than keeping it. */export declare function currentActivity(): Activity | null;
/** * Starts `intent` from the Activity in front and resolves with its result * (`getResultCode()`, `getResultData()`). The request survives the app's * Activity being recreated meanwhile. Rejects with `ERR_NO_ACTIVITY` without * an Activity, `ERR_ACTIVITY_NOT_FOUND` when no app handles the intent, and * the signal's reason if it aborts first: the answer, if it still comes, is * dropped (Lucent closes what it started when Android lets it). */export declare function startActivityForResult( intent: Intent, signal?: AbortSignal,): Promise<Instrumentation_ActivityResult>;
/** * Asks for runtime permissions (`"android.permission.CAMERA"`), and * resolves with whether each was granted, in order. Requests wait for the * one before them, as Android asks one at a time. Rejects like * `startActivityForResult`. */export declare function requestPermissions( permissions: string[], signal?: AbortSignal,): Promise<boolean[]>;
/** The lifecycle events of the app's Activities, and a new intent sent to one. */export type ActivityEvent = | "created" | "started" | "resumed" | "paused" | "stopped" | "destroyed" | "newIntent";
/** * Runs `handler` after each `event` of the app's Activities, with the * Activity (and the intent, for "newIntent"), in a turn of the calling * context. Returns the function that stops it. */export declare function onActivityEvent( event: ActivityEvent, handler: (activity: Activity, intent: Intent | null) => void,): () => void;