Type is_shared and say who the flag counts

stat() and readdir() return FSItemRead, so the is_shared the docs lead
with typechecks for TypeScript consumers rather than erroring on FSItem.

The docs said "you have shared", but the query has no issuer predicate:
a manage delegate's re-share sets the owner's flag too, which is the
useful answer and matches getShares().
This commit is contained in:
Juan Castro
2026-08-25 18:15:15 -04:00
parent bac3dde945
commit 2ef5d00d9b
8 changed files with 27 additions and 19 deletions
+1 -1
View File
@@ -41,7 +41,7 @@ An object with the following properties:
A `Promise` that resolves to an array of [`FSItem`](/Objects/fsitem/) objects (files and directories) within the specified directory.
Each item carries `is_shared`: `true` when you have shared it with someone, `false` when you have not, and `null` for items that are not yours. Only shares on the item itself count — the children of a folder you shared report `false`, since the share lives on the folder. Use [`getShares()`](/FS/getShares/) on an item to see who can reach it, including access inherited from a parent.
Each item carries `is_shared`: `true` when it has been shared with someone, `false` when it has not, and `null` for items that are not yours. Only shares on the item itself count — the children of a folder you shared report `false`, since the share lives on the folder. Use [`getShares()`](/FS/getShares/) on an item to see who can reach it, including access inherited from a parent.
When the request includes `cursor` (even `null`) or `includeTotal`, the promise instead resolves to a page object:
+1 -1
View File
@@ -37,7 +37,7 @@ An object with the following properties:
A `Promise` that resolves to the [`FSItem`](/Objects/fsitem) object of the specified file or directory.
The item carries `is_shared`: `true` when you have shared it with someone, `false` when you have not, and `null` when the item is not yours — whether someone else's file has other recipients is not yours to see. Only shares **on the item itself** count. A file inside a folder you shared is reachable through that folder without being shared itself, so it reports `false`; [`getShares()`](/FS/getShares/) is what reports inherited access.
The item carries `is_shared`: `true` when it has been shared with someone, `false` when it has not, and `null` when the item is not yours — whether someone else's file has other recipients is not yours to see. It covers shares granted by anyone holding `manage` on the item, not only your own, the same way [`getShares()`](/FS/getShares/) does. Only shares **on the item itself** count. A file inside a folder you shared is reachable through that folder without being shared itself, so it reports `false`; `getShares()` is what reports inherited access.
With `returnShares: true`, the result also carries `shares` — an array of the same share objects [`getShares()`](/FS/getShares/) returns, including access inherited from a parent folder and unclaimed invitations. It is empty unless you own the item or hold `manage` on it, so asking for it never fails a `stat()` you were otherwise allowed to make.
+1 -1
View File
@@ -42,7 +42,7 @@ An integer containing the size of the item in bytes. If the item is a directory,
#### `is_shared` (Boolean | null)
Whether you have shared this item with anyone: `true` if you have, `false` if you have not, and `null` when the item is not yours. Only shares on the item itself count, not access inherited from a shared parent folder. Set by [`stat()`](/FS/stat/) and [`readdir()`](/FS/readdir/); absent on items obtained any other way.
Whether this item has been shared with anyone: `true` if it has, `false` if it has not, and `null` when the item is not yours. Counts shares granted by anyone holding `manage` on it, and only shares on the item itself not access inherited from a shared parent folder. Set by [`stat()`](/FS/stat/) and [`readdir()`](/FS/readdir/); absent on items obtained any other way.
## Methods
+1
View File
@@ -96,6 +96,7 @@ export type {
export type {
CopyOptions,
DeleteOptions,
FSItemRead,
FSItemWithShares,
GetSharesOptions,
ListSharedOptions,
@@ -5,8 +5,8 @@ import mapV2EntryToV1 from '../utils/mapV2EntryToV1.js';
import { fsRequest, parseOperationArgs } from './scaffold.js';
/** @typedef {import('../types.js').ReaddirOptions} ReaddirOptions */
/** @typedef {import('../../FSItem.js').FSItem} FSItem */
/** @typedef {import('../../../lib/types.js').ListPage<FSItem>} FSItemPage */
/** @typedef {import('../types.js').FSItemRead} FSItemRead */
/** @typedef {import('../../../lib/types.js').ListPage<FSItemRead>} FSItemPage */
// Listings larger than this are served but never cached.
const MAX_CACHE_SIZE = 100 * 1024 * 1024;
@@ -166,18 +166,18 @@ const readdirPaged = async function (options) {
* @typedef {{
* (options: ReaddirOptions & { stream: true }): AsyncIterableIterator<FSItemPage>,
* (options: ReaddirOptions & ({ cursor: string | null } | { includeTotal: true })): Promise<FSItemPage>,
* (options: ReaddirOptions): Promise<FSItem[]>,
* (options: ReaddirOptions): Promise<FSItemRead[]>,
* (
* path: string,
* options?: ReaddirOptions,
* success?: (value: FSItem[]) => void,
* success?: (value: FSItemRead[]) => void,
* error?: (reason: unknown) => void,
* ): Promise<FSItem[]>,
* ): Promise<FSItemRead[]>,
* (
* path: string,
* success?: (value: FSItem[]) => void,
* success?: (value: FSItemRead[]) => void,
* error?: (reason: unknown) => void,
* ): Promise<FSItem[]>,
* ): Promise<FSItemRead[]>,
* }} ReaddirOperation
*/
@@ -4,13 +4,13 @@ import { fsRequest, parseOperationArgs } from './scaffold.js';
import { toShare } from './shareUtil.js';
/** @typedef {import('../types.js').StatOptions} StatOptions */
/** @typedef {import('../types.js').FSItemRead} FSItemRead */
/** @typedef {import('../types.js').FSItemWithShares} FSItemWithShares */
/** @typedef {import('../../FSItem.js').FSItem} FSItem */
/**
* @typedef {{
* (options: StatOptions & { returnShares: true }): Promise<FSItemWithShares>,
* (options: StatOptions): Promise<FSItem>,
* (options: StatOptions): Promise<FSItemRead>,
* (
* path: string,
* options: StatOptions & { returnShares: true },
@@ -20,14 +20,14 @@ import { toShare } from './shareUtil.js';
* (
* path: string,
* options?: StatOptions,
* success?: (value: FSItem) => void,
* success?: (value: FSItemRead) => void,
* error?: (reason: unknown) => void,
* ): Promise<FSItem>,
* ): Promise<FSItemRead>,
* (
* path: string,
* success: (value: FSItem) => void,
* success: (value: FSItemRead) => void,
* error?: (reason: unknown) => void,
* ): Promise<FSItem>,
* ): Promise<FSItemRead>,
* }} StatOperation
*/
@@ -43,7 +43,7 @@ const MAX_CACHE_SIZE = 20 * 1024 * 1024;
*
* @this {import('../index.js').PuterJSFileSystemModule}
* @param {...unknown} args
* @returns {Promise<FSItem>}
* @returns {Promise<FSItemRead>}
*/
const statImpl = async function (...args) {
const options = parseOperationArgs(args, ['path']);
+8 -1
View File
@@ -174,10 +174,17 @@
* @typedef {StatOptionsOwn & RequestCallbacks<FSItem>} StatOptions
*/
/**
* What `stat()` and `readdir()` return: an item plus the sharing state only
* those two report. `is_shared` is null for items the caller does not own.
*
* @typedef {FSItem & { is_shared?: boolean | null }} FSItemRead
*/
/**
* A `stat()` result with `returnShares` set.
*
* @typedef {FSItem & { shares: Share[] }} FSItemWithShares
* @typedef {FSItemRead & { shares: Share[] }} FSItemWithShares
*/
/**
@@ -96,7 +96,7 @@ export const loadPuterJsTestOptions = (
// 402 here. The gate itself is covered against those exact routes in
// `controllers/puterai/PuterAIController.subscription.http.test.ts`.
meteringEnforcement: { subscriptions: false },
// Same for rate limits: ~500 tests share one free-tier account.
// ~500 tests share one account; 'unlimited' means paid-base limits.
unlimitedMetering: true,
};