mirror of
https://github.com/HeyPuter/puter.git
synced 2026-10-12 14:50:39 +00:00
docs: add the medium fs recipe
readdir paging, streaming, sorting and recursion, plus the write and mkdir options that decide what happens to an existing name. One playground example per section.
This commit is contained in:
1 parent
445588f857
commit
1f2a42d55e
8 files changed
+322
No files matched your search
@@ -456,6 +456,42 @@ const examples = [
|
||||
slug: 'fs-share-a-file-mode',
|
||||
source: '/playground/examples/fs-share-a-file-mode.html',
|
||||
},
|
||||
{
|
||||
title: 'page through a large directory',
|
||||
description: 'Read a directory one page at a time with a cursor using Puter.js filesystem API. Run and experiment with this example in the playground.',
|
||||
slug: 'fs-large-directory-page',
|
||||
source: '/playground/examples/fs-large-directory-page.html',
|
||||
},
|
||||
{
|
||||
title: 'stream directory pages',
|
||||
description: 'Walk a large directory with an async iterator using Puter.js filesystem API. Run and modify this example instantly in your browser.',
|
||||
slug: 'fs-large-directory-stream',
|
||||
source: '/playground/examples/fs-large-directory-stream.html',
|
||||
},
|
||||
{
|
||||
title: 'sort and descend a directory',
|
||||
description: 'Sort a directory listing and read subdirectories with Puter.js filesystem API. Run and experiment with this example in the playground.',
|
||||
slug: 'fs-large-directory-sort',
|
||||
source: '/playground/examples/fs-large-directory-sort.html',
|
||||
},
|
||||
{
|
||||
title: 'count a directory while paging',
|
||||
description: 'Get a total entry count alongside a page of results with Puter.js filesystem API. Run and modify this example directly in your browser.',
|
||||
slug: 'fs-large-directory-total',
|
||||
source: '/playground/examples/fs-large-directory-total.html',
|
||||
},
|
||||
{
|
||||
title: 'write without clobbering',
|
||||
description: 'Keep both files or refuse to replace one with dedupeName and overwrite in Puter.js filesystem API. Run and experiment with this example in the playground.',
|
||||
slug: 'fs-large-directory-write',
|
||||
source: '/playground/examples/fs-large-directory-write.html',
|
||||
},
|
||||
{
|
||||
title: 'create the folders on the way',
|
||||
description: 'Write into a directory tree that does not exist yet with Puter.js filesystem API. Run and modify this example instantly in your browser.',
|
||||
slug: 'fs-large-directory-parents',
|
||||
source: '/playground/examples/fs-large-directory-parents.html',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<html>
|
||||
<body>
|
||||
<script src="https://js.puter.com/v2/"></script>
|
||||
<script>
|
||||
(async () => {
|
||||
await puter.fs.mkdir('uploads', { createMissingParents: true }).catch(() => {});
|
||||
for (let i = 0; i < 7; i++) {
|
||||
await puter.fs.write(`uploads/file-${i}.txt`, `n=${i}`);
|
||||
}
|
||||
|
||||
// cursor (even null) switches the result to a page object.
|
||||
let page = await puter.fs.readdir({ path: 'uploads', cursor: null, limit: 3 });
|
||||
let n = 0;
|
||||
|
||||
for (;;) {
|
||||
puter.print(`<b>page ${++n}</b>: ${page.items.map(i => i.name).join(', ')}<br>`);
|
||||
// Present only while more pages remain — that is the stop condition.
|
||||
if (!page.cursor) break;
|
||||
page = await puter.fs.readdir({ path: 'uploads', cursor: page.cursor, limit: 3 });
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,25 @@
|
||||
<html>
|
||||
<body>
|
||||
<script src="https://js.puter.com/v2/"></script>
|
||||
<script>
|
||||
(async () => {
|
||||
// Without createMissingParents this fails: reports/2026/q3 is absent.
|
||||
try {
|
||||
await puter.fs.write('reports/2026/q3/summary.txt', 'no parents');
|
||||
puter.print('wrote it (the folders already existed)<br>');
|
||||
} catch (e) {
|
||||
puter.print(`without the option: ${e.code ?? e.message}<br>`);
|
||||
}
|
||||
|
||||
await puter.fs.write('reports/2026/q3/summary.txt', 'with parents', {
|
||||
createMissingParents: true,
|
||||
});
|
||||
puter.print('with createMissingParents: written<br><br>');
|
||||
|
||||
// mkdir takes the same three options.
|
||||
await puter.fs.mkdir('reports/2027/q1', { createMissingParents: true });
|
||||
puter.print('and the same for mkdir');
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,36 @@
|
||||
<html>
|
||||
<body>
|
||||
<script src="https://js.puter.com/v2/"></script>
|
||||
<button id="name">Sort by name</button>
|
||||
<button id="modified">Newest first</button>
|
||||
<button id="deep">Recursive, depth 2</button>
|
||||
<script>
|
||||
const setup = async () => {
|
||||
await puter.fs.write('uploads/a.txt', 'a', { createMissingParents: true });
|
||||
await puter.fs.write('uploads/b.txt', 'b');
|
||||
await puter.fs.write('uploads/nested/c.txt', 'c', { createMissingParents: true });
|
||||
};
|
||||
|
||||
const show = (items) =>
|
||||
puter.print(items.map(i => i.name).join(', ') + '<br>');
|
||||
|
||||
document.getElementById('name').addEventListener('click', async () => {
|
||||
await setup();
|
||||
show(await puter.fs.readdir({ path: 'uploads', sortBy: 'name' }));
|
||||
});
|
||||
|
||||
document.getElementById('modified').addEventListener('click', async () => {
|
||||
await setup();
|
||||
show(await puter.fs.readdir({
|
||||
path: 'uploads', sortBy: 'modified', sortOrder: 'desc',
|
||||
}));
|
||||
});
|
||||
|
||||
document.getElementById('deep').addEventListener('click', async () => {
|
||||
await setup();
|
||||
// Sorting by name orders by full path, so folders stay together.
|
||||
show(await puter.fs.readdir({ path: 'uploads', recursive: true, depth: 2 }));
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,19 @@
|
||||
<html>
|
||||
<body>
|
||||
<script src="https://js.puter.com/v2/"></script>
|
||||
<script>
|
||||
(async () => {
|
||||
await puter.fs.mkdir('uploads', { createMissingParents: true }).catch(() => {});
|
||||
for (let i = 0; i < 7; i++) {
|
||||
await puter.fs.write(`uploads/file-${i}.txt`, `n=${i}`);
|
||||
}
|
||||
|
||||
// Same pages as the cursor loop, without the bookkeeping.
|
||||
let n = 0;
|
||||
for await (const page of puter.fs.readdir({ path: 'uploads', stream: true, limit: 3 })) {
|
||||
puter.print(`<b>page ${++n}</b>: ${page.items.map(i => i.name).join(', ')}<br>`);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,22 @@
|
||||
<html>
|
||||
<body>
|
||||
<script src="https://js.puter.com/v2/"></script>
|
||||
<script>
|
||||
(async () => {
|
||||
await puter.fs.write('uploads/a.txt', 'a', { createMissingParents: true });
|
||||
await puter.fs.write('uploads/b.txt', 'b');
|
||||
|
||||
// includeTotal also switches the result to a page object.
|
||||
const { items, total } = await puter.fs.readdir({
|
||||
path: 'uploads',
|
||||
cursor: null,
|
||||
limit: 1,
|
||||
includeTotal: true,
|
||||
});
|
||||
|
||||
puter.print(`this page: ${items.length}<br>`);
|
||||
puter.print(`total in the directory: ${total}`);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,29 @@
|
||||
<html>
|
||||
<body>
|
||||
<script src="https://js.puter.com/v2/"></script>
|
||||
<button id="dedupe">Write with dedupeName</button>
|
||||
<button id="refuse">Write with overwrite: false</button>
|
||||
<script>
|
||||
document.getElementById('dedupe').addEventListener('click', async () => {
|
||||
// Keeps both rather than replacing: report-1.txt, report-2.txt, …
|
||||
const file = await puter.fs.write('uploads/report.txt', 'data', {
|
||||
dedupeName: true,
|
||||
createMissingParents: true,
|
||||
});
|
||||
puter.print(`wrote ${file.name}<br>`);
|
||||
});
|
||||
|
||||
document.getElementById('refuse').addEventListener('click', async () => {
|
||||
try {
|
||||
await puter.fs.write('uploads/report.txt', 'data', {
|
||||
overwrite: false,
|
||||
createMissingParents: true,
|
||||
});
|
||||
puter.print('wrote it — the name was free<br>');
|
||||
} catch (e) {
|
||||
puter.print(`refused: ${e.code ?? e.message}<br>`);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,131 @@
|
||||
---
|
||||
title: Work With a Large Directory
|
||||
description: "Page through a directory that is too big to read at once, sort and descend into it, and write into it without clobbering what is already there."
|
||||
tags: [fs, performance]
|
||||
order: 46
|
||||
---
|
||||
|
||||
**Use this when** a directory has more in it than you want in memory at once, or
|
||||
when you are writing into a folder that other things are also writing into.
|
||||
|
||||
Reading a whole directory is one call and covered in
|
||||
[storing files](/recipes/store-files/). This is what to do when that is no
|
||||
longer enough.
|
||||
|
||||
## Page through it
|
||||
|
||||
`readdir()` normally resolves to a plain array. Passing `cursor` — even `null`
|
||||
for the first page — switches it to a page object instead:
|
||||
|
||||
```js
|
||||
let page = await puter.fs.readdir({ path: 'uploads', cursor: null, limit: 100 });
|
||||
|
||||
for (;;) {
|
||||
for (const item of page.items) {
|
||||
console.log(item.name);
|
||||
}
|
||||
if (!page.cursor) break;
|
||||
page = await puter.fs.readdir({ path: 'uploads', cursor: page.cursor, limit: 100 });
|
||||
}
|
||||
```
|
||||
|
||||
Stop when `cursor` is absent. It is present only while more pages remain, so
|
||||
that is the condition — not comparing `items.length` to `limit`.
|
||||
|
||||
## Stream the pages instead
|
||||
|
||||
`stream: true` hands back an async iterator, which is usually what you want when
|
||||
you are just walking the whole thing:
|
||||
|
||||
```js
|
||||
for await (const page of puter.fs.readdir({ path: 'uploads', stream: true })) {
|
||||
for (const item of page.items) {
|
||||
console.log(item.name);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Same pages, less bookkeeping. Combine it with `limit` to set the page size.
|
||||
|
||||
## Sort it, and descend
|
||||
|
||||
```js
|
||||
const items = await puter.fs.readdir({
|
||||
path: 'uploads',
|
||||
sortBy: 'modified',
|
||||
sortOrder: 'desc',
|
||||
});
|
||||
```
|
||||
|
||||
`sortBy` takes `name` (the default), `modified`, `type` or `size`.
|
||||
|
||||
`recursive: true` lists what is in the subdirectories too, and `depth` bounds
|
||||
how far down it goes:
|
||||
|
||||
```js
|
||||
const items = await puter.fs.readdir({
|
||||
path: 'uploads',
|
||||
recursive: true,
|
||||
depth: 2,
|
||||
});
|
||||
```
|
||||
|
||||
Sorting interacts with recursion in a way worth knowing: sorting by `name`
|
||||
orders by *full path*, so each directory's contents stay together. The other
|
||||
fields sort across the whole subtree, so files from different folders interleave.
|
||||
|
||||
## Count it
|
||||
|
||||
```js
|
||||
const { items, total } = await puter.fs.readdir({
|
||||
path: 'uploads',
|
||||
cursor: null,
|
||||
includeTotal: true,
|
||||
});
|
||||
```
|
||||
|
||||
`includeTotal` also switches the result to a page object, so you get a count
|
||||
without giving up paging.
|
||||
|
||||
## Write into it without clobbering
|
||||
|
||||
`write()` overwrites by default. Two options change that:
|
||||
|
||||
```js
|
||||
// Keep both: writes report-1.txt if report.txt is taken.
|
||||
await puter.fs.write('uploads/report.txt', data, { dedupeName: true });
|
||||
|
||||
// Refuse instead of replacing.
|
||||
await puter.fs.write('uploads/report.txt', data, { overwrite: false });
|
||||
```
|
||||
|
||||
`dedupeName` is what you want when two users might upload the same filename and
|
||||
you would rather keep both than lose one.
|
||||
|
||||
## Create the folders on the way
|
||||
|
||||
Writing into a folder that does not exist fails unless you say otherwise:
|
||||
|
||||
```js
|
||||
await puter.fs.write('reports/2026/q3/summary.txt', data, {
|
||||
createMissingParents: true,
|
||||
});
|
||||
```
|
||||
|
||||
`mkdir()` takes the same three options, so a directory tree can be created in
|
||||
one call rather than level by level.
|
||||
|
||||
## Notes
|
||||
|
||||
- `offset` still works but gets slower the further in you go, because the server
|
||||
counts past everything you skipped. Prefer `cursor`.
|
||||
- **A cursor pins the sort.** Later pages must not ask for a different `sortBy`
|
||||
or `sortOrder`, and `stream` cannot be combined with `offset`.
|
||||
- Each item carries `is_shared`. Only shares on the item itself count — the
|
||||
children of a folder you shared report `false`, because the share lives on the
|
||||
folder. See [sharing a file](/recipes/share-a-file/).
|
||||
- `mkdir()` defaults `overwrite` to `false`, while `write()` defaults it to
|
||||
`true`. They are not the same, which is easy to assume.
|
||||
- The same three result shapes — array, page object, async iterator — are how
|
||||
the other listing methods work too, including
|
||||
[`puter.fs.listShared()`](/FS/listShared/).
|
||||
Reference in new issue
Block a user