@cogenta/core 0.1.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 (193) hide show
  1. package/dist/cache/entry.d.ts +32 -0
  2. package/dist/cache/entry.d.ts.map +1 -0
  3. package/dist/cache/entry.js +72 -0
  4. package/dist/cache/entry.js.map +1 -0
  5. package/dist/cache/file.d.ts +16 -0
  6. package/dist/cache/file.d.ts.map +1 -0
  7. package/dist/cache/file.js +191 -0
  8. package/dist/cache/file.js.map +1 -0
  9. package/dist/cache/index.d.ts +20 -0
  10. package/dist/cache/index.d.ts.map +1 -0
  11. package/dist/cache/index.js +25 -0
  12. package/dist/cache/index.js.map +1 -0
  13. package/dist/cache/memory.d.ts +11 -0
  14. package/dist/cache/memory.d.ts.map +1 -0
  15. package/dist/cache/memory.js +89 -0
  16. package/dist/cache/memory.js.map +1 -0
  17. package/dist/cache/redis.d.ts +54 -0
  18. package/dist/cache/redis.d.ts.map +1 -0
  19. package/dist/cache/redis.js +167 -0
  20. package/dist/cache/redis.js.map +1 -0
  21. package/dist/cache/types.d.ts +37 -0
  22. package/dist/cache/types.d.ts.map +1 -0
  23. package/dist/cache/types.js +2 -0
  24. package/dist/cache/types.js.map +1 -0
  25. package/dist/config/define-config.d.ts +19 -0
  26. package/dist/config/define-config.d.ts.map +1 -0
  27. package/dist/config/define-config.js +20 -0
  28. package/dist/config/define-config.js.map +1 -0
  29. package/dist/config/env.d.ts +30 -0
  30. package/dist/config/env.d.ts.map +1 -0
  31. package/dist/config/env.js +139 -0
  32. package/dist/config/env.js.map +1 -0
  33. package/dist/config/index.d.ts +7 -0
  34. package/dist/config/index.d.ts.map +1 -0
  35. package/dist/config/index.js +5 -0
  36. package/dist/config/index.js.map +1 -0
  37. package/dist/config/load-config.d.ts +30 -0
  38. package/dist/config/load-config.d.ts.map +1 -0
  39. package/dist/config/load-config.js +83 -0
  40. package/dist/config/load-config.js.map +1 -0
  41. package/dist/config/resolve-config.d.ts +14 -0
  42. package/dist/config/resolve-config.d.ts.map +1 -0
  43. package/dist/config/resolve-config.js +146 -0
  44. package/dist/config/resolve-config.js.map +1 -0
  45. package/dist/config/schema.d.ts +68 -0
  46. package/dist/config/schema.d.ts.map +1 -0
  47. package/dist/config/schema.js +75 -0
  48. package/dist/config/schema.js.map +1 -0
  49. package/dist/config/types.d.ts +126 -0
  50. package/dist/config/types.d.ts.map +1 -0
  51. package/dist/config/types.js +6 -0
  52. package/dist/config/types.js.map +1 -0
  53. package/dist/db/dialect.d.ts +64 -0
  54. package/dist/db/dialect.d.ts.map +1 -0
  55. package/dist/db/dialect.js +147 -0
  56. package/dist/db/dialect.js.map +1 -0
  57. package/dist/db/drizzle.d.ts +35 -0
  58. package/dist/db/drizzle.d.ts.map +1 -0
  59. package/dist/db/drizzle.js +128 -0
  60. package/dist/db/drizzle.js.map +1 -0
  61. package/dist/db/index.d.ts +28 -0
  62. package/dist/db/index.d.ts.map +1 -0
  63. package/dist/db/index.js +30 -0
  64. package/dist/db/index.js.map +1 -0
  65. package/dist/db/mysql.d.ts +39 -0
  66. package/dist/db/mysql.d.ts.map +1 -0
  67. package/dist/db/mysql.js +224 -0
  68. package/dist/db/mysql.js.map +1 -0
  69. package/dist/db/postgres.d.ts +47 -0
  70. package/dist/db/postgres.d.ts.map +1 -0
  71. package/dist/db/postgres.js +174 -0
  72. package/dist/db/postgres.js.map +1 -0
  73. package/dist/db/sqlite.d.ts +41 -0
  74. package/dist/db/sqlite.d.ts.map +1 -0
  75. package/dist/db/sqlite.js +213 -0
  76. package/dist/db/sqlite.js.map +1 -0
  77. package/dist/db/types.d.ts +71 -0
  78. package/dist/db/types.d.ts.map +1 -0
  79. package/dist/db/types.js +2 -0
  80. package/dist/db/types.js.map +1 -0
  81. package/dist/drivers/index.d.ts +5 -0
  82. package/dist/drivers/index.d.ts.map +1 -0
  83. package/dist/drivers/index.js +3 -0
  84. package/dist/drivers/index.js.map +1 -0
  85. package/dist/drivers/registry.d.ts +27 -0
  86. package/dist/drivers/registry.d.ts.map +1 -0
  87. package/dist/drivers/registry.js +162 -0
  88. package/dist/drivers/registry.js.map +1 -0
  89. package/dist/drivers/types.d.ts +59 -0
  90. package/dist/drivers/types.d.ts.map +1 -0
  91. package/dist/drivers/types.js +2 -0
  92. package/dist/drivers/types.js.map +1 -0
  93. package/dist/errors/codes.d.ts +8 -0
  94. package/dist/errors/codes.d.ts.map +1 -0
  95. package/dist/errors/codes.js +201 -0
  96. package/dist/errors/codes.js.map +1 -0
  97. package/dist/errors/cogenta-error.d.ts +38 -0
  98. package/dist/errors/cogenta-error.d.ts.map +1 -0
  99. package/dist/errors/cogenta-error.js +31 -0
  100. package/dist/errors/cogenta-error.js.map +1 -0
  101. package/dist/errors/index.d.ts +5 -0
  102. package/dist/errors/index.d.ts.map +1 -0
  103. package/dist/errors/index.js +3 -0
  104. package/dist/errors/index.js.map +1 -0
  105. package/dist/id.d.ts +5 -0
  106. package/dist/id.d.ts.map +1 -0
  107. package/dist/id.js +82 -0
  108. package/dist/id.js.map +1 -0
  109. package/dist/index.d.ts +12 -0
  110. package/dist/index.d.ts.map +1 -0
  111. package/dist/index.js +12 -0
  112. package/dist/index.js.map +1 -0
  113. package/dist/logger/index.d.ts +5 -0
  114. package/dist/logger/index.d.ts.map +1 -0
  115. package/dist/logger/index.js +4 -0
  116. package/dist/logger/index.js.map +1 -0
  117. package/dist/logger/logger.d.ts +10 -0
  118. package/dist/logger/logger.d.ts.map +1 -0
  119. package/dist/logger/logger.js +108 -0
  120. package/dist/logger/logger.js.map +1 -0
  121. package/dist/logger/redact.d.ts +12 -0
  122. package/dist/logger/redact.d.ts.map +1 -0
  123. package/dist/logger/redact.js +96 -0
  124. package/dist/logger/redact.js.map +1 -0
  125. package/dist/logger/types.d.ts +32 -0
  126. package/dist/logger/types.d.ts.map +1 -0
  127. package/dist/logger/types.js +3 -0
  128. package/dist/logger/types.js.map +1 -0
  129. package/dist/media/format-sniff.d.ts +22 -0
  130. package/dist/media/format-sniff.d.ts.map +1 -0
  131. package/dist/media/format-sniff.js +77 -0
  132. package/dist/media/format-sniff.js.map +1 -0
  133. package/dist/media/index.d.ts +5 -0
  134. package/dist/media/index.d.ts.map +1 -0
  135. package/dist/media/index.js +4 -0
  136. package/dist/media/index.js.map +1 -0
  137. package/dist/media/store.d.ts +13 -0
  138. package/dist/media/store.d.ts.map +1 -0
  139. package/dist/media/store.js +211 -0
  140. package/dist/media/store.js.map +1 -0
  141. package/dist/media/types.d.ts +70 -0
  142. package/dist/media/types.d.ts.map +1 -0
  143. package/dist/media/types.js +2 -0
  144. package/dist/media/types.js.map +1 -0
  145. package/dist/migrations/index.d.ts +4 -0
  146. package/dist/migrations/index.d.ts.map +1 -0
  147. package/dist/migrations/index.js +2 -0
  148. package/dist/migrations/index.js.map +1 -0
  149. package/dist/migrations/migrator.d.ts +18 -0
  150. package/dist/migrations/migrator.d.ts.map +1 -0
  151. package/dist/migrations/migrator.js +247 -0
  152. package/dist/migrations/migrator.js.map +1 -0
  153. package/dist/migrations/types.d.ts +63 -0
  154. package/dist/migrations/types.d.ts.map +1 -0
  155. package/dist/migrations/types.js +2 -0
  156. package/dist/migrations/types.js.map +1 -0
  157. package/dist/queue/bullmq.d.ts +119 -0
  158. package/dist/queue/bullmq.d.ts.map +1 -0
  159. package/dist/queue/bullmq.js +391 -0
  160. package/dist/queue/bullmq.js.map +1 -0
  161. package/dist/queue/database.d.ts +17 -0
  162. package/dist/queue/database.d.ts.map +1 -0
  163. package/dist/queue/database.js +242 -0
  164. package/dist/queue/database.js.map +1 -0
  165. package/dist/queue/index.d.ts +7 -0
  166. package/dist/queue/index.d.ts.map +1 -0
  167. package/dist/queue/index.js +4 -0
  168. package/dist/queue/index.js.map +1 -0
  169. package/dist/queue/types.d.ts +61 -0
  170. package/dist/queue/types.d.ts.map +1 -0
  171. package/dist/queue/types.js +2 -0
  172. package/dist/queue/types.js.map +1 -0
  173. package/dist/storage/index.d.ts +14 -0
  174. package/dist/storage/index.d.ts.map +1 -0
  175. package/dist/storage/index.js +17 -0
  176. package/dist/storage/index.js.map +1 -0
  177. package/dist/storage/key.d.ts +12 -0
  178. package/dist/storage/key.d.ts.map +1 -0
  179. package/dist/storage/key.js +43 -0
  180. package/dist/storage/key.js.map +1 -0
  181. package/dist/storage/local.d.ts +23 -0
  182. package/dist/storage/local.d.ts.map +1 -0
  183. package/dist/storage/local.js +204 -0
  184. package/dist/storage/local.js.map +1 -0
  185. package/dist/storage/s3.d.ts +54 -0
  186. package/dist/storage/s3.d.ts.map +1 -0
  187. package/dist/storage/s3.js +240 -0
  188. package/dist/storage/s3.js.map +1 -0
  189. package/dist/storage/types.d.ts +45 -0
  190. package/dist/storage/types.d.ts.map +1 -0
  191. package/dist/storage/types.js +2 -0
  192. package/dist/storage/types.js.map +1 -0
  193. package/package.json +82 -0
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The envelope every driver stores: the value plus what is needed to expire and
3
+ * invalidate it. Identical across drivers, so an entry written by one is
4
+ * readable by another — which is what makes the contract suite meaningful.
5
+ */
6
+ export interface CacheEntry {
7
+ readonly value: unknown;
8
+ /** Epoch milliseconds, or `null` for "no expiry". */
9
+ readonly expiresAt: number | null;
10
+ readonly tags: readonly string[];
11
+ }
12
+ export declare function assertKey(key: string): void;
13
+ export declare function expiryFrom(ttl: number | undefined, now: number): number | null;
14
+ export declare function isExpired(expiresAt: number | null, now: number): boolean;
15
+ /**
16
+ * Round-trips a value through JSON, which is what the file and Redis drivers do
17
+ * anyway. Doing it in `memory` too is what keeps the three drivers substitutable:
18
+ * a caller must not be able to mutate a cached object by keeping the reference.
19
+ */
20
+ export declare function serialise(value: unknown): string;
21
+ export declare function deserialise(raw: string): unknown;
22
+ /**
23
+ * Encodes an entry for any driver that stores it as one blob.
24
+ *
25
+ * The value is serialised **first, on its own**. Serialising the whole envelope
26
+ * in one go would let `JSON.stringify` drop an unserialisable value silently —
27
+ * a function becomes a missing property rather than an error — and the caller
28
+ * would only find out on the next read, far from the cause.
29
+ */
30
+ export declare function encodeEntry(value: unknown, expiresAt: number | null, tags: readonly string[]): string;
31
+ export declare function decodeEntry(raw: string): CacheEntry;
32
+ //# sourceMappingURL=entry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["../../src/cache/entry.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,qDAAqD;IACrD,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;CACjC;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAQ3C;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAW9E;AAED,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAExE;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAahD;AAWD,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEhD;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,OAAO,EACd,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,IAAI,EAAE,SAAS,MAAM,EAAE,GACtB,MAAM,CAGR;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAEnD"}
@@ -0,0 +1,72 @@
1
+ import { CogentaError } from '../errors/index.js';
2
+ export function assertKey(key) {
3
+ if (key.length === 0) {
4
+ throw new CogentaError({
5
+ code: 'CACHE_FAILED',
6
+ message: 'A cache key must not be empty.',
7
+ hint: 'Build the key from something stable, such as "page:" plus the locale and the slug.',
8
+ });
9
+ }
10
+ }
11
+ export function expiryFrom(ttl, now) {
12
+ if (ttl === undefined)
13
+ return null;
14
+ if (!Number.isFinite(ttl) || ttl <= 0) {
15
+ throw new CogentaError({
16
+ code: 'CACHE_FAILED',
17
+ message: `A cache TTL must be a positive number of seconds, received ${String(ttl)}.`,
18
+ hint: 'Omit the ttl option for an entry that lives until it is invalidated.',
19
+ });
20
+ }
21
+ return now + ttl * 1000;
22
+ }
23
+ export function isExpired(expiresAt, now) {
24
+ return expiresAt !== null && expiresAt <= now;
25
+ }
26
+ /**
27
+ * Round-trips a value through JSON, which is what the file and Redis drivers do
28
+ * anyway. Doing it in `memory` too is what keeps the three drivers substitutable:
29
+ * a caller must not be able to mutate a cached object by keeping the reference.
30
+ */
31
+ export function serialise(value) {
32
+ let raw;
33
+ try {
34
+ raw = JSON.stringify(value ?? null);
35
+ }
36
+ catch (error) {
37
+ throw unserialisable(error);
38
+ }
39
+ // `JSON.stringify` returns undefined — it does not throw — for a function or a
40
+ // symbol. Left unchecked that caches the string "undefined" and hands it back
41
+ // on the next read, so the failure surfaces far from its cause.
42
+ if (raw === undefined)
43
+ throw unserialisable(undefined);
44
+ return raw;
45
+ }
46
+ function unserialisable(cause) {
47
+ return new CogentaError({
48
+ code: 'CACHE_FAILED',
49
+ message: 'This value cannot be cached: it is not serialisable to JSON.',
50
+ hint: 'Cache plain data. Functions, symbols, class instances and BigInt do not survive any cache driver.',
51
+ ...(cause === undefined ? {} : { cause }),
52
+ });
53
+ }
54
+ export function deserialise(raw) {
55
+ return JSON.parse(raw);
56
+ }
57
+ /**
58
+ * Encodes an entry for any driver that stores it as one blob.
59
+ *
60
+ * The value is serialised **first, on its own**. Serialising the whole envelope
61
+ * in one go would let `JSON.stringify` drop an unserialisable value silently —
62
+ * a function becomes a missing property rather than an error — and the caller
63
+ * would only find out on the next read, far from the cause.
64
+ */
65
+ export function encodeEntry(value, expiresAt, tags) {
66
+ const encodedValue = serialise(value);
67
+ return `{"value":${encodedValue},"expiresAt":${JSON.stringify(expiresAt)},"tags":${JSON.stringify(tags)}}`;
68
+ }
69
+ export function decodeEntry(raw) {
70
+ return JSON.parse(raw);
71
+ }
72
+ //# sourceMappingURL=entry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry.js","sourceRoot":"","sources":["../../src/cache/entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAcjD,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,YAAY,CAAC;YACrB,IAAI,EAAE,cAAc;YACpB,OAAO,EAAE,gCAAgC;YACzC,IAAI,EAAE,oFAAoF;SAC3F,CAAC,CAAA;IACJ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAuB,EAAE,GAAW;IAC7D,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,IAAI,CAAA;IAElC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,YAAY,CAAC;YACrB,IAAI,EAAE,cAAc;YACpB,OAAO,EAAE,8DAA8D,MAAM,CAAC,GAAG,CAAC,GAAG;YACrF,IAAI,EAAE,sEAAsE;SAC7E,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,GAAG,GAAG,GAAG,GAAG,IAAI,CAAA;AACzB,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,SAAwB,EAAE,GAAW;IAC7D,OAAO,SAAS,KAAK,IAAI,IAAI,SAAS,IAAI,GAAG,CAAA;AAC/C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,IAAI,GAAuB,CAAA;IAC3B,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI,IAAI,CAAC,CAAA;IACrC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,cAAc,CAAC,KAAK,CAAC,CAAA;IAC7B,CAAC;IAED,+EAA+E;IAC/E,8EAA8E;IAC9E,gEAAgE;IAChE,IAAI,GAAG,KAAK,SAAS;QAAE,MAAM,cAAc,CAAC,SAAS,CAAC,CAAA;IACtD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,SAAS,cAAc,CAAC,KAAc;IACpC,OAAO,IAAI,YAAY,CAAC;QACtB,IAAI,EAAE,cAAc;QACpB,OAAO,EAAE,8DAA8D;QACvE,IAAI,EAAE,mGAAmG;QACzG,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;KAC1C,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;AACxB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CACzB,KAAc,EACd,SAAwB,EACxB,IAAuB;IAEvB,MAAM,YAAY,GAAG,SAAS,CAAC,KAAK,CAAC,CAAA;IACrC,OAAO,YAAY,YAAY,gBAAgB,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,WAAW,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAA;AAC5G,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAe,CAAA;AACtC,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { Driver } from '../drivers/index.js';
2
+ import type { CacheConfig, CacheDriver, CacheDriverOptions } from './types.js';
3
+ export interface FileCacheOptions extends CacheDriverOptions {
4
+ readonly path: string;
5
+ }
6
+ /**
7
+ * Cache on disk. Survives a restart and is shared between processes on the same
8
+ * machine, which is what a shared-hosting deployment gets instead of Redis.
9
+ *
10
+ * Writes go to a temporary file and are then renamed. `rename` is atomic on a
11
+ * single filesystem, so a reader never sees a half-written entry — the failure
12
+ * mode that makes naive file caches corrupt data under load.
13
+ */
14
+ export declare function createFileCache(options: FileCacheOptions): CacheDriver;
15
+ export declare function fileCacheDriver(options?: CacheDriverOptions): Driver<CacheDriver, CacheConfig>;
16
+ //# sourceMappingURL=file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../src/cache/file.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAgB,MAAM,qBAAqB,CAAA;AAU/D,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,kBAAkB,EAAmB,MAAM,YAAY,CAAA;AA2C/F,MAAM,WAAW,gBAAiB,SAAQ,kBAAkB;IAC1D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,WAAW,CAgHtE;AAED,wBAAgB,eAAe,CAC7B,OAAO,GAAE,kBAAuB,GAC/B,MAAM,CAAC,WAAW,EAAE,WAAW,CAAC,CAwClC"}
@@ -0,0 +1,191 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { mkdir, readdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { CogentaError } from '../errors/index.js';
5
+ import { assertKey, decodeEntry, encodeEntry, expiryFrom, isExpired, } from './entry.js';
6
+ /**
7
+ * Keys become filenames through a hash, not through escaping. A cache key is
8
+ * arbitrary text — Unicode, slashes, hundreds of characters — and every escaping
9
+ * scheme eventually collides or produces a path the filesystem rejects.
10
+ */
11
+ function fileNameFor(key) {
12
+ return createHash('sha256').update(key).digest('hex');
13
+ }
14
+ // Exponential, with jitter so contending writers do not retry in lockstep and
15
+ // collide again. Worst case is about a second, and only under heavy contention.
16
+ const RENAME_RETRY_DELAYS_MS = [5, 10, 20, 40, 80, 160, 320, 640];
17
+ const RETRYABLE_RENAME_ERRORS = new Set(['EPERM', 'EACCES', 'EBUSY']);
18
+ /**
19
+ * Retries the transient failures Windows returns for a filesystem operation.
20
+ *
21
+ * Windows refuses an operation while another handle is still open on the target
22
+ * — a rename onto a file being read, or the removal of a directory that was
23
+ * just enumerated. Both surface as EPERM, EACCES or EBUSY, and both clear on
24
+ * their own within milliseconds. On POSIX the loop never runs a second time.
25
+ */
26
+ async function withWindowsRetry(operation) {
27
+ for (const [attempt, delay] of RENAME_RETRY_DELAYS_MS.entries()) {
28
+ try {
29
+ await operation();
30
+ return;
31
+ }
32
+ catch (error) {
33
+ const code = error.code ?? '';
34
+ const lastAttempt = attempt === RENAME_RETRY_DELAYS_MS.length - 1;
35
+ if (!RETRYABLE_RENAME_ERRORS.has(code) || lastAttempt)
36
+ throw error;
37
+ const jittered = delay * (0.5 + Math.random());
38
+ await new Promise((resolve) => setTimeout(resolve, jittered));
39
+ }
40
+ }
41
+ }
42
+ const replaceAtomically = (temporary, target) => withWindowsRetry(() => rename(temporary, target));
43
+ /**
44
+ * Cache on disk. Survives a restart and is shared between processes on the same
45
+ * machine, which is what a shared-hosting deployment gets instead of Redis.
46
+ *
47
+ * Writes go to a temporary file and are then renamed. `rename` is atomic on a
48
+ * single filesystem, so a reader never sees a half-written entry — the failure
49
+ * mode that makes naive file caches corrupt data under load.
50
+ */
51
+ export function createFileCache(options) {
52
+ const now = options.now ?? Date.now;
53
+ const entriesDir = join(options.path, 'entries');
54
+ const tagsDir = join(options.path, 'tags');
55
+ async function ensureDirectories() {
56
+ await mkdir(entriesDir, { recursive: true });
57
+ await mkdir(tagsDir, { recursive: true });
58
+ }
59
+ async function readEntry(name) {
60
+ try {
61
+ return decodeEntry(await readFile(join(entriesDir, name), 'utf8'));
62
+ }
63
+ catch {
64
+ // A missing or unreadable entry is a cache miss, never an outage: a cache
65
+ // that throws is worse than a cache that forgets.
66
+ return null;
67
+ }
68
+ }
69
+ /** Drops the tag markers pointing at an entry, leaving the entry itself alone. */
70
+ async function untag(name) {
71
+ const entry = await readEntry(name);
72
+ for (const tag of entry?.tags ?? []) {
73
+ await rm(join(tagsDir, fileNameFor(tag), name), { force: true });
74
+ }
75
+ }
76
+ async function removeEntry(name) {
77
+ await untag(name);
78
+ await rm(join(entriesDir, name), { force: true });
79
+ }
80
+ return {
81
+ get: async (key) => {
82
+ assertKey(key);
83
+ const name = fileNameFor(key);
84
+ const entry = await readEntry(name);
85
+ if (entry === null)
86
+ return null;
87
+ if (isExpired(entry.expiresAt, now())) {
88
+ await removeEntry(name);
89
+ return null;
90
+ }
91
+ return entry.value;
92
+ },
93
+ set: async (key, value, setOptions) => {
94
+ assertKey(key);
95
+ const expiresAt = expiryFrom(setOptions?.ttl, now());
96
+ const tags = [...new Set(setOptions?.tags ?? [])];
97
+ const name = fileNameFor(key);
98
+ // Encode before touching the disk, so an unserialisable value fails
99
+ // without leaving a half-updated cache behind.
100
+ const payload = encodeEntry(value, expiresAt, tags);
101
+ await ensureDirectories();
102
+ // Only the tag markers are cleared up front. The entry itself is replaced
103
+ // by the rename below, so a concurrent reader never sees it missing.
104
+ await untag(name);
105
+ const target = join(entriesDir, name);
106
+ // Unique per write, not per process: two concurrent writes to the same key
107
+ // sharing a temporary file means one of them renames it and the other
108
+ // fails on a file that is no longer there.
109
+ const temporary = `${target}.${randomUUID()}.tmp`;
110
+ try {
111
+ await writeFile(temporary, payload, 'utf8');
112
+ await replaceAtomically(temporary, target);
113
+ }
114
+ catch (error) {
115
+ await rm(temporary, { force: true });
116
+ throw new CogentaError({
117
+ code: 'CACHE_FAILED',
118
+ message: `Could not write the cache entry for "${key}".`,
119
+ hint: `Check that ${options.path} exists and is writable, or set cache.driver to "memory".`,
120
+ cause: error,
121
+ });
122
+ }
123
+ for (const tag of tags) {
124
+ const tagDir = join(tagsDir, fileNameFor(tag));
125
+ await mkdir(tagDir, { recursive: true });
126
+ await writeFile(join(tagDir, name), '', 'utf8');
127
+ }
128
+ },
129
+ delete: async (key) => {
130
+ assertKey(key);
131
+ await removeEntry(fileNameFor(key));
132
+ },
133
+ invalidateTags: async (tags) => {
134
+ for (const tag of tags) {
135
+ const tagDir = join(tagsDir, fileNameFor(tag));
136
+ let names;
137
+ try {
138
+ names = await readdir(tagDir);
139
+ }
140
+ catch {
141
+ continue;
142
+ }
143
+ for (const name of names)
144
+ await removeEntry(name);
145
+ await withWindowsRetry(() => rm(tagDir, { recursive: true, force: true }));
146
+ }
147
+ },
148
+ clear: async () => {
149
+ await rm(options.path, { recursive: true, force: true });
150
+ await ensureDirectories();
151
+ },
152
+ };
153
+ }
154
+ export function fileCacheDriver(options = {}) {
155
+ let instance;
156
+ let path;
157
+ return {
158
+ name: 'file',
159
+ tier: 'degraded',
160
+ // "Can we actually write there?" — not "is a path configured?". A read-only
161
+ // filesystem is exactly the case this check exists to catch.
162
+ available: async (config) => {
163
+ const candidate = config.path ?? './.cogenta/cache';
164
+ try {
165
+ await mkdir(candidate, { recursive: true });
166
+ const probe = join(candidate, `.probe-${process.pid}`);
167
+ await writeFile(probe, '', 'utf8');
168
+ await rm(probe, { force: true });
169
+ return true;
170
+ }
171
+ catch {
172
+ return false;
173
+ }
174
+ },
175
+ init: async (config) => {
176
+ path = config.path ?? './.cogenta/cache';
177
+ instance ??= createFileCache({ ...options, path });
178
+ return instance;
179
+ },
180
+ dispose: async () => {
181
+ instance = undefined;
182
+ },
183
+ health: async () => ({
184
+ status: 'degraded',
185
+ driver: 'file',
186
+ tier: 'degraded',
187
+ message: `Cache on disk at ${path ?? 'an unopened path'}. Shared between processes on this machine only.`,
188
+ }),
189
+ };
190
+ }
191
+ //# sourceMappingURL=file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file.js","sourceRoot":"","sources":["../../src/cache/file.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { type DriverRegistry } from '../drivers/index.js';
2
+ import type { Logger } from '../logger/index.js';
3
+ import type { CacheConfig, CacheDriver, CacheDriverOptions } from './types.js';
4
+ export type { FileCacheOptions } from './file.js';
5
+ export { createFileCache, fileCacheDriver } from './file.js';
6
+ export { createMemoryCache, memoryCacheDriver } from './memory.js';
7
+ export type { RedisCacheOptions } from './redis.js';
8
+ export { createRedisCache, loadRedisModule, redisCacheDriver } from './redis.js';
9
+ export type { CacheConfig, CacheDriver, CacheDriverOptions, CacheSetOptions } from './types.js';
10
+ export interface CacheRegistryOptions extends CacheDriverOptions {
11
+ readonly logger?: Logger;
12
+ }
13
+ /**
14
+ * The cache drivers Cogenta ships, in tier order.
15
+ *
16
+ * `file` before `memory`: both need no service, but a cache that survives a
17
+ * restart and is shared between processes is worth more than one that does not.
18
+ */
19
+ export declare function createCacheRegistry(options?: CacheRegistryOptions): DriverRegistry<CacheDriver, CacheConfig>;
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cache/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAC/E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAIhD,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAE9E,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AACjD,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAC5D,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAClE,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AACnD,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAChF,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAE/F,MAAM,WAAW,oBAAqB,SAAQ,kBAAkB;IAC9D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,GAAE,oBAAyB,GACjC,cAAc,CAAC,WAAW,EAAE,WAAW,CAAC,CAY1C"}
@@ -0,0 +1,25 @@
1
+ import { createDriverRegistry } from '../drivers/index.js';
2
+ import { fileCacheDriver } from './file.js';
3
+ import { memoryCacheDriver } from './memory.js';
4
+ import { redisCacheDriver } from './redis.js';
5
+ export { createFileCache, fileCacheDriver } from './file.js';
6
+ export { createMemoryCache, memoryCacheDriver } from './memory.js';
7
+ export { createRedisCache, loadRedisModule, redisCacheDriver } from './redis.js';
8
+ /**
9
+ * The cache drivers Cogenta ships, in tier order.
10
+ *
11
+ * `file` before `memory`: both need no service, but a cache that survives a
12
+ * restart and is shared between processes is worth more than one that does not.
13
+ */
14
+ export function createCacheRegistry(options = {}) {
15
+ const { logger, ...driverOptions } = options;
16
+ const registry = createDriverRegistry({
17
+ need: 'cache',
18
+ ...(logger === undefined ? {} : { logger }),
19
+ });
20
+ registry.register(redisCacheDriver(driverOptions));
21
+ registry.register(fileCacheDriver(driverOptions));
22
+ registry.register(memoryCacheDriver(driverOptions));
23
+ return registry;
24
+ }
25
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cache/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAuB,MAAM,qBAAqB,CAAA;AAE/E,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAI7C,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAC5D,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAElE,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAOhF;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CACjC,OAAO,GAAyB,EAAE;IAElC,MAAM,EAAE,MAAM,EAAE,GAAG,aAAa,EAAE,GAAG,OAAO,CAAA;IAC5C,MAAM,QAAQ,GAAG,oBAAoB,CAA2B;QAC9D,IAAI,EAAE,OAAO;QACb,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;KAC5C,CAAC,CAAA;IAEF,QAAQ,CAAC,QAAQ,CAAC,gBAAgB,CAAC,aAAa,CAAC,CAAC,CAAA;IAClD,QAAQ,CAAC,QAAQ,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC,CAAA;IACjD,QAAQ,CAAC,QAAQ,CAAC,iBAAiB,CAAC,aAAa,CAAC,CAAC,CAAA;IAEnD,OAAO,QAAQ,CAAA;AACjB,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { Driver } from '../drivers/index.js';
2
+ import type { CacheConfig, CacheDriver, CacheDriverOptions } from './types.js';
3
+ /**
4
+ * In-process cache. The last line of defence: it needs nothing at all, so it is
5
+ * what a site falls back to when there is neither Redis nor a writable cache
6
+ * directory. Its contents die with the process, which is correct — a cache is
7
+ * never a source of truth.
8
+ */
9
+ export declare function createMemoryCache(options?: CacheDriverOptions): CacheDriver;
10
+ export declare function memoryCacheDriver(options?: CacheDriverOptions): Driver<CacheDriver, CacheConfig>;
11
+ //# sourceMappingURL=memory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/cache/memory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAgB,MAAM,qBAAqB,CAAA;AAE/D,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,kBAAkB,EAAmB,MAAM,YAAY,CAAA;AAS/F;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,kBAAuB,GAAG,WAAW,CA+D/E;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,GAAE,kBAAuB,GAC/B,MAAM,CAAC,WAAW,EAAE,WAAW,CAAC,CAsBlC"}
@@ -0,0 +1,89 @@
1
+ import { assertKey, deserialise, expiryFrom, isExpired, serialise } from './entry.js';
2
+ /**
3
+ * In-process cache. The last line of defence: it needs nothing at all, so it is
4
+ * what a site falls back to when there is neither Redis nor a writable cache
5
+ * directory. Its contents die with the process, which is correct — a cache is
6
+ * never a source of truth.
7
+ */
8
+ export function createMemoryCache(options = {}) {
9
+ const now = options.now ?? Date.now;
10
+ const entries = new Map();
11
+ const byTag = new Map();
12
+ function forget(key) {
13
+ const entry = entries.get(key);
14
+ if (entry === undefined)
15
+ return;
16
+ for (const tag of entry.tags) {
17
+ const keys = byTag.get(tag);
18
+ if (keys === undefined)
19
+ continue;
20
+ keys.delete(key);
21
+ if (keys.size === 0)
22
+ byTag.delete(tag);
23
+ }
24
+ entries.delete(key);
25
+ }
26
+ return {
27
+ get: async (key) => {
28
+ assertKey(key);
29
+ const entry = entries.get(key);
30
+ if (entry === undefined)
31
+ return null;
32
+ if (isExpired(entry.expiresAt, now())) {
33
+ forget(key);
34
+ return null;
35
+ }
36
+ return deserialise(entry.raw);
37
+ },
38
+ set: async (key, value, setOptions) => {
39
+ assertKey(key);
40
+ const raw = serialise(value);
41
+ const expiresAt = expiryFrom(setOptions?.ttl, now());
42
+ const tags = [...new Set(setOptions?.tags ?? [])];
43
+ forget(key);
44
+ entries.set(key, { raw, expiresAt, tags });
45
+ for (const tag of tags) {
46
+ const keys = byTag.get(tag) ?? new Set();
47
+ keys.add(key);
48
+ byTag.set(tag, keys);
49
+ }
50
+ },
51
+ delete: async (key) => {
52
+ assertKey(key);
53
+ forget(key);
54
+ },
55
+ invalidateTags: async (tags) => {
56
+ for (const tag of tags) {
57
+ for (const key of byTag.get(tag) ?? [])
58
+ forget(key);
59
+ }
60
+ },
61
+ clear: async () => {
62
+ entries.clear();
63
+ byTag.clear();
64
+ },
65
+ };
66
+ }
67
+ export function memoryCacheDriver(options = {}) {
68
+ let instance;
69
+ return {
70
+ name: 'memory',
71
+ tier: 'degraded',
72
+ available: async () => true,
73
+ init: async () => {
74
+ instance ??= createMemoryCache(options);
75
+ return instance;
76
+ },
77
+ dispose: async () => {
78
+ await instance?.clear();
79
+ instance = undefined;
80
+ },
81
+ health: async () => ({
82
+ status: 'degraded',
83
+ driver: 'memory',
84
+ tier: 'degraded',
85
+ message: 'In-process cache. Not shared between processes and lost on restart.',
86
+ }),
87
+ };
88
+ }
89
+ //# sourceMappingURL=memory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/cache/memory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ import type { Driver } from '../drivers/index.js';
2
+ import type { CacheConfig, CacheDriver, CacheDriverOptions } from './types.js';
3
+ /**
4
+ * Only what this driver calls, described structurally.
5
+ *
6
+ * Importing the client's own types would put `@redis/client` in the published
7
+ * type declarations, and it is an *optional* peer: a site with no Redis must not
8
+ * be asked to install it to typecheck.
9
+ */
10
+ interface RedisClientLike {
11
+ connect(): Promise<unknown>;
12
+ quit(): Promise<unknown>;
13
+ ping(): Promise<string>;
14
+ get(key: string): Promise<string | null>;
15
+ set(key: string, value: string, options?: {
16
+ PX?: number;
17
+ }): Promise<unknown>;
18
+ del(keys: string[]): Promise<number>;
19
+ sAdd(key: string, members: string[]): Promise<number>;
20
+ sRem(key: string, members: string[]): Promise<number>;
21
+ sMembers(key: string): Promise<string[]>;
22
+ scan(cursor: string, options: {
23
+ MATCH: string;
24
+ COUNT: number;
25
+ }): Promise<{
26
+ cursor: string | number;
27
+ keys: string[];
28
+ }>;
29
+ }
30
+ interface RedisModuleLike {
31
+ createClient(options: {
32
+ url?: string;
33
+ }): RedisClientLike;
34
+ }
35
+ /**
36
+ * Loads the client if the host application installed it. Absent is a normal
37
+ * outcome, not an error: it is what makes the registry fall through to the file
38
+ * driver, and what lets `pnpm install` stay dependency-free (rule R1).
39
+ */
40
+ export declare function loadRedisModule(): Promise<RedisModuleLike | null>;
41
+ export interface RedisCacheOptions extends CacheDriverOptions {
42
+ readonly client: RedisClientLike;
43
+ /**
44
+ * Namespace for every key this driver writes. A Redis instance is often
45
+ * shared; `clear()` must never touch what it does not own.
46
+ */
47
+ readonly prefix?: string;
48
+ }
49
+ export declare function createRedisCache(options: RedisCacheOptions): CacheDriver;
50
+ export declare function redisCacheDriver(options?: CacheDriverOptions & {
51
+ prefix?: string;
52
+ }): Driver<CacheDriver, CacheConfig>;
53
+ export {};
54
+ //# sourceMappingURL=redis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../src/cache/redis.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAgB,MAAM,qBAAqB,CAAA;AAG/D,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,kBAAkB,EAAmB,MAAM,YAAY,CAAA;AAE/F;;;;;;GAMG;AACH,UAAU,eAAe;IACvB,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;IAC3B,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;IACxB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;IACvB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACxC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC5E,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACpC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACrD,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACrD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;IACxC,IAAI,CACF,MAAM,EAAE,MAAM,EACd,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GACxC,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAA;CACxD;AAED,UAAU,eAAe;IACvB,YAAY,CAAC,OAAO,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,eAAe,CAAA;CACzD;AAED;;;;GAIG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAMvE;AAED,MAAM,WAAW,iBAAkB,SAAQ,kBAAkB;IAC3D,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAA;IAChC;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,WAAW,CAgFxE;AAED,wBAAgB,gBAAgB,CAC9B,OAAO,GAAE,kBAAkB,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GACrD,MAAM,CAAC,WAAW,EAAE,WAAW,CAAC,CA8ElC"}