A plataforma Web oferece cada vez mais aos desenvolvedores as ferramentas necessárias para criar aplicativos de alto desempenho e bem ajustados para a Web. Principalmente, o WebAssembly (Wasm) abriu as portas para aplicativos da Web rápidos e poderosos, enquanto tecnologias como o Emscripten agora permitem que os desenvolvedores reutilizem códigos testados e comprovados na Web. Para aproveitar esse potencial, os desenvolvedores precisam ter o mesmo poder e flexibilidade quando se trata de armazenamento.
É aí que entra a API Storage Foundation. A API Storage Foundation é uma nova API de armazenamento rápida e sem opiniões que desbloqueia casos de uso novos e muito solicitados para a Web, como implementar bancos de dados eficientes e gerenciar arquivos temporários grandes de maneira adequada. Com essa nova interface, os desenvolvedores podem "trazer o próprio armazenamento" para a Web, reduzindo a lacuna de recursos entre a Web e o código específico da plataforma.
A API Storage Foundation foi projetada para se parecer com um sistema de arquivos muito básico. Assim, ela oferece flexibilidade aos desenvolvedores ao fornecer primitivos genéricos, simples e eficientes em que eles podem criar componentes de nível superior. Os aplicativos podem aproveitar a melhor ferramenta para as necessidades deles, encontrando o equilíbrio certo entre usabilidade, desempenho e confiabilidade.
Por que a Web precisa de outra API de armazenamento?
A plataforma Web oferece várias opções de armazenamento para desenvolvedores, cada uma criada com casos de uso específicos em mente.
- Algumas dessas opções claramente não se sobrepõem a esta proposta, já que permitem apenas o armazenamento de quantidades muito pequenas de dados, como cookies ou a API Web Storage, que consiste nos mecanismos
sessionStorage
elocalStorage
. - Outras opções já foram descontinuadas por vários motivos, como a API File and Directory Entries ou o WebSQL.
- A API File System Access tem uma superfície de API semelhante, mas é usada para interagir com o sistema de arquivos do cliente e fornecer acesso a dados que podem estar fora da propriedade da origem ou até mesmo do navegador. Esse foco diferente vem com considerações de segurança mais rigorosas e custos de performance mais altos.
- A API IndexedDB pode ser usada como um backend para alguns dos casos de uso da API Storage Foundation. Por exemplo, o Emscripten inclui o IDBFS, um sistema de arquivos persistente baseado em IndexedDB. No entanto, como o IndexedDB é fundamentalmente um armazenamento de chave-valor, ele tem limitações significativas de desempenho. Além disso, acessar diretamente as subseções de um arquivo é ainda mais difícil e lento no IndexedDB.
- Por fim, a interface CacheStorage é amplamente compatível e ajustada para armazenar dados grandes, como recursos de aplicativos da Web, mas os valores são imutáveis.
A API Storage Foundation tenta fechar todas as lacunas das opções de armazenamento anteriores, permitindo o armazenamento eficiente de arquivos grandes mutáveis definidos na origem do aplicativo.
Casos de uso sugeridos para a API Storage Foundation
Exemplos de sites que podem usar essa API:
- Apps de produtividade ou criatividade que operam com grandes quantidades de dados de vídeo, áudio ou imagem. Esses apps podem descarregar segmentos no disco em vez de mantê-los na memória.
- Apps que dependem de um sistema de arquivos persistente acessível do Wasm e que precisam de mais desempenho do que o IDBFS pode garantir.
O que é a API Storage Foundation?
A API tem duas partes principais:
- Chamadas do sistema de arquivos, que fornecem funcionalidade básica para interagir com arquivos e caminhos de arquivos.
- Handles de arquivo, que fornecem acesso de leitura e gravação a um arquivo existente.
Chamadas do sistema de arquivos
A API Storage Foundation apresenta um novo objeto, storageFoundation
, que reside no objeto window
e inclui várias funções:
storageFoundation.open(name)
: abre o arquivo com o nome especificado, se ele existir. Caso contrário, cria um novo arquivo. Retorna uma promessa que é resolvida com o arquivo aberto.
storageFoundation.delete(name)
: remove o arquivo com o nome especificado. Retorna uma promessa que é resolvida quando o arquivo é excluído.storageFoundation.rename(oldName, newName)
: renomeia o arquivo do nome antigo para o novo de forma atômica. Retorna uma promessa que é resolvida quando o arquivo é renomeado.storageFoundation.getAll()
: retorna uma promessa que é resolvida com uma matriz de todos os nomes de arquivo existentes.storageFoundation.requestCapacity(requestedCapacity)
: solicita nova capacidade (em bytes) para uso pelo contexto de execução atual. Retorna uma promessa resolvida com a quantidade restante de capacidade disponível.
storageFoundation.releaseCapacity(toBeReleasedCapacity)
: libera o número especificado de bytes do contexto de execução atual e retorna uma promessa que é resolvida com a capacidade restante.storageFoundation.getRemainingCapacity()
: retorna uma promessa que é resolvida com a capacidade disponível para o contexto de execução atual.
Handles de arquivo
O trabalho com arquivos acontece pelas seguintes funções:
NativeIOFile.close()
: fecha um arquivo e retorna uma promessa que é resolvida quando a operação é concluída.NativeIOFile.flush()
: sincroniza (ou seja, libera) o estado na memória de um arquivo com o dispositivo de armazenamento e retorna uma promessa que é resolvida quando a operação é concluída.
NativeIOFile.getLength()
: retorna uma promessa que é resolvida com o tamanho do arquivo em bytes.NativeIOFile.setLength(length)
: define o comprimento do arquivo em bytes e retorna uma promessa que é resolvida quando a operação é concluída. Se o novo comprimento for menor que o atual, os bytes serão removidos do final do arquivo. Caso contrário, o arquivo será estendido com bytes de valor zero.NativeIOFile.read(buffer, offset)
: lê o conteúdo do arquivo no deslocamento especificado usando um buffer que é o resultado da transferência do buffer especificado, que é deixado desconectado. Retorna umNativeIOReadResult
com o buffer transferido e o número de bytes lidos com sucesso.Um
NativeIOReadResult
é um objeto que consiste em duas entradas:buffer
: umArrayBufferView
, que é o resultado da transferência do buffer transmitido pararead()
. Ele é do mesmo tipo e comprimento do buffer de origem.readBytes
: o número de bytes que foram lidos com sucesso embuffer
. Isso pode ser menor que o tamanho do buffer se ocorrer um erro ou se o intervalo de leitura abranger além do fim do arquivo. Ele é definido como zero se o intervalo de leitura estiver além do final do arquivo.
NativeIOFile.write(buffer, offset)
: grava o conteúdo do buffer especificado no arquivo no deslocamento especificado. O buffer é transferido antes que qualquer dado seja gravado e, portanto, fica separado. Retorna umNativeIOWriteResult
com o buffer transferido e o número de bytes gravados com sucesso. O arquivo será estendido se o intervalo de gravação exceder o comprimento dele.Um
NativeIOWriteResult
é um objeto que consiste em duas entradas:buffer
: umArrayBufferView
que é o resultado da transferência do buffer transmitido parawrite()
. Ele é do mesmo tipo e comprimento do buffer de origem.writtenBytes
: o número de bytes gravados com sucesso embuffer
. Esse valor pode ser menor que o tamanho do buffer se ocorrer um erro.
Exemplos completos
Para deixar os conceitos apresentados acima mais claros, confira dois exemplos completos que mostram as diferentes etapas do ciclo de vida dos arquivos do Storage Foundation.
Abrir, escrever, ler, fechar
// Open a file (creating it if needed).
const file = await storageFoundation.open('test_file');
try {
// Request 100 bytes of capacity for this context.
await storageFoundation.requestCapacity(100);
const writeBuffer = new Uint8Array([64, 65, 66]);
// Write the buffer at offset 0. After this operation, `result.buffer`
// contains the transferred buffer and `result.writtenBytes` is 3,
// the number of bytes written. `writeBuffer` is left detached.
let result = await file.write(writeBuffer, 0);
const readBuffer = new Uint8Array(3);
// Read at offset 1. `result.buffer` contains the transferred buffer,
// `result.readBytes` is 2, the number of bytes read. `readBuffer` is left
// detached.
result = await file.read(readBuffer, 1);
// `Uint8Array(3) [65, 66, 0]`
console.log(result.buffer);
} finally {
file.close();
}
Abrir, listar, excluir
// Open three different files (creating them if needed).
await storageFoundation.open('sunrise');
await storageFoundation.open('noon');
await storageFoundation.open('sunset');
// List all existing files.
// `["sunset", "sunrise", "noon"]`
await storageFoundation.getAll();
// Delete one of the three files.
await storageFoundation.delete('noon');
// List all remaining existing files.
// `["sunrise", "noon"]`
await storageFoundation.getAll();
Segurança e permissões
A equipe do Chromium projetou e implementou a API Storage Foundation usando os princípios básicos definidos em Controlling Access to Powerful Web Platform Features (em inglês), incluindo controle do usuário, transparência e ergonomia.
Seguindo o mesmo padrão de outras APIs de armazenamento modernas na Web, o acesso à API Storage Foundation é vinculado à origem, ou seja, uma origem só pode acessar dados criados por ela mesma. Ele também é limitado a contextos seguros.
Controle do usuário
A cota de armazenamento será usada para distribuir o acesso ao espaço em disco e evitar abusos. A memória que você quer ocupar precisa ser solicitada primeiro. Assim como outras APIs de armazenamento, os usuários podem limpar o espaço ocupado pela API Storage Foundation no navegador.
Links úteis
- Explicação para o público
- Bug de rastreamento do Chromium
- Entrada do ChromeStatus.com
- Componente Blink:
Blink>Storage>NativeIO
- Revisão da TAG
- Intenção de prototipar
- Thread do WebKit
- Thread da Mozilla
Agradecimentos
A API Storage Foundation foi especificada e implementada por Emanuel Krivoy e Richard Stotz. Este artigo foi revisado por Pete LePage e Joe Medley.
Imagem principal de Markus Spiske no Unsplash.