@alabjs/compiler 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Cargo.toml ADDED
@@ -0,0 +1,19 @@
1
+ [package]
2
+ name = "alab-napi"
3
+ version.workspace = true
4
+ edition.workspace = true
5
+ license.workspace = true
6
+
7
+ [lib]
8
+ crate-type = ["cdylib"]
9
+
10
+ [dependencies]
11
+ alab-compiler = { path = "../alab-compiler" }
12
+ alab-router = { path = "../alab-router" }
13
+ napi = { version = "2", features = ["napi8", "async"] }
14
+ napi-derive.workspace = true
15
+ tokio = { version = "1", features = ["rt"] }
16
+ serde_json.workspace = true
17
+
18
+ [build-dependencies]
19
+ napi-build = "2"
package/build.rs ADDED
@@ -0,0 +1,5 @@
1
+ extern crate napi_build;
2
+
3
+ fn main() {
4
+ napi_build::setup();
5
+ }
package/index.d.ts ADDED
File without changes
package/index.js ADDED
@@ -0,0 +1,70 @@
1
+ /* eslint-disable */
2
+ // prettier-ignore
3
+ // Auto-generated by napi-rs — do not edit manually.
4
+ // Loads the correct platform-specific native binary.
5
+
6
+ const { existsSync } = require('fs')
7
+ const { join } = require('path')
8
+ const { platform, arch } = process
9
+
10
+ let nativeBinding = null
11
+ let loadError = null
12
+
13
+ function isMusl() {
14
+ if (process.versions?.electron) return false
15
+ // Check /proc/self/maps for musl
16
+ try {
17
+ return require('fs').readFileSync('/proc/self/maps', 'utf8').includes('musl')
18
+ } catch {
19
+ return false
20
+ }
21
+ }
22
+
23
+ const targets = {
24
+ win32: { x64: 'win32-x64-msvc' },
25
+ darwin: { x64: 'darwin-x64', arm64: 'darwin-arm64' },
26
+ linux: {
27
+ x64: isMusl() ? 'linux-x64-musl' : 'linux-x64-gnu',
28
+ arm64: isMusl() ? 'linux-arm64-musl' : 'linux-arm64-gnu',
29
+ },
30
+ }
31
+
32
+ const target = targets[platform]?.[arch]
33
+
34
+ if (target) {
35
+ const localFile = join(__dirname, `alab-napi.${target}.node`)
36
+
37
+ if (existsSync(localFile)) {
38
+ // Development: binary built locally via `cargo build`
39
+ try {
40
+ nativeBinding = require(localFile)
41
+ } catch (e) {
42
+ loadError = e
43
+ }
44
+ } else {
45
+ // Production: load from the scoped platform package
46
+ try {
47
+ nativeBinding = require(`@alabjs/compiler-${target}`)
48
+ } catch (e) {
49
+ loadError = e
50
+ }
51
+ }
52
+ }
53
+
54
+ if (!nativeBinding) {
55
+ const supported = Object.entries(targets)
56
+ .flatMap(([os, archs]) => Object.values(archs).map((t) => `${os}-${t}`))
57
+ .join(', ')
58
+
59
+ throw new Error(
60
+ `[alabjs] Failed to load the Rust compiler binary.\n` +
61
+ ` Platform: ${platform}-${arch}\n` +
62
+ ` Supported: ${supported}\n` +
63
+ (loadError ? ` Error: ${loadError.message}\n` : '') +
64
+ `\n` +
65
+ ` To build locally: cargo build --release -p alab-napi\n` +
66
+ ` Then copy: target/release/libalab_napi.dylib → crates/alab-napi/alab-napi.darwin-arm64.node`
67
+ )
68
+ }
69
+
70
+ module.exports = nativeBinding
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@alabjs/compiler-darwin-arm64",
3
+ "version": "0.1.0",
4
+ "description": "AlabJS compiler binary for darwin-arm64",
5
+ "license": "MIT",
6
+ "main": "alab-napi.darwin-arm64.node",
7
+ "files": [
8
+ "alab-napi.darwin-arm64.node"
9
+ ],
10
+ "os": [
11
+ "darwin"
12
+ ],
13
+ "cpu": [
14
+ "arm64"
15
+ ],
16
+ "engines": {
17
+ "node": ">=22"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/thinkgrid-labs/alabjs.git"
22
+ }
23
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@alabjs/compiler-darwin-x64",
3
+ "version": "0.1.0",
4
+ "description": "AlabJS compiler binary for darwin-x64",
5
+ "license": "MIT",
6
+ "main": "alab-napi.darwin-x64.node",
7
+ "files": [
8
+ "alab-napi.darwin-x64.node"
9
+ ],
10
+ "os": [
11
+ "darwin"
12
+ ],
13
+ "cpu": [
14
+ "x64"
15
+ ],
16
+ "engines": {
17
+ "node": ">=22"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/thinkgrid-labs/alabjs.git"
22
+ }
23
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@alabjs/compiler-linux-arm64-gnu",
3
+ "version": "0.1.0",
4
+ "description": "AlabJS compiler binary for linux-arm64-gnu",
5
+ "license": "MIT",
6
+ "main": "alab-napi.linux-arm64-gnu.node",
7
+ "files": [
8
+ "alab-napi.linux-arm64-gnu.node"
9
+ ],
10
+ "os": [
11
+ "linux"
12
+ ],
13
+ "cpu": [
14
+ "arm64"
15
+ ],
16
+ "engines": {
17
+ "node": ">=22"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/thinkgrid-labs/alabjs.git"
22
+ }
23
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@alabjs/compiler-linux-x64-gnu",
3
+ "version": "0.1.0",
4
+ "description": "AlabJS compiler binary for linux-x64-gnu",
5
+ "license": "MIT",
6
+ "main": "alab-napi.linux-x64-gnu.node",
7
+ "files": [
8
+ "alab-napi.linux-x64-gnu.node"
9
+ ],
10
+ "os": [
11
+ "linux"
12
+ ],
13
+ "cpu": [
14
+ "x64"
15
+ ],
16
+ "engines": {
17
+ "node": ">=22"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/thinkgrid-labs/alabjs.git"
22
+ }
23
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@alabjs/compiler-win32-x64-msvc",
3
+ "version": "0.1.0",
4
+ "description": "AlabJS compiler binary for win32-x64-msvc",
5
+ "license": "MIT",
6
+ "main": "alab-napi.win32-x64-msvc.node",
7
+ "files": [
8
+ "alab-napi.win32-x64-msvc.node"
9
+ ],
10
+ "os": [
11
+ "win32"
12
+ ],
13
+ "cpu": [
14
+ "x64"
15
+ ],
16
+ "engines": {
17
+ "node": ">=22"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/thinkgrid-labs/alabjs.git"
22
+ }
23
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@alabjs/compiler",
3
+ "version": "0.1.0",
4
+ "description": "AlabJS Rust compiler core — napi-rs native addon",
5
+ "license": "MIT",
6
+ "main": "index.js",
7
+ "types": "index.d.ts",
8
+ "napi": {
9
+ "binaryName": "alab-napi",
10
+ "targets": [
11
+ "x86_64-unknown-linux-gnu",
12
+ "aarch64-unknown-linux-gnu",
13
+ "x86_64-apple-darwin",
14
+ "aarch64-apple-darwin",
15
+ "x86_64-pc-windows-msvc"
16
+ ]
17
+ },
18
+ "scripts": {
19
+ "build": "napi build --platform --release --js index.js --dts index.d.ts",
20
+ "build:debug": "napi build --platform --js index.js --dts index.d.ts",
21
+ "prepublishOnly": "napi prepublish -t npm",
22
+ "universal": "napi universal"
23
+ },
24
+ "optionalDependencies": {
25
+ "@alabjs/compiler-linux-x64-gnu": "0.1.0",
26
+ "@alabjs/compiler-linux-arm64-gnu": "0.1.0",
27
+ "@alabjs/compiler-darwin-x64": "0.1.0",
28
+ "@alabjs/compiler-darwin-arm64": "0.1.0",
29
+ "@alabjs/compiler-win32-x64-msvc": "0.1.0"
30
+ },
31
+ "devDependencies": {
32
+ "@napi-rs/cli": "^3.0.0"
33
+ },
34
+ "engines": {
35
+ "node": ">= 22"
36
+ },
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://github.com/thinkgrid-labs/alabjs.git"
40
+ }
41
+ }
package/src/lib.rs ADDED
@@ -0,0 +1,118 @@
1
+ #![deny(clippy::all)]
2
+
3
+ use napi::bindgen_prelude::Buffer;
4
+ use napi_derive::napi;
5
+ use alab_compiler::{
6
+ compile, check_server_boundary, CompileOptions,
7
+ optimize_buffer, OptimizeOptions, OutputFormat,
8
+ extract_server_fns as alab_extract_server_fns,
9
+ server_fn_client_stub as alab_server_fn_client_stub,
10
+ };
11
+ use alab_router::scan_routes;
12
+
13
+ /// Compile a TypeScript / TSX source string to JavaScript.
14
+ ///
15
+ /// Returns a JSON string `{ code: string, map: string | null }`.
16
+ /// Pass `source_map: true` to include a VLQ source map in `map`.
17
+ #[napi]
18
+ pub fn compile_source(source: String, filename: String, minify: bool, source_map: bool) -> napi::Result<String> {
19
+ let opts = CompileOptions { filename, source_map, minify };
20
+ let output = compile(&source, &opts)
21
+ .map_err(|e| napi::Error::from_reason(e.to_string()))?;
22
+ serde_json::to_string(&output)
23
+ .map_err(|e| napi::Error::from_reason(e.to_string()))
24
+ }
25
+
26
+ /// Check a source file for server-boundary violations.
27
+ ///
28
+ /// Returns a JSON array of `{ import, source, offset }` objects.
29
+ /// An empty array means no violations.
30
+ #[napi]
31
+ pub fn check_boundary(source: String, filename: String) -> napi::Result<String> {
32
+ let violations = check_server_boundary(&source, &filename);
33
+ serde_json::to_string(&violations)
34
+ .map_err(|e| napi::Error::from_reason(e.to_string()))
35
+ }
36
+
37
+ /// Scan an `app/` directory and return the full route manifest as JSON.
38
+ ///
39
+ /// Returns `{ routes: Route[] }`.
40
+ #[napi]
41
+ pub fn build_routes(app_dir: String) -> napi::Result<String> {
42
+ let manifest = scan_routes(&app_dir);
43
+ serde_json::to_string(&manifest)
44
+ .map_err(|e| napi::Error::from_reason(e.to_string()))
45
+ }
46
+
47
+ /// Optimise a single image buffer — same interface as snapbolt-cli.
48
+ ///
49
+ /// Runs on a Tokio blocking thread so it never blocks the Node.js event loop,
50
+ /// keeping the server at full throughput even during heavy image processing.
51
+ ///
52
+ /// - `input` — raw image bytes (JPEG, PNG, GIF, or WebP)
53
+ /// - `quality` — 1.0–100.0, defaults to 80
54
+ /// - `width` — target width in pixels; omit to keep original width
55
+ /// - `height` — target height in pixels; omit to keep original height
56
+ /// - `format` — `"webp"` (default), `"jpeg"`, or `"png"`
57
+ ///
58
+ /// WebP encoding uses libwebp-sys when the crate is compiled with
59
+ /// `--features native`; otherwise falls back to the pure-Rust encoder.
60
+ /// Returns a Promise that resolves to the encoded bytes as a Node.js `Buffer`.
61
+ #[napi]
62
+ pub async fn optimize_image(
63
+ input: Buffer,
64
+ quality: Option<f64>,
65
+ width: Option<u32>,
66
+ height: Option<u32>,
67
+ format: Option<String>,
68
+ ) -> napi::Result<Buffer> {
69
+ let fmt = match format.as_deref() {
70
+ Some("jpeg") | Some("jpg") => OutputFormat::Jpeg,
71
+ Some("png") => OutputFormat::Png,
72
+ _ => OutputFormat::WebP,
73
+ };
74
+ let options = OptimizeOptions {
75
+ quality: quality.unwrap_or(80.0) as f32,
76
+ width,
77
+ height,
78
+ format: fmt,
79
+ };
80
+ // Move CPU-intensive work off the event loop thread.
81
+ let data: Vec<u8> = input.to_vec();
82
+ let result = tokio::task::spawn_blocking(move || optimize_buffer(&data, &options))
83
+ .await
84
+ .map_err(|e| napi::Error::from_reason(e.to_string()))?;
85
+
86
+ match result {
87
+ Ok((bytes, _mime)) => Ok(bytes.into()),
88
+ Err(e) => Err(napi::Error::from_reason(e.to_string())),
89
+ }
90
+ }
91
+
92
+ /// Scan a `.server.ts` source file for `export const NAME = defineServerFn(handler)`
93
+ /// declarations and return them as JSON.
94
+ ///
95
+ /// Returns a JSON array of `{ name: string, endpoint: string }` objects.
96
+ /// The caller (Vite plugin) uses the endpoint list to:
97
+ /// 1. Register `POST /_alabjs/fn/<name>` handlers in the production server.
98
+ /// 2. Replace handler bodies with thin fetch stubs in client bundles.
99
+ #[napi]
100
+ pub fn extract_server_fns(source: String, filename: String) -> napi::Result<String> {
101
+ let fns = alab_extract_server_fns(&source, &filename);
102
+ serde_json::to_string(&fns)
103
+ .map_err(|e| napi::Error::from_reason(e.to_string()))
104
+ }
105
+
106
+ /// Generate a client-side stub for a single server function.
107
+ ///
108
+ /// Used by the Vite plugin during client builds to replace the real handler
109
+ /// (which may contain DB calls and secrets) with a `fetch("/_alabjs/fn/<name>")`
110
+ /// stub that works identically from the browser's perspective.
111
+ ///
112
+ /// @param name - The exported binding name (e.g. `"getUser"`)
113
+ /// @param endpoint - The HTTP endpoint (e.g. `"/_alabjs/fn/getUser"`)
114
+ /// @returns ES module snippet ready to be injected into the client bundle
115
+ #[napi]
116
+ pub fn server_fn_stub(name: String, endpoint: String) -> String {
117
+ alab_server_fn_client_stub(&name, &endpoint)
118
+ }