@krizaka/storage
Fichiers derrière un seul port : noms hachés par le contenu, liens signés et publics ; une zone HTTP, tout bucket compatible S3 ou un dossier, au choix de l'admin ; octets vérifiés par nombre magique.
Installer
npm install @krizaka/storageAussi publié en sous-chemins : @krizaka/storage/node, @krizaka/storage/settings, @krizaka/storage/resolve, @krizaka/storage/testkit, @krizaka/storage/provider-admin, @krizaka/storage/opendal.
Le README ci-dessous est celui du paquet sur npm, en anglais.
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
Les 49 noms que le paquet exporte, lus dans ses types.
| Nom | Nature | Ce que c’est |
|---|---|---|
assertKey | fonction | A key is a/b/name.ext: no empty segment, no . or .., no leading or trailing slash, no backslash, no control character. |
checkEndpoint | fonction | 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 | constante | The country of each provider region (data: a new region is a line here, one table for every product, ADR-010). |
countryOf | fonction | The ISO country of a provider region, or undefined when the region is not mapped. |
defineLayout | fonction | |
describeStore | fonction | 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 | fonction | 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 | fonction | The endpoint a preset gives for a region, or null for the generic preset (typed by the person). |
PROVIDER_KINDS | constante | |
providerKind | fonction | |
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 | constante | The province of a Canadian region. |
provinceOf | fonction | |
Residency | type | |
residencyLabel | fonction | 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 | fonction | Canada only when the table says so; an unmapped region is « elsewhere », never a guess in Canada's favour. |
residencyOfConfig | fonction | Residency of a configured store: the region AND the endpoint have to agree. |
S3_PRESETS | constante | |
sniffType | fonction | 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 | constante | The refusals of this package: a stable code, and the HTTP status a route would answer. |
StorageCode | type | |
StorageError | classe | |
storageNotice | fonction | 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 | constante | 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 | fonction | 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 | fonction | 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 | fonction | Trims slashes at the ends without a regular expression (a long run of slashes must not be quadratic). |
typeOfName | fonction | The content type a file name suggests, for stores that do not keep one. |
where | fonction | Where an object lives, from the store kind recorded with it and the store configured now. |
WhereOptions | type |
@krizaka/storage/node
| Nom | Nature | Ce que c’est |
|---|---|---|
AdapterInput | type | What an adapter receives: values already checked by the registry (required fields present, url fields passed checkEndpoint). |
assertKey | fonction | A key is a/b/name.ext: no empty segment, no . or .., no leading or trailing slash, no backslash, no control character. |
bucketStore | fonction | Any S3-compatible service (Spaces, a customer's bucket, MinIO) over plain HTTP: path-style URLs and Signature V4, no SDK. |
BucketStoreOptions | type | |
checkEndpoint | fonction | An endpoint an administrator typed (their own bucket) is called by our server: it must not reach inside our network. |
checkEndpointDns | fonction | 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 | constante | The clouds a person may connect (ADR-005 amendment, krizaka-ts#235), as data: one line per provider, read by one flow. |
cloudConnector | fonction | |
CloudConnector | type | |
CloudConnectorName | type | |
connectionAccessToken | fonction | 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 | constante | The country of each provider region (data: a new region is a line here, one table for every product, ADR-010). |
countryOf | fonction | The ISO country of a provider region, or undefined when the region is not mapped. |
createProviderRegistry | fonction | 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 | fonction | 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 | constante | The built-in kinds with their adapters. |
defineLayout | fonction | |
describeStore | fonction | The descriptor of a configured store, from its configuration alone (see residencyOfConfig: a Bunny-shaped zone is dev only and never Canadian). |
dropboxAuthorizeUrl | fonction | The address the person is sent to. |
dropboxStore | fonction | A person's Dropbox as an ObjectStore, App-folder access only. |
DropboxStoreOptions | type | |
encryptedStore | fonction | 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 | fonction | 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 | fonction | Step 2: the code the provider sent back, with the verifier of step 1. |
FolderOptions | type | |
folderStore | fonction | A folder on disk, for development, tests and the Mac apps. |
hashedName | fonction | photo.jpg and its bytes give photo.<sha10>.jpg: a URL never changes content, so it is cached for good. |
hashOf | fonction | The first 10 hex characters of the SHA-256: the rule of the Krizaka media library (name.<hash>.ext). |
isPrivateAddress | fonction | A loopback, private, link-local, unspecified or unique-local address, v4 or v6 (an IPv4-mapped v6 address is read as its v4). |
keyIdOf | fonction | 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 | fonction | 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 | fonction | 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 | fonction | |
OpenContext | type | |
OpenedAdapter | type | |
OpenedProvider | type | |
openEnvelope | fonction | Opens a v2. value with the key named in it, or a v1. value with the legacy key. |
openProvider | fonction | defaultProviderRegistry.open (or the registry given): the factory of stores from a registered kind. |
openWith | fonction | openEnvelope through a keyring. |
parseEncryptionKey | fonction | The 32-byte AES key of the encryption at rest, from its base64 form (the owner's key, passed in as a parameter). |
parseVaultKey | fonction | The 32-byte key of the vault, from its base64 form (the deployment's, passed in). |
pkcePair | fonction | A PKCE pair (RFC 7636): keep the verifier on the server with the state, send the challenge. |
Preset | type | |
presetEndpoint | fonction | The endpoint a preset gives for a region, or null for the generic preset (typed by the person). |
PROVIDER_KINDS | constante | |
ProviderAdapter | type | |
providerKind | fonction | |
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 | constante | The province of a Canadian region. |
provinceOf | fonction | |
refreshConnection | fonction | A fresh access token. |
refreshDropboxToken | fonction | A fresh access token from the refresh token. |
remoteStore | fonction | 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 | fonction | 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 | fonction | Canada only when the table says so; an unmapped region is « elsewhere », never a guess in Canada's favour. |
residencyOfConfig | fonction | Residency of a configured store: the region AND the endpoint have to agree. |
Resolver | type | |
revokeConnection | fonction | The person's disconnect: revoked at the provider where it can be (already revoked is fine); the caller then deletes the sealed token. |
revokeDropbox | fonction | Revokes the connection at Dropbox (the person's disconnect). |
rewrapEnvelope | fonction | Re-wraps the data key under to (the rotation): the sealed secret is untouched. |
rewrapWith | fonction | rewrapEnvelope through a keyring: the data key is re-wrapped under ring.keyId; the sealed secret is untouched. |
S3_PRESETS | constante | |
seal | fonction | Seals a credential for the database (AES-256-GCM, a fresh IV, v1.<iv|tag|ciphertext> in base64url). |
sealEnvelope | fonction | 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 | fonction | sealEnvelope through a keyring (a KMS can wrap the data key): v2.<ring.keyId>…. |
sniffType | fonction | 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 | fonction | Step 1: where the person is sent. |
Storage | type | |
STORAGE_CODE | constante | The refusals of this package: a stable code, and the HTTP status a route would answer. |
StorageCode | type | |
StorageDeps | type | |
StorageError | classe | |
StorageLimits | type | |
storageNotice | fonction | 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 | constante | 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 | fonction | 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 | fonction | 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 | fonction | Trims slashes at the ends without a regular expression (a long run of slashes must not be quadratic). |
typeOfName | fonction | The content type a file name suggests, for stores that do not keep one. |
UploadInput | type | |
where | fonction | Where an object lives, from the store kind recorded with it and the store configured now. |
WhereOptions | type |
@krizaka/storage/settings
| Nom | Nature | Ce que c’est |
|---|---|---|
limitsOf | fonction | |
readStorageSettings | fonction | The module's settings from the product's accessor ((key) => service.get(key, ctx)); an unknown key falls back to the default. |
STORAGE_DEFAULTS | constante | |
STORAGE_PROVIDERS | constante | |
StorageSecrets | type | The credentials the product reads from its environment and passes in. |
storageSettings | constante | 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 | fonction | The store the admin chose. |
userConnectorsAllowed | fonction | The admin's policy on user-connected clouds, from the product's accessor. |
@krizaka/storage/resolve
| Nom | Nature | Ce que c’est |
|---|---|---|
DEFAULT_MAX_SIGNED_SECONDS | constante | 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 | fonction | The store of a resolved configuration, encrypted when the environment holds a key, with the descriptor the product says. |
ResolvedStorage | type | |
resolveStorage | fonction | The provider that applies to a kind of data, from the admin's settings over the deployment's environment. |
@krizaka/storage/testkit
| Nom | Nature | Ce que c’est |
|---|---|---|
objectStoreContract | fonction | What every ObjectStore adapter must do: the same behaviour behind the same port. |
providerStoreContract | fonction | What every ProviderStore must do. |
@krizaka/storage/provider-admin
| Nom | Nature | Ce que c’est |
|---|---|---|
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 | fonction | |
dbProviderStore | fonction | 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 | fonction | The in-memory store, for unit tests. |
moveBatch | fonction | 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 | fonction | Reads from the provider the row names, and falls back to the older providers while a move is running. |
sha256Hex | fonction | |
storageSchema | constante | The tables of the admin's providers, in their own Postgres schema (ADR-001 §3). |
@krizaka/storage/opendal
| Nom | Nature | Ce que c’est |
|---|---|---|
OPENDAL_SERVICES | constante | 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 | fonction | 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 | fonction | OpenDAL's errors carry their kind as the first word of the message (NotFound (permanent) at read…): mapped onto this package's codes. |
Paquet
| Version sur npm | 0.8.1 (npm) |
| Licence | Apache-2.0 |
| Node.js | >=22 |
| Dépendances | @krizaka/db workspace:^, @krizaka/settings workspace:^, arctic 3.7.0 |
| Dépendances à fournir | vitest >=4, zod >=3.23, opendal >=0.49.13 <0.50 |
| Code source | storage |
Journal des versions
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
Vidéo derrière un seul port : téléversements reprenables signés côté serveur, jetons de lecture et liens HLS signés, statuts de webhook signés.
@krizaka/notifications
La cloche, les canaux et les préférences : un notify écrit l'élément et un message d'outbox par canal activé ; les plafonds de rafale tiennent en concurrence ; l'admin règle les défauts.