Compatibility
WinterTC web APIs, Node.js polyfills, nano: built-in modules, Google Apps Script, and framework support — what works and what isn't supported
Overview
NANO speaks three dialects. Your code can use any combination.
Web-standard APIs. Drop-in replacement for Workers code. 100% of the minimum common API surface.
fetch · Request · Response · WebCrypto · streams
Most npm packages that target edge runtimes work out of the box via built-in polyfills.
require('path') · require('buffer') · process.env
localStorage shim built on nano:kv means browser-targeting code runs unmodified.
nano:kv · openKV · localStorage shim
NANO is a WinterTC-compatible runtime. Where Node.js APIs are available they are polyfilled — not at full fidelity, but enough for the common edge patterns. http, net, os, and native addons are out of scope by design.
WebAssembly
V8's built-in WASM engine — compile, instantiate, and call exports with no external dependencies. See Runtime → WebAssembly for the full capability reference and configuration.
WinterTC API Matrix
Implementation status of WinterTC standard APIs.
| API | Status | Notes |
|---|---|---|
| fetch() | Complete | Full implementation with streaming |
| Request | Complete | Constructor with method, headers, body |
| Response | Complete | Constructor with status, headers, body |
| Headers | Complete | Map-like interface, case-insensitive |
| URL | Complete | Full URL parsing with all properties |
| URLSearchParams | Complete | Query string manipulation |
| TextEncoder | Complete | UTF-8 encoding to Uint8Array |
| TextDecoder | Complete | UTF-8 decoding from Uint8Array |
| ReadableStream | Complete | Streaming data interface |
| WritableStream | Complete | Output streaming with backpressure |
| crypto.getRandomValues | Complete | All TypedArray types supported |
| crypto.subtle.digest | Complete | SHA-256, SHA-384, SHA-512 |
| crypto.subtle.generateKey | Complete | AES-GCM, HMAC, RSA-OAEP/PSS/PKCS1, ECDSA, ECDH |
| crypto.subtle.sign / verify | Complete | HMAC, RSA-PSS, RSASSA-PKCS1-v1_5, ECDSA |
| crypto.subtle.encrypt / decrypt | Complete | AES-GCM, RSA-OAEP |
| WebAssembly | Complete | V8 built-in WASM engine |
| console | Complete | log, error, warn, info, debug |
| setTimeout/setInterval | Complete | Timer functions with clearing |
| atob/btoa | Complete | Base64 encoding/decoding |
| structuredClone | Complete | Deep object cloning |
| WebSocketPair | Complete | Cloudflare Workers compatible API. |
nano: Built-in Modules
nano-rs exposes its own APIs under the nano: ESM namespace — clearly distinct from WinterTC and Node.js. Import them in any ESM handler.
import { kv, openKV } from 'nano:kv';
// Default namespace (hostname-scoped, EdgeStore backed)
await kv.set('hits', new TextEncoder().encode('1'));
const val = await kv.get('hits');
const num = await kv.getJSON('counter');
// Named namespace
const cache = openKV('cache');
await cache.setJSON('config', { version: 2 });
const cfg = await cache.getJSON('config');
// List keys by prefix
const entries = await kv.list('user:');
| Module | Status | Notes |
|---|---|---|
| nano:kv | Complete | EdgeStore-backed KV. kv + openKV(name). Hostname-namespaced. Bytes primitive + JSON helpers. |
| nano:localStorage | Userland | Ships today as examples/localStorage-shim.js — copy into your app. See Storage. |
| nano:gas | Complete | Google Apps Script compatibility shim. SpreadsheetApp, DocumentApp, DriveApp, PropertiesService, CacheService backed by service account. See section below. |
Node.js Compatibility
Common Node.js modules available via require(). These cover ~80% of npm packages that run in edge runtimes.
const path = require('path');
const { from, isBuffer, concat } = require('buffer');
const assert = require('assert');
const joined = path.join('/var', 'app', 'config.json'); // /var/app/config.json
const dir = path.dirname(joined); // /var/app
const ext = path.extname(joined); // .json
const env = process.env.NODE_ENV; // from host environment
const ver = process.version; // "v18.0.0"
| Module / Global | Status | Notes |
|---|---|---|
| require('path') | Complete | join, dirname, basename, extname, resolve, isAbsolute, normalize, sep, delimiter |
| require('buffer') | Complete | Buffer.from, Buffer.alloc, Buffer.isBuffer, Buffer.concat. Returns Uint8Array. |
| require('assert') | Complete | assert.ok, assert.equal, assert.strictEqual, assert.notEqual |
| require('fs') | Complete | readFileSync, writeFileSync, existsSync, unlinkSync + async variants. VFS-backed. |
| process.env | Complete | App-configured env vars (set via env_vars in config). Never the host process env. process.version = "v18.0.0", process.platform = "linux". |
| require('events') | Complete | EventEmitter — on/once/off/emit/removeAllListeners/listeners. |
| crypto (Node) | Not Available | Use WebCrypto crypto.subtle instead. |
| http / https / net | Out of Scope | Use WinterTC fetch() for outbound HTTP. |
| child_process | Out of Scope | Sandboxed execution model — no subprocess spawning. |
Framework Compatibility
Verified framework support for WinterTC-compatible JavaScript frameworks.
Hono
Ultra-lightweight web framework
Fully CompatibleAstro
Static site generator with islands
Fully CompatibleNext.js
Static export only (no SSR)
PartialGoogle Apps Script (nano:gas)
v2.6.0+
Run .gs files on nano-rs with a service-account-backed shim. SpreadsheetApp, DocumentApp, DriveApp, PropertiesService, CacheService, Logger, Utilities, and more are available out of the box.
GAS_COMPAT=trueSet GAS_COMPAT=true in env_vars. Upload your .gs file unchanged. The runtime injects the shim prefix and suffix automatically — no import required.
import 'nano:gas'In an ESM handler, import { dispatch } from 'nano:gas'. GAS globals are installed as a side-effect. Use dispatch(request, { doGet, doPost }) as your fetch handler. Named service exports (SpreadsheetApp, DocumentApp, etc.) are also importable directly.
Quick example — classic mode
// GET / → return sheet rows as JSON
async function doGet(e) {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const values = await (await sheet.getDataRange()).getValues();
return JSON.stringify(values);
}
// POST / → append a row
async function doPost(e) {
const body = JSON.parse(e.postData.contents);
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
await sheet.appendRow([body.name, body.value, new Date().toISOString()]);
return { ok: true };
}
// POST with {"function":"processItem","args":[42]} → direct RPC dispatch
async function processItem(id) {
// PropertiesService is synchronous (backed by nano:kv)
PropertiesService.getScriptProperties().setProperty('last_id', String(id));
return { processed: id };
}
Required env_vars
| Variable | When | Description |
|---|---|---|
| GAS_COMPAT | Classic mode | Set to "true" to enable shim injection. |
| GOOGLE_SERVICE_ACCOUNT_KEY | Required | Full JSON string of the service account key file. |
| SPREADSHEET_ID | Conditional | Needed if your script calls SpreadsheetApp.getActiveSpreadsheet(). |
| SHEET_NAME | Optional | Active sheet for getActiveSheet(). Defaults to first sheet. |
| GAS_USER_EMAIL | Optional | Returned by Session.getEffectiveUser().getEmail(). |
Service support matrix
| Service | Status | Notes |
|---|---|---|
| SpreadsheetApp | Complete | Sheets API v4. setValues() calls are batched and flushed at handler return. |
| DocumentApp | Complete | Docs API v1. Read-only body + paragraph access. |
| DriveApp | Complete | Drive API v3. File/folder listing, blob download. |
| UrlFetchApp | Complete | Thin wrapper over fetch(). |
| PropertiesService | Complete | Synchronous — backed by nano:kv native bindings. No await needed. |
| CacheService | Complete | Synchronous — same as PropertiesService, put(key, val, ttlSeconds). |
| Logger / Utilities / HtmlService | Complete | Synchronous. Logger maps to console.*. Utilities includes base64, UUID, CSV, Blob, formatDate. |
| GmailApp / CalendarApp / MailApp | Stub | Require a logged-in user account. Throw descriptive errors when called. |
Key differences from real GAS
Google API calls are async. Real GAS blocks synchronously. nano:gas uses async fetch(), so SpreadsheetApp / DriveApp / DocumentApp calls return Promises — add async to handler functions and await each API call. Exception: PropertiesService and CacheService call native V8 bindings and are genuinely synchronous.
Service account only. All requests are made as the service account, not a logged-in user. Share your Spreadsheet/Document/Drive folder with the service account email (as Editor) for it to have access.
No GAS triggers. Use an external cron or HTTP scheduler to POST to your nano-rs endpoint on a schedule.