# Acequia Runtime Contexts

Acequia is designed to run in multiple JavaScript runtime contexts. Each context has different capabilities and requires different initialization logic.

## Supported Contexts

| Context | Detection | Storage | navigator.serviceWorker | fetch intercept |
|---------|-----------|---------|------------------------|-----------------|
| Browser (main page) | `typeof window !== 'undefined'` | IndexedDB, localStorage, sessionStorage | Yes | No |
| Service Worker | `typeof ServiceWorkerGlobalScope !== 'undefined'` | IndexedDB only | Limited* | Yes |
| Web Worker | `typeof WorkerGlobalScope !== 'undefined'` | IndexedDB only | No | No |
| Node.js | `typeof process !== 'undefined' && process.versions?.node` | Filesystem | No | No |
| Deno | `typeof Deno !== 'undefined'` | Filesystem / KV | No | No |

*Service workers can access `self.registration` but not register new service workers.

## Context Detection

```javascript
// acequia/utils.js - Detect current runtime context
function getContext() {
    // Deno - check before Node.js since Deno can have process polyfills
    if (typeof Deno !== 'undefined') {
        return 'deno'
    }

    // Node.js
    if (typeof process !== 'undefined' && process.versions?.node) {
        return 'node'
    }

    // Service Worker
    if (typeof ServiceWorkerGlobalScope !== 'undefined' &&
        self instanceof ServiceWorkerGlobalScope) {
        return 'service-worker'
    }

    // Web Worker (SharedWorker, DedicatedWorker)
    if (typeof WorkerGlobalScope !== 'undefined' &&
        self instanceof WorkerGlobalScope) {
        return 'web-worker'
    }

    // Browser main thread
    if (typeof window !== 'undefined') {
        return 'browser'
    }

    return 'unknown'
}

// Convenience helpers
export const isBrowser = () => getContext() === 'browser'
export const isServiceWorker = () => getContext() === 'service-worker'
export const isWebWorker = () => getContext() === 'web-worker'
export const isNode = () => getContext() === 'node'
export const isDeno = () => getContext() === 'deno'
export const isWorkerContext = () => ['service-worker', 'web-worker'].includes(getContext())
```

## Shared Storage

Service workers and the main page share the same IndexedDB storage because they're on the same origin. This means:

- **deviceId** - Same across main page and service worker
- **userId** - Same across main page and service worker
- **keys** - Same cryptographic keys accessible from both contexts
- **acequiaMounts** - Mount points set by main page are accessible to service worker
- **acequiaCredentials** - Credentials set by main page are accessible to service worker

**Important**: `sessionStorage` and `localStorage` are NOT available in service workers. Only IndexedDB works across both contexts.

## What Should Run Where

### Browser Main Page Only

These should only run in the main browser context:

```javascript
if (isBrowser) {
    // Service worker registration
    await navigator.serviceWorker.register('/sw.js')

    // Service worker message listeners
    navigator.serviceWorker.addEventListener('message', handler)

    // Local Network Access permission requests
    await requestLocalNetworkAccess()

    // Session-based instance ID (uses sessionStorage)
    instanceId = sessionStorage.getItem('instanceId') || createId()

    // postToSW (communicates with controlling service worker)
    navigator.serviceWorker.controller?.postMessage(message)
}
```

### Service Worker Only

These only make sense in the service worker context:

```javascript
if (isServiceWorker) {
    // Fetch event interception
    self.addEventListener('fetch', (event) => {
        event.respondWith(interceptFetch(event))
    })

    // Install/activate lifecycle
    // IMPORTANT: event.waitUntil() is required for async operations.
    // Without it, the browser may terminate the SW before the work completes.
    self.addEventListener('install', () => self.skipWaiting())
    self.addEventListener('activate', (event) => {
        event.waitUntil(clients.claim())
    })

    // Handle CLAIM_CLIENTS requests from pages needing SW control
    self.addEventListener('message', async (e) => {
        if (e.data?.type === 'CLAIM_CLIENTS') {
            await clients.claim()
        }
    })

    // Client communication
    self.clients.matchAll().then(clients => { ... })
}
```

### All Contexts

These can run anywhere:

```javascript
// IndexedDB access via localforage (works in browser, SW, web workers)
const store = localforage.createInstance({ name: 'acequia' })
await store.getItem('deviceId')

// Cryptographic operations (Web Crypto API available everywhere)
await crypto.subtle.generateKey(...)

// GUIDs generation
const id = createId()

// Utility functions
utils.matchPattern(pattern, path)
```

### Node.js Specific

```javascript
if (isNode) {
    // Use filesystem instead of IndexedDB
    // Use node-fetch or native fetch
    // Different crypto APIs may be needed
}
```

## Initialization Architecture (Implemented)

The `acequia/acequia.js` initialization is context-guarded. When the module is imported, it detects the runtime context and runs only the appropriate initialization:

```javascript
// acequia/acequia.js - Context-aware initialization

// Storage setup: browser/SW is synchronous, Node/Deno deferred
if (!isDeno() && !isNode()) {
    acequiaStore = localforageModule.createInstance({ name: 'acequia' })
}

// Deferred init for Node.js/Deno (avoids top-level await)
async function initStore() {
    if (acequiaStore) return
    await initRuntime()  // Load Node/Deno deps from deps.js
    if (isDeno()) { /* Deno filesystem store */ }
    else if (isNode()) { /* Node.js filesystem store */ }
}

// Context-guarded startup
ensureGUIDs().then(async (guids) => {
    // ... set deviceId, userId, keys (all contexts) ...

    if (isBrowser()) {
        await requestLocalNetworkAccess()
        await registerServiceWorker()  // Includes CLAIM_CLIENTS mechanism
        // Set up SW message listener, sessionStorage instanceId, etc.
        postToSW({ type: 'REGISTER_INSTANCE', instanceId })
    } else if (isServiceWorker()) {
        instanceId = utils.createId()  // Ephemeral (no sessionStorage in SW)
    } else if (isNode()) {
        instanceId = utils.createId()
    } else if (isDeno()) {
        instanceId = utils.createId()
    }

    makeReady({ deviceId, userId, instanceId, keys, ... })
})
```

### Service Worker Control via CLAIM_CLIENTS

When a page loads, the SW may be active but not yet controlling the page (e.g., after a fresh SW installation). The `registerServiceWorker()` function handles this:

1. Registers the SW with `type: 'module'`
2. Waits for `navigator.serviceWorker.ready`
3. If `navigator.serviceWorker.controller` is null, sends `CLAIM_CLIENTS` message to the SW
4. The SW calls `clients.claim()` and the page waits for the `controllerchange` event (with 3s timeout)
5. After the SW controls the page, all fetch events from that page are intercepted by the SW

### Top-Level Await Constraint

**Critical:** The bundled `acequia.js` is imported by `sw.js`. Service workers cannot be async modules - any `await` at module top level (even in dead code branches like `if (isNode()) { await import(...) }`) makes the entire bundle an async module, which service workers reject at parse time with "Top-level await is disallowed in service workers".

**Solution:** All dynamic imports for Node.js/Deno are deferred to `initRuntime()` (in `deps.js`) and `initStore()` (in `acequia/acequia.js`). Browser/SW dependencies are set synchronously from static imports bundled by rollup.

## Instance IDs Across Contexts

| Context | instanceId Source | Lifetime |
|---------|------------------|----------|
| Browser tab | sessionStorage | Tab session |
| Service Worker | Generated on import | SW lifecycle |
| Web Worker | Generated on creation | Worker lifetime |
| Node.js process | Generated on startup | Process lifetime |

The **deviceId** and **userId** remain consistent across all contexts on the same origin because they're stored in IndexedDB.

## Service Worker Specific Considerations

### What the SW Needs from acequia.js

The service worker imports acequia.js to access:

```javascript
// From sw.js
import acequia from './acequia.js'

acequia.config              // Configuration
acequia.utils.localforage   // IndexedDB access for mounts/credentials
acequia.utils.webdav        // WebDAV client for /acq/ handling
acequia.tokens              // Token creation/verification
acequia.routes              // Route pattern matching
```

### What the SW Does NOT Need

- Service worker registration (it IS the service worker)
- `navigator.serviceWorker` message listeners
- `sessionStorage` access
- Local Network Access permission requests
- `postToSW()` (it receives messages, doesn't send to itself)

## acequiaReady() Behavior

The `acequiaReady()` promise should resolve in all contexts, but with context-appropriate initialization:

```javascript
// Browser: Full initialization with SW registration
await acequia.acequiaReady()
// deviceId, userId, instanceId, keys all available
// Service worker is registered and controlling

// Service Worker: Minimal initialization
await acequia.acequiaReady()
// deviceId, userId, keys available from IndexedDB
// instanceId is ephemeral
// No SW registration (we ARE the SW)

// Node.js: Filesystem-based initialization
await acequia.acequiaReady()
// IDs loaded from filesystem
// Different storage backend
```

## Testing Context Detection

```javascript
// Add to acequia for debugging
export function getContextInfo() {
    return {
        context: getContext(),
        hasWindow: typeof window !== 'undefined',
        hasServiceWorkerGlobalScope: typeof ServiceWorkerGlobalScope !== 'undefined',
        hasWorkerGlobalScope: typeof WorkerGlobalScope !== 'undefined',
        hasProcess: typeof process !== 'undefined',
        hasNavigator: typeof navigator !== 'undefined',
        hasSessionStorage: typeof sessionStorage !== 'undefined',
        hasIndexedDB: typeof indexedDB !== 'undefined',
    }
}
```

## Node.js Storage Strategy

In the browser, Acequia uses IndexedDB for persistent storage. In Node.js, we use the **filesystem** as the storage backend. This aligns with Acequia's core philosophy: **everything has a URI/URL**.

### Why Filesystem?

1. **WebDAV Integration** - The localWebDAV server already manages filesystem-based storage. Node.js Acequia instances can use the same data structures.

2. **URI Addressability** - Files on disk map directly to WebDAV URLs:
   ```
   Filesystem:  /acequia/devices/{deviceId}/keys.json
   WebDAV URL:  https://server.com/devices/{deviceId}/keys.json
   ```

3. **Shared State** - Multiple Node.js processes can share state through the filesystem, just as multiple browser tabs share IndexedDB.

4. **Persistence** - No need for a separate database; the filesystem IS the database.

### Storage Layout

```
{acequia_root}/
├── devices/
│   └── {deviceId}/
│       ├── device.json      # { deviceId, createdAt }
│       ├── keys.json        # { publicKey, privateKey } (JWK format)
│       └── instances/
│           └── {instanceId}.json
├── users/
│   └── {userId}/
│       ├── user.json        # { userId, createdAt }
│       └── keys.json        # User-specific keypair
├── mounts/
│   └── {mountPath}.json     # Mount point configuration
└── credentials/
    └── {serverUrl-hash}.json # Server credentials
```

### localforage-Compatible API

To maintain API compatibility, Node.js uses a filesystem adapter that mimics localforage:

```javascript
// node/storage.js
import { readFile, writeFile, mkdir } from 'fs/promises'
import { join } from 'path'

export function createFilesystemStore(basePath, storeName) {
    const storePath = join(basePath, storeName)

    return {
        async getItem(key) {
            try {
                const filePath = join(storePath, `${key}.json`)
                const data = await readFile(filePath, 'utf-8')
                return JSON.parse(data)
            } catch (err) {
                if (err.code === 'ENOENT') return null
                throw err
            }
        },

        async setItem(key, value) {
            await mkdir(storePath, { recursive: true })
            const filePath = join(storePath, `${key}.json`)
            await writeFile(filePath, JSON.stringify(value, null, 2))
            return value
        },

        async removeItem(key) {
            const filePath = join(storePath, `${key}.json`)
            await unlink(filePath).catch(() => {})
        },

        async keys() {
            try {
                const files = await readdir(storePath)
                return files
                    .filter(f => f.endsWith('.json'))
                    .map(f => f.slice(0, -5))
            } catch (err) {
                if (err.code === 'ENOENT') return []
                throw err
            }
        }
    }
}

// Usage - same API as browser
const acequiaStore = createFilesystemStore('./data', 'acequia')
const deviceId = await acequiaStore.getItem('deviceId')
```

### Configuration

Node.js instances specify their storage root via config:

```javascript
// config.js (Node.js)
export const config = {
    // Storage root directory
    storageRoot: process.env.ACEQUIA_STORAGE || './acequia-data',

    // Or connect to a WebDAV server for storage
    storageWebDAV: 'https://storage.example.com/acequia/',

    // ... other config
}
```

### WebDAV as Storage Backend

For distributed Node.js deployments, storage can be a remote WebDAV server:

```javascript
// All Node.js instances share state through WebDAV
const store = createWebDAVStore('https://storage.acequia.io/node-instances/')

// Same API, but reads/writes go to WebDAV server
await store.setItem('deviceId', deviceId)
```

This enables:
- **Clustered deployments** - Multiple Node.js processes share identity
- **Browser-Node synchronization** - Browser and Node.js can share the same WebDAV storage
- **Everything is a URL** - Node.js state is accessible via HTTP

### Crypto in Node.js

Node.js uses the `crypto` module instead of Web Crypto API:

```javascript
import { generateKeyPairSync, createSign, createVerify } from 'crypto'
import * as jose from 'jose'  // jose works in Node.js too

// Key generation works the same with jose
const keys = await jose.generateKeyPair('PS256', { extractable: true })

// Export as JWK (same format as browser)
const publicJWK = await jose.exportJWK(keys.publicKey)
const privateJWK = await jose.exportJWK(keys.privateKey)
```

### Node.js Initialization

```javascript
if (context === 'node') {
    const store = createFilesystemStore(config.storageRoot, 'acequia')

    // Ensure device identity
    let deviceId = await store.getItem('deviceId')
    if (!deviceId) {
        deviceId = createId()
        await store.setItem('deviceId', deviceId)
    }

    // Ensure keys
    let keys = await store.getItem('keys')
    if (!keys) {
        const keyPair = await jose.generateKeyPair('PS256', { extractable: true })
        keys = {
            publicKey: await jose.exportJWK(keyPair.publicKey),
            privateKey: await jose.exportJWK(keyPair.privateKey),
        }
        await store.setItem('keys', keys)
    }

    // Instance ID is process-scoped
    instanceId = createId()

    makeReady({ deviceId, userId, instanceId, keys })
}
```

## Migration Path (Completed)

1. [x] Add `getContext()` utility function to `acequia/utils.js`
2. [x] Guard initialization in `acequia/acequia.js` based on context
3. [x] Ensure `acequiaReady()` works correctly in all contexts
4. [x] Update sw.js to use context-appropriate initialization
5. [x] Add Node.js support with filesystem storage backend
6. [x] Create `node/storage.js` with localforage-compatible filesystem adapter
7. [x] Document context-specific APIs

## Deno Runtime Support (Implemented)

Deno is a secure JavaScript/TypeScript runtime that shares many characteristics with both browsers and Node.js. Deno support is implemented with context detection, filesystem storage adapter, and dependency handling via `initRuntime()`.

### Why Deno is a Good Fit for Acequia

1. **Web Crypto API** - Deno has native `crypto.subtle` support, same as browsers
2. **ES Modules** - Native ESM support, no bundling required
3. **fetch()** - Built-in, same API as browsers
4. **TypeScript** - First-class support without configuration
5. **URL imports** - Can import directly from URLs or use `npm:` specifiers

### Deno Detection

Add to `acequia/utils.js`:

```javascript
export function getContext() {
    // Deno - check before Node.js since Deno can have process polyfills
    if (typeof Deno !== 'undefined') {
        return 'deno'
    }

    // Node.js
    if (typeof process !== 'undefined' && process.versions?.node) {
        return 'node'
    }

    // Service Worker
    if (typeof ServiceWorkerGlobalScope !== 'undefined' &&
        self instanceof ServiceWorkerGlobalScope) {
        return 'service-worker'
    }

    // Web Worker
    if (typeof WorkerGlobalScope !== 'undefined' &&
        self instanceof WorkerGlobalScope) {
        return 'web-worker'
    }

    // Browser
    if (typeof window !== 'undefined') {
        return 'browser'
    }

    return 'unknown'
}

export const isDeno = () => getContext() === 'deno'
```

### Deno vs Node.js API Differences

| Operation | Node.js | Deno |
|-----------|---------|------|
| Read file | `fs.readFile(path, 'utf-8')` | `Deno.readTextFile(path)` |
| Write file | `fs.writeFile(path, data)` | `Deno.writeTextFile(path, data)` |
| Delete file | `fs.unlink(path)` | `Deno.remove(path)` |
| Create dir | `fs.mkdir(path, { recursive: true })` | `Deno.mkdir(path, { recursive: true })` |
| List dir | `fs.readdir(path)` | `Deno.readDir(path)` (returns iterator) |
| Path join | `path.join(a, b)` | `join(a, b)` from `@std/path` |
| Env vars | `process.env.VAR` | `Deno.env.get('VAR')` |

### Deno Storage Adapter

Create `acequia/deno/storage.js`:

```javascript
/**
 * Deno filesystem storage adapter
 * Mimics localforage API for compatibility
 */
import { join } from 'jsr:@std/path'

export function createFilesystemStore(basePath, storeName) {
    const storePath = join(basePath, storeName)

    return {
        _storePath: storePath,

        async getItem(key) {
            try {
                const filePath = join(storePath, `${key}.json`)
                const data = await Deno.readTextFile(filePath)
                return JSON.parse(data)
            } catch (err) {
                if (err instanceof Deno.errors.NotFound) return null
                throw err
            }
        },

        async setItem(key, value) {
            await Deno.mkdir(storePath, { recursive: true })
            const filePath = join(storePath, `${key}.json`)
            await Deno.writeTextFile(filePath, JSON.stringify(value, null, 2))
            return value
        },

        async removeItem(key) {
            try {
                const filePath = join(storePath, `${key}.json`)
                await Deno.remove(filePath)
            } catch (err) {
                if (!(err instanceof Deno.errors.NotFound)) throw err
            }
        },

        async keys() {
            try {
                const files = []
                for await (const entry of Deno.readDir(storePath)) {
                    if (entry.isFile && entry.name.endsWith('.json')) {
                        files.push(entry.name.slice(0, -5))
                    }
                }
                return files
            } catch (err) {
                if (err instanceof Deno.errors.NotFound) return []
                throw err
            }
        },

        async clear() {
            try {
                for await (const entry of Deno.readDir(storePath)) {
                    if (entry.isFile) {
                        await Deno.remove(join(storePath, entry.name))
                    }
                }
            } catch (err) {
                if (!(err instanceof Deno.errors.NotFound)) throw err
            }
        }
    }
}

export function createLocalforageAdapter(storageRoot) {
    return {
        createInstance({ name }) {
            return createFilesystemStore(storageRoot, name)
        }
    }
}
```

### Deno Dependencies

Deno supports multiple import strategies:

```javascript
// Option 1: npm: specifiers (Deno 1.28+)
import * as jose from 'npm:jose@6'
import { createId } from 'npm:@paralleldrive/cuid2'
import slugify from 'npm:slugify'

// Option 2: JSR (Deno's native registry)
import { join } from 'jsr:@std/path'

// Option 3: URL imports (for acequia modules)
import acequia from 'https://acequia.io/acequia.js'

// Option 4: Import maps (deno.json)
{
    "imports": {
        "jose": "npm:jose@6",
        "@paralleldrive/cuid2": "npm:@paralleldrive/cuid2",
        "acequia/": "./acequia/"
    }
}
```

### deps.js: Avoiding Top-Level Await (Implemented)

`deps.js` uses static imports for browser/SW (bundled by rollup) and defers Node.js/Deno imports to `initRuntime()` to avoid top-level await:

```javascript
// Static imports for browser - bundled by rollup
import localforageModule from './web_modules/localforage.js'
import { createId as createIdBrowser } from './web_modules/@paralleldrive/cuid2.js'
import slugifyBrowser from './web_modules/slugify.js'

const isDeno = typeof Deno !== 'undefined'
const isNode = !isDeno && typeof process !== 'undefined' && process.versions?.node

// Browser/SW defaults - set synchronously (no top-level await)
let localforage = localforageModule
let createId = createIdBrowser
let slugify = slugifyBrowser

// Initialize runtime-specific dependencies for Node.js/Deno
export async function initRuntime() {
    if (isDeno) {
        const { createLocalforageAdapter } = await import('./acequia/deno/storage.js')
        localforage = createLocalforageAdapter(Deno.env.get('ACEQUIA_STORAGE') || './acequia-data')
        createId = (await import('npm:@paralleldrive/cuid2')).createId
        slugify = (await import('npm:slugify')).default
    } else if (isNode) {
        const { createLocalforageAdapter } = await import('./acequia/node/storage.js')
        localforage = createLocalforageAdapter(process.env.ACEQUIA_STORAGE || './acequia-data')
        createId = (await import('@paralleldrive/cuid2')).createId
        slugify = (await import('slugify')).default
    }
}

export { localforage, createId, slugify }
export * as jose from './node_modules/jose/dist/webapi/index.js'
```

### Deno Permissions

Deno requires explicit permissions. Acequia would need:

```bash
# Run with required permissions
deno run \
    --allow-read=./acequia-data \
    --allow-write=./acequia-data \
    --allow-net=acequia.io,localhost \
    --allow-env=ACEQUIA_STORAGE \
    your-app.js
```

| Permission | Why Needed |
|------------|------------|
| `--allow-read` | Read stored keys, deviceId, credentials |
| `--allow-write` | Store keys, deviceId, credentials |
| `--allow-net` | Fetch from WebDAV servers, auth endpoints |
| `--allow-env` | Read ACEQUIA_STORAGE environment variable |

### Deno Initialization in acequia.js

```javascript
} else if (isDeno()) {
    // Deno context - filesystem-based storage
    const storageRoot = Deno.env.get('ACEQUIA_STORAGE') || './acequia-data'
    console.log('Acequia initialized in Deno context')
    console.log(`Storage root: ${storageRoot}`)
}
```

### What Works Out of the Box in Deno

These Acequia features require **no changes** for Deno:

| Feature | Why It Works |
|---------|--------------|
| `jose` library | Pure JS, uses Web Crypto API |
| `crypto.subtle` | Native Deno support |
| `fetch()` | Native Deno support |
| ES Modules | Native Deno support |
| `createId` (cuid2) | Pure JS |
| Token creation/verification | Uses jose |
| WebDAV client | Uses fetch |

### What Needs Deno-Specific Code

| Feature | Deno Implementation |
|---------|---------------------|
| Storage | `Deno.readTextFile` / `Deno.writeTextFile` |
| Environment | `Deno.env.get()` |
| Context detection | Check `typeof Deno` |

### Deno Deploy Considerations

For serverless Deno (Deno Deploy):

1. **No filesystem access** - Use KV storage or external storage
2. **Web standard APIs** - fetch, crypto work normally
3. **Environment variables** - Set in Deno Deploy dashboard

```javascript
// For Deno Deploy, use Deno KV instead of filesystem
const kv = await Deno.openKv()

const acequiaStore = {
    async getItem(key) {
        const result = await kv.get(['acequia', key])
        return result.value
    },
    async setItem(key, value) {
        await kv.set(['acequia', key], value)
        return value
    },
    async removeItem(key) {
        await kv.delete(['acequia', key])
    },
    async keys() {
        const keys = []
        for await (const entry of kv.list({ prefix: ['acequia'] })) {
            keys.push(entry.key[1])
        }
        return keys
    }
}
```

### Implementation Checklist for Deno Support

1. [x] Add `isDeno()` to `acequia/utils.js`
2. [x] Create `acequia/deno/storage.js` with Deno filesystem adapter
3. [x] Update `deps.js` to handle Deno context (via `initRuntime()`)
4. [x] Update `acequia/acequia.js` initialization for Deno (via `initStore()`)
5. [ ] Create `deno.json` with import map
6. [ ] Add Deno test script (`test-deno.ts`)
7. [x] Document required permissions
8. [ ] (Optional) Create Deno KV adapter for Deno Deploy

### Example: Running Acequia in Deno

```typescript
// app.ts
import acequia from './acequia.js'

const { acequiaReady, getDeviceId, getUserId } = acequia

console.log('Starting Acequia in Deno...')

const ready = await acequiaReady()
console.log('Device ID:', getDeviceId())
console.log('User ID:', getUserId())
```

```bash
# Run with permissions
deno run --allow-read --allow-write --allow-env app.ts

# Or use a task in deno.json
{
    "tasks": {
        "start": "deno run --allow-read=./acequia-data --allow-write=./acequia-data --allow-env app.ts"
    }
}
```

### Summary: Runtime Context Comparison

| Feature | Browser | Service Worker | Node.js | Deno |
|---------|---------|----------------|---------|------|
| Storage | IndexedDB | IndexedDB | Filesystem | Filesystem/KV |
| Crypto | Web Crypto | Web Crypto | crypto + jose | Web Crypto |
| fetch | Native | Native | Native (18+) | Native |
| ES Modules | Native | Native | Native | Native |
| sessionStorage | Yes | No | No | No |
| Service Workers | Register | IS one | No | No |
| Permissions | Browser | Browser | None | Explicit |

## Related Files

- `acequia/acequia.js` - Main initialization, needs context guards
- `acequia/utils.js` - Add context detection utilities
- `acequia/node/storage.js` - Node.js filesystem adapter
- `acequia/deno/storage.js` - Deno filesystem adapter (future)
- `sw.js` - Service worker, imports acequia.js
- `config.js` - Configuration, should work in all contexts