@krizaka/storage
Files behind one port: content-hashed names, signed and public links; an HTTP zone, any S3-compatible bucket or a folder, chosen by the admin; bytes checked by magic number.
Install
npm install @krizaka/storageAlso published as subpath exports: @krizaka/storage/node, @krizaka/storage/settings, @krizaka/storage/resolve, @krizaka/storage/testkit, @krizaka/storage/provider-admin, @krizaka/storage/opendal.
Files behind one port for every Krizaka product. The port is named by what it does (ObjectStore), the provider is a value the admin chooses. No vendor SDK (plain HTTP through an injected fetch), no environment variable, no wall clock.
- The port.
put,get,head,delete,list(prefix),signedUrl(key, ttl)for a private zone,publicUrl(key)when a pull zone or CDN exists. - Adapters.
remoteStore(an HTTP zone with the shape of Bunny Storage:AccessKeyheader,PUT/GET/DELETE, a directory listed by its path with a trailing slash, a pull zone with token-signed links),bucketStore(any S3-compatible service: Signature V4, presigned GET,ListObjectsV2, no SDK),folderStore(a folder, for development, tests and the Mac apps; a key never leaves the root),memoryObjectStore. All runobjectStoreContract(@krizaka/storage/testkit). - Rules every upload obeys (
createStorage): the size limit is passed in, the prefix must be declared in adefineLayout(an unknown prefix is refused), the bytes are checked by magic number (sniffType: png, jpeg, gif, webp, pdf, mp4, webm, svg), never by the extension, and a declared type that contradicts the bytes is refused. The stored name is content-hashed,<name>.<sha10>.<ext>, so a URL never changes content. - The admin chooses (
@krizaka/storage/settings):storage.provider(local,http,s3),storage.maxMegabytes,storage.signedUrlSeconds, and the non-secret coordinates (endpoint,region,bucket,zone,prefix,pullHostname,publicBaseUrl). Keys stay in the environment;storeFromSettingstakes them as parameters and refuses a provider chosen without what it needs, instead of falling back to another. An admin-typed S3 endpoint goes throughcheckEndpoint(https only, no credentials, no local or private address). - Encryption at rest, the vault, regions and
where.encryptedStore(inner, key)(AES-256-GCM over anyObjectStore; the key is a parameter; the provider holds ciphertext; no link is handed out),seal/open(credentials in the database,v1.envelope),COUNTRY_OF_REGION(the one region table: an unlisted region is never a country;residencyOfConfigsays « canada » only when the region and the endpoint agree, never for a Bunny-shaped zone),where(kind, active, { fixed, devOnly })andstorageNotice(the sentence key of the info panel, from the configuration). - Admin over environment (
@krizaka/storage/resolve).resolveStorage(env, admin)serves every kind of data (documents, avatars: pass each its ownDeploymentStorage): the admin'sstorage.*wins, the environment gives the secrets and the fallback when the admin leftlocal. If the environment saysnoneand the admin turns keeping on, it throwsStorageErrorSTORAGE_KEEPING_OFF(call it when saving the setting to refuse early): keeping is turned on in the environment, never by a setting alone.openResolvedbuilds the store, encrypted when a key is given. - Provider registry (ADR-005).
PROVIDER_KINDSis the table of kinds as data (bunny,s3with presets DigitalOcean Spaces, AWS S3, OVHcloud and generic,dropbox, reservedgoogle-drive/onedrive,folder,device): scopes, runtimes,devOnly, capabilities, coordinate and credential field schemas.openProvider(kind, coordinates, credentials, { runtime, environment, fetch, clock })(/node) is the only factory: it refuses (STORAGE_BAD_CONFIG)devOnlykinds inprod,folderoutsidedev, an unknown or unimplemented kind and a missing field, and never falls back. It returns the store and aResidencyLabel(PROVINCE_OF_REGION: « Canada (Québec) », « Canada (Ontario) »…) for the screen to show before saving.testConnection(probe write, read, compare, delete) andcheckEndpointDns(the written form plus the DNS answer: a private address is refused) serve every product. - The registry opens the OpenDAL kinds and private kinds (ADR-005 amendment, krizaka-ts#239).
google-driveandonedriveare implemented at user scope (throughopendalStoreandCLOUD_CONNECTORS: the access token is renewed from the sealed refresh token, a rotated one comes back throughOpenContext.onRotateand is sealed again by the service);azblob,gcs,b2andwebdavat admin scope (their field names are OpenDAL's own options; a typedurlfield passescheckEndpointand the DNS check).openProvideris now asynchronous. Each kind maps to its adapter in a table (BUILT_IN_ADAPTERS), never a branch. A private package adds a kind without touching the core:defaultProviderRegistry.with({ kind, open })(orcreateProviderRegistry({ kinds, adapters })) returns a new registry; pass it toopenProvider(…, registry)andcreateProviderService({ registry }). A duplicate name is refused, a secret may not be a coordinate, a credential must be asecretfield (sealed), and the runtime,devOnly, required-field and url rules apply to private kinds as to the built-in ones. Keyringis a port ({ keyId, wrap, unwrap, openLegacy? }).localKeyring({ keyId, key, ring })is the in-process default (the oldKeyringshape{ keys, legacy }is nowLocalKeys);sealWith,openWithandrewrapWithtake any keyring, so a KMS (AWS KMS ca-central-1, OVHcloud KMS, Vault transit) wraps the data key and the key never reaches the app.createProviderService({ vault })takes either the old{ keyId, key, ring }or aKeyring. The storedv2.<key_id>…format is unchanged: moving to a KMS isrotateVaultKey, not a re-entry.- Envelope vault.
sealEnvelope/openEnvelope/rewrapEnvelope: a data key per sealed value, wrapped by the key-encryption key (MEDIA_VAULT_KEY) named by akey_id(v2.<key_id>.<wrapped>.<sealed>);v1.values still open with the legacy key; rotating the key re-wraps the data keys only. - Providers in the database (
@krizaka/storage/provider-admin, ADR-005 slice 2).storageSchema(tablesstorage.providerandstorage.default_provider),dbProviderStore/memoryProviderStore(contract:providerStoreContract), andcreateProviderService: create (adraft, secrets sealed in an envelope), test (only a green test verifies),setDefault(verified only; the previous default becomesretiring),changeKey(the candidate is tested first),disable/retire(refused while the product still holds objects there),rotateVaultKey,bootstrap(imports the oldMEDIA_*environment once as anactiveprovider,source: env). Secrets come back asset/not setonly; every change goes to the injectedauditwith secret fields aschanged.moveBatch(get, check the SHA-256, put, read back and compare, then switch the row in the product transaction; failures are returned, never switched) andreadDual(the row provider, then the retiring ones) are the move and the dual read. - Users' own clouds (ADR-005 slice 4).
dropboxStore(dropboxkind, user scope): App folder only, upload sessions above 150 MB, no signed or public link (Dropbox links outlive our 900 s cap, so the application streams).pkcePair,dropboxAuthorizeUrl,exchangeDropboxCode,refreshDropboxToken,revokeDropboxare the OAuth 2 PKCE steps; the refresh token is sealed like any credential, and a revoked or expired one answersSTORAGE_DISCONNECTED(nothing is moved or deleted). A user's own S3 is the sames3kind atuser:<id>scope, with the DNS check of its endpoint. - Every classic cloud through libraries (ADR-005 amendment, krizaka-ts#235).
opendalStore(@krizaka/storage/opendal) is one adapter over Apache OpenDAL for Google Drive, OneDrive, Dropbox, Azure Blob, GCS, B2, Swift and WebDAV. Only the services ofOPENDAL_SERVICESopen;fs/memoryneedallowLocal; a typed endpoint passescheckEndpointand the DNS check first. OpenDAL's errors map ontoSTORAGE_*(PermissionDeniedon an OAuth cloud →STORAGE_DISCONNECTED), andreadStream(key)streams large files.opendalis an optional peer (a native module of 59–82 MB, glibc ≥ 2.38 or musl), loaded on first use.CLOUD_CONNECTORS(/node) is the table of user clouds (Dropbox, Google Drive, OneDrive: endpoints, narrowest scope, revocation style).startConnection,finishConnection,refreshConnection,revokeConnectionrun Arctic's authorization code + PKCE S256, andconnectionAccessTokenfeedsopendalStorea fresh access token and hands a rotated refresh token back to be resealed.dropboxStoreand its helpers are superseded and stay until the products move. - Signatures. SigV4 is checked against AWS's published vectors. The token form of the HTTP zone's signed links (SHA-256 of key + path + expires, URL-safe base64) is the one Yomizaka runs against Bunny's pull zones; Bunny's documentation publishes no expected output for it.
import { createStorage, defineLayout, folderStore } from "@krizaka/storage";
import { limitsOf, readStorageSettings, storeFromSettings } from "@krizaka/storage/settings";
const settings = await readStorageSettings((key) => service.get(key, ctx));
const storage = createStorage({
store: storeFromSettings(settings, { accessKeyId: env.MEDIA_STORAGE_ACCESS_KEY_ID, secretAccessKey: env.MEDIA_STORAGE_SECRET }, { fetch, clock }),
layout: defineLayout(["people/", "site/home/"]),
limits: () => readStorageSettings(read).then(limitsOf),
});
const { key, url } = await storage.upload({ prefix: "people/", name: "ana.jpg", bytes, allow: ["image/jpeg", "image/png"], declaredType: file.type });Apache-2.0.
Exports
The 49 names the package exports, read from its types.
| Name | Kind | What it is |
|---|---|---|
assertKey | function | A key is a/b/name.ext: no empty segment, no . or .., no leading or trailing slash, no backslash, no control character. |
checkEndpoint | function | An endpoint an administrator typed (their own bucket) is called by our server: it must not reach inside our network. |
Clock | type | |
COUNTRY_OF_REGION | constant | The country of each provider region (data: a new region is a line here, one table for every product, ADR-010). |
countryOf | function | The ISO country of a provider region, or undefined when the region is not mapped. |
defineLayout | function | |
describeStore | function | The descriptor of a configured store, from its configuration alone (see residencyOfConfig: a Bunny-shaped zone is dev only and never Canadian). |
FetchLike | type | The part of fetch the adapters use: inject the global one (fetch), or a fake in a test. |
FieldSchema | type | |
Layout | type | The prefixes a zone allows, as data: an unknown prefix is refused. |
memoryObjectStore | function | An in-memory store, for unit tests and demos: the behaviour of the others (the contract kit runs on all). |
NoticeKey | type | |
ObjectInfo | type | |
ObjectStore | type | One place files live. |
Preset | type | |
presetEndpoint | function | The endpoint a preset gives for a region, or null for the generic preset (typed by the person). |
PROVIDER_KINDS | constant | |
providerKind | function | |
ProviderKind | type | |
ProviderKindName | type | The kinds this package ships. |
ProviderRuntime | type | server = a deployed web BFF, dev = a developer's machine or CI, device = a mobile or macOS client. |
ProviderScope | type | |
Province | type | |
PROVINCE_OF_REGION | constant | The province of a Canadian region. |
provinceOf | function | |
Residency | type | |
residencyLabel | function | The label of a configured store: Canada only when region and endpoint agree (residencyOfConfig); the province from the region table. |
ResidencyLabel | type | Where the files would live, as the screen says it before saving: key for the product's messages, text the English sentence. |
residencyOf | function | Canada only when the table says so; an unmapped region is « elsewhere », never a guess in Canada's favour. |
residencyOfConfig | function | Residency of a configured store: the region AND the endpoint have to agree. |
S3_PRESETS | constant | |
sniffType | function | The content type the BYTES say they are, by magic number, never by the file name: png, jpeg, gif, webp, pdf, mp4, webm and svg. |
STORAGE_CODE | constant | The refusals of this package: a stable code, and the HTTP status a route would answer. |
StorageCode | type | |
StorageError | class | |
storageNotice | function | Which sentence says where this object lives (« Not kept », « Kept: Krizaka (Canada, encrypted) »…), with its variables; the product's messages hold the words. |
STORE_KIND | constant | The two kinds every product shares; the others are the product's. |
StoreDescriptor | type | Where objects live, as the product says it: computed from the configuration, never typed. |
StoredObject | type | |
StoreOwner | type | Whose store it is: Krizaka's (the deployment's), the workspace's own, or the person's device. |
SweepOptions | type | |
sweepOrphans | function | Deletes the files under prefix that isReferenced does not know: the leftovers of an upload whose row was rolled back, of a purge interrupted between the… |
SweepResult | type | |
testConnection | function | The connection test of a store: writes a small probe object, reads it back and compares, then deletes it, the three things a kept file needs. |
TestStep | type | |
trimSlashes | function | Trims slashes at the ends without a regular expression (a long run of slashes must not be quadratic). |
typeOfName | function | The content type a file name suggests, for stores that do not keep one. |
where | function | Where an object lives, from the store kind recorded with it and the store configured now. |
WhereOptions | type |
@krizaka/storage/node
| Name | Kind | What it is |
|---|---|---|
AdapterInput | type | What an adapter receives: values already checked by the registry (required fields present, url fields passed checkEndpoint). |
assertKey | function | A key is a/b/name.ext: no empty segment, no . or .., no leading or trailing slash, no backslash, no control character. |
bucketStore | function | Any S3-compatible service (Spaces, a customer's bucket, MinIO) over plain HTTP: path-style URLs and Signature V4, no SDK. |
BucketStoreOptions | type | |
checkEndpoint | function | An endpoint an administrator typed (their own bucket) is called by our server: it must not reach inside our network. |
checkEndpointDns | function | checkEndpoint (the written form) plus the DNS answer: a name that resolves to a private address is refused too, so a person's own bucket can never make our… |
Clock | type | |
CLOUD_CONNECTORS | constant | The clouds a person may connect (ADR-005 amendment, krizaka-ts#235), as data: one line per provider, read by one flow. |
cloudConnector | function | |
CloudConnector | type | |
CloudConnectorName | type | |
connectionAccessToken | function | The accessToken an opendalStore asks for: renewed from the refresh token a minute before it expires, the rotated refresh token handed to onRotate (to r… |
ConnectionTokens | type | |
ConnectorApp | type | The app's registration at the provider (from the admin's settings, the secret sealed). |
ConnectorOverride | type | Tests point a connector at a fake OAuth server; never built from what a person typed. |
COUNTRY_OF_REGION | constant | The country of each provider region (data: a new region is a line here, one table for every product, ADR-010). |
countryOf | function | The ISO country of a provider region, or undefined when the region is not mapped. |
createProviderRegistry | function | Builds a registry from data (kinds) and the function that opens each implemented kind (adapters, by kind name): the table maps a kind to its adapter, nev… |
createStorage | function | The storage of a product: an ObjectStore with the rules every upload obeys (a size limit passed in, a declared layout, bytes checked by magic number, a con… |
defaultProviderRegistry | constant | The built-in kinds with their adapters. |
defineLayout | function | |
describeStore | function | The descriptor of a configured store, from its configuration alone (see residencyOfConfig: a Bunny-shaped zone is dev only and never Canadian). |
dropboxAuthorizeUrl | function | The address the person is sent to. |
dropboxStore | function | A person's Dropbox as an ObjectStore, App-folder access only. |
DropboxStoreOptions | type | |
encryptedStore | function | Encryption at rest over any ObjectStore (AES-256-GCM, a fresh IV per object, the object's key as authenticated data, so a file moved under another name doe… |
exchangeDropboxCode | function | Exchanges the code Dropbox sent back (with the verifier kept at the start). |
FetchLike | type | The part of fetch the adapters use: inject the global one (fetch), or a fake in a test. |
FieldSchema | type | |
finishConnection | function | Step 2: the code the provider sent back, with the verifier of step 1. |
FolderOptions | type | |
folderStore | function | A folder on disk, for development, tests and the Mac apps. |
hashedName | function | photo.jpg and its bytes give photo.<sha10>.jpg: a URL never changes content, so it is cached for good. |
hashOf | function | The first 10 hex characters of the SHA-256: the rule of the Krizaka media library (name.<hash>.ext). |
isPrivateAddress | function | A loopback, private, link-local, unspecified or unique-local address, v4 or v6 (an IPv4-mapped v6 address is read as its v4). |
keyIdOf | function | The key_id a sealed value was wrapped under, or null for a v1. value (sealed by the single key). |
Keyring | type | The key port (ADR-005 amendment, krizaka-ts#239): all the vault asks of the key-encryption key is to wrap and unwrap a 32-byte data key. |
Layout | type | The prefixes a zone allows, as data: an unknown prefix is refused. |
localKeyring | function | The in-process keyring: key is the current key-encryption key (named keyId), ring the older ones still needed to open values. |
LocalKeys | type | The key-encryption keys held in this process: by key_id for v2. values, and the old single key (legacy) that still opens v1. values. |
memoryObjectStore | function | An in-memory store, for unit tests and demos: the behaviour of the others (the contract kit runs on all). |
NoticeKey | type | |
ObjectInfo | type | |
ObjectStore | type | One place files live. |
open | function | |
OpenContext | type | |
OpenedAdapter | type | |
OpenedProvider | type | |
openEnvelope | function | Opens a v2. value with the key named in it, or a v1. value with the legacy key. |
openProvider | function | defaultProviderRegistry.open (or the registry given): the factory of stores from a registered kind. |
openWith | function | openEnvelope through a keyring. |
parseEncryptionKey | function | The 32-byte AES key of the encryption at rest, from its base64 form (the owner's key, passed in as a parameter). |
parseVaultKey | function | The 32-byte key of the vault, from its base64 form (the deployment's, passed in). |
pkcePair | function | A PKCE pair (RFC 7636): keep the verifier on the server with the state, send the challenge. |
Preset | type | |
presetEndpoint | function | The endpoint a preset gives for a region, or null for the generic preset (typed by the person). |
PROVIDER_KINDS | constant | |
ProviderAdapter | type | |
providerKind | function | |
ProviderKind | type | |
ProviderKindName | type | The kinds this package ships. |
ProviderPlugin | type | What a private package registers: the kind's data (the same shape as the built-in ones) and the function that opens its store. |
ProviderRegistry | type | |
ProviderRuntime | type | server = a deployed web BFF, dev = a developer's machine or CI, device = a mobile or macOS client. |
ProviderScope | type | |
Province | type | |
PROVINCE_OF_REGION | constant | The province of a Canadian region. |
provinceOf | function | |
refreshConnection | function | A fresh access token. |
refreshDropboxToken | function | A fresh access token from the refresh token. |
remoteStore | function | An HTTP object store with the shape of Bunny Storage: PUT/GET/DELETE on https://<endpoint>/<zone>/<prefix>/<key> with an AccessKey header, a direct… |
RemoteStoreOptions | type | |
Residency | type | |
residencyLabel | function | The label of a configured store: Canada only when region and endpoint agree (residencyOfConfig); the province from the region table. |
ResidencyLabel | type | Where the files would live, as the screen says it before saving: key for the product's messages, text the English sentence. |
residencyOf | function | Canada only when the table says so; an unmapped region is « elsewhere », never a guess in Canada's favour. |
residencyOfConfig | function | Residency of a configured store: the region AND the endpoint have to agree. |
Resolver | type | |
revokeConnection | function | The person's disconnect: revoked at the provider where it can be (already revoked is fine); the caller then deletes the sealed token. |
revokeDropbox | function | Revokes the connection at Dropbox (the person's disconnect). |
rewrapEnvelope | function | Re-wraps the data key under to (the rotation): the sealed secret is untouched. |
rewrapWith | function | rewrapEnvelope through a keyring: the data key is re-wrapped under ring.keyId; the sealed secret is untouched. |
S3_PRESETS | constant | |
seal | function | Seals a credential for the database (AES-256-GCM, a fresh IV, v1.<iv|tag|ciphertext> in base64url). |
sealEnvelope | function | Envelope encryption (ADR-005): a fresh data key seals the secret, and the key-encryption key (MEDIA_VAULT_KEY, named by keyId) wraps the data key. |
sealWith | function | sealEnvelope through a keyring (a KMS can wrap the data key): v2.<ring.keyId>…. |
sniffType | function | The content type the BYTES say they are, by magic number, never by the file name: png, jpeg, gif, webp, pdf, mp4, webm and svg. |
startConnection | function | Step 1: where the person is sent. |
Storage | type | |
STORAGE_CODE | constant | The refusals of this package: a stable code, and the HTTP status a route would answer. |
StorageCode | type | |
StorageDeps | type | |
StorageError | class | |
StorageLimits | type | |
storageNotice | function | Which sentence says where this object lives (« Not kept », « Kept: Krizaka (Canada, encrypted) »…), with its variables; the product's messages hold the words. |
STORE_KIND | constant | The two kinds every product shares; the others are the product's. |
StoreDescriptor | type | Where objects live, as the product says it: computed from the configuration, never typed. |
StoredObject | type | |
StoreOwner | type | Whose store it is: Krizaka's (the deployment's), the workspace's own, or the person's device. |
SweepOptions | type | |
sweepOrphans | function | Deletes the files under prefix that isReferenced does not know: the leftovers of an upload whose row was rolled back, of a purge interrupted between the… |
SweepResult | type | |
testConnection | function | The connection test of a store: writes a small probe object, reads it back and compares, then deletes it, the three things a kept file needs. |
TestStep | type | |
trimSlashes | function | Trims slashes at the ends without a regular expression (a long run of slashes must not be quadratic). |
typeOfName | function | The content type a file name suggests, for stores that do not keep one. |
UploadInput | type | |
where | function | Where an object lives, from the store kind recorded with it and the store configured now. |
WhereOptions | type |
@krizaka/storage/settings
| Name | Kind | What it is |
|---|---|---|
limitsOf | function | |
readStorageSettings | function | The module's settings from the product's accessor ((key) => service.get(key, ctx)); an unknown key falls back to the default. |
STORAGE_DEFAULTS | constant | |
STORAGE_PROVIDERS | constant | |
StorageSecrets | type | The credentials the product reads from its environment and passes in. |
storageSettings | constant | The settings of « storage », a fragment the product adds to its registry (@krizaka/storage/settings): the admin picks the provider (local, http for a B… |
StorageSettings | type | |
storeFromSettings | function | The store the admin chose. |
userConnectorsAllowed | function | The admin's policy on user-connected clouds, from the product's accessor. |
@krizaka/storage/resolve
| Name | Kind | What it is |
|---|---|---|
DEFAULT_MAX_SIGNED_SECONDS | constant | A signed link never lives longer than this, whatever the admin or a caller asks (a kept file is personal data). |
DeploymentStorage | type | What a deployment's environment says about one kind of kept data (documents, avatars, exports…), read by the product's edge and passed in: this module reads… |
openResolved | function | The store of a resolved configuration, encrypted when the environment holds a key, with the descriptor the product says. |
ResolvedStorage | type | |
resolveStorage | function | The provider that applies to a kind of data, from the admin's settings over the deployment's environment. |
@krizaka/storage/testkit
| Name | Kind | What it is |
|---|---|---|
objectStoreContract | function | What every ObjectStore adapter must do: the same behaviour behind the same port. |
providerStoreContract | function | What every ProviderStore must do. |
@krizaka/storage/provider-admin
| Name | Kind | What it is |
|---|---|---|
Actor | type | Who acts: recorded in the audit trail. |
AuditNote | type | What the service tells the audit trail (@krizaka/audit's record, wired by the product). |
CatalogObject | type | An object the product holds, with the provider it lives at and the SHA-256 recorded at upload. |
CreateProvider | type | |
createProviderService | function | |
dbProviderStore | function | The PostgreSQL store, on <schema>.provider and <schema>.default_provider (storageSchema), over the product's query. |
LastTest | type | The last connection test: steps and the residency it found, never a value. |
memoryProviderStore | function | The in-memory store, for unit tests. |
moveBatch | function | One bounded batch of the move (ADR-005 §3), run from a leased outbox worker, resumable: get from the old store, check the hash recorded at upload, put to the… |
MoveResult | type | |
ObjectCatalog | type | The product's side of a move: it owns the objects table. |
ProviderRecord | type | A provider as stored. |
ProviderService | type | |
ProviderServiceDeps | type | |
ProviderStatus | type | |
ProviderStore | type | Where the providers live. |
PublicProvider | type | A provider as the API returns it: every secret is set or not set, nothing else, ever. |
readDual | function | Reads from the provider the row names, and falls back to the older providers while a move is running. |
sha256Hex | function | |
storageSchema | constant | The tables of the admin's providers, in their own Postgres schema (ADR-001 §3). |
@krizaka/storage/opendal
| Name | Kind | What it is |
|---|---|---|
OPENDAL_SERVICES | constant | The OpenDAL services this package lets a provider open (ADR-005 amendment, krizaka-ts#235), as data. |
OpendalObjectStore | type | The port, plus a streaming read for large files (the port reads whole files): OpenDAL's reader as a Node stream. |
OpendalService | type | |
opendalStore | function | One adapter over Apache OpenDAL for the providers that have no adapter of their own (Google Drive, OneDrive, Azure Blob, GCS, Backblaze B2, Swift, WebDAV/Nex… |
OpendalStoreOptions | type | |
storageErrorOf | function | OpenDAL's errors carry their kind as the first word of the message (NotFound (permanent) at read…): mapped onto this package's codes. |
Package
| Version on npm | 0.8.1 (npm) |
| Licence | Apache-2.0 |
| Node.js | >=22 |
| Dependencies | @krizaka/db workspace:^, @krizaka/settings workspace:^, arctic 3.7.0 |
| Peer dependencies | vitest >=4, zod >=3.23, opendal >=0.49.13 <0.50 |
| Source | storage |
Changelog
0.8.1
Patch changes
- The READMEs, comments and published declarations no longer name the products that left the organisation (krizaka/.github#413, rule
no-departed-published). The sources were cleaned in #262 with an empty changeset, so the published tarballs still carried the old text; this release replaces them. No behaviour change. - Updated dependencies [2272b57]
- @krizaka/[email protected]
0.8.0
Minor changes
- Every classic cloud through libraries (ADR-005 amendment, #235):
opendalStore(@krizaka/storage/opendal, Apache OpenDAL as an optional peer) behind the port, with an allow-list of services, the endpoint and DNS checks kept, error mapping ontoSTORAGE_*andreadStream;CLOUD_CONNECTORSand the Arctic-based connection flow (authorization code + PKCE S256, refresh with rotation, revocation) for Dropbox, Google Drive and OneDrive. - The registry opens the OpenDAL kinds and takes private kinds (ADR-005 amendment, #239):
google-driveandonedriveimplemented at user scope,azblob,gcs,b2andwebdavat admin scope, each kind mapped to its adapter through a table;createProviderRegistry({ kinds, adapters }).with({ kind, open })registers a private provider without touching the core (every rule applies: scopes, runtimes,devOnly, url fields checked, secrets sealed);Keyringbecomes a port ({ keyId, wrap, unwrap },localKeyringthe default,sealWith/openWith/rewrapWith), ready for a KMS with the storedv2.<key_id>format unchanged. Breaking for 0.x consumers:openProvideris asynchronous, and the oldKeyringtype ({ keys, legacy }) is renamedLocalKeys.
Patch changes
- Updated dependencies [ce40b60]
- @krizaka/[email protected]
0.7.0
Minor changes
storageSettingshasstorage.userConnectors(flag, off by default): the admin's policy on whether people may connect their own cloud (Dropbox, an S3 of their own).userConnectorsAllowed(read)reads it; anything but an explicittrueis off.
Patch changes
- Updated dependencies [0d19b64]
- @krizaka/[email protected]
- @krizaka/[email protected]
0.6.0
Minor changes
- Dropbox as a user-scope provider (ADR-005, slice 4):
dropboxStore(App folder, upload sessions above 150 MB, no signed or public link: the app streams), the OAuth 2 PKCE helpers (pkcePair,dropboxAuthorizeUrl,exchangeDropboxCode,refreshDropboxToken,revokeDropbox), the new codeSTORAGE_DISCONNECTEDfor a revoked or expired connection, anddropboxopened byopenProvider. - Providers in the database (ADR-005, slice 2), in
@krizaka/storage/provider-admin:storageSchema,dbProviderStore/memoryProviderStorewith a contract,createProviderService(create, test, set default, change key, disable, retire, rotate the vault key, bootstrap from the environment; secrets returned as set / not set, every change audited),moveBatch(SHA-256-verified move) andreadDual(reads from both stores during a move).
0.5.0
Minor changes
- Provider registry (ADR-005, slice 1):
PROVIDER_KINDSas data,openProviderwith the runtime and environment guards (devOnlyrefused inprod,folderonly indev),PROVINCE_OF_REGIONandresidencyLabel,testConnectionandcheckEndpointDnsmoved from Yomizaka, and the envelope vault (sealEnvelope,openEnvelope,rewrapEnvelope,keyIdOf,v2.values with akey_id;v1.still opens).
0.4.0
Minor changes
sweepOrphans(store, prefix, isReferenced, { dryRun, limit }): deletes the files under a prefix that nothing references (leftovers of a rolled-back upload or an interrupted purge), reports scanned / orphans / deleted / failed, never stops on one failing delete.
0.3.1
Patch changes
- The default entry is safe on Edge and in the browser: no
node:import, nothing reads a file. It carries the types and the port, the region table,describeStore,storageNotice, the endpoint check,sniffType, the key rules and the in-memory store. The adapters that hash, sign, encrypt or touch the disk (createStorage,bucketStore,remoteStore,folderStore,encryptedStore,hashedName,seal/open) are in@krizaka/storage/node, and.still resolves to them under thenodecondition, so Node code keeps working unchanged. A test bundles the default entry for the browser and fails on anynode:orfsimport.
0.3.0
Minor changes
- The four pieces Yomizaka kept locally move in (#146), and the admin-over-environment rule is decided.
encryptedStore(inner, key)(AES-256-GCM over anyObjectStore, key as a parameter, ciphertext only at the provider,signedUrl/publicUrlnull) withparseEncryptionKey; the vaultseal/open/parseVaultKeyfor credentials in the database; the shared region tableCOUNTRY_OF_REGIONwithcountryOf,residencyOfandresidencyOfConfig(Canada only when the region AND the endpoint agree, never for a Bunny-shaped zone or an unmapped region);where(kind, active, { fixed, devOnly }),describeStoreandstorageNotice(the generated residency sentence, from the configuration). New subpath@krizaka/storage/resolve:resolveStorage(env, admin)applies the admin'sstorage.*settings over the deployment's environment for any kind of data (documents, avatars), caps the signed link, andopenResolvedbuilds the store (encrypted when a key is given). When the environment saysnoneand the admin turns keeping on,resolveStoragethrowsStorageErrorSTORAGE_KEEPING_OFF(new codesSTORAGE_KEEPING_OFF,STORAGE_UNREADABLE,STORAGE_BAD_SECRET_KEY): keeping is only turned on from the environment.
0.2.0
Minor changes
- New packages (#44).
@krizaka/storage: theObjectStoreport (put,get,head,delete,list,signedUrl,publicUrl),remoteStore(HTTP zone, token-signed links),bucketStore(any S3-compatible service, Signature V4 checked against AWS's vectors),folderStore,memoryObjectStore,createStorage(size limit passed in, declared layout, magic-number check, content-hashed names),checkEndpoint,@krizaka/storage/settings(storage.providerlocal / http / s3, limits and coordinates;storeFromSettings) andobjectStoreContract.@krizaka/video: theVideoProviderport,streamVideo(Bunny Stream: create, delete, server-signed TUS sessions, embed and mobile view tokens, signed HLS and poster links, signed webhooks), the signing helpers andvideoProviderContract.
@krizaka/video
Video behind one port: server-signed resumable uploads, playback tokens and signed HLS links, signed webhook statuses.
@krizaka/notifications
The bell, channels and preferences: one notify writes the item and one outbox message per enabled channel; burst caps hold under concurrency; the admin tunes defaults.