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.

WinterTC / Cloudflare Workers

Web-standard APIs. Drop-in replacement for Workers code. 100% of the minimum common API surface.

fetch · Request · Response · WebCrypto · streams
Node.js packages

Most npm packages that target edge runtimes work out of the box via built-in polyfills.

require('path') · require('buffer') · process.env
Browser APIs + nano:kv

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.

Example — nano:kv
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.

Example — require('path') + process.env
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 Compatible

Astro

Static site generator with islands

Fully Compatible

Next.js

Static export only (no SSR)

Partial

Google 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.

Classic mode — GAS_COMPAT=true

Set GAS_COMPAT=true in env_vars. Upload your .gs file unchanged. The runtime injects the shim prefix and suffix automatically — no import required.

ESM mode — 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

handler.gs
// 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_COMPATClassic modeSet to "true" to enable shim injection.
GOOGLE_SERVICE_ACCOUNT_KEYRequiredFull JSON string of the service account key file.
SPREADSHEET_IDConditionalNeeded if your script calls SpreadsheetApp.getActiveSpreadsheet().
SHEET_NAMEOptionalActive sheet for getActiveSheet(). Defaults to first sheet.
GAS_USER_EMAILOptionalReturned by Session.getEffectiveUser().getEmail().

Service support matrix

Service Status Notes
SpreadsheetAppCompleteSheets API v4. setValues() calls are batched and flushed at handler return.
DocumentAppCompleteDocs API v1. Read-only body + paragraph access.
DriveAppCompleteDrive API v3. File/folder listing, blob download.
UrlFetchAppCompleteThin wrapper over fetch().
PropertiesServiceCompleteSynchronous — backed by nano:kv native bindings. No await needed.
CacheServiceCompleteSynchronous — same as PropertiesService, put(key, val, ttlSeconds).
Logger / Utilities / HtmlServiceCompleteSynchronous. Logger maps to console.*. Utilities includes base64, UUID, CSV, Blob, formatDate.
GmailApp / CalendarApp / MailAppStubRequire 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.