lightweight-dms-core
v1.0.0
Published
`lightweight-dms-core` is a stable, embeddable TypeScript library for tenant-scoped document storage. It provides PostgreSQL row-level security (RLS), document metadata, durable processing boundaries, webhooks, exports, and tenant deletion workflows. The
Readme
lightweight-dms-core
lightweight-dms-core is a stable, embeddable TypeScript library for tenant-scoped document
storage. It provides PostgreSQL row-level security (RLS), document metadata, durable processing
boundaries, webhooks, exports, and tenant deletion workflows. The host application owns its
database roles, storage, workers, and external services.
Requirements
- Node.js 22.14 or later
- PostgreSQL with separate application and migration-owner roles in production
- A durable
StorageAdapter;LocalFsAdapteris suitable only for local development
Installation
npm install lightweight-dms-coreThe package is ESM-only. Use TypeScript with Node's ESM resolution and import from
lightweight-dms-core rather than its internal paths.
Database roles and migrations
Use the application role for tenant-scoped requests. Use a distinct owner role for migrations and other owner-only maintenance. Run migrations before serving traffic:
const dms = createDms({
database: {
connectionString: process.env.DATABASE_URL!,
ownerConnectionString: process.env.OWNER_DATABASE_URL!,
},
storage: new LocalFsAdapter('/var/lib/lightweight-dms'),
maxUploadBytes: 64 * 1024 * 1024,
});
await dms.migrate();With connection-string configuration, migrate() uses ownerConnectionString when it is set and
otherwise falls back to connectionString. Use a distinct owner connection in production; the
fallback supports a single-role local setup. A pool-based configuration has no connection string
for migrate(), so run migrations through a connection-string-configured instance.
Create a DMS instance
import { randomUUID } from 'node:crypto';
import { Readable } from 'node:stream';
import { createDms, LocalFsAdapter } from 'lightweight-dms-core';
const dms = createDms({
database: {
connectionString: process.env.DATABASE_URL!,
ownerConnectionString: process.env.OWNER_DATABASE_URL!,
},
storage: new LocalFsAdapter('/var/lib/lightweight-dms'),
maxUploadBytes: 64 * 1024 * 1024,
});
await dms.migrate();
const tenant = await dms.tenants.provision({
name: 'Example tenant',
ocrLanguages: ['de'],
searchLanguage: 'german',
});
const png = Buffer.from('...');
const document = await dms.documents.upload(
{ tenantId: tenant.id, actorId: randomUUID() },
{
filename: 'document.png',
mimeType: 'image/png',
content: Readable.from(png),
contentLength: png.length,
},
);
await dms.close();The checked repository quickstart
uses the same public API. It needs DATABASE_URL and OWNER_DATABASE_URL and is typechecked by
npm run docs:example:check from this package directory.
Provision OCR and search languages
Tenant provisioning requires both independent language settings. ocrLanguages is a non-empty
array of OCR engine language codes. searchLanguage controls PostgreSQL full-text indexing and
must be one of simple, english, or german; it is not inferred from OCR configuration.
await dms.tenants.provision({
name: 'German documents',
ocrLanguages: ['de'],
searchLanguage: 'german',
});Upload limits and streaming
maxUploadBytes is required and must be a positive safe integer. It is the application-level
ceiling used by document ingestion. Supply contentLength when the upload size is known; the
library checks it against the measured stream and the configured ceiling. The upload content is a
Node Readable, so the host must avoid buffering untrusted uploads in memory before calling
documents.upload.
StorageAdapter contract
StorageAdapter.put(key, body, options) is create-only: a key must be fresh and an adapter must
reject rather than overwrite an existing object. options can include an AbortSignal and an
optional contentLength. On cancellation, put, get, and delete must not settle until local
work that could still affect the object has quiesced. Implementations must keep durable cleanup
and ambiguity handling appropriate for their object store.
LocalFsAdapter stores canonical library-generated keys below a local root. S3Adapter supports
S3-compatible object storage and bounded multipart writes. Its defaults are an 8 MiB small-object
threshold, 8 MiB multipart parts, and multipart concurrency of 2; multipart parts cannot be below
5 MiB and concurrency must be between 1 and 8. Multipart cleanup requires the S3 principal to allow
s3:ListBucketMultipartUploads and s3:AbortMultipartUpload. When using S3Adapter directly,
serialize put and delete calls for the same key; DMS-managed write and purge paths already do so.
Workers and external services
createDms() opens database resources but does not start a queue or workers. The embedding host
must configure the queue, start startWorkers, and monitor worker health separately. Production
also needs durable storage, malware scanning, OCR, Redis when queues are configured, process
supervision, and a graceful shutdown path. Do not use a request-serving process as a substitute
for durable worker operation.
Shutdown
Stop accepting host traffic, quiesce worker activity under host control, then call:
await dms.close();This destroys the Kysely database instances and ends every distinct underlying pool used by the
instance, including caller-supplied pools as well. Do not share a supplied pool with other live
components unless the host coordinates their shutdown around dms.close(). The host remains
responsible for closing queues, storage clients it created, and external service connections.
Public API and SemVer
The root export of lightweight-dms-core is the public API. Public names, type contracts, and
runtime behavior follow semantic versioning from 1.0.0 onward. Import only root exports, including
createDms, DmsConfig, DmsContext, StorageAdapter, LocalFsAdapter, S3Adapter,
ProvisionInput, SearchLanguage, queue and worker seams, and documented error classes.
Internal source paths and the private HTTP service are not part of this npm package contract.
Security reporting
Report vulnerabilities privately using the GitHub security advisory form. Do not include production credentials or personal data.
License
Apache-2.0. Third-party attributions are in NOTICE.
