@zudojs/database 0.0.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +223 -17
- package/dist/cache/cache.memory.d.ts +167 -0
- package/dist/cache/cache.memory.js +186 -40
- package/dist/cache/index.d.ts +2 -2
- package/dist/cache/index.js +2 -2
- package/dist/database/database.core.d.ts +91 -0
- package/dist/database/database.core.js +150 -0
- package/dist/databaseClient/databaseClient.core.d.ts +165 -14
- package/dist/databaseClient/databaseClient.core.js +304 -70
- package/dist/databaseClient/databaseClient.errors.d.ts +112 -0
- package/dist/databaseClient/databaseClient.errors.js +425 -0
- package/dist/databaseClient/index.d.ts +2 -1
- package/dist/databaseClient/index.js +2 -1
- package/dist/databaseConnection/databaseConnection.manager.d.ts +163 -0
- package/dist/databaseConnection/databaseConnection.manager.js +138 -76
- package/dist/databaseConnection/index.d.ts +1 -1
- package/dist/databaseType/databaseType.type.d.ts +224 -0
- package/dist/databaseType/databaseType.type.js +10 -0
- package/dist/health/health.check.d.ts +74 -0
- package/dist/health/health.check.js +78 -47
- package/dist/health/index.d.ts +1 -1
- package/dist/health/index.js +1 -1
- package/dist/index.d.ts +14 -14
- package/dist/index.js +11 -11
- package/dist/locks/index.d.ts +2 -2
- package/dist/locks/index.js +2 -2
- package/dist/locks/locks.core.d.ts +128 -0
- package/dist/locks/locks.core.js +144 -85
- package/dist/migration/index.d.ts +3 -1
- package/dist/migration/index.js +2 -0
- package/dist/migration/migration.dialect.d.ts +65 -0
- package/dist/migration/migration.dialect.js +63 -0
- package/dist/migration/migration.helpers.d.ts +65 -0
- package/dist/migration/migration.helpers.js +141 -0
- package/dist/migration/migration.runner.d.ts +75 -0
- package/dist/migration/migration.runner.js +195 -107
- package/dist/migration/migration.types.d.ts +85 -0
- package/dist/migration/migration.types.js +2 -0
- package/dist/pagination/index.d.ts +2 -1
- package/dist/pagination/index.js +2 -1
- package/dist/pagination/pagination.core.d.ts +163 -0
- package/dist/pagination/pagination.core.js +279 -0
- package/dist/pagination/pagination.keyset.d.ts +55 -0
- package/dist/pagination/pagination.keyset.js +108 -0
- package/dist/queryBuilder/index.d.ts +4 -3
- package/dist/queryBuilder/index.js +3 -2
- package/dist/queryBuilder/queryBuilder.core.d.ts +178 -0
- package/dist/queryBuilder/queryBuilder.core.js +420 -0
- package/dist/queryBuilder/queryBuilder.factory.d.ts +6 -0
- package/dist/queryBuilder/queryBuilder.factory.js +8 -0
- package/dist/queryBuilder/queryBuilder.filter.d.ts +170 -0
- package/dist/queryBuilder/queryBuilder.filter.js +497 -0
- package/dist/queryBuilder/queryBuilder.prisma.d.ts +60 -0
- package/dist/queryBuilder/queryBuilder.prisma.js +233 -0
- package/dist/queryBuilder/queryBuilder.type.d.ts +48 -0
- package/dist/queryBuilder/queryBuilder.type.js +2 -0
- package/dist/relations/index.d.ts +1 -1
- package/dist/relations/index.js +1 -1
- package/dist/relations/relations.definition.d.ts +184 -0
- package/dist/relations/relations.definition.js +362 -0
- package/dist/repository/index.d.ts +2 -1
- package/dist/repository/index.js +1 -0
- package/dist/repository/repository.base.d.ts +270 -0
- package/dist/repository/repository.base.js +350 -84
- package/dist/repository/repository.errors.d.ts +60 -0
- package/dist/repository/repository.errors.js +235 -0
- package/dist/seed/index.d.ts +2 -2
- package/dist/seed/index.js +1 -1
- package/dist/seed/seed.runner.d.ts +160 -0
- package/dist/seed/seed.runner.js +194 -225
- package/dist/transaction/index.d.ts +1 -1
- package/dist/transaction/index.js +1 -1
- package/dist/transaction/transaction.core.d.ts +117 -0
- package/dist/transaction/transaction.core.js +83 -56
- package/dist/unitOfWork/unitOfWork.core.d.ts +44 -0
- package/dist/unitOfWork/unitOfWork.core.js +9 -20
- package/package.json +35 -19
- package/dist/cache/cache.memory.d.ts.map +0 -1
- package/dist/cache/cache.memory.js.map +0 -1
- package/dist/cache/index.d.ts.map +0 -1
- package/dist/cache/index.js.map +0 -1
- package/dist/database/database.core.d.ts.map +0 -1
- package/dist/database/index.d.ts.map +0 -1
- package/dist/database/index.js.map +0 -1
- package/dist/databaseClient/databaseClient.core.d.ts.map +0 -1
- package/dist/databaseClient/databaseClient.core.js.map +0 -1
- package/dist/databaseClient/databaseClient.logger.d.ts.map +0 -1
- package/dist/databaseClient/databaseClient.logger.js.map +0 -1
- package/dist/databaseClient/index.d.ts.map +0 -1
- package/dist/databaseClient/index.js.map +0 -1
- package/dist/databaseConnection/databaseConnection.manager.d.ts.map +0 -1
- package/dist/databaseConnection/databaseConnection.manager.js.map +0 -1
- package/dist/databaseConnection/index.d.ts.map +0 -1
- package/dist/databaseConnection/index.js.map +0 -1
- package/dist/databaseType/index.d.ts.map +0 -1
- package/dist/databaseType/index.js.map +0 -1
- package/dist/health/health.check.d.ts.map +0 -1
- package/dist/health/health.check.js.map +0 -1
- package/dist/health/index.d.ts.map +0 -1
- package/dist/health/index.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/locks/index.d.ts.map +0 -1
- package/dist/locks/index.js.map +0 -1
- package/dist/locks/locks.core.d.ts.map +0 -1
- package/dist/locks/locks.core.js.map +0 -1
- package/dist/migration/index.d.ts.map +0 -1
- package/dist/migration/index.js.map +0 -1
- package/dist/migration/migration.helpers.d.ts.map +0 -1
- package/dist/migration/migration.runner.d.ts.map +0 -1
- package/dist/migration/migration.runner.js.map +0 -1
- package/dist/migration/migration.types.d.ts.map +0 -1
- package/dist/pagination/index.d.ts.map +0 -1
- package/dist/pagination/index.js.map +0 -1
- package/dist/queryBuilder/index.d.ts.map +0 -1
- package/dist/queryBuilder/index.js.map +0 -1
- package/dist/relations/index.d.ts.map +0 -1
- package/dist/relations/index.js.map +0 -1
- package/dist/repository/index.d.ts.map +0 -1
- package/dist/repository/index.js.map +0 -1
- package/dist/repository/repository.base.d.ts.map +0 -1
- package/dist/repository/repository.base.js.map +0 -1
- package/dist/seed/index.d.ts.map +0 -1
- package/dist/seed/index.js.map +0 -1
- package/dist/seed/seed.runner.d.ts.map +0 -1
- package/dist/seed/seed.runner.js.map +0 -1
- package/dist/transaction/index.d.ts.map +0 -1
- package/dist/transaction/index.js.map +0 -1
- package/dist/transaction/transaction.core.d.ts.map +0 -1
- package/dist/transaction/transaction.core.js.map +0 -1
- package/dist/unitOfWork/index.d.ts.map +0 -1
- package/dist/unitOfWork/index.js.map +0 -1
- package/dist/unitOfWork/unitOfWork.core.d.ts.map +0 -1
- package/dist/unitOfWork/unitOfWork.core.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,38 +1,244 @@
|
|
|
1
1
|
# @zudojs/database
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
PostgreSQL data-access layer for Zudojs applications, built on Prisma 7: a
|
|
4
|
+
lifecycle-aware client, generic repositories with soft delete, a query builder
|
|
5
|
+
that translates to Prisma `where` clauses, managed transactions, keyset
|
|
6
|
+
pagination with signed cursors, migration and seed runners guarded by advisory
|
|
7
|
+
locks, health checks with reconnect, and a bounded in-memory cache.
|
|
4
8
|
|
|
5
9
|
## Installation
|
|
6
10
|
|
|
7
11
|
```bash
|
|
8
|
-
npm install @zudojs/database
|
|
12
|
+
npm install @zudojs/database @prisma/client @prisma/adapter-pg
|
|
9
13
|
```
|
|
10
14
|
|
|
15
|
+
`@prisma/client` (`>=7 <8`) is a peer dependency. Prisma 7 connects through a
|
|
16
|
+
driver adapter, so you also need an adapter package for your database
|
|
17
|
+
(`@prisma/adapter-pg` for PostgreSQL).
|
|
18
|
+
|
|
11
19
|
## Quick Start
|
|
12
20
|
|
|
13
21
|
```typescript
|
|
14
|
-
import {
|
|
22
|
+
import { PrismaClient } from "@prisma/client";
|
|
23
|
+
import { PrismaPg } from "@prisma/adapter-pg";
|
|
24
|
+
import {
|
|
25
|
+
BaseRepository,
|
|
26
|
+
createDatabaseClient,
|
|
27
|
+
withTransaction,
|
|
28
|
+
} from "@zudojs/database";
|
|
29
|
+
|
|
30
|
+
interface User {
|
|
31
|
+
id: string;
|
|
32
|
+
email: string;
|
|
33
|
+
name: string;
|
|
34
|
+
deletedAt: Date | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 1. Build the Prisma client with a driver adapter and hand it to the wrapper.
|
|
38
|
+
const prisma = new PrismaClient({
|
|
39
|
+
adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }),
|
|
40
|
+
});
|
|
41
|
+
const client = createDatabaseClient({ prisma });
|
|
42
|
+
await client.connect();
|
|
43
|
+
|
|
44
|
+
// 2. Repositories wrap a Prisma model delegate.
|
|
45
|
+
class UserRepository extends BaseRepository<User> {
|
|
46
|
+
constructor(delegate: ConstructorParameters<typeof BaseRepository<User>>[0]) {
|
|
47
|
+
super(delegate, { modelName: "User", softDelete: true });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
findByEmail(email: string) {
|
|
51
|
+
return this.findOne({ email });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const users = new UserRepository(prisma.user);
|
|
56
|
+
const alice = await users.create({ email: "alice@example.com", name: "Alice" });
|
|
57
|
+
await users.softDelete(alice.id); // sets deletedAt; reads now skip the row
|
|
15
58
|
|
|
16
|
-
|
|
17
|
-
|
|
59
|
+
// 3. Transactions: rebind the repository to the transaction client.
|
|
60
|
+
await withTransaction(client, async (tx) => {
|
|
61
|
+
const txUsers = users.withTransaction(tx);
|
|
62
|
+
await txUsers.restore(alice.id);
|
|
63
|
+
await txUsers.update(alice.id, { name: "Alice Doe" });
|
|
18
64
|
});
|
|
19
65
|
|
|
20
|
-
|
|
66
|
+
await client.disconnect();
|
|
21
67
|
```
|
|
22
68
|
|
|
69
|
+
`createDatabaseClient` accepts either a pre-built `prisma` instance or an
|
|
70
|
+
`adapter` (in which case it constructs the `PrismaClient` for you). It throws a
|
|
71
|
+
`DatabaseError` if neither is supplied.
|
|
72
|
+
|
|
73
|
+
## Querying
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { createQueryBuilder, toPrismaWhere, and, equals, isAfter } from "@zudojs/database";
|
|
77
|
+
|
|
78
|
+
const query = createQueryBuilder<"email" | "createdAt" | "role">()
|
|
79
|
+
.where("role", "admin")
|
|
80
|
+
.whereContains("email", "@example.com")
|
|
81
|
+
.orderByDesc("createdAt")
|
|
82
|
+
.paginate({ page: 1, limit: 20 });
|
|
83
|
+
|
|
84
|
+
// Repositories consume the builder directly ...
|
|
85
|
+
const admins = await users.findByQuery(query);
|
|
86
|
+
|
|
87
|
+
// ... or translate the filter yourself.
|
|
88
|
+
const where = toPrismaWhere(
|
|
89
|
+
and(equals("role", "admin"), isAfter("createdAt", new Date("2026-01-01"))),
|
|
90
|
+
);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Pagination
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
// Offset pagination
|
|
97
|
+
const page = await users.findPaginated(undefined, {
|
|
98
|
+
pagination: { page: 2, limit: 25 },
|
|
99
|
+
sort: [{ field: "createdAt", direction: "desc" }],
|
|
100
|
+
});
|
|
101
|
+
page.meta.totalPages;
|
|
102
|
+
|
|
103
|
+
// Keyset pagination with HMAC-signed cursors (set `cursorSecret` on the repository)
|
|
104
|
+
const first = await users.paginateCursor(undefined, {
|
|
105
|
+
limit: 25,
|
|
106
|
+
sort: [{ field: "createdAt", direction: "desc" }],
|
|
107
|
+
});
|
|
108
|
+
const next = await users.paginateCursor(undefined, {
|
|
109
|
+
cursor: first.meta.nextCursor,
|
|
110
|
+
limit: 25,
|
|
111
|
+
sort: [{ field: "createdAt", direction: "desc" }],
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Migrations and seeds
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import { createMigrationRunner, createSeedRunner } from "@zudojs/database";
|
|
119
|
+
|
|
120
|
+
const migrations = createMigrationRunner(
|
|
121
|
+
client,
|
|
122
|
+
[
|
|
123
|
+
{
|
|
124
|
+
version: 1,
|
|
125
|
+
name: "create-users",
|
|
126
|
+
up: async (tx) => {
|
|
127
|
+
await tx.$executeRawUnsafe(
|
|
128
|
+
'CREATE TABLE "users" ("id" TEXT PRIMARY KEY, "email" TEXT UNIQUE NOT NULL)',
|
|
129
|
+
);
|
|
130
|
+
},
|
|
131
|
+
down: async (tx) => {
|
|
132
|
+
await tx.$executeRawUnsafe('DROP TABLE "users"');
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
{ transaction: { timeoutMs: 60_000 } },
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
await migrations.migrate(); // applies pending migrations under an advisory lock
|
|
140
|
+
await migrations.rollback(); // reverts the most recent one
|
|
141
|
+
|
|
142
|
+
const seeds = createSeedRunner(client, [
|
|
143
|
+
{
|
|
144
|
+
name: "roles",
|
|
145
|
+
order: 0,
|
|
146
|
+
run: async (tx) => {
|
|
147
|
+
await tx.$executeRawUnsafe('INSERT INTO "roles" ("name") VALUES ($1)', "admin");
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
]);
|
|
151
|
+
await seeds.run();
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Each migration or seed runs in its own transaction by default
|
|
155
|
+
(`perItemTransaction: true`); pass `transaction: { timeoutMs, maxWaitMs, isolationLevel }`
|
|
156
|
+
to size it for long-running steps.
|
|
157
|
+
|
|
158
|
+
## Errors
|
|
159
|
+
|
|
160
|
+
Every failure surfaces as a `DatabaseError` from `@zudojs/errors`. Prisma codes
|
|
161
|
+
are mapped to `databaseCode`, an `ErrorCode` and an HTTP status (`P2002` /
|
|
162
|
+
`P2003` → 409, `P2025` → 404, `P2034` → retryable, `P1xxx` → 503 with a fixed
|
|
163
|
+
message that never includes the host name).
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
import {
|
|
167
|
+
isConflictError,
|
|
168
|
+
isNotFoundError,
|
|
169
|
+
isRetryableTransactionError,
|
|
170
|
+
toDatabaseErrorInfo,
|
|
171
|
+
} from "@zudojs/database";
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
await users.create({ email: "alice@example.com", name: "Alice" });
|
|
175
|
+
} catch (error) {
|
|
176
|
+
if (isConflictError(error)) {
|
|
177
|
+
// 409: unique or foreign-key violation
|
|
178
|
+
} else if (isNotFoundError(error)) {
|
|
179
|
+
// 404
|
|
180
|
+
} else if (!isRetryableTransactionError(error)) {
|
|
181
|
+
console.error(toDatabaseErrorInfo(error)); // { code: "P2002", message, operation, ... }
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Locks and health
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { assertDatabaseHealth, checkDatabaseHealth, createLockManager } from "@zudojs/database";
|
|
190
|
+
|
|
191
|
+
const locks = createLockManager(client);
|
|
192
|
+
|
|
193
|
+
// Advisory lock (FNV-1a 64 key, transaction-scoped). `timeoutMs` becomes
|
|
194
|
+
// `SET LOCAL lock_timeout`; the transaction timeout is raised to cover it.
|
|
195
|
+
await locks.withAdvisoryLock("reports:nightly", async (tx) => {
|
|
196
|
+
await tx.$executeRawUnsafe("REFRESH MATERIALIZED VIEW nightly_report");
|
|
197
|
+
}, { timeoutMs: 10_000 });
|
|
198
|
+
|
|
199
|
+
// Row lock: throws 404 when the row is missing, 409 when skipLocked skips it.
|
|
200
|
+
await locks.withRowLock("orders", orderId, async (tx) => {
|
|
201
|
+
await tx.$executeRawUnsafe('UPDATE "orders" SET "status" = $1 WHERE "id" = $2', "paid", orderId);
|
|
202
|
+
}, { mode: "for-no-key-update", skipLocked: true });
|
|
203
|
+
|
|
204
|
+
const health = await checkDatabaseHealth(client, { timeoutMs: 2_000 });
|
|
205
|
+
health.status; // "healthy" | "degraded" | "unhealthy"
|
|
206
|
+
await assertDatabaseHealth(client); // throws DatabaseUnhealthyError with the real cause
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Subpath exports
|
|
210
|
+
|
|
211
|
+
| Import | Contents |
|
|
212
|
+
| --- | --- |
|
|
213
|
+
| `@zudojs/database` | Everything |
|
|
214
|
+
| `@zudojs/database/client` | `DatabaseClient`, `createDatabaseClient`, error helpers |
|
|
215
|
+
| `@zudojs/database/repositories` | `BaseRepository`, `mapRepositoryError` |
|
|
216
|
+
| `@zudojs/database/transactions` | `TransactionManager`, `withTransaction`, `withTransactionRetry` |
|
|
217
|
+
|
|
23
218
|
## Features
|
|
24
219
|
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
220
|
+
- Prisma 7 client wrapper with connection lifecycle, in-flight connect de-duplication, raw queries, and typed error normalisation (Prisma `P*` codes mapped to conflict / not-found / timeout / connection outcomes)
|
|
221
|
+
- `BaseRepository` with CRUD, `createMany` / `deleteMany`, soft delete (`softDelete`, `restore`, `findDeleted`, `withDeleted`), transaction rebinding (`withTransaction`), and `findByQuery`
|
|
222
|
+
- Query builder and 40 filter helpers that translate to Prisma `where` / `orderBy` / `select` / `include`
|
|
223
|
+
- Managed transactions with context, error enrichment, and `withTransactionRetry` (serialization failures retried by default)
|
|
224
|
+
- Offset pagination and keyset pagination with HMAC-signed, shape-validated cursors
|
|
225
|
+
- Migration and seed runners using PostgreSQL advisory locks, per-item transactions, and BIGINT versioning
|
|
226
|
+
- Advisory and row locks with `lock_timeout`, `SKIP LOCKED` / `NOWAIT`, and namespaced keys
|
|
227
|
+
- Health and readiness probes with timeouts; connection manager with overlap-guarded scheduled checks and reconnect back-off
|
|
228
|
+
- Bounded in-memory LRU cache with TTL, periodic pruning, and loader coalescing
|
|
229
|
+
- Relation definitions with a registry and `toPrismaInclude` (depth and cycle guarded)
|
|
230
|
+
|
|
231
|
+
## Limitations
|
|
232
|
+
|
|
233
|
+
- PostgreSQL only. Migration, seed, and lock helpers emit PostgreSQL SQL; other dialects throw `UnsupportedDialectError`.
|
|
234
|
+
- No savepoints or nested transactions. Prisma interactive transactions are used as-is.
|
|
235
|
+
- Connection pooling is handled by the driver adapter, not by this package.
|
|
236
|
+
- `timeoutMs` and `signal` on repository operations are client-side only; the database query is not cancelled server-side.
|
|
237
|
+
- The cache is not transaction-aware. Do not populate it from inside a transaction that may roll back.
|
|
32
238
|
|
|
33
239
|
## Use Cases
|
|
34
240
|
|
|
35
|
-
- Data access layer for
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
241
|
+
- Data access layer for Zudojs services
|
|
242
|
+
- Transaction coordination across repositories (rebind with `withTransaction`)
|
|
243
|
+
- Database migrations and seeding with single-runner guarantees
|
|
244
|
+
- Health endpoints and readiness gating
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache entry stored by the database cache.
|
|
3
|
+
*/
|
|
4
|
+
export interface CacheEntry<TValue> {
|
|
5
|
+
readonly value: TValue;
|
|
6
|
+
readonly createdAt: number;
|
|
7
|
+
readonly expiresAt?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Options for a cache operation.
|
|
11
|
+
*/
|
|
12
|
+
export interface CacheOptions {
|
|
13
|
+
readonly ttlMs?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Options for constructing a {@link MemoryDatabaseCache}.
|
|
17
|
+
*/
|
|
18
|
+
export interface MemoryCacheOptions extends CacheOptions {
|
|
19
|
+
/**
|
|
20
|
+
* Maximum number of entries. When exceeded the least recently used entry
|
|
21
|
+
* is evicted. Unbounded when omitted.
|
|
22
|
+
*/
|
|
23
|
+
readonly maxEntries?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Interval at which expired entries are pruned in the background. The
|
|
26
|
+
* timer is `unref`'d so it never keeps the process alive. Disabled when
|
|
27
|
+
* omitted; expired entries are still removed lazily on access.
|
|
28
|
+
*/
|
|
29
|
+
readonly pruneIntervalMs?: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Statistics exposed by the cache.
|
|
33
|
+
*/
|
|
34
|
+
export interface CacheStats {
|
|
35
|
+
readonly size: number;
|
|
36
|
+
readonly hits: number;
|
|
37
|
+
readonly misses: number;
|
|
38
|
+
readonly hitRate: number;
|
|
39
|
+
readonly evictions: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Generic cache contract.
|
|
43
|
+
*/
|
|
44
|
+
export interface DatabaseCache<TValue = unknown> {
|
|
45
|
+
get(key: string): TValue | undefined;
|
|
46
|
+
set(key: string, value: TValue, options?: CacheOptions): void;
|
|
47
|
+
has(key: string): boolean;
|
|
48
|
+
delete(key: string): boolean;
|
|
49
|
+
clear(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Returns all currently valid keys. Optional; required by
|
|
52
|
+
* {@link invalidateByPrefix}.
|
|
53
|
+
*/
|
|
54
|
+
keys?(): readonly string[];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* In-memory LRU cache for database read results.
|
|
58
|
+
*
|
|
59
|
+
* This cache is process-local and is not transaction-aware: never populate
|
|
60
|
+
* it from inside a transaction that may still roll back, because other
|
|
61
|
+
* callers would observe the uncommitted value.
|
|
62
|
+
*/
|
|
63
|
+
export declare class MemoryDatabaseCache<TValue = unknown> implements DatabaseCache<TValue> {
|
|
64
|
+
private readonly entries;
|
|
65
|
+
private hits;
|
|
66
|
+
private misses;
|
|
67
|
+
private evictions;
|
|
68
|
+
private readonly defaultTtlMs?;
|
|
69
|
+
private readonly maxEntries?;
|
|
70
|
+
private pruneTimer?;
|
|
71
|
+
constructor(options?: MemoryCacheOptions);
|
|
72
|
+
/**
|
|
73
|
+
* Gets a cached value and marks it as recently used.
|
|
74
|
+
*/
|
|
75
|
+
get(key: string): TValue | undefined;
|
|
76
|
+
/**
|
|
77
|
+
* Sets a cached value, evicting the least recently used entry when the
|
|
78
|
+
* cache is full.
|
|
79
|
+
*/
|
|
80
|
+
set(key: string, value: TValue, options?: CacheOptions): void;
|
|
81
|
+
/**
|
|
82
|
+
* Checks whether a valid cached value exists (does not touch recency).
|
|
83
|
+
*/
|
|
84
|
+
has(key: string): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Deletes a cache entry.
|
|
87
|
+
*/
|
|
88
|
+
delete(key: string): boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Clears the entire cache.
|
|
91
|
+
*/
|
|
92
|
+
clear(): void;
|
|
93
|
+
/**
|
|
94
|
+
* Removes expired entries.
|
|
95
|
+
*/
|
|
96
|
+
prune(): number;
|
|
97
|
+
/**
|
|
98
|
+
* Stops the background prune timer.
|
|
99
|
+
*/
|
|
100
|
+
dispose(): void;
|
|
101
|
+
/**
|
|
102
|
+
* Returns the number of valid entries.
|
|
103
|
+
*/
|
|
104
|
+
get size(): number;
|
|
105
|
+
/**
|
|
106
|
+
* Returns cache statistics.
|
|
107
|
+
*/
|
|
108
|
+
getStats(): CacheStats;
|
|
109
|
+
/**
|
|
110
|
+
* Resets hit/miss/eviction counters.
|
|
111
|
+
*/
|
|
112
|
+
resetStats(): void;
|
|
113
|
+
/**
|
|
114
|
+
* Returns all currently valid cache keys (least recently used first).
|
|
115
|
+
*/
|
|
116
|
+
keys(): readonly string[];
|
|
117
|
+
private evictOverflow;
|
|
118
|
+
private startPruning;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Creates an in-memory database cache.
|
|
122
|
+
*/
|
|
123
|
+
export declare function createDatabaseCache<TValue = unknown>(options?: MemoryCacheOptions): MemoryDatabaseCache<TValue>;
|
|
124
|
+
/**
|
|
125
|
+
* Separator used between cache key parts.
|
|
126
|
+
*/
|
|
127
|
+
export declare const CACHE_KEY_SEPARATOR = ":";
|
|
128
|
+
/**
|
|
129
|
+
* Builds a stable cache key from a namespace and parts.
|
|
130
|
+
*
|
|
131
|
+
* Each part is serialised deterministically (nested keys sorted at every
|
|
132
|
+
* level). Plain string parts are escaped so that a separator inside a part
|
|
133
|
+
* cannot collide with the part boundary: `("ns", "a:b", "c")` and
|
|
134
|
+
* `("ns", "a", "b:c")` produce different keys; serialised objects are
|
|
135
|
+
* self-delimiting and are left as-is. The namespace itself is used verbatim
|
|
136
|
+
* so it can be used as a prefix with {@link invalidateByPrefix}.
|
|
137
|
+
*/
|
|
138
|
+
export declare function createCacheKey(namespace: string, ...parts: readonly unknown[]): string;
|
|
139
|
+
/**
|
|
140
|
+
* Escapes separators and backslashes inside a key part.
|
|
141
|
+
*/
|
|
142
|
+
export declare function escapeCachePart(part: string): string;
|
|
143
|
+
/**
|
|
144
|
+
* Serializes a cache key component deterministically.
|
|
145
|
+
*
|
|
146
|
+
* Objects are serialised with keys sorted at every nesting level;
|
|
147
|
+
* `bigint`, `Date`, `undefined`, `Map` and `Set` are handled explicitly.
|
|
148
|
+
* Functions and symbols cannot be part of a cache key and throw.
|
|
149
|
+
*/
|
|
150
|
+
export declare function serializeCachePart(value: unknown): string;
|
|
151
|
+
/**
|
|
152
|
+
* Wraps a cache around an asynchronous loader.
|
|
153
|
+
*
|
|
154
|
+
* Concurrent misses for the same key on the same cache share one loader
|
|
155
|
+
* call (stampede protection). Values are stored only when the loader
|
|
156
|
+
* resolves; `undefined` results are never cached.
|
|
157
|
+
*/
|
|
158
|
+
export declare function getOrSet<TValue>(cache: DatabaseCache<TValue>, key: string, loader: () => Promise<TValue>, options?: CacheOptions): Promise<TValue>;
|
|
159
|
+
/**
|
|
160
|
+
* Invalidates all entries whose keys start with a prefix.
|
|
161
|
+
*
|
|
162
|
+
* Matching is separator-aware: `"user"` matches `"user"` and `"user:…"`
|
|
163
|
+
* but not `"users:…"`. Pass a prefix ending in the separator to match a
|
|
164
|
+
* namespace only.
|
|
165
|
+
*/
|
|
166
|
+
export declare function invalidateByPrefix(cache: DatabaseCache, prefix: string): number;
|
|
167
|
+
//# sourceMappingURL=cache.memory.d.ts.map
|