@anteros/core 0.0.38 → 0.0.39

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.
@@ -1,7 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import path from "node:path";
3
3
  import type { FindOptions, FindCallOptions } from "../types/mongo";
4
- import { Cache, useFilesystemCache, useMemoryCache, useRedisCache } from "../utils/cache";
4
+ import { type Cache, useFilesystemCache, useMemoryCache, useRedisCache } from "../utils/cache";
5
5
  import { cfg } from "../server/config";
6
6
 
7
7
  /**
@@ -1144,8 +1144,8 @@ class MongoRest {
1144
1144
  missing: (key) => ns().missing({ key }),
1145
1145
  delete: (key) => ns().delete({ key }),
1146
1146
  deleteMany: (keys) => ns().deleteMany({ keys }),
1147
- pull: (key) => ns().pull(key),
1148
- expire: (key, ttl) => ns().expire({ key, ttl }),
1147
+ pull: (key) => ns().pull(key) as Promise<any>, // bentocache types are stricter than the TenantCache contract
1148
+ expire: (key) => ns().expire({ key }), // bentocache soft-expire takes { key } only (grace is a getOrSet option)
1149
1149
  clear: () => ns().clear(),
1150
1150
  namespace: (name) => cache.namespace(`app:${tenantId}:${name}`),
1151
1151
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@anteros/core",
3
3
  "module": "index.ts",
4
- "version": "0.0.38",
4
+ "version": "0.0.39",
5
5
  "type": "module",
6
6
  "description": "Anteros Core",
7
7
  "private": false,
package/probe-cache.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { Cache } from "./utils/cache";
1
+ import { useMemoryCache } from "./utils/cache";
2
2
 
3
3
  async function probe() {
4
- const c = new Cache("memory");
4
+ const c = useMemoryCache();
5
5
  const v = await c.get({ key: "k" });
6
- const p = await c.pull({ key: "p" });
6
+ const p = await c.pull("p");
7
7
  const g = await c.getOrSet({ key: "g", factory: () => 42 });
8
8
  const s = await c.set({ key: "a", value: 1 });
9
9
  const h = await c.has({ key: "a" });
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, beforeEach } from "bun:test";
2
- import { Cache, useMemoryCache, useFilesystemCache } from "../utils/cache";
2
+ import { type Cache, useMemoryCache, useFilesystemCache } from "../utils/cache";
3
3
 
4
4
  const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
5
5
 
@@ -12,16 +12,19 @@ describe("Cache (memory)", () => {
12
12
 
13
13
  it("set + get round-trip", async () => {
14
14
  await c.set({ key: "k", value: { id: 1 }, ttl: "5m" });
15
- expect(await c.get({ key: "k" })).toEqual({ id: 1 });
15
+ const v = await c.get({ key: "k" });
16
+ expect(v).toEqual({ id: 1 });
16
17
  });
17
18
 
18
19
  it("setForever never expires", async () => {
19
20
  await c.setForever({ key: "cfg", value: { theme: "dark" } });
20
- expect(await c.get({ key: "cfg" })).toEqual({ theme: "dark" });
21
+ const v = await c.get({ key: "cfg" });
22
+ expect(v).toEqual({ theme: "dark" });
21
23
  });
22
24
 
23
- it("get returns null on miss", async () => {
24
- expect(await c.get({ key: "nope" })).toBeUndefined();
25
+ it("get returns undefined on miss", async () => {
26
+ const v = await c.get({ key: "nope" });
27
+ expect(v).toBeUndefined();
25
28
  });
26
29
 
27
30
  it("has / missing", async () => {
@@ -33,9 +36,11 @@ describe("Cache (memory)", () => {
33
36
 
34
37
  it("ttl expiry (ms)", async () => {
35
38
  await c.set({ key: "x", value: 1, ttl: 50 });
36
- expect(await c.get({ key: "x" })).toBe(1);
39
+ const v1 = await c.get({ key: "x" });
40
+ expect(v1).toBe(1);
37
41
  await sleep(80);
38
- expect(await c.get({ key: "x" })).toBeUndefined();
42
+ const v2 = await c.get({ key: "x" });
43
+ expect(v2).toBeUndefined();
39
44
  });
40
45
 
41
46
  it("getOrSet uses factory and caches", async () => {
@@ -44,21 +49,25 @@ describe("Cache (memory)", () => {
44
49
  calls++;
45
50
  return { n: calls };
46
51
  };
47
- expect(await c.getOrSet({ key: "g", factory })).toEqual({ n: 1 });
48
- expect(await c.getOrSet({ key: "g", factory })).toEqual({ n: 1 });
52
+ const a = await c.getOrSet({ key: "g", factory });
53
+ expect(a).toEqual({ n: 1 });
54
+ const b = await c.getOrSet({ key: "g", factory });
55
+ expect(b).toEqual({ n: 1 });
49
56
  expect(calls).toBe(1);
50
57
  });
51
58
 
52
59
  it("getOrSetForever", async () => {
53
60
  const value = await c.getOrSetForever({ key: "f", factory: () => 42 });
54
61
  expect(value).toBe(42);
55
- expect(await c.get({ key: "f" })).toBe(42);
62
+ const v = await c.get({ key: "f" });
63
+ expect(v).toBe(42);
56
64
  });
57
65
 
58
66
  it("delete removes entry", async () => {
59
67
  await c.set({ key: "d", value: 1 });
60
68
  await c.delete({ key: "d" });
61
- expect(await c.get({ key: "d" })).toBeUndefined();
69
+ const v = await c.get({ key: "d" });
70
+ expect(v).toBeUndefined();
62
71
  });
63
72
 
64
73
  it("deleteMany removes several keys", async () => {
@@ -71,16 +80,20 @@ describe("Cache (memory)", () => {
71
80
 
72
81
  it("pull returns and removes", async () => {
73
82
  await c.set({ key: "p", value: 7 });
74
- expect(await c.pull("p")).toBe(7);
75
- expect(await c.get({ key: "p" })).toBeUndefined();
83
+ const p = await c.pull("p");
84
+ expect(p).toBe(7);
85
+ const v = await c.get({ key: "p" });
86
+ expect(v).toBeUndefined();
76
87
  });
77
88
 
78
89
  it("clear empties everything", async () => {
79
90
  await c.set({ key: "a", value: 1 });
80
91
  await c.set({ key: "b", value: 2 });
81
92
  await c.clear();
82
- expect(await c.get({ key: "a" })).toBeUndefined();
83
- expect(await c.get({ key: "b" })).toBeUndefined();
93
+ const va = await c.get({ key: "a" });
94
+ const vb = await c.get({ key: "b" });
95
+ expect(va).toBeUndefined();
96
+ expect(vb).toBeUndefined();
84
97
  });
85
98
 
86
99
  it("getOrSet recovers after ttl expiry (grace accepted)", async () => {
@@ -89,19 +102,22 @@ describe("Cache (memory)", () => {
89
102
  expect(v1).toBe("fresh");
90
103
 
91
104
  await sleep(60); // past ttl
92
- expect(await c.get({ key: "e" })).toBeUndefined(); // expired
105
+ const v2 = await c.get({ key: "e" });
106
+ expect(v2).toBeUndefined(); // expired
93
107
 
94
108
  // getOrSet re-runs the factory after expiry
95
- const v2 = await c.getOrSet({ key: "e", factory: () => "fresh2" });
96
- expect(v2).toBe("fresh2");
109
+ const v3 = await c.getOrSet({ key: "e", factory: () => "fresh2" });
110
+ expect(v3).toBe("fresh2");
97
111
  });
98
112
 
99
113
  it("namespace groups keys", async () => {
100
114
  const users = c.namespace("users");
101
115
  await users.set({ key: "1", value: { name: "John" }, ttl: "5m" });
102
116
  await users.set({ key: "2", value: { name: "Jane" } });
103
- expect(await users.get({ key: "1" })).toEqual({ name: "John" });
104
- expect(await c.get({ key: "1" })).toBeUndefined(); // not in root
117
+ const u1 = await users.get({ key: "1" });
118
+ expect(u1).toEqual({ name: "John" });
119
+ const root = await c.get({ key: "1" });
120
+ expect(root).toBeUndefined(); // not in root
105
121
  await users.clear();
106
122
  expect(await users.missing({ key: "1" })).toBe(true);
107
123
  expect(await users.missing({ key: "2" })).toBe(true);
@@ -116,9 +132,11 @@ describe("Cache (filesystem)", () => {
116
132
  await a.disconnect();
117
133
 
118
134
  const b = useFilesystemCache({ directory: dir });
119
- expect(await b.get({ key: "persist" })).toEqual({ ok: true });
135
+ const v = await b.get({ key: "persist" });
136
+ expect(v).toEqual({ ok: true });
120
137
  await b.delete({ key: "persist" });
121
- expect(await b.get({ key: "persist" })).toBeUndefined();
138
+ const gone = await b.get({ key: "persist" });
139
+ expect(gone).toBeUndefined();
122
140
  await b.disconnect();
123
141
  });
124
142
  });
package/utils/cache.ts CHANGED
@@ -3,11 +3,9 @@ import { memoryDriver } from "bentocache/drivers/memory";
3
3
  import { redisDriver } from "bentocache/drivers/redis";
4
4
  import { fileDriver } from "bentocache/drivers/file";
5
5
  import path from "path";
6
- const drivers = {
7
- memoryDriver,
8
- redisDriver,
9
- fileDriver,
10
- }
6
+
7
+ /** The underlying BentoCache instance — full provider API, with autocompletion */
8
+ export type Cache = InstanceType<typeof BentoCache>;
11
9
 
12
10
  type Driver = 'memory' | 'redis' | 'filesystem';
13
11
 
@@ -25,50 +23,27 @@ type CacheOptions = {
25
23
  pruneInterval?: string;
26
24
  }
27
25
 
28
- class Cache {
29
- #cache: InstanceType<typeof BentoCache>;
30
- constructor(driver: Driver, options?: CacheOptions) {
31
- let store = bentostore().useL1Layer(memoryDriver({
32
- }))
33
-
34
- if (driver == 'filesystem') {
35
- store = store.useL2Layer(fileDriver({
36
- directory: options?.filesystem?.directory ?? path.join(process.cwd(), '.cache'),
37
- pruneInterval: options?.pruneInterval ?? '1h'
38
- }))
39
- }
26
+ function createCache(driver: Driver, options?: CacheOptions): Cache {
27
+ let store = bentostore().useL1Layer(memoryDriver({}))
40
28
 
41
- if (driver == 'redis' && options?.redis?.connection) {
42
- store = store.useL2Layer(redisDriver({
43
- connection: options.redis.connection
44
- }))
45
- }
46
- this.#cache = new BentoCache({
47
- default: driver,
48
- stores: {
49
- [driver]: store,
50
- }
51
- })
29
+ if (driver == 'filesystem') {
30
+ store = store.useL2Layer(fileDriver({
31
+ directory: options?.filesystem?.directory ?? path.join(process.cwd(), '.cache'),
32
+ pruneInterval: options?.pruneInterval ?? '1h'
33
+ }))
52
34
  }
53
35
 
54
- // ─── BentoCache provider API (delegated to the underlying instance) ───
55
-
56
- set(options: any): Promise<boolean> { return this.#cache.set(options); }
57
- setForever(options: any): Promise<boolean> { return this.#cache.setForever(options); }
58
- get(options: any): Promise<unknown> { return this.#cache.get(options); }
59
- getOrSet(options: any): Promise<unknown> { return this.#cache.getOrSet(options); }
60
- getOrSetForever(options: any): Promise<unknown> { return this.#cache.getOrSetForever(options); }
61
- has(options: any): Promise<boolean> { return this.#cache.has(options); }
62
- missing(options: any): Promise<boolean> { return this.#cache.missing(options); }
63
- pull(key: string): Promise<unknown> { return this.#cache.pull(key); }
64
- delete(options: any): Promise<boolean> { return this.#cache.delete(options); }
65
- deleteMany(options: any): Promise<boolean> { return this.#cache.deleteMany(options); }
66
- deleteByTag(options: any): Promise<boolean> { return this.#cache.deleteByTag(options); }
67
- expire(options: any): Promise<boolean> { return this.#cache.expire(options); }
68
- clear(options?: any): Promise<void> { return this.#cache.clear(options); }
69
- prune(): Promise<void> { return this.#cache.prune(); }
70
- namespace(namespace: string): any { return this.#cache.namespace(namespace); }
71
- disconnect(): Promise<void> { return this.#cache.disconnect(); }
36
+ if (driver == 'redis' && options?.redis?.connection) {
37
+ store = store.useL2Layer(redisDriver({
38
+ connection: options.redis.connection
39
+ }))
40
+ }
41
+ return new BentoCache({
42
+ default: driver,
43
+ stores: {
44
+ [driver]: store,
45
+ }
46
+ })
72
47
  }
73
48
 
74
49
 
@@ -79,8 +54,8 @@ function useRedisCache(options: {
79
54
  password: string;
80
55
  };
81
56
  pruneInterval?: string;
82
- }) {
83
- return new Cache('redis', {
57
+ }): Cache {
58
+ return createCache('redis', {
84
59
  redis: {
85
60
  connection: options.connection,
86
61
  },
@@ -88,15 +63,15 @@ function useRedisCache(options: {
88
63
  });
89
64
  }
90
65
 
91
- function useMemoryCache() {
92
- return new Cache('memory');
66
+ function useMemoryCache(): Cache {
67
+ return createCache('memory');
93
68
  }
94
69
 
95
70
  function useFilesystemCache(options: {
96
71
  directory: string;
97
72
  pruneInterval?: string;
98
- }) {
99
- return new Cache('filesystem', {
73
+ }): Cache {
74
+ return createCache('filesystem', {
100
75
  filesystem: {
101
76
  directory: options.directory,
102
77
  },
@@ -106,9 +81,8 @@ function useFilesystemCache(options: {
106
81
 
107
82
 
108
83
 
109
- export default Cache;
110
84
  export {
111
- Cache,
85
+
112
86
  useFilesystemCache,
113
87
  useMemoryCache,
114
88
  useRedisCache
package/bench-cache.ts DELETED
@@ -1,70 +0,0 @@
1
- // Scratch benchmark — `bun run bench-cache.ts` (depuis packages/core)
2
- // Vérifie que useCache fonctionne vraiment et mesure les ms (miss vs hit vs sans cache).
3
- import { useRest } from "./database/rest";
4
- import { formatConfig, cfg } from "./server/config";
5
- import { syncTenants } from "./database/tenant";
6
- import { syncCollections } from "./database/collection";
7
-
8
- const TENANT = "bench";
9
- const DB = "mongodb://localhost:27017/_DB_BENCH";
10
- const N = 10000; // docs seedés
11
-
12
- async function main() {
13
- formatConfig({
14
- server: { port: 4000, cache: { enabled: true, driver: "memory" } },
15
- tenants: [{ id: TENANT, dir: "bench-tenant", database: { uri: DB } }],
16
- });
17
- await syncTenants();
18
- await syncCollections();
19
-
20
- (cfg as any).collections = [
21
- { _tenant_: TENANT, slug: "items", fields: [{ name: "title", type: "string" }, { name: "count", type: "number" }], api: { access: { "*": true } } },
22
- ];
23
- const rest = new useRest({ internal: false, tenant_id: TENANT });
24
- const col = rest.db.collection("items");
25
-
26
- // Seed
27
- console.log(`Seeding ${N} docs…`);
28
- const batch = [];
29
- for (let i = 0; i < N; i++) batch.push({ title: `item-${i % 100}`, count: i });
30
- for (let i = 0; i < batch.length; i += 1000) await col.insertMany(batch.slice(i, i + 1000));
31
- console.log("Seeded ✓");
32
-
33
- const params = { $match: { count: { $gte: 1000, $lte: 9000 } }, $sort: { count: -1 }, $limit: 50 };
34
-
35
- // 1. Sans cache — moyenne sur 100 requêtes
36
- const t0 = performance.now();
37
- const ITERS = 100;
38
- for (let i = 0; i < ITERS; i++) await rest.find("items", params);
39
- const noCacheAvg = (performance.now() - t0) / ITERS;
40
-
41
- // 2. Avec cache — 1er appel (MISS → requête DB) puis hits
42
- const tMiss = performance.now();
43
- const r1 = await rest.find("items", params, { useCache: true });
44
- const missMs = performance.now() - tMiss;
45
- console.log(`MISS: ${r1.length} docs retournés (requête DB réelle)`);
46
-
47
- const tHits = performance.now();
48
- for (let i = 0; i < ITERS; i++) await rest.find("items", params, { useCache: true });
49
- const hitAvg = (performance.now() - tHits) / ITERS;
50
-
51
- // 3. Preuve que le hit sert BIEN depuis le cache (pas la DB) :
52
- // on mute DIRECTEMENT en DB (bypass framework) un doc présent dans le résultat.
53
- await col.updateMany({ count: 9000 }, { $set: { title: "MUTATED-DIRECT-DB" } });
54
- const cached = await rest.find("items", params, { useCache: true });
55
- const dbFresh = await rest.find("items", params);
56
- const stale = cached.some((d: any) => d.title === "MUTATED-DIRECT-DB");
57
- const fresh = dbFresh.some((d: any) => d.title === "MUTATED-DIRECT-DB");
58
-
59
- console.log("\n─── RÉSULTATS (driver memory, Mongo local) ───");
60
- console.log(`Sans cache : ${noCacheAvg.toFixed(3)} ms/requête (${ITERS} requêtes)`);
61
- console.log(`MISS (cache): ${missMs.toFixed(3)} ms (1er appel → requête DB, résultat mis en cache)`);
62
- console.log(`HIT (cache) : ${hitAvg.toFixed(3)} ms/requête (${ITERS} requêtes)`);
63
- console.log(`Gain : ${(noCacheAvg / hitAvg).toFixed(0)}× plus rapide`);
64
- console.log(`Preuve cache: cache sert ${stale ? "la VALEUR MUTÉE (bug! cache pas servi)" : "l'ANCIENNE valeur (cache servi ✓)"}, requête fraîche = ${fresh ? "nouvelle valeur (DB bien mutée ✓)" : "?"}`);
65
-
66
- await col.drop();
67
- process.exit(0);
68
- }
69
-
70
- main().catch((e) => { console.error(e); process.exit(1); });