@onlineapps/storage-core
v2.0.1
Published
Core MinIO storage operations for OA Drive - shared by business and infrastructure services
Readme
Status: current Owns: the plain MinIO operations that
conn-base-storageandinfrastructure-toolsboth build on
Uniform: library/connector
Duty sections that apply:
all: L-MAIN, L-ENGINES, L-TESTS, L-TEST-SCRIPT, L-PACK-TESTS, L-PINS, L-NO-FILE-RANGE, L-CHANGELOG, L-README, L-README-REGION, L-CONSUMERconnector: L-CONNECTOR-ENV
@onlineapps/storage-core
Core MinIO storage operations for OA Drive - shared by business and infrastructure services.
Purpose
This package provides basic storage operations without business-specific features (caching, shared URLs, logging). It is used by:
- Business services via
@onlineapps/conn-base-storage(which adds business features) - Infrastructure services via
@onlineapps/infrastructure-tools(which re-exports this package)
Installation
npm install @onlineapps/storage-coreQuick Start
const { StorageCore } = require('@onlineapps/storage-core');
const storage = new StorageCore({
// FAIL-FAST: topology must be explicit (no defaults for hosts/credentials)
endPoint: process.env.MINIO_ENDPOINT,
port: parseInt(process.env.MINIO_PORT, 10),
accessKey: process.env.MINIO_ACCESS_KEY,
secretKey: process.env.MINIO_SECRET_KEY
});
await storage.initialize();
// Upload
await storage.putObject('bucket', 'path/to/file', buffer, {
'Content-Type': 'application/json'
});
// Download
const buffer = await storage.getObject('bucket', 'path/to/file');
// Fingerprint
const fingerprint = storage.calculateFingerprint(buffer);API
Constructor
const storage = new StorageCore(config);Config options:
endPoint- MinIO server endpoint (required, env:MINIO_ENDPOINT)port- MinIO server port (required, env:MINIO_PORT)useSSL- Speak TLS to the object store (required, env:MINIO_USE_SSL). It has no default here: the value is declared once, byapi/config/shared-env.json, which everyshared.envis rendered from (d.411). A default in this schema would make the package a second owner of a platform value.accessKey- Access key (required, env:MINIO_ACCESS_KEY)secretKey- Secret key (required, env:MINIO_SECRET_KEY)actualHost- The host the client can actually reach (env:MINIO_ACTUAL_HOST). When it differs fromendPoint, requests are signed for the loopback literal127.0.0.1and sent toactualHost; the instance exposes the latter asreachableHost. The split exists because MinIO refuses aHostheader that is not a valid hostname, and every Docker service name here carries an underscore (api_minio_proxy→400 invalid hostname, measured 2026-09-14). The other half of the mechanism is the nginx sidecar, which presents the signature endpoint asHostupstream —infra/api_minio_proxy/nginx.conf.
Core Methods
initialize()
Initialize storage client (currently a no-op, kept for API consistency).
bucketExists(bucket)
Check if bucket exists.
ensureBucket(bucket, region?)
Ensure bucket exists, create if it doesn't.
putObject(bucket, path, data, metadata?)
Upload object to storage.
getObject(bucket, path)
Download object from storage. Resolves with a readable stream, not a Buffer — the caller collects it if it needs the bytes in memory.
objectExists(bucket, path)
Check if object exists.
statObject(bucket, path)
Get object metadata.
deleteObject(bucket, path)
Delete object from storage.
calculateFingerprint(content)
Generate SHA256 fingerprint for content (string, Buffer, or Object).
verifyFingerprint(bucket, path, expectedFingerprint)
Download object and verify its fingerprint matches expected value.
getContentType(filename)
Get MIME type from filename extension.
getPresignedUrl(bucket, path, expiry?)
Generate presigned URL for object access. The URL names reachableHost, not the
signature endpoint — it is opened by a client outside this process, and a URL
naming the loopback literal sends that client to its own machine. The signature
keeps naming the endpoint: the proxy rewrites Host before MinIO sees it, so a
URL signed for the reachable host would be answered 403 (both halves measured
against the live stack; see the integration tier).
listByPrefix(bucket, prefix?, recursive?)
List objects by prefix.
Architecture
storage-core (this package)
↓
conn-base-storage (business services)
+ business features (caching, shared URLs, logging)
storage-core (this package)
↓
infrastructure-tools (infrastructure services)
+ re-export for infra servicesError contract
All messages follow [StorageCore] Problem - Expected/Fix
(.claude/rules/architecture-principles.md §5).
A call refused at this package's own boundary — a missing bucket, path, data or
expected fingerprint — throws a StorageInputError carrying
code === 'STORAGE_ARGUMENT_INVALID'. Nothing was sent to storage, and nothing
was written; a retry with the same arguments never helps.
const { StorageCore, STORAGE_ERROR_CODES } = require('@onlineapps/storage-core');
try {
await storage.getObject('bucket', '');
} catch (err) {
if (err.code === STORAGE_ERROR_CODES.ARGUMENT_INVALID) {
// the caller's arguments were wrong
}
}
// [StorageCore] getObject: path is required - pass a non-empty object path as the second argument.Branch on err.code, never on instanceof: this package is installed as a
nested copy under several consumers, so the class object a consumer imports is
not necessarily the class that threw. This is the one owner of these codes in
the L1 storage layer — @onlineapps/conn-base-storage declares the identical
contract above it, so a consumer reads one code whichever layer refused.
Everything else keeps a plain Error, because the arguments were not the
problem:
[StorageCore] Missing required config: endPoint (set MINIO_ENDPOINT env or pass config.endPoint)
[StorageCore] Fingerprint mismatch: expected abc123, got def456Errors the MinIO client raises are passed through untouched, keeping their own
code (the S3 <Code> element, or a syscall code).
Dependencies
minio- MinIO client library
No other external dependencies - minimal footprint.
License
ISC
