Providers

One per URI scheme. Register one and every resource under that scheme is reachable through app.resources, which is the only thing callers touch - there is no public path to a provider, and a caller that found one would be hardcoding a scheme.

import type { Resource } from '@textui/core';

app.resources.registerProvider({
  scheme: 'service',
  async stat(uri): Promise<Resource | null> {
    const service = await lookup(uri);
    if (!service) return null;
    return {
      uri,
      kind: 'service',
      metadata: { name: service.name },
      capabilities: ['read', 'watch'],
    };
  },
  read: async (uri) => `${uri} is up`,
  watch: (_uri, fn) => {
    const timer = setInterval(() => fn('change'), 1000);
    return { dispose: () => clearInterval(timer) };
  },
});

stat is the only method a provider must have. A provider may return kind: 'unknown' and leave classification to the registry, which keeps the rules in one place.

What the registry forwards

Everything a provider can offer: stat, list, read, write, delete, rename and watch. Which means the caller writes the same line whether the file is on this disk or on a machine somewhere else - only the scheme differs, and the scheme is what picks the provider.

await app.resources.delete(uri);

What happens when a provider cannot

Two answers, and the split is intent rather than convenience:

  unimplemented
list, watch answers emptily - [], and a disposable that does nothing
read, write, delete, rename throws

An absent listing is a fact about the resource. An absent delete is a request that did not happen, and it has to be heard: a Delete that appears in a menu and removes nothing is the worst outcome available, and it is what an optional call - resources.delete?.(uri) - quietly produces.

A rename is within one scheme. Across two it throws, because that is a copy and a delete and belongs above a registry that dispatches on a single scheme by construction.

Asking before you call

Resource.capabilities is what decides whether to offer an action.

const resource = await app.resources.stat(uri);
if (resource?.capabilities.includes('delete')) {
  await app.resources.delete(uri);
}

It is per resource rather than per provider, so a read-only mount, a file the user has no permission on and something inherently immutable all come back the same shape - and a caller deciding whether to draw a Delete never has to know which of the three it is looking at.

The throw is still there for the race that asking cannot close: the remote goes away, the permission changes, between the offer and the call.


Back to top

MIT licensed. Pre-1.0 - the surface is still moving.