Three supported paths
Choose your setup.
DBOPFS is always a browser ES module. The difference is how the same runtime files reach your web server.
1. Arcane OS
Point Arcane's modules mount at the installed package's arcane/modules directory. Existing Arcane imports remain unchanged, and the native application identity is authoritative.
import '/arcane/modules/DBOPFS.js';
const db=window.dbopfs;
await db.readyPromise;
await db.set('examples','hello.json',{message:'Hello from Arcane'});
2. npm application
Install the package, then configure your web server to expose the complete node_modules/dbopfs package root at /vendor/dbopfs/. A browser cannot resolve the bare name dbopfs unless a bundler or import map provides that mapping.
npm install dbopfs@1.0.0
<meta name="arcane-app-id" content="npm-example">
<script type="module">
import '/vendor/dbopfs/arcane/modules/DBOPFS.js';
const db=window.dbopfs;
await db.readyPromise;
await db.set('examples','hello.txt','Hello from npm');
console.log(await db.get('examples','hello.txt'));
</script>
3. Plain browser JavaScript without npm
Download the verified GitHub release tarball, extract its package/ directory, and publish that directory as /vendor/dbopfs/. Keep the layout intact:
curl -fL https://github.com/TheWizardNexus/DBOPFS/releases/download/v1.0.0/dbopfs-1.0.0.tgz -o dbopfs-1.0.0.tgz
mkdir -p vendor/dbopfs
tar -xzf dbopfs-1.0.0.tgz -C vendor/dbopfs --strip-components=1
Invoke-WebRequest 'https://github.com/TheWizardNexus/DBOPFS/releases/download/v1.0.0/dbopfs-1.0.0.tgz' -OutFile 'dbopfs-1.0.0.tgz'
New-Item -ItemType Directory -Force 'vendor/dbopfs' | Out-Null
tar -xzf 'dbopfs-1.0.0.tgz' -C 'vendor/dbopfs' --strip-components=1
Run the matching command set from the web-project root. Both produce the same public package path used by the npm example, so the browser import remains unchanged.
/vendor/dbopfs/
├── arcane/modules/
│ ├── AppDataScope.js
│ ├── DBOPFS.js
│ └── DBOPFSWorker.js
└── node_modules/strong-type/
└── index.js
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="arcane-app-id" content="raw-js-example">
<title>DBOPFS raw JavaScript example</title>
</head>
<body>
<output id="result">Loading…</output>
<script type="module">
import '/vendor/dbopfs/arcane/modules/DBOPFS.js';
const db=window.dbopfs;
await db.readyPromise;
await db.set('examples','hello.txt','Hello from raw JavaScript');
document.querySelector('#result').textContent=
await db.get('examples','hello.txt');
</script>
</body>
</html>
Serve the page over HTTPS or localhost. Opening it directly as a file: URL does not provide the secure origin that OPFS requires.
Choose by filename
Record formats
set() serializes non-string values with JSON.stringify(). get() then decides how to read text by looking at the filename extension.
| Filename | Write behavior | Read behavior |
|---|---|---|
profile.json | Objects, arrays, numbers, and booleans are JSON-serialized. | Valid JSON is parsed. Invalid JSON remains text. |
events.ndjson or .jsonl | Write a newline-delimited string, or use append for complete JSON rows. | Each valid nonempty JSON row is parsed into an array; invalid rows are skipped. |
note.txt | Strings are written as-is. | Returns text. |
profile | An object is still JSON-serialized. | Returns the serialized text because there is no .json extension. |
await dbopfs.set('profiles','alex.json',{active:true});
await dbopfs.set('notes','welcome.txt','Hello OPFS');
const profile=await dbopfs.get('profiles','alex.json');
const note=await dbopfs.get('notes','welcome.txt');
Do not append to a JSON document. Appending a second serialized value creates invalid JSON. Reserve append for raw text or complete newline-delimited rows.
Keep every outcome
Batch operations
setMany(), getMany(), and deleteMany() return Promise.allSettled() results. Inspect each status when partial success matters.
const results=await dbopfs.setMany(
'settings',
{
'appearance.json':{mode:'dark'},
'locale.json':{language:'en-US'}
}
);
for(const result of results){
if(result.status==='rejected'){
console.error(result.reason);
}
}
The object entry order determines the returned result order. The result objects do not add filenames, so keep the original entry list if you need to associate a rejection with its key.
Page-local speed
Cache and fresh reads
Page-local means the cache lives in JavaScript memory for this loaded page only. It is not a second database and it disappears when the page closes or reloads. Other tabs, windows, workers, and same-origin scripts have separate memory, so their writes do not refresh this page's cached values.
DBOPFS caches parsed values after this page reads or writes them. An ordinary get() can return a cached truthy value without reopening the OPFS file, which avoids repeated filesystem work. Cached false, 0, '', and null are read from OPFS again because the preserved runtime checks cache truthiness.
const db=window.dbopfs;
await db.readyPromise;
const cached=await db.get('users','alex.json');
const fresh=await db.get('users','alex.json',true);
| Situation | Recommended read | Why |
|---|---|---|
| This page just wrote or read the record. | get(table,key) | The page-local value is normally current and avoids extra I/O. |
| Another tab, worker, or same-origin script may have changed the file. | get(table,key,true) | force=true bypasses this page's cached value and reads OPFS again. |
| The page reloaded. | Either form | The old in-memory cache is gone; the first read must open the file. |
The public tables property exposes the actual current in-memory value map, not a copy and not a list of every persistent record. Mutating that object—or an object returned by get()—changes memory only and does not write OPFS. The special tables setter starts a write but exposes no promise; prefer await db.set().
writeFile() writes raw bytes but does not refresh or invalidate parsed cache entries. Follow a raw write with get(table,key,true) when parsed freshness matters, or use readFile() for raw-file access. DBOPFS does not provide cross-tab subscriptions, automatic cache invalidation, or conflict resolution.
For promise ordering and safe concurrency, continue to Async patterns.
Below parsed values
Raw files and metadata
Use readFile() when you need the browser File, and writeFile() when you want to control the exact bytes or string content.
await dbopfs.writeFile(
'logs',
'session.log',
'started\n'
);
const file=await dbopfs.readFile('logs','session.log');
const text=await file.text();
const metadata=await dbopfs.getFileMetadata(
'logs',
'session.log'
);
Metadata contains lastModified, size, and type. On a worker-only fallback that cannot expose getFile(), modification time and size are returned as null.
Logical and physical names
Tables, keys, and discovery
DBOPFS creates default directories during initialization. It can also create any named table when first requested.
| Default alias | Physical directory |
|---|---|
users, scores, chats, notes | Same as alias |
documents, songs, images, reports, errors | Same as alias |
journal_entries, streams_of_consciousness | Same as alias |
memories | memory |
const registered=await dbopfs.getTableNames();
const physical=await dbopfs.getTableNames(true);
const keys=await dbopfs.getAllKeys('notes');
const matches=await dbopfs.filterKeyIncludes(
'notes',
'meeting'
);
const exists=await dbopfs.hasKey('notes','meeting.json');
const total=await dbopfs.count('notes');
Use simple single-entry table and file names. They are OPFS directory/file names, not path expressions.
Default alias edge case: memories points to the physical memory directory. Prefer clear('memories') when emptying that default table; direct table deletion uses the literal name passed to deleteTable().
Current app only
Backup and restore
downloadCompressedPNG() serializes the current app's discovered tables, compresses the JSON with the browser's deflate stream, and encodes the bytes in RGB pixel channels.
var db=window.dbopfs;
await db.readyPromise;
await db.set(
'examples',
'hello-world.txt',
'Hello, world from DBOPFS!'
);
await db.downloadCompressedPNG('hello-world-backup');
Working artifact
Hello-world backup PNG
This 342-byte image was generated by the production 1.0.0 downloadCompressedPNG() method and restore-verified in Chrome. It contains one record: examples/hello-world.txt with the value Hello, world from DBOPFS!.
var db=window.dbopfs;
await db.readyPromise;
var picker=document.createElement('input');
picker.type='file';
picker.accept='image/png';
picker.onchange=async()=>{
var file=picker.files[0];
if(!file){
return;
}
await db.restoreFromPNG(file);
console.log(await db.get(
'examples',
'hello-world.txt',
true
));
};
picker.click();
Restore merges and overwrites matching keys. It does not clear the destination first and does not treat the backup as authority to select another application ID.
Backup requires CompressionStream, canvas, and download APIs. Restore requires createImageBitmap, canvas pixel access, and DecompressionStream.
The PNG is a data container, so a small backup may look like a tiny field of colored pixels rather than a conventional illustration. Arcane OS uses these DBOPFS import/export capabilities and can also ship prepopulated databases.
Destructive operations
Clear safely
| Operation | Scope |
|---|---|
delete(table,key) | One file in one table. |
clear(table) | All files in one table; the table remains. |
deleteTable(table) | The named directory and its records. |
clearAllStorage() | Everything below the current apps/<id> directory, then recreates default tables. |
Ask the user for confirmation before destructive UI actions. Never present DBOPFS clearing as deletion of server copies, synced copies, another origin, or browser backups.
Pointer-only integration
Use the package from Arcane.
Keep the existing module filenames and co-location intact. Configure Arcane's public modules pointer to the installed package's arcane/modules directory, then retain the existing import:
import '/arcane/modules/DBOPFS.js';
await window.dbopfs.readyPromise;
Application pages still declare their canonical IDs. In a native Arcane host, the bound package identity remains authoritative and mismatches fail closed.
Inspect the current site
DevTools (the Console) and DBOPFS Studio
For a database-aware workspace, install the DBOPFS Studio Extension. Open DevTools on the current site, select the dedicated DBOPFS Studio launcher panel, and choose Open Studio window. Studio connects to the page DevTools is inspecting so you can work with the DBOPFS application namespaces exposed by the current Studio UI.
The Studio launcher is a DevTools panel, not a pasted command. The extension adds the panel alongside the browser's other developer tools and opens the Studio workspace in a separate window bound to the inspected page.
Quick read-only Console helper
If you only need an unstructured filesystem listing, run this read-only helper in the JavaScript Console on the same page as your application. It lists OPFS entries without installing the extension.
async function opfsTree(directory,prefix=''){
const rows=[];
for await(const [name,handle] of directory.entries()){
const path=`${prefix}${name}`;
rows.push(`${handle.kind}: ${path}`);
if(handle.kind==='directory'){
rows.push(...await opfsTree(handle,`${path}/`));
}
}
return rows;
}
const root=await navigator.storage.getDirectory();
console.log((await opfsTree(root)).join('\n'));
Use the Network panel to confirm DBOPFS.js, AppDataScope.js, the bundled runtime dependency, and DBOPFSWorker.js all load successfully. Browser site-data controls operate at origin scope, which may include multiple GitHub Pages projects.