@zudojs/database 0.1.0 → 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.
Files changed (150) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +223 -17
  3. package/dist/cache/cache.memory.d.ts +167 -0
  4. package/dist/cache/cache.memory.js +186 -40
  5. package/dist/cache/index.d.ts +2 -2
  6. package/dist/cache/index.js +2 -2
  7. package/dist/database/database.core.d.ts +91 -0
  8. package/dist/database/database.core.js +21 -4
  9. package/dist/databaseClient/databaseClient.core.d.ts +165 -14
  10. package/dist/databaseClient/databaseClient.core.js +304 -70
  11. package/dist/databaseClient/databaseClient.errors.d.ts +112 -0
  12. package/dist/databaseClient/databaseClient.errors.js +425 -0
  13. package/dist/databaseClient/index.d.ts +2 -1
  14. package/dist/databaseClient/index.js +2 -1
  15. package/dist/databaseConnection/databaseConnection.manager.d.ts +163 -0
  16. package/dist/databaseConnection/databaseConnection.manager.js +138 -76
  17. package/dist/databaseConnection/index.d.ts +1 -1
  18. package/dist/databaseType/databaseType.type.d.ts +224 -0
  19. package/dist/databaseType/databaseType.type.js +0 -35
  20. package/dist/health/health.check.d.ts +74 -0
  21. package/dist/health/health.check.js +78 -47
  22. package/dist/health/index.d.ts +1 -1
  23. package/dist/health/index.js +1 -1
  24. package/dist/index.d.ts +14 -14
  25. package/dist/index.js +11 -11
  26. package/dist/locks/index.d.ts +2 -2
  27. package/dist/locks/index.js +2 -2
  28. package/dist/locks/locks.core.d.ts +128 -0
  29. package/dist/locks/locks.core.js +144 -85
  30. package/dist/migration/index.d.ts +3 -1
  31. package/dist/migration/index.js +2 -0
  32. package/dist/migration/migration.dialect.d.ts +65 -0
  33. package/dist/migration/migration.dialect.js +63 -0
  34. package/dist/migration/migration.helpers.d.ts +65 -0
  35. package/dist/migration/migration.helpers.js +60 -18
  36. package/dist/migration/migration.runner.d.ts +75 -0
  37. package/dist/migration/migration.runner.js +195 -107
  38. package/dist/migration/migration.types.d.ts +85 -0
  39. package/dist/pagination/index.d.ts +2 -1
  40. package/dist/pagination/index.js +2 -1
  41. package/dist/pagination/pagination.core.d.ts +163 -0
  42. package/dist/pagination/pagination.core.js +78 -9
  43. package/dist/pagination/pagination.keyset.d.ts +55 -0
  44. package/dist/pagination/pagination.keyset.js +108 -0
  45. package/dist/queryBuilder/index.d.ts +4 -3
  46. package/dist/queryBuilder/index.js +3 -2
  47. package/dist/queryBuilder/queryBuilder.core.d.ts +178 -0
  48. package/dist/queryBuilder/queryBuilder.core.js +92 -14
  49. package/dist/queryBuilder/queryBuilder.factory.d.ts +6 -0
  50. package/dist/queryBuilder/queryBuilder.factory.js +0 -25
  51. package/dist/queryBuilder/queryBuilder.filter.d.ts +170 -0
  52. package/dist/queryBuilder/queryBuilder.filter.js +191 -30
  53. package/dist/queryBuilder/queryBuilder.prisma.d.ts +60 -0
  54. package/dist/queryBuilder/queryBuilder.prisma.js +233 -0
  55. package/dist/queryBuilder/queryBuilder.type.d.ts +48 -0
  56. package/dist/relations/index.d.ts +1 -1
  57. package/dist/relations/index.js +1 -1
  58. package/dist/relations/relations.definition.d.ts +184 -0
  59. package/dist/relations/relations.definition.js +186 -19
  60. package/dist/repository/index.d.ts +2 -1
  61. package/dist/repository/index.js +1 -0
  62. package/dist/repository/repository.base.d.ts +270 -0
  63. package/dist/repository/repository.base.js +350 -84
  64. package/dist/repository/repository.errors.d.ts +60 -0
  65. package/dist/repository/repository.errors.js +235 -0
  66. package/dist/seed/index.d.ts +2 -2
  67. package/dist/seed/index.js +1 -1
  68. package/dist/seed/seed.runner.d.ts +160 -0
  69. package/dist/seed/seed.runner.js +194 -225
  70. package/dist/transaction/index.d.ts +1 -1
  71. package/dist/transaction/index.js +1 -1
  72. package/dist/transaction/transaction.core.d.ts +117 -0
  73. package/dist/transaction/transaction.core.js +83 -56
  74. package/dist/unitOfWork/unitOfWork.core.d.ts +44 -0
  75. package/dist/unitOfWork/unitOfWork.core.js +9 -20
  76. package/package.json +40 -24
  77. package/dist/cache/cache.memory.d.ts.map +0 -1
  78. package/dist/cache/cache.memory.js.map +0 -1
  79. package/dist/cache/index.d.ts.map +0 -1
  80. package/dist/cache/index.js.map +0 -1
  81. package/dist/database/database.core.d.ts.map +0 -1
  82. package/dist/database/database.core.js.map +0 -1
  83. package/dist/database/index.d.ts.map +0 -1
  84. package/dist/database/index.js.map +0 -1
  85. package/dist/databaseClient/databaseClient.core.d.ts.map +0 -1
  86. package/dist/databaseClient/databaseClient.core.js.map +0 -1
  87. package/dist/databaseClient/databaseClient.logger.d.ts.map +0 -1
  88. package/dist/databaseClient/databaseClient.logger.js.map +0 -1
  89. package/dist/databaseClient/index.d.ts.map +0 -1
  90. package/dist/databaseClient/index.js.map +0 -1
  91. package/dist/databaseConnection/databaseConnection.manager.d.ts.map +0 -1
  92. package/dist/databaseConnection/databaseConnection.manager.js.map +0 -1
  93. package/dist/databaseConnection/index.d.ts.map +0 -1
  94. package/dist/databaseConnection/index.js.map +0 -1
  95. package/dist/databaseType/databaseType.type.d.ts.map +0 -1
  96. package/dist/databaseType/databaseType.type.js.map +0 -1
  97. package/dist/databaseType/index.d.ts.map +0 -1
  98. package/dist/databaseType/index.js.map +0 -1
  99. package/dist/health/health.check.d.ts.map +0 -1
  100. package/dist/health/health.check.js.map +0 -1
  101. package/dist/health/index.d.ts.map +0 -1
  102. package/dist/health/index.js.map +0 -1
  103. package/dist/index.d.ts.map +0 -1
  104. package/dist/index.js.map +0 -1
  105. package/dist/locks/index.d.ts.map +0 -1
  106. package/dist/locks/index.js.map +0 -1
  107. package/dist/locks/locks.core.d.ts.map +0 -1
  108. package/dist/locks/locks.core.js.map +0 -1
  109. package/dist/migration/index.d.ts.map +0 -1
  110. package/dist/migration/index.js.map +0 -1
  111. package/dist/migration/migration.helpers.d.ts.map +0 -1
  112. package/dist/migration/migration.helpers.js.map +0 -1
  113. package/dist/migration/migration.runner.d.ts.map +0 -1
  114. package/dist/migration/migration.runner.js.map +0 -1
  115. package/dist/migration/migration.types.d.ts.map +0 -1
  116. package/dist/migration/migration.types.js.map +0 -1
  117. package/dist/pagination/index.d.ts.map +0 -1
  118. package/dist/pagination/index.js.map +0 -1
  119. package/dist/pagination/pagination.core.d.ts.map +0 -1
  120. package/dist/pagination/pagination.core.js.map +0 -1
  121. package/dist/queryBuilder/index.d.ts.map +0 -1
  122. package/dist/queryBuilder/index.js.map +0 -1
  123. package/dist/queryBuilder/queryBuilder.core.d.ts.map +0 -1
  124. package/dist/queryBuilder/queryBuilder.core.js.map +0 -1
  125. package/dist/queryBuilder/queryBuilder.factory.d.ts.map +0 -1
  126. package/dist/queryBuilder/queryBuilder.factory.js.map +0 -1
  127. package/dist/queryBuilder/queryBuilder.filter.d.ts.map +0 -1
  128. package/dist/queryBuilder/queryBuilder.filter.js.map +0 -1
  129. package/dist/queryBuilder/queryBuilder.type.d.ts.map +0 -1
  130. package/dist/queryBuilder/queryBuilder.type.js.map +0 -1
  131. package/dist/relations/index.d.ts.map +0 -1
  132. package/dist/relations/index.js.map +0 -1
  133. package/dist/relations/relations.definition.d.ts.map +0 -1
  134. package/dist/relations/relations.definition.js.map +0 -1
  135. package/dist/repository/index.d.ts.map +0 -1
  136. package/dist/repository/index.js.map +0 -1
  137. package/dist/repository/repository.base.d.ts.map +0 -1
  138. package/dist/repository/repository.base.js.map +0 -1
  139. package/dist/seed/index.d.ts.map +0 -1
  140. package/dist/seed/index.js.map +0 -1
  141. package/dist/seed/seed.runner.d.ts.map +0 -1
  142. package/dist/seed/seed.runner.js.map +0 -1
  143. package/dist/transaction/index.d.ts.map +0 -1
  144. package/dist/transaction/index.js.map +0 -1
  145. package/dist/transaction/transaction.core.d.ts.map +0 -1
  146. package/dist/transaction/transaction.core.js.map +0 -1
  147. package/dist/unitOfWork/index.d.ts.map +0 -1
  148. package/dist/unitOfWork/index.js.map +0 -1
  149. package/dist/unitOfWork/unitOfWork.core.d.ts.map +0 -1
  150. package/dist/unitOfWork/unitOfWork.core.js.map +0 -1
@@ -1,20 +1,26 @@
1
1
  import { DatabaseError } from "@zudojs/errors";
2
2
  /**
3
- * In-memory cache implementation for database read results.
3
+ * In-memory LRU cache for database read results.
4
4
  *
5
- * This cache is intentionally process-local. It should not be used
6
- * where multiple application instances require shared cache state.
5
+ * This cache is process-local and is not transaction-aware: never populate
6
+ * it from inside a transaction that may still roll back, because other
7
+ * callers would observe the uncommitted value.
7
8
  */
8
9
  export class MemoryDatabaseCache {
9
10
  entries = new Map();
10
11
  hits = 0;
11
12
  misses = 0;
13
+ evictions = 0;
12
14
  defaultTtlMs;
15
+ maxEntries;
16
+ pruneTimer;
13
17
  constructor(options = {}) {
14
18
  this.defaultTtlMs = normalizeTtl(options.ttlMs);
19
+ this.maxEntries = normalizeMaxEntries(options.maxEntries);
20
+ this.startPruning(options.pruneIntervalMs);
15
21
  }
16
22
  /**
17
- * Gets a cached value.
23
+ * Gets a cached value and marks it as recently used.
18
24
  */
19
25
  get(key) {
20
26
  validateKey(key);
@@ -28,31 +34,36 @@ export class MemoryDatabaseCache {
28
34
  this.misses += 1;
29
35
  return undefined;
30
36
  }
37
+ // Re-insert to move the key to the most-recently-used position.
38
+ this.entries.delete(key);
39
+ this.entries.set(key, entry);
31
40
  this.hits += 1;
32
41
  return entry.value;
33
42
  }
34
43
  /**
35
- * Sets a cached value.
44
+ * Sets a cached value, evicting the least recently used entry when the
45
+ * cache is full.
36
46
  */
37
47
  set(key, value, options = {}) {
38
48
  validateKey(key);
39
49
  const ttlMs = normalizeTtl(options.ttlMs ?? this.defaultTtlMs);
40
50
  const createdAt = Date.now();
51
+ this.entries.delete(key);
41
52
  this.entries.set(key, {
42
53
  value,
43
54
  createdAt,
44
55
  expiresAt: ttlMs === undefined ? undefined : createdAt + ttlMs,
45
56
  });
57
+ this.evictOverflow();
46
58
  }
47
59
  /**
48
- * Checks whether a valid cached value exists.
60
+ * Checks whether a valid cached value exists (does not touch recency).
49
61
  */
50
62
  has(key) {
51
63
  validateKey(key);
52
64
  const entry = this.entries.get(key);
53
- if (!entry) {
65
+ if (!entry)
54
66
  return false;
55
- }
56
67
  if (isExpired(entry)) {
57
68
  this.entries.delete(key);
58
69
  return false;
@@ -85,6 +96,15 @@ export class MemoryDatabaseCache {
85
96
  }
86
97
  return removed;
87
98
  }
99
+ /**
100
+ * Stops the background prune timer.
101
+ */
102
+ dispose() {
103
+ if (this.pruneTimer) {
104
+ clearInterval(this.pruneTimer);
105
+ this.pruneTimer = undefined;
106
+ }
107
+ }
88
108
  /**
89
109
  * Returns the number of valid entries.
90
110
  */
@@ -103,22 +123,46 @@ export class MemoryDatabaseCache {
103
123
  hits: this.hits,
104
124
  misses: this.misses,
105
125
  hitRate: total === 0 ? 0 : this.hits / total,
126
+ evictions: this.evictions,
106
127
  };
107
128
  }
108
129
  /**
109
- * Resets hit/miss counters.
130
+ * Resets hit/miss/eviction counters.
110
131
  */
111
132
  resetStats() {
112
133
  this.hits = 0;
113
134
  this.misses = 0;
135
+ this.evictions = 0;
114
136
  }
115
137
  /**
116
- * Returns all currently valid cache keys.
138
+ * Returns all currently valid cache keys (least recently used first).
117
139
  */
118
140
  keys() {
119
141
  this.prune();
120
142
  return Object.freeze([...this.entries.keys()]);
121
143
  }
144
+ evictOverflow() {
145
+ if (this.maxEntries === undefined)
146
+ return;
147
+ while (this.entries.size > this.maxEntries) {
148
+ const oldest = this.entries.keys().next();
149
+ if (oldest.done)
150
+ break;
151
+ this.entries.delete(oldest.value);
152
+ this.evictions += 1;
153
+ }
154
+ }
155
+ startPruning(intervalMs) {
156
+ if (intervalMs === undefined)
157
+ return;
158
+ if (!Number.isFinite(intervalMs) || intervalMs <= 0) {
159
+ throw new TypeError("Cache prune interval must be a positive finite number.");
160
+ }
161
+ this.pruneTimer = setInterval(() => {
162
+ this.prune();
163
+ }, Math.floor(intervalMs));
164
+ this.pruneTimer.unref?.();
165
+ }
122
166
  }
123
167
  /**
124
168
  * Creates an in-memory database cache.
@@ -126,34 +170,51 @@ export class MemoryDatabaseCache {
126
170
  export function createDatabaseCache(options = {}) {
127
171
  return new MemoryDatabaseCache(options);
128
172
  }
173
+ /**
174
+ * Separator used between cache key parts.
175
+ */
176
+ export const CACHE_KEY_SEPARATOR = ":";
129
177
  /**
130
178
  * Builds a stable cache key from a namespace and parts.
179
+ *
180
+ * Each part is serialised deterministically (nested keys sorted at every
181
+ * level). Plain string parts are escaped so that a separator inside a part
182
+ * cannot collide with the part boundary: `("ns", "a:b", "c")` and
183
+ * `("ns", "a", "b:c")` produce different keys; serialised objects are
184
+ * self-delimiting and are left as-is. The namespace itself is used verbatim
185
+ * so it can be used as a prefix with {@link invalidateByPrefix}.
131
186
  */
132
187
  export function createCacheKey(namespace, ...parts) {
133
188
  validateKey(namespace);
134
- return [namespace, ...parts.map(serializeCachePart)].join(":");
189
+ return [
190
+ namespace,
191
+ ...parts.map((part) => typeof part === "string"
192
+ ? escapeCachePart(part)
193
+ : serializeCachePart(part)),
194
+ ].join(CACHE_KEY_SEPARATOR);
195
+ }
196
+ /**
197
+ * Escapes separators and backslashes inside a key part.
198
+ */
199
+ export function escapeCachePart(part) {
200
+ return part.replace(/\\/g, "\\\\").replace(/:/g, "\\:");
135
201
  }
136
202
  /**
137
203
  * Serializes a cache key component deterministically.
204
+ *
205
+ * Objects are serialised with keys sorted at every nesting level;
206
+ * `bigint`, `Date`, `undefined`, `Map` and `Set` are handled explicitly.
207
+ * Functions and symbols cannot be part of a cache key and throw.
138
208
  */
139
209
  export function serializeCachePart(value) {
140
- if (value === null) {
141
- return "null";
142
- }
143
- if (value === undefined) {
144
- return "undefined";
145
- }
146
210
  if (typeof value === "string" ||
147
211
  typeof value === "number" ||
148
212
  typeof value === "boolean" ||
149
213
  typeof value === "bigint") {
150
214
  return String(value);
151
215
  }
152
- if (value instanceof Date) {
153
- return value.toISOString();
154
- }
155
216
  try {
156
- return JSON.stringify(value, Object.keys(value).sort());
217
+ return stableSerialize(value, new Set());
157
218
  }
158
219
  catch (error) {
159
220
  throw new DatabaseError("Unable to serialize database cache key.", {
@@ -161,8 +222,70 @@ export function serializeCachePart(value) {
161
222
  });
162
223
  }
163
224
  }
225
+ function stableSerialize(value, seen) {
226
+ if (value === null)
227
+ return "null";
228
+ if (value === undefined)
229
+ return "undefined";
230
+ switch (typeof value) {
231
+ case "string":
232
+ return JSON.stringify(value);
233
+ case "number":
234
+ return Number.isFinite(value) ? String(value) : JSON.stringify(String(value));
235
+ case "boolean":
236
+ return String(value);
237
+ case "bigint":
238
+ return `${value.toString()}n`;
239
+ case "function":
240
+ case "symbol":
241
+ throw new TypeError(`Cannot serialize a ${typeof value} as a cache key part.`);
242
+ default:
243
+ break;
244
+ }
245
+ if (value instanceof Date) {
246
+ return Number.isNaN(value.getTime()) ? '"Invalid Date"' : JSON.stringify(value.toISOString());
247
+ }
248
+ if (typeof value === "object") {
249
+ if (seen.has(value)) {
250
+ throw new TypeError("Cannot serialize a circular structure as a cache key part.");
251
+ }
252
+ seen.add(value);
253
+ try {
254
+ if (Array.isArray(value)) {
255
+ return `[${value.map((item) => stableSerialize(item, seen)).join(",")}]`;
256
+ }
257
+ if (value instanceof Set) {
258
+ return `Set[${[...value].map((item) => stableSerialize(item, seen)).sort().join(",")}]`;
259
+ }
260
+ if (value instanceof Map) {
261
+ const entries = [...value.entries()]
262
+ .map(([k, v]) => `${stableSerialize(k, seen)}=>${stableSerialize(v, seen)}`)
263
+ .sort();
264
+ return `Map{${entries.join(",")}}`;
265
+ }
266
+ if (typeof value.toJSON === "function") {
267
+ return stableSerialize(value.toJSON(), seen);
268
+ }
269
+ const record = value;
270
+ const keys = Object.keys(record).sort();
271
+ const fields = keys
272
+ .filter((key) => record[key] !== undefined)
273
+ .map((key) => `${JSON.stringify(key)}:${stableSerialize(record[key], seen)}`);
274
+ return `{${fields.join(",")}}`;
275
+ }
276
+ finally {
277
+ seen.delete(value);
278
+ }
279
+ }
280
+ return JSON.stringify(String(value));
281
+ }
282
+ const inflightLoaders = new WeakMap();
164
283
  /**
165
284
  * Wraps a cache around an asynchronous loader.
285
+ *
286
+ * Concurrent misses for the same key on the same cache share one loader
287
+ * call (stampede protection). Values are stored only when the loader
288
+ * resolves; `undefined` results are never cached.
166
289
  */
167
290
  export async function getOrSet(cache, key, loader, options) {
168
291
  validateKey(key);
@@ -170,43 +293,61 @@ export async function getOrSet(cache, key, loader, options) {
170
293
  throw new TypeError("A cache loader function is required.");
171
294
  }
172
295
  const cached = cache.get(key);
173
- if (cached !== undefined) {
296
+ if (cached !== undefined)
174
297
  return cached;
298
+ let inflight = inflightLoaders.get(cache);
299
+ if (!inflight) {
300
+ inflight = new Map();
301
+ inflightLoaders.set(cache, inflight);
175
302
  }
176
- const value = await loader();
177
- cache.set(key, value, options);
178
- return value;
303
+ const pending = inflight.get(key);
304
+ if (pending)
305
+ return pending;
306
+ const promise = (async () => {
307
+ try {
308
+ const value = await loader();
309
+ if (value !== undefined)
310
+ cache.set(key, value, options);
311
+ return value;
312
+ }
313
+ finally {
314
+ inflight.delete(key);
315
+ }
316
+ })();
317
+ inflight.set(key, promise);
318
+ return promise;
179
319
  }
180
320
  /**
181
321
  * Invalidates all entries whose keys start with a prefix.
182
322
  *
183
- * This is useful when a write invalidates a group of related reads.
323
+ * Matching is separator-aware: `"user"` matches `"user"` and `"user:…"`
324
+ * but not `"users:…"`. Pass a prefix ending in the separator to match a
325
+ * namespace only.
184
326
  */
185
327
  export function invalidateByPrefix(cache, prefix) {
186
328
  validateKey(prefix);
329
+ if (typeof cache.keys !== "function") {
330
+ throw new TypeError("The cache does not expose keys(); cannot invalidate by prefix.");
331
+ }
332
+ const normalized = prefix.endsWith(CACHE_KEY_SEPARATOR)
333
+ ? prefix.slice(0, -CACHE_KEY_SEPARATOR.length)
334
+ : prefix;
335
+ const withSeparator = normalized + CACHE_KEY_SEPARATOR;
187
336
  let removed = 0;
188
337
  for (const key of cache.keys()) {
189
- if (key.startsWith(prefix)) {
190
- if (cache.delete(key)) {
338
+ if (key === normalized || key.startsWith(withSeparator)) {
339
+ if (cache.delete(key))
191
340
  removed += 1;
192
- }
193
341
  }
194
342
  }
195
343
  return removed;
196
344
  }
197
- /**
198
- * Checks whether a cache entry has expired.
199
- */
200
345
  function isExpired(entry) {
201
346
  return entry.expiresAt !== undefined && entry.expiresAt <= Date.now();
202
347
  }
203
- /**
204
- * Normalizes a TTL value.
205
- */
206
348
  function normalizeTtl(ttlMs) {
207
- if (ttlMs === undefined) {
349
+ if (ttlMs === undefined)
208
350
  return undefined;
209
- }
210
351
  if (!Number.isFinite(ttlMs)) {
211
352
  throw new TypeError("Cache TTL must be a finite number.");
212
353
  }
@@ -215,9 +356,14 @@ function normalizeTtl(ttlMs) {
215
356
  }
216
357
  return Math.floor(ttlMs);
217
358
  }
218
- /**
219
- * Validates a cache key.
220
- */
359
+ function normalizeMaxEntries(maxEntries) {
360
+ if (maxEntries === undefined)
361
+ return undefined;
362
+ if (!Number.isInteger(maxEntries) || maxEntries <= 0) {
363
+ throw new TypeError("Cache maxEntries must be a positive integer.");
364
+ }
365
+ return maxEntries;
366
+ }
221
367
  function validateKey(key) {
222
368
  if (typeof key !== "string" || key.trim().length === 0) {
223
369
  throw new TypeError("A non-empty cache key is required.");
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @zudojs/database — Database Cache
3
3
  *
4
- * In-memory cache for database read results.
4
+ * In-memory LRU cache for database read results.
5
5
  */
6
- export { MemoryDatabaseCache, createDatabaseCache, createCacheKey, serializeCachePart, getOrSet, invalidateByPrefix, type CacheEntry, type CacheOptions, type CacheStats, type DatabaseCache, } from "./cache.memory.js";
6
+ export { MemoryDatabaseCache, createDatabaseCache, createCacheKey, escapeCachePart, serializeCachePart, getOrSet, invalidateByPrefix, CACHE_KEY_SEPARATOR, type CacheEntry, type CacheOptions, type MemoryCacheOptions, type CacheStats, type DatabaseCache, } from "./cache.memory.js";
7
7
  //# sourceMappingURL=index.d.ts.map
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @zudojs/database — Database Cache
3
3
  *
4
- * In-memory cache for database read results.
4
+ * In-memory LRU cache for database read results.
5
5
  */
6
- export { MemoryDatabaseCache, createDatabaseCache, createCacheKey, serializeCachePart, getOrSet, invalidateByPrefix, } from "./cache.memory.js";
6
+ export { MemoryDatabaseCache, createDatabaseCache, createCacheKey, escapeCachePart, serializeCachePart, getOrSet, invalidateByPrefix, CACHE_KEY_SEPARATOR, } from "./cache.memory.js";
7
7
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,91 @@
1
+ import { DatabaseClient, type DatabaseClientOptions, type DatabaseTransactionContext, type PrismaClientLike } from "../databaseClient/databaseClient.core.js";
2
+ import type { DatabaseHealth, DatabaseStatus, TransactionCallback, TransactionOptions } from "../databaseType/databaseType.type.js";
3
+ /**
4
+ * Database facade used by the application layer.
5
+ *
6
+ * This module provides a single database lifecycle entry point while
7
+ * keeping the underlying Prisma client implementation inside the
8
+ * database package.
9
+ */
10
+ export declare class Database {
11
+ private readonly client;
12
+ /**
13
+ * @param options Client options, or an existing {@link DatabaseClient}
14
+ * to wrap so a single client is shared by the facade and other managers.
15
+ */
16
+ constructor(options?: DatabaseClientOptions | DatabaseClient);
17
+ /**
18
+ * Initializes the database connection.
19
+ */
20
+ connect(): Promise<void>;
21
+ /**
22
+ * Closes the database connection.
23
+ */
24
+ disconnect(): Promise<void>;
25
+ /**
26
+ * Ensures the database is connected.
27
+ */
28
+ ensureConnected(): Promise<void>;
29
+ /**
30
+ * Checks database connectivity.
31
+ */
32
+ ping(): Promise<void>;
33
+ /**
34
+ * Returns the current database status.
35
+ */
36
+ getStatus(): DatabaseStatus;
37
+ /**
38
+ * Returns database health information.
39
+ */
40
+ healthCheck(): Promise<DatabaseHealth>;
41
+ /**
42
+ * Executes work inside a database transaction.
43
+ */
44
+ transaction<TResult>(callback: TransactionCallback<DatabaseTransactionContext, TResult>, options?: TransactionOptions): Promise<TResult>;
45
+ /**
46
+ * Returns the underlying database client.
47
+ *
48
+ * This should primarily be used by repository and infrastructure
49
+ * implementations that require direct Prisma access.
50
+ */
51
+ getClient(): DatabaseClient;
52
+ /**
53
+ * Returns the underlying Prisma client.
54
+ */
55
+ getPrisma(): PrismaClientLike;
56
+ /**
57
+ * Releases all database resources.
58
+ */
59
+ destroy(): Promise<void>;
60
+ }
61
+ /**
62
+ * Creates a database facade.
63
+ */
64
+ export declare function createDatabase(options?: DatabaseClientOptions | DatabaseClient): Database;
65
+ /**
66
+ * Returns the shared application database instance.
67
+ *
68
+ * The connection is not established automatically. Call
69
+ * `connect()` during application bootstrap.
70
+ *
71
+ * @throws {TypeError} when options are supplied after the shared instance
72
+ * has already been created; they would otherwise be silently ignored.
73
+ * Call {@link resetDatabase} first to reconfigure.
74
+ */
75
+ export declare function getDatabase(options?: DatabaseClientOptions | DatabaseClient): Database;
76
+ /**
77
+ * Connects the shared application database.
78
+ */
79
+ export declare function connectDatabase(options?: DatabaseClientOptions | DatabaseClient): Promise<Database>;
80
+ /**
81
+ * Disconnects the shared application database.
82
+ */
83
+ export declare function disconnectDatabase(): Promise<void>;
84
+ /**
85
+ * Resets the shared database instance.
86
+ *
87
+ * Primarily useful for application shutdown, tests, and isolated
88
+ * runtime environments.
89
+ */
90
+ export declare function resetDatabase(): Promise<void>;
91
+ //# sourceMappingURL=database.core.d.ts.map
@@ -1,4 +1,4 @@
1
- import { createDatabaseClient, } from "../databaseClient/databaseClient.core.js";
1
+ import { DatabaseClient, createDatabaseClient, } from "../databaseClient/databaseClient.core.js";
2
2
  /**
3
3
  * Database facade used by the application layer.
4
4
  *
@@ -8,8 +8,13 @@ import { createDatabaseClient, } from "../databaseClient/databaseClient.core.js"
8
8
  */
9
9
  export class Database {
10
10
  client;
11
+ /**
12
+ * @param options Client options, or an existing {@link DatabaseClient}
13
+ * to wrap so a single client is shared by the facade and other managers.
14
+ */
11
15
  constructor(options = {}) {
12
- this.client = createDatabaseClient(options);
16
+ this.client =
17
+ options instanceof DatabaseClient ? options : createDatabaseClient(options);
13
18
  }
14
19
  /**
15
20
  * Initializes the database connection.
@@ -93,10 +98,19 @@ let defaultDatabase;
93
98
  *
94
99
  * The connection is not established automatically. Call
95
100
  * `connect()` during application bootstrap.
101
+ *
102
+ * @throws {TypeError} when options are supplied after the shared instance
103
+ * has already been created; they would otherwise be silently ignored.
104
+ * Call {@link resetDatabase} first to reconfigure.
96
105
  */
97
106
  export function getDatabase(options = {}) {
98
107
  if (!defaultDatabase) {
99
108
  defaultDatabase = createDatabase(options);
109
+ return defaultDatabase;
110
+ }
111
+ const hasOptions = options instanceof DatabaseClient || Object.keys(options).length > 0;
112
+ if (hasOptions && !(options instanceof DatabaseClient && defaultDatabase.getClient() === options)) {
113
+ throw new TypeError("The shared database instance already exists; options passed to getDatabase() would be ignored. Call resetDatabase() before reconfiguring.");
100
114
  }
101
115
  return defaultDatabase;
102
116
  }
@@ -124,10 +138,13 @@ export async function disconnectDatabase() {
124
138
  * runtime environments.
125
139
  */
126
140
  export async function resetDatabase() {
127
- if (!defaultDatabase) {
141
+ const database = defaultDatabase;
142
+ if (!database) {
128
143
  return;
129
144
  }
130
- await defaultDatabase.destroy();
145
+ // Clear the singleton first so a failing destroy() never leaves a stale
146
+ // instance behind.
131
147
  defaultDatabase = undefined;
148
+ await database.destroy();
132
149
  }
133
150
  //# sourceMappingURL=database.core.js.map