@daiso-tech/core 0.45.0 → 0.46.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 (21) hide show
  1. package/README.md +50 -44
  2. package/dist/cache/contracts/database-cache-adapter.contract.d.ts +10 -48
  3. package/dist/cache/contracts/database-cache-adapter.contract.js +1 -1
  4. package/dist/cache/contracts/database-cache-adapter.contract.js.map +1 -1
  5. package/dist/cache/implementations/adapters/kysely-cache-adapter/kysely-cache-adapter.d.ts +24 -19
  6. package/dist/cache/implementations/adapters/kysely-cache-adapter/kysely-cache-adapter.js +138 -134
  7. package/dist/cache/implementations/adapters/kysely-cache-adapter/kysely-cache-adapter.js.map +1 -1
  8. package/dist/cache/implementations/derivables/cache/database-cache-adapter.d.ts +3 -1
  9. package/dist/cache/implementations/derivables/cache/database-cache-adapter.js +49 -64
  10. package/dist/cache/implementations/derivables/cache/database-cache-adapter.js.map +1 -1
  11. package/dist/cache/implementations/derivables/cache/is-database-cache-adapter.js +5 -10
  12. package/dist/cache/implementations/derivables/cache/is-database-cache-adapter.js.map +1 -1
  13. package/dist/cache/implementations/test-utilities/cache-adapter.test-suite.js +240 -238
  14. package/dist/cache/implementations/test-utilities/cache-adapter.test-suite.js.map +1 -1
  15. package/dist/cache/implementations/test-utilities/database-cache-dapter.test-suite.d.ts +0 -1
  16. package/dist/cache/implementations/test-utilities/database-cache-dapter.test-suite.js +215 -630
  17. package/dist/cache/implementations/test-utilities/database-cache-dapter.test-suite.js.map +1 -1
  18. package/dist/utilities/functions/error-policy.d.ts +1 -1
  19. package/dist/utilities/functions/error-policy.js +1 -2
  20. package/dist/utilities/functions/error-policy.js.map +1 -1
  21. package/package.json +38 -56
package/README.md CHANGED
@@ -1,64 +1,70 @@
1
+ # @daiso-tech/core
2
+
1
3
  [![npm version](https://img.shields.io/npm/v/@daiso-tech/core)](https://www.npmjs.com/package/@daiso-tech/core)
2
4
  ![NPM Downloads](https://img.shields.io/npm/dy/@daiso-tech/core)
3
- ![Static Badge](https://img.shields.io/badge/TypeScript-3178C6?logo=TypeScript&logoColor=white)
5
+ ![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?logo=TypeScript&logoColor=white)
4
6
  [![ES Modules](https://img.shields.io/badge/module%20type-ESM-blue)](https://nodejs.org/api/esm.html)
5
7
  [![License](https://img.shields.io/npm/l/@daiso-tech/core)](LICENSE)
6
8
 
7
- # @daiso-tech/core
8
-
9
- `@daiso-tech/core` is a TypeScript-first backend library for building web apps and API servers. It includes an ecosystem of official packages designed to work seamlessly together.
10
-
11
- **@daiso-tech/core** is a library of backend server components designed for maximum flexibility. While each component is fully functional on its own, they are written to work seamlessly together to build robust server-side logic.
12
-
13
- Key Features:
14
- - **Framework Agnostic:** Built without a dependency injection (DI) container, the library integrates effortlessly into any framework / library. Whether you use Express.js, backend frameworks like AdonisJS or NestJS, or full-stack frameworks like Next.js, Nuxt, or TanStack Start, these components "just work."
15
-
16
- - **Runtime Portability:** By leveraging the Adapter Pattern, the library remains decoupled from specific runtimes. Easily implement custom adapters, such as Cloudflare Durable Objects or Amazon DynamoDB, for better serverless integration with cloudflare workers or aws lambda.
17
-
18
- - **Test Friendly:** Testing is simple because every component comes with an "in-memory" adapter. This means you can run your tests instantly without needing to connect to a real database or server.
19
-
20
- - **Developer Friendly:** Save time with high-quality documentation and a readable, well-documented codebase. Clear comments and a logical structure make it easy to understand and extend system functionality.
21
-
22
- - **Type safe:**
23
- We pay a closer look at type-safety, seamless intellisense, and support for auto imports when designing library APIs.
9
+ `@daiso-tech/core` is a **TypeScript-first backend toolkit** designed for building resilient web applications and API servers. It provides a suite of decoupled, high-performance components that work seamlessly across any JavaScript runtime.
24
10
 
25
- - **ESM ready:**
26
- @daiso-tech/core leverages modern JavaScript primitives, including ES modules
11
+ [**Explore the Docs**](https://daiso-core.vercel.app/docs/Installation) | [**NPM Package**](https://www.npmjs.com/package/@daiso-tech/core)
27
12
 
28
- - **Supports standard schema:**
29
- Integrated seamlessly with [standard schema](https://standardschema.dev/) allowing you to use libraries like zod to ensure both compile time and runtime typesafety.
13
+ ---
30
14
 
31
- [Get started now](https://daiso-core.vercel.app/docs/Installation)
15
+ ## 🚀 Key Features
32
16
 
33
- ## A growing collection of officially maintained components
17
+ * **Framework Agnostic** No Dependency Injection (DI) containers required. Effortlessly integrate with Express, NestJS, AdonisJS, or full-stack frameworks like Next.js, Nuxt, and TanStack Start.
18
+ * **Runtime Portability** Leverages the **Adapter Pattern** to decouple your logic from the runtime. Switch between Node.js, Cloudflare Workers (Durable Objects), or AWS Lambda without rewriting core logic.
19
+ * **Test-Driven Excellence** Every component includes a built-in **"in-memory" adapter**. Run unit tests instantly without spinning up databases or external infrastructure.
20
+ * **Type Safety & DX** Deep IntelliSense support and strict type-safety. Designed for auto-imports and modern developer workflows.
21
+ * **Standard Schema Support** Native integration with [Standard Schema](https://standardschema.dev/), allowing you to use **Zod**, **Valibot**, or **ArkType** for unified runtime validation.
34
22
 
35
- - **Cache:**
36
- Speed up your applications by storing slowly changing data in a cache store.
23
+ ---
37
24
 
38
- - **EventBus:**
39
- Easily send events accross different applications or in-memory.
25
+ ## 🛠 Quick Start
40
26
 
41
- - **Circuit-breaker:**
42
- A circuit-breaker is a resilience primitive preventing cascading failures from external services by stopping calls to a failing service.
27
+ ```bash
28
+ npm install @daiso-tech/core
29
+ ```
43
30
 
44
- - **Rate-limiter:**
45
- A Rate-limiter is a resilience primitive used to control the rate of traffic sent or received by a network interface or a service.
31
+ ## 📦 Core Components
46
32
 
47
- - **Lock:**
48
- Synchronize the access to a shared resource to prevents several processes, or concurrent code, from executing a section of code at the same time.
33
+ The `@daiso-tech/core` ecosystem provides a growing collection of officially maintained primitives for building robust systems:
49
34
 
50
- - **Semaphore:**
51
- A semaphore is a concurrency control primitive used to limit the number of processes or systems that can access a shared resource of code concurrently.
35
+ ### Resilience
36
+ | | |
37
+ | :--- | :--- |
38
+ | **Circuit Breaker** | Prevents cascading failures by stopping calls to failing external services. |
39
+ | **Rate Limiter** | Controls traffic flow to protect your network interfaces and services. |
40
+ | **Retry** | Retry middleware with support for different backoffs with jitter. |
41
+ | **Timeout** | Timeout middleware that prevents resource exhaustion by killing long-running tasks. |
42
+ | **Fallback** | Fallback middleware that ensures graceful degradation by returning default values. |
52
43
 
53
- - **Shared-lock:**
54
- A shared-lock (a.k.a reader writer lock) is a concurrency primitive offering better concurrency than a lock by coordinating a reader semaphore for concurrent access and an writer lock for mutual exclusion, strictly preventing conflicting simultaneous access and maintaining data consistency.
44
+ ### Concurrency
45
+ | | |
46
+ | :--- | :--- |
47
+ | **Lock** | Ensures mutual exclusion for shared resources across servers or procceses. |
48
+ | **Semaphore** | Limits the number of concurrent servers or procceses accessing a specific resource. |
49
+ | **Shared Lock** | Reader-writer lock coordinating concurrent reads and exclusive writes. |
55
50
 
56
- - **Serde:**
57
- Add custom serialization and deserialization logic that seamlessly integrates with all other components.
51
+ ### Misc
52
+ | | |
53
+ | :--- | :--- |
54
+ | **Cache** | High-performance caching with support for multiple store adapters. |
55
+ | **EventBus** | Decoupled event-driven communication (In-memory or Distributed via redis). |
58
56
 
59
- - **Collection:**
60
- Effortlessly work with Arrays, Iterables, and AsyncIterables. Filter and transform with precision.
57
+ ### Utilities
58
+ | | |
59
+ | :--- | :--- |
60
+ | **Hooks** | Agnostic sync/async middleware that integrates with all components. |
61
+ | **Serde** | Custom serialization/deserialization logic that integrates with all components. |
62
+ | **Collection** | Precision filtering and transformation for Arrays, Iterables, AsyncIterables and ArrayLike objects. |
63
+ | **TimeSpan** | A duration class offering seamless time manipulation while integrating with all components. |
64
+ ---
61
65
 
62
- - **Hooks:**
63
- Extend any sync and async function with agnostic hooks.@daiso-tech/core includes predefined retry, fallback, timeout and hedging hooks to easily allow handling transient failures.
66
+ ## 🛠 Quick Start
64
67
 
68
+ ```bash
69
+ npm install @daiso-tech/core
70
+ ```
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @module Cache
3
3
  */
4
+ import { type InvokableFn } from "../../utilities/_module.js";
4
5
  /**
5
6
  *
6
7
  * IMPORT_PATH: `"@daiso-tech/core/cache/contracts"`
@@ -11,32 +12,20 @@ export type ICacheData<TType = unknown> = {
11
12
  expiration: Date | null;
12
13
  };
13
14
  /**
14
- *
15
15
  * IMPORT_PATH: `"@daiso-tech/core/cache/contracts"`
16
16
  * @group Contracts
17
17
  */
18
- export type ICacheDataExpiration = {
19
- expiration: Date | null;
18
+ export type IDatabaseCacheTransaction<TType = unknown> = {
19
+ find(key: string): Promise<ICacheData<TType> | null>;
20
+ upsert(key: string, value: TType, expiration?: Date | null): Promise<void>;
20
21
  };
21
22
  /**
22
- *
23
23
  * IMPORT_PATH: `"@daiso-tech/core/cache/contracts"`
24
24
  * @group Contracts
25
25
  */
26
- export type ICacheInsert<TType = unknown> = {
27
- key: string;
28
- value: TType;
26
+ export type ICacheDataExpiration = {
29
27
  expiration: Date | null;
30
28
  };
31
- /**
32
- *
33
- * IMPORT_PATH: `"@daiso-tech/core/cache/contracts"`
34
- * @group Contracts
35
- */
36
- export type ICacheUpdate<TType = unknown> = {
37
- key: string;
38
- value: TType;
39
- };
40
29
  /**
41
30
  * The `IDatabaseCacheAdapter` contract defines a way for as key-value pairs independent of data storage.
42
31
  * This contract simplifies the implementation of cache adapters with CRUD-based databases, such as SQL databases and ORMs like TypeOrm and MikroOrm.
@@ -45,39 +34,12 @@ export type ICacheUpdate<TType = unknown> = {
45
34
  * @group Contracts
46
35
  */
47
36
  export type IDatabaseCacheAdapter<TType = unknown> = {
48
- /**
49
- * The `find` method returns the the `key` data which includs {@link ICacheData | `ICacheData.value`} and {@link ICacheData | `ICacheData.expiration`}.
50
- */
51
37
  find(key: string): Promise<ICacheData<TType> | null>;
52
- /**
53
- * The `insert` method inserts the given cache `data`.
54
- */
55
- insert(data: ICacheInsert<TType>): Promise<void>;
56
- /**
57
- * The `upsert` method inserts a key and if the key already exists then key will be updated with new `data.value` and `data.expiration`.
58
- * The method always returns the old cache data if it exists otherwise null will be returned.
59
- */
60
- upsert(data: ICacheInsert<TType>): Promise<ICacheDataExpiration | null>;
61
- /**
62
- * The `removeExpiredMany` method updates a expired `key`.
63
- */
64
- updateExpired(data: ICacheInsert<TType>): Promise<number>;
65
- /**
66
- * The `removeExpiredMany` method updates a unexpired `key`.
67
- */
68
- updateUnexpired(data: ICacheUpdate<TType>): Promise<number>;
69
- /**
70
- * The `incrementUnexpired` should always throw an error if the existing item is not a number type.
71
- */
72
- incrementUnexpired(data: ICacheUpdate<number>): Promise<number>;
73
- /**
74
- * The `removeExpiredMany` method removes multiple expired `keys`.
75
- */
76
- removeExpiredMany(keys: string[]): Promise<number>;
77
- /**
78
- * The `removeExpiredMany` method removes multiple unexpired `keys`.
79
- */
80
- removeUnexpiredMany(keys: string[]): Promise<number>;
38
+ transaction<TValue>(trxFn: InvokableFn<[
39
+ trx: IDatabaseCacheTransaction<TType>
40
+ ], Promise<TValue>>): Promise<TValue>;
41
+ update(key: string, value: TType): Promise<ICacheDataExpiration | null>;
42
+ removeMany(keys: string[]): Promise<ICacheDataExpiration[]>;
81
43
  /**
82
44
  * The `removeAll` method removes all keys from the cache.
83
45
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * @module Cache
3
3
  */
4
- export {};
4
+ import {} from "../../utilities/_module.js";
5
5
  //# sourceMappingURL=database-cache-adapter.contract.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"database-cache-adapter.contract.js","sourceRoot":"","sources":["../../../src/cache/contracts/database-cache-adapter.contract.ts"],"names":[],"mappings":"AAAA;;GAEG"}
1
+ {"version":3,"file":"database-cache-adapter.contract.js","sourceRoot":"","sources":["../../../src/cache/contracts/database-cache-adapter.contract.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,MAAM,wBAAwB,CAAC"}
@@ -2,35 +2,32 @@
2
2
  * @module Cache
3
3
  */
4
4
  import { type Kysely } from "kysely";
5
- import { type ICacheData, type ICacheInsert, type ICacheUpdate, type IDatabaseCacheAdapter } from "../../../../cache/contracts/_module.js";
5
+ import { type ICacheData, type ICacheDataExpiration, type IDatabaseCacheAdapter, type IDatabaseCacheTransaction } from "../../../../cache/contracts/_module.js";
6
6
  import { type ISerde } from "../../../../serde/contracts/_module.js";
7
7
  import { type ITimeSpan } from "../../../../time-span/contracts/_module.js";
8
- import { type IDeinitizable, type IInitizable, type IPrunable } from "../../../../utilities/_module.js";
8
+ import { type IDeinitizable, type IInitizable, type InvokableFn, type IPrunable } from "../../../../utilities/_module.js";
9
9
  /**
10
- *
11
10
  * IMPORT_PATH: `"@daiso-tech/core/cache/kysely-cache-adapter"`
12
11
  * @group Adapters
13
12
  */
14
- export type KyselyCacheAdapterTable = {
13
+ export type KyselyCacheTable = {
15
14
  key: string;
16
15
  value: string;
17
16
  expiration: number | string | null;
18
17
  };
19
18
  /**
20
- *
21
19
  * IMPORT_PATH: `"@daiso-tech/core/cache/kysely-cache-adapter"`
22
20
  * @group Adapters
23
21
  */
24
- export type KyselyCacheAdapterTables = {
25
- cache: KyselyCacheAdapterTable;
22
+ export type KyselyCacheTables = {
23
+ cache: KyselyCacheTable;
26
24
  };
27
25
  /**
28
- *
29
26
  * IMPORT_PATH: `"@daiso-tech/core/cache/kysely-cache-adapter"`
30
27
  * @group Adapters
31
28
  */
32
29
  export type KyselyCacheAdapterSettings = {
33
- kysely: Kysely<KyselyCacheAdapterTables>;
30
+ kysely: Kysely<KyselyCacheTables>;
34
31
  serde: ISerde<string>;
35
32
  /**
36
33
  * @default
@@ -46,6 +43,17 @@ export type KyselyCacheAdapterSettings = {
46
43
  */
47
44
  shouldRemoveExpiredKeys?: boolean;
48
45
  };
46
+ /**
47
+ * @internal
48
+ */
49
+ export declare class DatabaseCacheTransaction<TType> implements IDatabaseCacheTransaction<TType> {
50
+ private readonly kysely;
51
+ private readonly serde;
52
+ private readonly isMysql;
53
+ constructor(kysely: Kysely<KyselyCacheTables>, serde: ISerde<string>);
54
+ find(key: string): Promise<ICacheData<TType> | null>;
55
+ upsert(key: string, value: TType, expiration?: Date | null): Promise<void>;
56
+ }
49
57
  /**
50
58
  * To utilize the `KyselyCacheAdapter`, you must install the [`"kysely"`](https://www.npmjs.com/package/kysely) package and configure a `Kysely` class instance.
51
59
  * The adapter have been tested with `sqlite`, `postgres` and `mysql` databases.
@@ -54,8 +62,7 @@ export type KyselyCacheAdapterSettings = {
54
62
  * @group Adapters
55
63
  */
56
64
  export declare class KyselyCacheAdapter<TType = unknown> implements IDatabaseCacheAdapter<TType>, IInitizable, IDeinitizable, IPrunable {
57
- private static filterUnexpiredKeys;
58
- private static filterExpiredKeys;
65
+ private readonly isMysql;
59
66
  private readonly serde;
60
67
  private readonly kysely;
61
68
  private readonly shouldRemoveExpiredKeys;
@@ -93,14 +100,12 @@ export declare class KyselyCacheAdapter<TType = unknown> implements IDatabaseCac
93
100
  */
94
101
  deInit(): Promise<void>;
95
102
  find(key: string): Promise<ICacheData<TType> | null>;
96
- insert(data: ICacheInsert<TType>): Promise<void>;
97
- private transaction;
98
- upsert(data: ICacheInsert<TType>): Promise<ICacheData<TType> | null>;
99
- updateExpired(data: ICacheInsert<TType>): Promise<number>;
100
- updateUnexpired(data: ICacheUpdate<TType>): Promise<number>;
101
- incrementUnexpired(data: ICacheUpdate<number>): Promise<number>;
102
- removeExpiredMany(keys: string[]): Promise<number>;
103
- removeUnexpiredMany(keys: string[]): Promise<number>;
103
+ private _transaction;
104
+ transaction<TValue>(trxFn: InvokableFn<[
105
+ trx: IDatabaseCacheTransaction<TType>
106
+ ], Promise<TValue>>): Promise<TValue>;
107
+ update(key: string, value: TType): Promise<ICacheDataExpiration | null>;
108
+ removeMany(keys: string[]): Promise<ICacheDataExpiration[]>;
104
109
  removeAll(): Promise<void>;
105
110
  removeByKeyPrefix(prefix: string): Promise<void>;
106
111
  }
@@ -1,12 +1,70 @@
1
1
  /**
2
2
  * @module Cache
3
3
  */
4
- import { MysqlAdapter, Transaction, } from "kysely";
4
+ import { MysqlAdapter, Transaction } from "kysely";
5
5
  import {} from "../../../../cache/contracts/_module.js";
6
6
  import {} from "../../../../serde/contracts/_module.js";
7
7
  import {} from "../../../../time-span/contracts/_module.js";
8
8
  import { TimeSpan } from "../../../../time-span/implementations/_module.js";
9
9
  import {} from "../../../../utilities/_module.js";
10
+ /**
11
+ * @internal
12
+ */
13
+ export class DatabaseCacheTransaction {
14
+ kysely;
15
+ serde;
16
+ isMysql;
17
+ constructor(kysely, serde) {
18
+ this.kysely = kysely;
19
+ this.serde = serde;
20
+ this.isMysql =
21
+ this.kysely.getExecutor().adapter instanceof MysqlAdapter;
22
+ }
23
+ async find(key) {
24
+ const cacheData = await this.kysely
25
+ .selectFrom("cache")
26
+ .where("cache.key", "=", key)
27
+ .select(["cache.expiration", "cache.value"])
28
+ .executeTakeFirst();
29
+ if (cacheData === undefined) {
30
+ return null;
31
+ }
32
+ return {
33
+ value: this.serde.deserialize(cacheData.value),
34
+ expiration: cacheData.expiration === null
35
+ ? null
36
+ : new Date(Number(cacheData.expiration)),
37
+ };
38
+ }
39
+ async upsert(key, value, expiration) {
40
+ let expirationAsMs;
41
+ if (expiration instanceof Date) {
42
+ expirationAsMs = expiration.getTime();
43
+ }
44
+ else {
45
+ expirationAsMs = expiration;
46
+ }
47
+ const serializedValue = this.serde.serialize(value);
48
+ await this.kysely
49
+ .insertInto("cache")
50
+ .values({
51
+ key,
52
+ value: serializedValue,
53
+ expiration: expirationAsMs,
54
+ })
55
+ .$if(!this.isMysql, (eb) => eb.onConflict((eb) => eb.column("key").doUpdateSet({
56
+ key,
57
+ value: serializedValue,
58
+ expiration: expirationAsMs,
59
+ })))
60
+ .$if(this.isMysql, (eb) => eb.onDuplicateKeyUpdate({
61
+ key,
62
+ value: serializedValue,
63
+ expiration: expirationAsMs,
64
+ }))
65
+ .execute();
66
+ }
67
+ }
10
68
  /**
11
69
  * To utilize the `KyselyCacheAdapter`, you must install the [`"kysely"`](https://www.npmjs.com/package/kysely) package and configure a `Kysely` class instance.
12
70
  * The adapter have been tested with `sqlite`, `postgres` and `mysql` databases.
@@ -15,29 +73,7 @@ import {} from "../../../../utilities/_module.js";
15
73
  * @group Adapters
16
74
  */
17
75
  export class KyselyCacheAdapter {
18
- static filterUnexpiredKeys(keys) {
19
- return (eb) => {
20
- const hasNoExpiration = eb("cache.expiration", "is", null);
21
- const hasExpiration = eb("cache.expiration", "is not", null);
22
- const hasNotExpired = eb("cache.expiration", ">", Date.now());
23
- const keysMatch = eb("cache.key", "in", keys);
24
- return eb.and([
25
- keysMatch,
26
- eb.or([
27
- hasNoExpiration,
28
- eb.and([hasExpiration, hasNotExpired]),
29
- ]),
30
- ]);
31
- };
32
- }
33
- static filterExpiredKeys(keys) {
34
- return (eb) => {
35
- const keysMatch = eb("cache.key", "in", keys);
36
- const hasExpiration = eb("cache.expiration", "is not", null);
37
- const hasExpired = eb("cache.expiration", "<=", Date.now());
38
- return eb.and([keysMatch, hasExpiration, hasExpired]);
39
- };
40
- }
76
+ isMysql;
41
77
  serde;
42
78
  kysely;
43
79
  shouldRemoveExpiredKeys;
@@ -73,6 +109,8 @@ export class KyselyCacheAdapter {
73
109
  this.serde = serde;
74
110
  this.expiredKeysRemovalInterval = TimeSpan.fromTimeSpan(expiredKeysRemovalInterval);
75
111
  this.shouldRemoveExpiredKeys = shouldRemoveExpiredKeys;
112
+ this.isMysql =
113
+ this.kysely.getExecutor().adapter instanceof MysqlAdapter;
76
114
  }
77
115
  async removeAllExpired() {
78
116
  await this.kysely
@@ -138,135 +176,101 @@ export class KyselyCacheAdapter {
138
176
  }
139
177
  }
140
178
  async find(key) {
141
- const row = await this.kysely
179
+ const cacheData = await this.kysely
142
180
  .selectFrom("cache")
143
181
  .where("cache.key", "=", key)
144
182
  .select(["cache.expiration", "cache.value"])
145
183
  .executeTakeFirst();
146
- if (row === undefined) {
184
+ if (cacheData === undefined) {
147
185
  return null;
148
186
  }
149
187
  return {
150
- value: this.serde.deserialize(row.value),
151
- expiration: row.expiration !== null
152
- ? new Date(Number(row.expiration))
153
- : null,
188
+ value: this.serde.deserialize(cacheData.value),
189
+ expiration: cacheData.expiration === null
190
+ ? null
191
+ : new Date(Number(cacheData.expiration)),
154
192
  };
155
193
  }
156
- async insert(data) {
157
- await this.kysely
158
- .insertInto("cache")
159
- .values({
160
- key: data.key,
161
- value: this.serde.serialize(data.value),
162
- expiration: data.expiration?.getTime() ?? null,
163
- })
164
- .executeTakeFirst();
165
- }
166
- async transaction(fn) {
194
+ _transaction(trxFn) {
167
195
  if (this.disableTransaction) {
168
- return fn(this.kysely);
196
+ return trxFn(this.kysely);
169
197
  }
170
- return await this.kysely
171
- .transaction()
172
- .setIsolationLevel("serializable")
173
- .execute(fn);
174
- }
175
- async upsert(data) {
176
- const expiration = data.expiration?.getTime() ?? null;
177
- return await this.transaction(async (trx) => {
178
- const prevRow = await trx
179
- .selectFrom("cache")
180
- .select(["cache.key", "cache.value", "cache.expiration"])
181
- .where(KyselyCacheAdapter.filterUnexpiredKeys([data.key]))
182
- .executeTakeFirst();
183
- const isMysqlRunning = this.kysely.getExecutor().adapter instanceof MysqlAdapter;
184
- await trx
185
- .insertInto("cache")
186
- .values({
187
- key: data.key,
188
- value: this.serde.serialize(data.value),
189
- expiration,
190
- })
191
- .$if(isMysqlRunning, (eb) => eb.onDuplicateKeyUpdate({
192
- value: this.serde.serialize(data.value),
193
- expiration,
194
- }))
195
- .$if(!isMysqlRunning, (eb) => eb.onConflict((eb) => eb.columns(["key"]).doUpdateSet({
196
- value: this.serde.serialize(data.value),
197
- expiration,
198
- })))
199
- .executeTakeFirst();
200
- if (prevRow === undefined) {
201
- return null;
202
- }
203
- return {
204
- value: this.serde.deserialize(prevRow.value),
205
- expiration: prevRow.expiration !== null
206
- ? new Date(Number(prevRow.expiration))
207
- : null,
208
- };
198
+ return this.kysely.transaction().execute(async (trx) => {
199
+ return await trxFn(trx);
209
200
  });
210
201
  }
211
- async updateExpired(data) {
212
- const updateResult = await this.kysely
213
- .updateTable("cache")
214
- .where(KyselyCacheAdapter.filterExpiredKeys([data.key]))
215
- .set({
216
- value: this.serde.serialize(data.value),
217
- expiration: data.expiration?.getTime() ?? null,
218
- })
219
- .executeTakeFirst();
220
- return Number(updateResult.numUpdatedRows);
221
- }
222
- async updateUnexpired(data) {
223
- const updateResult = await this.kysely
224
- .updateTable("cache")
225
- .where(KyselyCacheAdapter.filterUnexpiredKeys([data.key]))
226
- .set({
227
- value: this.serde.serialize(data.value),
228
- })
229
- .executeTakeFirst();
230
- return Number(updateResult.numUpdatedRows);
202
+ async transaction(trxFn) {
203
+ return await this._transaction((trx) => trxFn(new DatabaseCacheTransaction(trx, this.serde)));
231
204
  }
232
- async incrementUnexpired(data) {
233
- return await this.transaction(async (trx) => {
234
- const row = await trx
235
- .selectFrom("cache")
236
- .select(["cache.value"])
237
- .where(KyselyCacheAdapter.filterUnexpiredKeys([data.key]))
238
- .executeTakeFirst();
239
- if (row === undefined) {
240
- return 0;
241
- }
242
- const { value: serializedValue } = row;
243
- const value = this.serde.deserialize(serializedValue);
244
- if (typeof value !== "number") {
245
- throw new TypeError(`Unable to increment or decrement none number type key "${data.key}"`);
246
- }
247
- const updateResult = await trx
205
+ async update(key, value) {
206
+ let row;
207
+ const serializedValue = this.serde.serialize(value);
208
+ if (this.isMysql) {
209
+ row = await this._transaction(async (trx) => {
210
+ const rows = await trx
211
+ .selectFrom("cache")
212
+ .where("cache.key", "=", key)
213
+ .select("cache.expiration")
214
+ .executeTakeFirst();
215
+ await trx
216
+ .updateTable("cache")
217
+ .where("cache.key", "=", key)
218
+ .set({
219
+ value: serializedValue,
220
+ })
221
+ .execute();
222
+ return rows;
223
+ });
224
+ }
225
+ else {
226
+ row = await this.kysely
248
227
  .updateTable("cache")
249
- .where(KyselyCacheAdapter.filterUnexpiredKeys([data.key]))
228
+ .where("cache.key", "=", key)
250
229
  .set({
251
- value: this.serde.serialize(value + data.value),
230
+ value: serializedValue,
252
231
  })
232
+ .returning("cache.expiration")
253
233
  .executeTakeFirst();
254
- return Number(updateResult.numUpdatedRows);
255
- });
256
- }
257
- async removeExpiredMany(keys) {
258
- const deleteResult = await this.kysely
259
- .deleteFrom("cache")
260
- .where(KyselyCacheAdapter.filterExpiredKeys(keys))
261
- .executeTakeFirst();
262
- return Number(deleteResult.numDeletedRows);
234
+ }
235
+ if (row === undefined) {
236
+ return null;
237
+ }
238
+ return {
239
+ expiration: row.expiration === null
240
+ ? null
241
+ : new Date(Number(row.expiration)),
242
+ };
263
243
  }
264
- async removeUnexpiredMany(keys) {
265
- const deleteResult = await this.kysely
266
- .deleteFrom("cache")
267
- .where(KyselyCacheAdapter.filterUnexpiredKeys(keys))
268
- .executeTakeFirst();
269
- return Number(deleteResult.numDeletedRows);
244
+ async removeMany(keys) {
245
+ let rows;
246
+ if (this.isMysql) {
247
+ rows = await this._transaction(async (trx) => {
248
+ const rows = await trx
249
+ .selectFrom("cache")
250
+ .where("cache.key", "in", keys)
251
+ .select("cache.expiration")
252
+ .execute();
253
+ await trx
254
+ .deleteFrom("cache")
255
+ .where("cache.key", "in", keys)
256
+ .execute();
257
+ return rows;
258
+ });
259
+ }
260
+ else {
261
+ rows = await this.kysely
262
+ .deleteFrom("cache")
263
+ .where("cache.key", "in", keys)
264
+ .returning("cache.expiration")
265
+ .execute();
266
+ }
267
+ return rows.map((row) => {
268
+ return {
269
+ expiration: row.expiration === null
270
+ ? null
271
+ : new Date(Number(row.expiration)),
272
+ };
273
+ });
270
274
  }
271
275
  async removeAll() {
272
276
  await this.kysely.deleteFrom("cache").execute();