mirror of
https://github.com/HeyPuter/puter.git
synced 2026-10-12 23:00:06 +00:00
refine
This commit is contained in:
1 parent
b52128b46a
commit
cdc40aeee6
6 files changed
+124
-200
No files matched your search
+24
-36
@@ -420,6 +420,30 @@ const examples = [
|
||||
slug: 'fs-delete-directory',
|
||||
source: '/playground/examples/fs-delete-directory.html',
|
||||
},
|
||||
{
|
||||
title: 'Read a directory page by page',
|
||||
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: 'Read 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 a directory and include subdirectories',
|
||||
description: 'Sort a directory listing and include 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: 'Share a file',
|
||||
description: 'Share a file with another user using Puter.js filesystem API. Run and experiment with this sharing example in the playground.',
|
||||
@@ -456,42 +480,6 @@ 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',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,35 +1,41 @@
|
||||
<html>
|
||||
<body>
|
||||
<script src="https://js.puter.com/v2/"></script>
|
||||
<h3>File browser</h3>
|
||||
<button id="more">Load more</button>
|
||||
<button id="next" disabled>Load next page</button>
|
||||
<div id="output"></div>
|
||||
<script>
|
||||
// An infinite-scrolling list: one page per scroll to the bottom,
|
||||
// standing in here for a button.
|
||||
let cursor = null, done = false, seeded = false;
|
||||
const button = document.getElementById('next');
|
||||
const output = document.getElementById('output');
|
||||
let cursor = null;
|
||||
let pageNumber = 0;
|
||||
|
||||
document.getElementById('more').addEventListener('click', async () => {
|
||||
if (done) return;
|
||||
// Read one page of 3 items and pass the cursor to get the next one
|
||||
async function loadNextPage() {
|
||||
const page = await puter.fs.readdir({ path: 'reports', cursor, limit: 3 });
|
||||
pageNumber++;
|
||||
output.innerHTML += `Page ${pageNumber}: ${page.items.map(item => item.name).join(', ')}<br>`;
|
||||
|
||||
if (!seeded) {
|
||||
for (let i = 0; i < 7; i++) {
|
||||
await puter.fs.write(`uploads/file-${i}.txt`, `n=${i}`, {
|
||||
createMissingParents: true,
|
||||
});
|
||||
}
|
||||
seeded = true;
|
||||
// A missing cursor means this was the last page
|
||||
if (page.cursor) {
|
||||
cursor = page.cursor;
|
||||
button.disabled = false;
|
||||
} else {
|
||||
output.innerHTML += 'No more pages<br>';
|
||||
button.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
const page = await puter.fs.readdir({ path: 'uploads', cursor, limit: 3 });
|
||||
for (const item of page.items) puter.print(`${item.name}<br>`);
|
||||
|
||||
// Absent cursor is the end — not a short page.
|
||||
cursor = page.cursor ?? null;
|
||||
if (!page.cursor) {
|
||||
done = true;
|
||||
puter.print('<b>— end of directory —</b>');
|
||||
(async () => {
|
||||
// Create 7 files so there is something to page through
|
||||
for (let i = 1; i <= 7; i++) {
|
||||
await puter.fs.write(`reports/report-${i}.txt`, `Report ${i}`, { createMissingParents: true });
|
||||
}
|
||||
});
|
||||
output.innerHTML += 'Created 7 files in reports<br><br>';
|
||||
|
||||
await loadNextPage();
|
||||
})();
|
||||
|
||||
button.addEventListener('click', loadNextPage);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,26 +0,0 @@
|
||||
<html>
|
||||
<body>
|
||||
<script src="https://js.puter.com/v2/"></script>
|
||||
<h3>Report generator</h3>
|
||||
<script>
|
||||
// Writing this quarter's report before the year/quarter folders exist.
|
||||
(async () => {
|
||||
const path = 'reports/2026/q3/summary.txt';
|
||||
|
||||
try {
|
||||
await puter.fs.write(path, '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(path, 'Q3 summary', { createMissingParents: true });
|
||||
puter.print('with createMissingParents: written<br><br>');
|
||||
|
||||
// mkdir takes the same three options, so next quarter is one call.
|
||||
await puter.fs.mkdir('reports/2026/q4', { createMissingParents: true });
|
||||
puter.print('q4 folder ready for next time');
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,31 +0,0 @@
|
||||
<html>
|
||||
<body>
|
||||
<script src="https://js.puter.com/v2/"></script>
|
||||
<h3>Upload endpoint</h3>
|
||||
<button id="dedupe">Upload invoice.pdf (keep both)</button>
|
||||
<button id="refuse">Upload invoice.pdf (refuse duplicates)</button>
|
||||
<script>
|
||||
// Two people send the same filename seconds apart. One policy keeps
|
||||
// both; the other refuses the second and lets the caller rename it.
|
||||
document.getElementById('dedupe').addEventListener('click', async () => {
|
||||
const file = await puter.fs.write('uploads/invoice.pdf', 'data', {
|
||||
dedupeName: true,
|
||||
createMissingParents: true,
|
||||
});
|
||||
puter.print(`stored as ${file.name}<br>`);
|
||||
});
|
||||
|
||||
document.getElementById('refuse').addEventListener('click', async () => {
|
||||
try {
|
||||
await puter.fs.write('uploads/invoice.pdf', 'data', {
|
||||
overwrite: false,
|
||||
createMissingParents: true,
|
||||
});
|
||||
puter.print('stored — the name was free<br>');
|
||||
} catch (e) {
|
||||
puter.print(`rejected, ask for a new name: ${e.code ?? e.message}<br>`);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -23,13 +23,21 @@ await puter.fs.write('notes/todo.txt', 'Buy milk');
|
||||
|
||||
The contents can be a `String`, `File`, `Blob`, `ArrayBuffer` or typed array, so
|
||||
text and binary data are written the same way. Writing to a path that already
|
||||
exists replaces the file, and passing `dedupeName` keeps both copies by saving
|
||||
the new one under a free name:
|
||||
exists replaces the file. Passing `dedupeName` keeps both copies by saving the
|
||||
new one under a free name, and passing `overwrite: false` rejects the write
|
||||
instead:
|
||||
|
||||
```js
|
||||
// Saves photo-1.png if photo.png is taken.
|
||||
await puter.fs.write('uploads/photo.png', blob, { dedupeName: true });
|
||||
|
||||
// Rejects if photo.png is taken.
|
||||
await puter.fs.write('uploads/photo.png', blob, { overwrite: false });
|
||||
```
|
||||
|
||||
Use `dedupeName` when two users might upload a file with the same name and both
|
||||
files should be kept.
|
||||
|
||||
Writing into a directory that does not exist yet is one more option:
|
||||
|
||||
```js
|
||||
@@ -60,6 +68,20 @@ A relative path resolves against `~/AppData/<your-app-id>/`, the sandbox Puter
|
||||
creates for your app the first time the user signs in. You can create any files
|
||||
and folders you like inside it, and your app cannot see anything outside it.
|
||||
|
||||
## Create a Folder
|
||||
|
||||
To create a directory, use the [`puter.fs.mkdir()`](/FS/mkdir/) method. With
|
||||
`createMissingParents`, it creates a whole tree in one call:
|
||||
|
||||
```js
|
||||
await puter.fs.mkdir('reports/2026/q3', { createMissingParents: true });
|
||||
```
|
||||
|
||||
The [`mkdir()`](/FS/mkdir/) method takes the same `dedupeName` and `overwrite`
|
||||
options as [`write()`](/FS/write/). The difference is the default. Creating a
|
||||
directory that already exists fails unless you pass `overwrite: true`, while a
|
||||
write replaces an existing file.
|
||||
|
||||
## Upload
|
||||
|
||||
To take a file from an `<input type="file">`, use the
|
||||
@@ -87,8 +109,9 @@ const items = await puter.fs.readdir('uploads', { sortBy: 'modified', sortOrder:
|
||||
```
|
||||
|
||||
It pages the way a key listing does, with `limit` and `cursor`, or `stream:
|
||||
true` for `for await`, which is what keeps a directory of thousands affordable
|
||||
to display.
|
||||
true` for `for await`. [Work with a large
|
||||
directory](/recipes/work-with-a-large-directory/) covers paging, sorting and
|
||||
counting a directory of thousands of files.
|
||||
|
||||
To read the metadata of one file, use the [`puter.fs.stat()`](/FS/stat/) method:
|
||||
|
||||
|
||||
@@ -1,31 +1,23 @@
|
||||
---
|
||||
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."
|
||||
description: "Learn how to list a directory with thousands of files in Puter.js, so your app can load and show it one page at a time."
|
||||
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.
|
||||
Typically:
|
||||
Reading a directory with [`puter.fs.readdir()`](/FS/readdir/) returns every item
|
||||
in one call, as shown in [Store Files](/recipes/store-files/). That works for a
|
||||
folder of a few dozen files. A folder of user uploads or generated exports can
|
||||
grow to thousands of items, and loading all of them before showing anything
|
||||
makes your app slow. The same method can return the directory in pages, sort
|
||||
it, include subdirectories and count it, which is what a file browser with
|
||||
infinite scrolling and a "showing 50 of 214" label needs.
|
||||
|
||||
- A **file browser** with an infinite-scrolling list, a sort header, and a
|
||||
"showing 50 of 214" label.
|
||||
- A **media gallery** that shows the newest uploads first and walks
|
||||
subdirectories to find them.
|
||||
- An **upload endpoint** where two people can send `invoice.pdf` a second apart
|
||||
and neither should lose theirs.
|
||||
- A **report generator** writing to `reports/2026/q3/` before that folder tree
|
||||
exists.
|
||||
## Read One Page at a Time
|
||||
|
||||
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:
|
||||
By default, the [`readdir()`](/FS/readdir/) method resolves to a plain array.
|
||||
Passing a `cursor` switches the result to a page object with `items` and
|
||||
`cursor`. For the first page, pass `null`:
|
||||
|
||||
```js
|
||||
let page = await puter.fs.readdir({ path: 'uploads', cursor: null, limit: 100 });
|
||||
@@ -39,13 +31,17 @@ for (;;) {
|
||||
}
|
||||
```
|
||||
|
||||
Stop when `cursor` is absent. It is present only while more pages remain, so
|
||||
that is the condition — not comparing `items.length` to `limit`.
|
||||
Each page includes a `cursor` if there are more pages to load. When `cursor` is
|
||||
missing, you've reached the last page.
|
||||
|
||||
## Stream the pages instead
|
||||
Paging with a cursor is fast on every page. The `offset` option also works, but
|
||||
the server has to count past every item you skip, so later pages get slower.
|
||||
|
||||
`stream: true` hands back an async iterator, which is usually what you want when
|
||||
you are just walking the whole thing:
|
||||
## Stream the Pages
|
||||
|
||||
To read the whole directory, pass `stream: true`. The
|
||||
[`readdir()`](/FS/readdir/) method then returns an async iterator that you read
|
||||
with `for await`:
|
||||
|
||||
```js
|
||||
for await (const page of puter.fs.readdir({ path: 'uploads', stream: true })) {
|
||||
@@ -55,9 +51,12 @@ for await (const page of puter.fs.readdir({ path: 'uploads', stream: true })) {
|
||||
}
|
||||
```
|
||||
|
||||
Same pages, less bookkeeping. Combine it with `limit` to set the page size.
|
||||
Each loop gives you one page, and the cursor is handled for you. Set
|
||||
`limit` to choose the page size. Streaming cannot be combined with `offset`.
|
||||
|
||||
## Sort it, and descend
|
||||
## Sort the Results
|
||||
|
||||
To change the order, pass `sortBy` and `sortOrder`:
|
||||
|
||||
```js
|
||||
const items = await puter.fs.readdir({
|
||||
@@ -67,10 +66,14 @@ const items = await puter.fs.readdir({
|
||||
});
|
||||
```
|
||||
|
||||
`sortBy` takes `name` (the default), `modified`, `type` or `size`.
|
||||
The `sortBy` option takes `name` (the default), `modified`, `type` or `size`,
|
||||
and `sortOrder` takes `asc` (the default) or `desc`. When you read pages with a
|
||||
cursor, every page must use the same sort as the first one.
|
||||
|
||||
`recursive: true` lists what is in the subdirectories too, and `depth` bounds
|
||||
how far down it goes:
|
||||
## Include Subdirectories
|
||||
|
||||
To list the contents of subdirectories as well, pass `recursive: true`. Set
|
||||
`depth` to limit how many levels down it goes:
|
||||
|
||||
```js
|
||||
const items = await puter.fs.readdir({
|
||||
@@ -80,62 +83,23 @@ const items = await puter.fs.readdir({
|
||||
});
|
||||
```
|
||||
|
||||
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.
|
||||
With `recursive`, sorting by `name` sorts by full path, so the contents of each
|
||||
directory stay together. Sorting by `modified`, `type` or `size` sorts across
|
||||
all levels at once, so files from different directories are mixed together.
|
||||
|
||||
## Count it
|
||||
## Count the Items
|
||||
|
||||
To show a total such as "showing 50 of 214", pass `includeTotal: true`. It also
|
||||
switches the result to a page object, so you get the count along with the first
|
||||
page:
|
||||
|
||||
```js
|
||||
const { items, total } = await puter.fs.readdir({
|
||||
const { items, total, cursor } = await puter.fs.readdir({
|
||||
path: 'uploads',
|
||||
cursor: null,
|
||||
limit: 50,
|
||||
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/).
|
||||
When you stream with `includeTotal`, only the first page carries `total`.
|
||||
Reference in new issue
Block a user