@prisma/orm-extension-middleware-cache 8.0.0-rc.18-dev.17 → 8.0.0-rc.18-dev.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -8
- package/dist/index.d.mts +51 -33
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +140 -31
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -20,23 +20,23 @@ import {
|
|
|
20
20
|
|
|
21
21
|
## Responsibilities
|
|
22
22
|
|
|
23
|
-
A read-through cache middleware: on a hit it returns cached rows and never invokes the driver; on a miss it buffers the driver's rows and stores them when the read completes. A read opts in with `cacheAnnotation({ key?, meta?, bypass? })`: `key` names the entry
|
|
23
|
+
A read-through cache middleware. An annotated ORM call, such as `db.orm.public.User.first(...)`, is cached as a whole: on a hit the middleware supplies the call's result and the ORM sends no statement; on a miss it stores the value the call returned. An annotated statement, such as a SQL builder query, is cached as rows: on a hit it returns the cached rows and never invokes the driver; on a miss it buffers the driver's rows and stores them when the read completes. A read opts in with `cacheAnnotation({ key?, meta?, bypass? })`: `key` names the entry; otherwise an ORM call is keyed by its content hash, a digest of the terminal method and the statement it sends, and a statement by the `deriveKey` option (default: the family runtime's content hash, `deriveKeyFromContentHash`); `meta` is handed to the store with the entry; `bypass: true` skips the cache for that call. A read inside a transaction or on a held connection, such as a Supabase role session, is neither served nor stored.
|
|
24
24
|
|
|
25
25
|
How long an entry lives is the store's policy. The default store, `createInMemoryCacheStore({ maxEntries?, ttlMs?, clock? })`, keeps up to 1000 values for 60 seconds each; `ttlMs: Infinity` never expires. A key's version is kept for `ttlMs` after the `unset` that moved it. Versions are not bounded by `maxEntries`. A forgotten version reads as 0, so with a finite `ttlMs` a read that takes longer than `ttlMs` and overlaps an `invalidate` can store stale rows. With `ttlMs: Infinity`, the versions of invalidated keys are never forgotten and grow with the number of distinct keys invalidated. A `maxEntries` that is not a positive integer or a `ttlMs` that is not positive throws `RUNTIME.ARGUMENT_INVALID`. Implement the `CacheStore` interface to use Redis, Memcached, or any other backend.
|
|
26
26
|
|
|
27
27
|
## The store
|
|
28
28
|
|
|
29
|
-
The store is a cache of values; this middleware stores `
|
|
29
|
+
The store is a cache of values; this middleware stores `CachedValue`, the raw rows of one statement or the result of one ORM call. The middleware tags each value `statement-rows` or `call-result` and serves it only to the same kind of read; under one key, a value with the other tag is a miss.
|
|
30
30
|
|
|
31
31
|
```ts
|
|
32
|
-
interface CacheEntry<TMeta = unknown, TValue =
|
|
32
|
+
interface CacheEntry<TMeta = unknown, TValue = CachedValue> {
|
|
33
33
|
readonly key: string;
|
|
34
34
|
readonly meta: TMeta | undefined;
|
|
35
35
|
readonly version: number;
|
|
36
36
|
readonly data: { readonly empty: true } | { readonly empty: false; readonly value: TValue };
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
interface CacheStore<TMeta = unknown, TValue =
|
|
39
|
+
interface CacheStore<TMeta = unknown, TValue = CachedValue> {
|
|
40
40
|
get(target: {
|
|
41
41
|
readonly key: string;
|
|
42
42
|
readonly meta: TMeta | undefined;
|
|
@@ -49,9 +49,13 @@ interface CacheStore<TMeta = unknown, TValue = CachedRows> {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
type CachedRows = readonly Record<string, unknown>[];
|
|
52
|
+
|
|
53
|
+
type CachedValue =
|
|
54
|
+
| { readonly kind: 'statement-rows'; readonly rows: CachedRows }
|
|
55
|
+
| { readonly kind: 'call-result'; readonly result: unknown };
|
|
52
56
|
```
|
|
53
57
|
|
|
54
|
-
The store keeps a version per key, even for a key that holds no value; a key never seen has version 0, and only `unset` changes it. `get({ key, meta })` always returns a `CacheEntry` with the key, the read annotation's `meta`, the current version and `data`, which is `{ empty: true }` on a miss. A store that matches `meta` folds the versions of whatever `meta` names into that version, and does the same when `set` compares, so an `invalidate({ meta })` refuses an overlapping read's `set` even for a key the store has never seen. `set(entry, value)` stores `value` if and only if the key's version still equals `entry.version`, and returns whether it stored; there is no unconditional write, so a prefill is `get` then `set`. `unset` removes every value named in `keys` (`undefined` or non-empty) and every value that matches `meta`, increments the version of every key it removes or would remove, and an `unset` by key also drops that key from any `meta` index. `set` must be atomic against `unset`: one synchronous step in memory, or one script on a server. Keep a moved version at least as long as a read can take; the default store keeps it for `ttlMs`. The store interprets `meta` in all three methods; the middleware never does. A store that cannot act on a `meta` given to `unset` must throw: the default store throws `RUNTIME.CACHE_STORE_META_UNSUPPORTED`. `unset` must not run queries through the runtime that uses the middleware. A store used with this middleware holds
|
|
58
|
+
The store keeps a version per key, even for a key that holds no value; a key never seen has version 0, and only `unset` changes it. `get({ key, meta })` always returns a `CacheEntry` with the key, the read annotation's `meta`, the current version and `data`, which is `{ empty: true }` on a miss. A store that matches `meta` folds the versions of whatever `meta` names into that version, and does the same when `set` compares, so an `invalidate({ meta })` refuses an overlapping read's `set` even for a key the store has never seen. `set(entry, value)` stores `value` if and only if the key's version still equals `entry.version`, and returns whether it stored; there is no unconditional write, so a prefill is `get` then `set`. `unset` removes every value named in `keys` (`undefined` or non-empty) and every value that matches `meta`, increments the version of every key it removes or would remove, and an `unset` by key also drops that key from any `meta` index. `set` must be atomic against `unset`: one synchronous step in memory, or one script on a server. Keep a moved version at least as long as a read can take; the default store keeps it for `ttlMs`. The store interprets `meta` in all three methods; the middleware never does. A store that cannot act on a `meta` given to `unset` must throw: the default store throws `RUNTIME.CACHE_STORE_META_UNSUPPORTED`. `unset` must not run queries through the runtime that uses the middleware. An error from `get` or `set` fails the read, for statements and ORM calls alike, even when the database answered it; nothing is stored, so the next read fails the same way while the store keeps throwing. A store used with this middleware holds cached values: `createCacheMiddleware` takes a `CacheStore<TMeta, CachedValue>`, and a store of any other value type, `unknown` included, is a type error. `TValue` defaults to `CachedValue`, so `CacheStore<TMeta>` is a store of cached values; it exists so the same store type can serve other caches. A store must give back a value equivalent to the one it was given, `kind` included. An ORM call's result holds the values codecs decoded, such as `Date`, `bigint`, Temporal values and extension class instances, so a store that serializes needs an application-supplied serializer that rebuilds those types; `JSON.stringify` throws on a `bigint` and turns a `Date` into a string. A deploy that changes what a codec returns needs a new key prefix for a shared store, because the key does not cover how values are decoded. The default store, `createInMemoryCacheStore<TMeta>()`, holds cached values. It keeps statement rows and ORM call results differently. Statement rows are copied one level deep on `set` and `get`: the array, and each row object with its prototype. The driver's values in them, such as BSON `ObjectId`s or a `Buffer`, are kept by reference and shared with every hit; the runtime decodes them on each hit, and a codec that returns its wire value unchanged, such as a date or byte-array codec, hands every caller that same shared object. An ORM call's result has its plain structure copied on `set` and on every `get`: plain and null-prototype objects, arrays (their elements only; holes read as `undefined`), `Map`, `Set`, `Date`, `RegExp`, `ArrayBuffer`, typed arrays (a subclass such as Node's `Buffer` keeps its class) and `DataView` (copied as a plain `DataView`). Every other value in it, such as a class instance (a Temporal value, a decimal, a vector, a BSON value, an error) or a function, is shared by reference with the first caller and every hit. So, once the call has ended, changing the plain objects and arrays of a result changes no later hit; changing a shared value in place does, so codecs should return values that are not mutated in place. The store never refuses a value: `set` returns `false` only when the key's version moved. A miss copies the result once and each hit copies it again: about the work of decoding rows, linear in the size of the result. A custom store decides for itself whether to copy and what it can store.
|
|
55
59
|
|
|
56
60
|
## Typed meta
|
|
57
61
|
|
|
@@ -79,7 +83,7 @@ export const cached = (o: CacheAnnotationOptions<TagMeta>) => cacheAnnotation<Ta
|
|
|
79
83
|
|
|
80
84
|
## Deriving keys
|
|
81
85
|
|
|
82
|
-
`createCacheMiddleware({ deriveKey })` computes the key of every cached
|
|
86
|
+
`createCacheMiddleware({ deriveKey })` computes the key of every cached statement whose annotation has no `key`. It takes the statement's execution plan, so it does not apply to an ORM call, which is keyed by its content hash. Build on the default to add a prefix:
|
|
83
87
|
|
|
84
88
|
```ts
|
|
85
89
|
const cache = createCacheMiddleware({
|
|
@@ -87,7 +91,7 @@ const cache = createCacheMiddleware({
|
|
|
87
91
|
});
|
|
88
92
|
```
|
|
89
93
|
|
|
90
|
-
An explicit annotation `key` is used literally and bypasses `deriveKey`, so a tenant or namespace prefix must be part of it too. A derivation that returns the same key for different plans serves one plan's rows to the other, and one that drops the content hash no longer changes on a schema migration. If `deriveKey` throws, the read fails.
|
|
94
|
+
An explicit annotation `key` is used literally and bypasses `deriveKey`, so a tenant or namespace prefix must be part of it too. An app that relies on `deriveKey` to separate tenants must also separate them for annotated ORM calls, for example with a tenant-prefixed `key`; otherwise one tenant's cached ORM read can be served to another. A key belongs to one query and one terminal method: if `first()` and `all()` share a key, the one that reads the other's stored result fails with `ORM.INTERCEPTED_RESULT_INVALID` or returns the wrong shape. A derivation that returns the same key for different plans serves one plan's rows to the other, and one that drops the content hash no longer changes on a schema migration. If `deriveKey` throws, the read fails. A builder query and an ORM call that share a key evict each other's entries, so give each its own key.
|
|
91
95
|
|
|
92
96
|
## Invalidation
|
|
93
97
|
|
|
@@ -112,4 +116,4 @@ A tag scheme is a store policy: `cacheAnnotation({ meta: { tags: ['users'] } })`
|
|
|
112
116
|
|
|
113
117
|
The middleware is a read-through cache with a control surface: keys and `deriveKey`, `meta`, `bypass`, `invalidate`, and the `CacheStore` interface. It carries data between the annotations and the store and never interprets it. Lifetime and the meaning of `meta` are the store's; deciding what to invalidate and when, request coalescing and routing between several stores belong in extensions built on these primitives.
|
|
114
118
|
|
|
115
|
-
Invalidating as part of a write is not supported yet: it must wait for the transaction to commit
|
|
119
|
+
Invalidating as part of a write is not supported yet: it must wait for the transaction to commit. Every ORM write is an `orm-call` activity that carries the user's annotations, including its own transaction, and `activityFinalized` fires for it once it has committed, but the middleware does not yet act on that event. Until then, call `invalidate` after the write returns. Serve-stale strategies such as stale-while-revalidate need the hit-or-miss decision, which only the middleware makes.
|
package/dist/index.d.mts
CHANGED
|
@@ -29,7 +29,7 @@ type CacheAnnotationHandle = (<TMeta = unknown>(options: CacheAnnotationOptions<
|
|
|
29
29
|
};
|
|
30
30
|
/**
|
|
31
31
|
* Marks a read for the cache middleware. It applies to reads only: a write terminal refuses it at
|
|
32
|
-
* compile time and at run time. The middleware reads it from `
|
|
32
|
+
* compile time and at run time. The middleware reads it from a statement plan's `meta.annotations.cache`, or from the annotations of a client activity, such as an ORM read, which is then cached as a whole.
|
|
33
33
|
*
|
|
34
34
|
* `TMeta` types `meta` for this call only. Nothing ties it to the store's `TMeta`, so a store
|
|
35
35
|
* package should export a wrapper typed with its own meta, such as
|
|
@@ -57,11 +57,20 @@ declare const cacheAnnotation: CacheAnnotationHandle;
|
|
|
57
57
|
//#endregion
|
|
58
58
|
//#region src/cache-store.d.ts
|
|
59
59
|
/**
|
|
60
|
-
* The rows one
|
|
61
|
-
*
|
|
62
|
-
* the same values to the consumer.
|
|
60
|
+
* The rows one statement produced, stored raw (undecoded). The SQL runtime decodes intercepted rows
|
|
61
|
+
* the same way as driver rows, so a hit and a miss yield the same values to the consumer.
|
|
63
62
|
*/
|
|
64
63
|
type CachedRows = readonly Record<string, unknown>[];
|
|
64
|
+
/**
|
|
65
|
+
* The value type the cache middleware stores: a statement's raw rows, or the result of a client activity, such as an ORM call. The middleware tags each value with what produced it: `statement-rows` for a statement's rows, `call-result` for a client activity's result. It serves `statement-rows` only to a statement and `call-result` only to a client activity; under one key, a value with the other tag is a miss.
|
|
66
|
+
*/
|
|
67
|
+
type CachedValue = {
|
|
68
|
+
readonly kind: 'statement-rows';
|
|
69
|
+
readonly rows: CachedRows;
|
|
70
|
+
} | {
|
|
71
|
+
readonly kind: 'call-result';
|
|
72
|
+
readonly result: unknown;
|
|
73
|
+
};
|
|
65
74
|
/**
|
|
66
75
|
* One key of a `CacheStore`, as `get` saw it.
|
|
67
76
|
*
|
|
@@ -72,7 +81,7 @@ type CachedRows = readonly Record<string, unknown>[];
|
|
|
72
81
|
* - `data` — `{ empty: true }` when the store holds no live value for the key, otherwise that
|
|
73
82
|
* value.
|
|
74
83
|
*/
|
|
75
|
-
interface CacheEntry<TMeta = unknown, TValue =
|
|
84
|
+
interface CacheEntry<TMeta = unknown, TValue = CachedValue> {
|
|
76
85
|
readonly key: string;
|
|
77
86
|
readonly meta: TMeta | undefined;
|
|
78
87
|
readonly version: number;
|
|
@@ -85,7 +94,7 @@ interface CacheEntry<TMeta = unknown, TValue = CachedRows> {
|
|
|
85
94
|
}
|
|
86
95
|
/**
|
|
87
96
|
* A cache of values that the cache middleware reads from and writes to. The middleware stores
|
|
88
|
-
* `
|
|
97
|
+
* `CachedValue`.
|
|
89
98
|
*
|
|
90
99
|
* The store keeps a version per key, an integer it keeps even for a key that holds no value. A key
|
|
91
100
|
* never seen, or forgotten, has version 0. Only `unset` changes a version.
|
|
@@ -105,14 +114,14 @@ interface CacheEntry<TMeta = unknown, TValue = CachedRows> {
|
|
|
105
114
|
* must throw rather than ignore it. An `unset` by key must also drop that key from any `meta`
|
|
106
115
|
* index the store keeps.
|
|
107
116
|
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* must not run queries through the runtime that uses the middleware.
|
|
117
|
+
* `get` must give back a value equivalent to the one `set` was given, `kind` included. A call result holds the values codecs decoded, such as a `Date`, a `bigint`, a Temporal value or an extension's class instance, so a store that serializes needs a serializer that rebuilds those types; JSON does not.
|
|
118
|
+
*
|
|
119
|
+
* Lifetime, eviction and whether values are copied are the store's policy; the middleware hands `set` the value it returns to its caller, so a store that keeps references shares that object. A version that `unset` moved must be kept at least as long as a read can take, so that a read that started before the `unset` cannot store. `unset` must not run queries through the runtime that uses the middleware.
|
|
111
120
|
*
|
|
112
121
|
* `TMeta` is the shape of `meta` the store understands. The middleware does not check that a read
|
|
113
122
|
* annotation's `meta` has this shape; see `cacheAnnotation`.
|
|
114
123
|
*/
|
|
115
|
-
interface CacheStore<TMeta = unknown, TValue =
|
|
124
|
+
interface CacheStore<TMeta = unknown, TValue = CachedValue> {
|
|
116
125
|
get(target: {
|
|
117
126
|
readonly key: string;
|
|
118
127
|
readonly meta: TMeta | undefined;
|
|
@@ -142,29 +151,29 @@ interface InMemoryCacheStoreOptions {
|
|
|
142
151
|
readonly clock?: () => number;
|
|
143
152
|
}
|
|
144
153
|
/**
|
|
145
|
-
* The default cache store: a least-recently-used map with one lifetime for every value, local to
|
|
146
|
-
* the process. It holds `CachedRows` unless given another `TValue`. A key's version is kept for
|
|
147
|
-
* `ttlMs` after the `unset` that moved it (see `InMemoryCacheStoreOptions`). It ignores `meta` in
|
|
148
|
-
* `get` and `set`, and its `unset` rejects any `meta`, including `null`, before changing anything.
|
|
149
|
-
* It throws `RUNTIME.ARGUMENT_INVALID` for a `maxEntries` or `ttlMs` outside the ranges above.
|
|
154
|
+
* The default cache store: a least-recently-used map of `CachedValue` with one lifetime for every value, local to the process. Statement rows are copied one level deep on `set` and `get`: the array, and each row object with its prototype. The driver's values in them are kept by reference and shared with every hit; the runtime decodes them on each hit, and a codec that returns its wire value unchanged, such as a date or byte-array codec, hands that shared object to the caller. A call result has its plain structure copied on `set` and on every `get`: plain and null-prototype objects, arrays (their elements only; holes read as `undefined`), `Map`, `Set`, `Date`, `RegExp`, `ArrayBuffer`, typed arrays (a subclass such as Node's `Buffer` keeps its class) and `DataView` (copied as a plain `DataView`). Every other value in it, such as a class instance (a Temporal value, a BSON value, an error) or a function, is shared by reference. So after `set` returns, a change to the plain structure of the value `set` was given, or of a value `get` returned, reaches no later `get`; a change made to a shared value, or made before `set`, does. It never refuses a value: `set` returns `false` only when the version moved. A key's version is kept for `ttlMs` after the `unset` that moved it (see `InMemoryCacheStoreOptions`). It ignores `meta` in `get` and `set`, and its `unset` rejects any `meta`, including `null`, before changing anything. It throws `RUNTIME.ARGUMENT_INVALID` for a `maxEntries` or `ttlMs` outside the ranges above.
|
|
150
155
|
*/
|
|
151
|
-
declare function createInMemoryCacheStore<TMeta = unknown
|
|
156
|
+
declare function createInMemoryCacheStore<TMeta = unknown>(options?: InMemoryCacheStoreOptions): CacheStore<TMeta, CachedValue>;
|
|
152
157
|
//#endregion
|
|
153
158
|
//#region src/cache-middleware.d.ts
|
|
154
159
|
/**
|
|
155
160
|
* Options accepted by `createCacheMiddleware`.
|
|
156
161
|
*
|
|
157
162
|
* - `store` — the cache backend. Defaults to `createInMemoryCacheStore()`: 1000 values, 60 s.
|
|
158
|
-
* - `deriveKey` — computes the key of a cached
|
|
159
|
-
* `deriveKeyFromContentHash`. It runs on every such
|
|
160
|
-
* fails the
|
|
161
|
-
* `deriveKeyFromContentHash` to keep the statement, parameters and storage hash.
|
|
163
|
+
* - `deriveKey` — computes the key of a cached statement whose annotation has no `key`. Defaults
|
|
164
|
+
* to `deriveKeyFromContentHash`. It runs on every such statement, hit or miss, and an error from
|
|
165
|
+
* it fails the statement. It must return different keys whenever the rows can differ; build on
|
|
166
|
+
* `deriveKeyFromContentHash` to keep the statement, parameters and storage hash. It does not
|
|
167
|
+
* apply to a client activity, such as an ORM call, which has no execution plan: a client
|
|
168
|
+
* activity whose annotation has no `key` is keyed by `activity.contentHash()`. A `deriveKey` that
|
|
169
|
+
* separates tenants does not separate ORM calls; give each annotated ORM call a tenant-prefixed
|
|
170
|
+
* `key`, or one tenant's cached ORM read can be served to another.
|
|
162
171
|
*
|
|
163
172
|
* `TMeta` is the store's meta type; `createCacheMiddleware` infers it from `store`. The store holds
|
|
164
|
-
* `
|
|
173
|
+
* `CachedValue`.
|
|
165
174
|
*/
|
|
166
175
|
interface CacheMiddlewareOptions<TMeta = unknown> {
|
|
167
|
-
readonly store?: CacheStore<TMeta,
|
|
176
|
+
readonly store?: CacheStore<TMeta, CachedValue>;
|
|
168
177
|
readonly deriveKey?: (exec: ExecutionPlan, ctx: ActivityContext) => string | Promise<string>;
|
|
169
178
|
}
|
|
170
179
|
/**
|
|
@@ -192,17 +201,26 @@ declare function deriveKeyFromContentHash(exec: ExecutionPlan, ctx: ActivityCont
|
|
|
192
201
|
/**
|
|
193
202
|
* Creates a read-through cache middleware that works with every family runtime.
|
|
194
203
|
*
|
|
195
|
-
* It caches
|
|
196
|
-
*
|
|
197
|
-
*
|
|
204
|
+
* It caches an activity that carries `cacheAnnotation` without `bypass` and runs in runtime scope,
|
|
205
|
+
* outside any transaction: never on a held connection, whose session may change what a read
|
|
206
|
+
* returns, and never inside a transaction, which must read its own uncommitted writes. Two kinds
|
|
207
|
+
* of activity are cached:
|
|
208
|
+
*
|
|
209
|
+
* - A statement whose plan carries the annotation, such as an annotated SQL builder query. Its key
|
|
210
|
+
* is the annotation's `key`, else `deriveKey(exec, ctx)`; its value is the raw rows.
|
|
211
|
+
* - A client activity that carries the annotation, such as an annotated ORM read. Its key is the
|
|
212
|
+
* annotation's `key`, else `await activity.contentHash()`; its value is the result the client
|
|
213
|
+
* ended the activity with. It is served as a whole, so a hit sends no statement.
|
|
214
|
+
*
|
|
215
|
+
* The middleware tags each stored value `statement-rows` or `call-result` and serves `statement-rows` only to a statement and `call-result` only to a client activity; under the same key, a value with the other tag is a miss, and the read stores its own value in its place. A key therefore belongs to one query and one terminal method: an ORM call served another method's result under a shared key fails with `ORM.INTERCEPTED_RESULT_INVALID` or returns the wrong shape.
|
|
198
216
|
*
|
|
199
|
-
* - `intercept.onQuery` — on a hit,
|
|
200
|
-
* miss,
|
|
201
|
-
* - `onRow` — collects each row of a miss.
|
|
202
|
-
* - `activityEnded` — when the driver completed the
|
|
203
|
-
*
|
|
204
|
-
* `
|
|
205
|
-
* `middleware.cache.store-skipped`.
|
|
217
|
+
* - `intercept.onQuery` and `intercept.onActivity` — on a hit, return the stored rows or result,
|
|
218
|
+
* and nothing runs. On a miss, record the pending entry.
|
|
219
|
+
* - `onRow` — collects each row of a statement miss.
|
|
220
|
+
* - `activityEnded` — when the driver completed the statement, or the client ended the call
|
|
221
|
+
* `succeeded` with a result (`null` is a result), calls `store.set(entry, value)` with the entry
|
|
222
|
+
* `store.get` returned, which stores only if the key's version has not moved. If an `unset` moved
|
|
223
|
+
* it meanwhile, `set` returns `false` and the middleware logs `middleware.cache.store-skipped`.
|
|
206
224
|
*
|
|
207
225
|
* @example
|
|
208
226
|
* ```typescript
|
|
@@ -219,5 +237,5 @@ declare function deriveKeyFromContentHash(exec: ExecutionPlan, ctx: ActivityCont
|
|
|
219
237
|
*/
|
|
220
238
|
declare function createCacheMiddleware<TMeta = unknown>(options?: CacheMiddlewareOptions<TMeta>): CacheMiddleware<TMeta>;
|
|
221
239
|
//#endregion
|
|
222
|
-
export { type CacheAnnotationHandle, type CacheAnnotationOptions, type CacheEntry, type CacheMiddleware, type CacheMiddlewareOptions, type CacheStore, type CachedRows, type InMemoryCacheStoreOptions, cacheAnnotation, createCacheMiddleware, createInMemoryCacheStore, deriveKeyFromContentHash };
|
|
240
|
+
export { type CacheAnnotationHandle, type CacheAnnotationOptions, type CacheEntry, type CacheMiddleware, type CacheMiddlewareOptions, type CacheStore, type CachedRows, type CachedValue, type InMemoryCacheStoreOptions, cacheAnnotation, createCacheMiddleware, createInMemoryCacheStore, deriveKeyFromContentHash };
|
|
223
241
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../../3-extensions/middleware-cache/dist/index.d.mts"],"mappings":";;;;;;;;;;;;;;;UAcU,uBAAuB;WACtB;WACA,OAAO;WACP;;;;;;KAMN,0BAA0B,iBAAiB,SAAS,uBAAuB,WAAW,gBAAgB;WAChG;WACA,cAAc;WACd,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA4BpB,iBAAiB
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../../3-extensions/middleware-cache/dist/index.d.mts"],"mappings":";;;;;;;;;;;;;;;UAcU,uBAAuB;WACtB;WACA,OAAO;WACP;;;;;;KAMN,0BAA0B,iBAAiB,SAAS,uBAAuB,WAAW,gBAAgB;WAChG;WACA,cAAc;WACd,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA4BpB,iBAAiB;;;;;;;KAO1B,sBAAsB;;;;KAItB;WACM;WACA,MAAM;;WAEN;WACA;;;;;;;;;;;;UAYD,WAAW,iBAAiB,SAAS;WACpC;WACA,MAAM;WACN;WACA;aACE;;aAEA;aACA,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAgCV,WAAW,iBAAiB,SAAS;EAC7C,IAAI;aACO;aACA,MAAM;MACb,QAAQ,WAAW,OAAO;EAC9B,IAAI,OAAO,WAAW,OAAO,SAAS,OAAO,SAAS;EACtD,MAAM;aACK;aACA,MAAM;MACb;;;;;;;;;;;;;;;UAeI;WACC;WACA;WACA;;;;;iBAKM,yBAAyB,iBAAiB,UAAU,4BAA4B,WAAW,OAAO;;;;;;;;;;;;;;;;;;;UAmBzG,uBAAuB;WACtB,QAAQ,WAAW,OAAO;WAC1B,aAAa,MAAM,eAAe,KAAK,6BAA6B;;;;;;;;;;;;;KAa1E,gBAAgB,mBAAmB;WAC7B,aAAa;aACX;aACA,OAAO;QACZ;;;;;;iBAMS,yBAAyB,MAAM,eAAe,KAAK,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAsCrE,sBAAsB,iBAAiB,UAAU,uBAAuB,SAAS,gBAAgB"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { defineAnnotation } from "@prisma/orm-framework/components/runtime";
|
|
2
|
+
import { blindCast } from "@prisma/orm-framework/utils/casts";
|
|
2
3
|
import { structuredError } from "@prisma/orm-framework/utils/structured-error";
|
|
3
4
|
//#region ../../../3-extensions/middleware-cache/dist/index.mjs
|
|
4
5
|
/**
|
|
5
6
|
* Marks a read for the cache middleware. It applies to reads only: a write terminal refuses it at
|
|
6
|
-
* compile time and at run time. The middleware reads it from `
|
|
7
|
+
* compile time and at run time. The middleware reads it from a statement plan's `meta.annotations.cache`, or from the annotations of a client activity, such as an ORM read, which is then cached as a whole.
|
|
7
8
|
*
|
|
8
9
|
* `TMeta` types `meta` for this call only. Nothing ties it to the store's `TMeta`, so a store
|
|
9
10
|
* package should export a wrapper typed with its own meta, such as
|
|
@@ -41,6 +42,76 @@ function withTypedMeta(handle) {
|
|
|
41
42
|
read: handle.read
|
|
42
43
|
}));
|
|
43
44
|
}
|
|
45
|
+
const typedArraySlice = Reflect.get(Object.getPrototypeOf(Uint8Array.prototype), "slice");
|
|
46
|
+
/**
|
|
47
|
+
* A copy of the plain structure of `value`: plain and null-prototype objects, arrays, `Map`, `Set`,
|
|
48
|
+
* `Date`, `RegExp`, `ArrayBuffer`, typed arrays and `DataView` are copied, and every other value,
|
|
49
|
+
* such as a class instance or a function, is kept by reference. A typed array is copied with
|
|
50
|
+
* `TypedArray.prototype.slice`, which keeps its class, so a `Buffer` copies to a `Buffer` with its
|
|
51
|
+
* own memory; a `DataView` copies to a plain `DataView`; an array copies to its elements only, with
|
|
52
|
+
* holes read as `undefined`. Shared references and cycles are kept.
|
|
53
|
+
*/
|
|
54
|
+
function copyValue(value, copies = /* @__PURE__ */ new Map()) {
|
|
55
|
+
if (typeof value !== "object" || value === null) return value;
|
|
56
|
+
const known = copies.get(value);
|
|
57
|
+
if (known !== void 0) return blindCast(known);
|
|
58
|
+
const copy = copyObject(value, copies);
|
|
59
|
+
copies.set(value, copy);
|
|
60
|
+
return blindCast(copy);
|
|
61
|
+
}
|
|
62
|
+
function copyObject(value, copies) {
|
|
63
|
+
if (value instanceof DataView) {
|
|
64
|
+
const start = value.byteOffset;
|
|
65
|
+
return new DataView(value.buffer.slice(start, start + value.byteLength));
|
|
66
|
+
}
|
|
67
|
+
if (ArrayBuffer.isView(value)) return typedArraySlice.call(value);
|
|
68
|
+
const prototype = Object.getPrototypeOf(value);
|
|
69
|
+
if (prototype === ArrayBuffer.prototype && value instanceof ArrayBuffer) return value.slice(0);
|
|
70
|
+
if (prototype === Date.prototype && value instanceof Date) return new Date(value.getTime());
|
|
71
|
+
if (prototype === RegExp.prototype && value instanceof RegExp) return new RegExp(value.source, value.flags);
|
|
72
|
+
if (prototype === Map.prototype && value instanceof Map) {
|
|
73
|
+
const copy = /* @__PURE__ */ new Map();
|
|
74
|
+
copies.set(value, copy);
|
|
75
|
+
for (const [key, entry] of value) copy.set(copyValue(key, copies), copyValue(entry, copies));
|
|
76
|
+
return copy;
|
|
77
|
+
}
|
|
78
|
+
if (prototype === Set.prototype && value instanceof Set) {
|
|
79
|
+
const copy = /* @__PURE__ */ new Set();
|
|
80
|
+
copies.set(value, copy);
|
|
81
|
+
for (const entry of value) copy.add(copyValue(entry, copies));
|
|
82
|
+
return copy;
|
|
83
|
+
}
|
|
84
|
+
if (prototype === Array.prototype && Array.isArray(value)) {
|
|
85
|
+
const copy = [];
|
|
86
|
+
copies.set(value, copy);
|
|
87
|
+
for (const entry of value) copy.push(copyValue(entry, copies));
|
|
88
|
+
return copy;
|
|
89
|
+
}
|
|
90
|
+
if (prototype !== Object.prototype && prototype !== null) return value;
|
|
91
|
+
const copy = Object.create(prototype);
|
|
92
|
+
copies.set(value, copy);
|
|
93
|
+
for (const key of Reflect.ownKeys(value)) if (Object.prototype.propertyIsEnumerable.call(value, key)) copy[key] = copyValue(Reflect.get(value, key), copies);
|
|
94
|
+
return copy;
|
|
95
|
+
}
|
|
96
|
+
/** Statement rows hold driver values, such as BSON `ObjectId`s, that this package cannot copy. So the array and each row object are copied, each row keeping its prototype, and the driver's values are kept by reference and shared with every hit. */
|
|
97
|
+
function copyRows(rows) {
|
|
98
|
+
return rows.map((row) => {
|
|
99
|
+
const copy = Object.create(Object.getPrototypeOf(row));
|
|
100
|
+
return Object.assign(copy, row);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
function copyStored(value) {
|
|
104
|
+
switch (value.kind) {
|
|
105
|
+
case "statement-rows": return {
|
|
106
|
+
kind: "statement-rows",
|
|
107
|
+
rows: copyRows(value.rows)
|
|
108
|
+
};
|
|
109
|
+
case "call-result": return {
|
|
110
|
+
kind: "call-result",
|
|
111
|
+
result: copyValue(value.result)
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
44
115
|
function metaUnsupported() {
|
|
45
116
|
return structuredError("RUNTIME.CACHE_STORE_META_UNSUPPORTED", "The in-memory cache store cannot remove entries by meta because it does not index meta", { fix: "Invalidate by keys, or supply a CacheStore that indexes meta in set and unset." });
|
|
46
117
|
}
|
|
@@ -55,11 +126,7 @@ function invalidOption(argument, received, expected) {
|
|
|
55
126
|
});
|
|
56
127
|
}
|
|
57
128
|
/**
|
|
58
|
-
* The default cache store: a least-recently-used map with one lifetime for every value, local to
|
|
59
|
-
* the process. It holds `CachedRows` unless given another `TValue`. A key's version is kept for
|
|
60
|
-
* `ttlMs` after the `unset` that moved it (see `InMemoryCacheStoreOptions`). It ignores `meta` in
|
|
61
|
-
* `get` and `set`, and its `unset` rejects any `meta`, including `null`, before changing anything.
|
|
62
|
-
* It throws `RUNTIME.ARGUMENT_INVALID` for a `maxEntries` or `ttlMs` outside the ranges above.
|
|
129
|
+
* The default cache store: a least-recently-used map of `CachedValue` with one lifetime for every value, local to the process. Statement rows are copied one level deep on `set` and `get`: the array, and each row object with its prototype. The driver's values in them are kept by reference and shared with every hit; the runtime decodes them on each hit, and a codec that returns its wire value unchanged, such as a date or byte-array codec, hands that shared object to the caller. A call result has its plain structure copied on `set` and on every `get`: plain and null-prototype objects, arrays (their elements only; holes read as `undefined`), `Map`, `Set`, `Date`, `RegExp`, `ArrayBuffer`, typed arrays (a subclass such as Node's `Buffer` keeps its class) and `DataView` (copied as a plain `DataView`). Every other value in it, such as a class instance (a Temporal value, a BSON value, an error) or a function, is shared by reference. So after `set` returns, a change to the plain structure of the value `set` was given, or of a value `get` returned, reaches no later `get`; a change made to a shared value, or made before `set`, does. It never refuses a value: `set` returns `false` only when the version moved. A key's version is kept for `ttlMs` after the `unset` that moved it (see `InMemoryCacheStoreOptions`). It ignores `meta` in `get` and `set`, and its `unset` rejects any `meta`, including `null`, before changing anything. It throws `RUNTIME.ARGUMENT_INVALID` for a `maxEntries` or `ttlMs` outside the ranges above.
|
|
63
130
|
*/
|
|
64
131
|
function createInMemoryCacheStore(options) {
|
|
65
132
|
const maxEntries = options?.maxEntries ?? 1e3;
|
|
@@ -96,7 +163,7 @@ function createInMemoryCacheStore(options) {
|
|
|
96
163
|
version: versionOf(target.key),
|
|
97
164
|
data: record === void 0 ? { empty: true } : {
|
|
98
165
|
empty: false,
|
|
99
|
-
value: record.value
|
|
166
|
+
value: copyStored(record.value)
|
|
100
167
|
}
|
|
101
168
|
};
|
|
102
169
|
}
|
|
@@ -104,7 +171,7 @@ function createInMemoryCacheStore(options) {
|
|
|
104
171
|
if (entry.version !== versionOf(entry.key)) return false;
|
|
105
172
|
records.delete(entry.key);
|
|
106
173
|
records.set(entry.key, {
|
|
107
|
-
value,
|
|
174
|
+
value: copyStored(value),
|
|
108
175
|
expiresAt: clock() + ttlMs
|
|
109
176
|
});
|
|
110
177
|
for (const leastRecentlyUsed of records.keys()) {
|
|
@@ -146,20 +213,48 @@ function pendingOf(ctx) {
|
|
|
146
213
|
function isPendingMiss(value) {
|
|
147
214
|
return typeof value === "object" && value !== null && "entry" in value && "buffer" in value;
|
|
148
215
|
}
|
|
216
|
+
function hasTag(value, tag) {
|
|
217
|
+
return value.kind === tag;
|
|
218
|
+
}
|
|
219
|
+
function cacheable(ctx) {
|
|
220
|
+
return ctx.scope === "runtime" && !ctx.inTransaction();
|
|
221
|
+
}
|
|
222
|
+
/** The value a pending miss stores when its activity ends this way, or `undefined` to store nothing. */
|
|
223
|
+
function valueToStore(miss, result) {
|
|
224
|
+
if (result.outcome !== "succeeded") return void 0;
|
|
225
|
+
if (result.kind === "statement") return result.source === "driver" && miss.buffer !== void 0 ? {
|
|
226
|
+
kind: "statement-rows",
|
|
227
|
+
rows: miss.buffer
|
|
228
|
+
} : void 0;
|
|
229
|
+
if (result.kind === "transaction") return void 0;
|
|
230
|
+
return result.source === "client" && result.result !== void 0 ? {
|
|
231
|
+
kind: "call-result",
|
|
232
|
+
result: result.result
|
|
233
|
+
} : void 0;
|
|
234
|
+
}
|
|
149
235
|
/**
|
|
150
236
|
* Creates a read-through cache middleware that works with every family runtime.
|
|
151
237
|
*
|
|
152
|
-
* It caches
|
|
153
|
-
*
|
|
154
|
-
*
|
|
238
|
+
* It caches an activity that carries `cacheAnnotation` without `bypass` and runs in runtime scope,
|
|
239
|
+
* outside any transaction: never on a held connection, whose session may change what a read
|
|
240
|
+
* returns, and never inside a transaction, which must read its own uncommitted writes. Two kinds
|
|
241
|
+
* of activity are cached:
|
|
242
|
+
*
|
|
243
|
+
* - A statement whose plan carries the annotation, such as an annotated SQL builder query. Its key
|
|
244
|
+
* is the annotation's `key`, else `deriveKey(exec, ctx)`; its value is the raw rows.
|
|
245
|
+
* - A client activity that carries the annotation, such as an annotated ORM read. Its key is the
|
|
246
|
+
* annotation's `key`, else `await activity.contentHash()`; its value is the result the client
|
|
247
|
+
* ended the activity with. It is served as a whole, so a hit sends no statement.
|
|
155
248
|
*
|
|
156
|
-
* - `
|
|
157
|
-
*
|
|
158
|
-
* - `
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
* `
|
|
249
|
+
* The middleware tags each stored value `statement-rows` or `call-result` and serves `statement-rows` only to a statement and `call-result` only to a client activity; under the same key, a value with the other tag is a miss, and the read stores its own value in its place. A key therefore belongs to one query and one terminal method: an ORM call served another method's result under a shared key fails with `ORM.INTERCEPTED_RESULT_INVALID` or returns the wrong shape.
|
|
250
|
+
*
|
|
251
|
+
* - `intercept.onQuery` and `intercept.onActivity` — on a hit, return the stored rows or result,
|
|
252
|
+
* and nothing runs. On a miss, record the pending entry.
|
|
253
|
+
* - `onRow` — collects each row of a statement miss.
|
|
254
|
+
* - `activityEnded` — when the driver completed the statement, or the client ended the call
|
|
255
|
+
* `succeeded` with a result (`null` is a result), calls `store.set(entry, value)` with the entry
|
|
256
|
+
* `store.get` returned, which stores only if the key's version has not moved. If an `unset` moved
|
|
257
|
+
* it meanwhile, `set` returns `false` and the middleware logs `middleware.cache.store-skipped`.
|
|
163
258
|
*
|
|
164
259
|
* @example
|
|
165
260
|
* ```typescript
|
|
@@ -177,26 +272,22 @@ function isPendingMiss(value) {
|
|
|
177
272
|
function createCacheMiddleware(options) {
|
|
178
273
|
const store = options?.store ?? createInMemoryCacheStore();
|
|
179
274
|
const deriveKey = options?.deriveKey ?? deriveKeyFromContentHash;
|
|
180
|
-
async function
|
|
181
|
-
if (ctx.scope !== "runtime") return;
|
|
182
|
-
const annotation = cacheAnnotation.read(exec);
|
|
183
|
-
if (annotation === void 0 || annotation.bypass === true) return;
|
|
184
|
-
const key = annotation.key ?? await deriveKey(exec, ctx);
|
|
275
|
+
async function lookUp(tag, key, meta, ctx, buffer) {
|
|
185
276
|
const entry = await store.get({
|
|
186
277
|
key,
|
|
187
|
-
meta
|
|
278
|
+
meta
|
|
188
279
|
});
|
|
189
|
-
if (!entry.data.empty) {
|
|
280
|
+
if (!entry.data.empty && hasTag(entry.data.value, tag)) {
|
|
190
281
|
ctx.log.debug?.({
|
|
191
282
|
event: "middleware.cache.hit",
|
|
192
283
|
middleware: "cache",
|
|
193
284
|
key
|
|
194
285
|
});
|
|
195
|
-
return
|
|
286
|
+
return entry.data.value;
|
|
196
287
|
}
|
|
197
288
|
ctx.state(pendingMiss)["miss"] = {
|
|
198
289
|
entry,
|
|
199
|
-
buffer
|
|
290
|
+
buffer
|
|
200
291
|
};
|
|
201
292
|
ctx.log.debug?.({
|
|
202
293
|
event: "middleware.cache.miss",
|
|
@@ -204,15 +295,30 @@ function createCacheMiddleware(options) {
|
|
|
204
295
|
key
|
|
205
296
|
});
|
|
206
297
|
}
|
|
298
|
+
async function onQuery(exec, ctx) {
|
|
299
|
+
if (!cacheable(ctx)) return;
|
|
300
|
+
const annotation = cacheAnnotation.read(exec);
|
|
301
|
+
if (annotation === void 0 || annotation.bypass === true) return;
|
|
302
|
+
const value = await lookUp("statement-rows", annotation.key ?? await deriveKey(exec, ctx), annotation.meta, ctx, []);
|
|
303
|
+
return value === void 0 ? void 0 : { rows: value.rows };
|
|
304
|
+
}
|
|
305
|
+
async function onActivity(activity, ctx) {
|
|
306
|
+
if (!cacheable(ctx)) return;
|
|
307
|
+
const annotation = cacheAnnotation.read(activity);
|
|
308
|
+
if (annotation === void 0 || annotation.bypass === true) return;
|
|
309
|
+
const value = await lookUp("call-result", annotation.key ?? await activity.contentHash(), annotation.meta, ctx, void 0);
|
|
310
|
+
return value === void 0 ? void 0 : { result: value.result };
|
|
311
|
+
}
|
|
207
312
|
async function onRow(row, _exec, ctx) {
|
|
208
|
-
pendingOf(ctx)?.buffer
|
|
313
|
+
pendingOf(ctx)?.buffer?.push(row);
|
|
209
314
|
}
|
|
210
315
|
async function activityEnded(_activity, result, ctx) {
|
|
211
316
|
const miss = pendingOf(ctx);
|
|
212
317
|
if (miss === void 0) return;
|
|
213
318
|
delete ctx.state(pendingMiss)["miss"];
|
|
214
|
-
|
|
215
|
-
|
|
319
|
+
const value = valueToStore(miss, result);
|
|
320
|
+
if (value === void 0) return;
|
|
321
|
+
const stored = await store.set(miss.entry, value);
|
|
216
322
|
ctx.log.debug?.({
|
|
217
323
|
event: stored ? "middleware.cache.store" : "middleware.cache.store-skipped",
|
|
218
324
|
middleware: "cache",
|
|
@@ -229,7 +335,10 @@ function createCacheMiddleware(options) {
|
|
|
229
335
|
}
|
|
230
336
|
return {
|
|
231
337
|
name: "cache",
|
|
232
|
-
intercept: {
|
|
338
|
+
intercept: {
|
|
339
|
+
onQuery,
|
|
340
|
+
onActivity
|
|
341
|
+
},
|
|
233
342
|
onRow,
|
|
234
343
|
activityEnded,
|
|
235
344
|
invalidate
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../3-extensions/middleware-cache/dist/index.mjs"],"sourcesContent":["import { defineAnnotation } from \"@internal/framework-components/runtime\";\nimport { structuredError } from \"@internal/utils/structured-error\";\n//#region src/cache-annotation.ts\n/**\n* Marks a read for the cache middleware. It applies to reads only: a write terminal refuses it at\n* compile time and at run time. The middleware reads it from `plan.meta.annotations.cache`.\n*\n* `TMeta` types `meta` for this call only. Nothing ties it to the store's `TMeta`, so a store\n* package should export a wrapper typed with its own meta, such as\n* `(o: CacheAnnotationOptions<TagMeta>) => cacheAnnotation<TagMeta>(o)`.\n*\n* @example\n* ```typescript\n* import { cacheAnnotation } from '@internal/middleware-cache';\n*\n* // ORM read terminal — accepts the read-only annotation via the meta callback.\n* const user = await db.orm.public.User.first(\n* { id },\n* (meta) => meta.annotate(cacheAnnotation({ key: `user-${id}` })),\n* );\n*\n* // SQL DSL select builder — chainable.\n* const plan = db.sql\n* .from(tables.user)\n* .annotate(cacheAnnotation({}))\n* .select({ id: tables.user.columns.id })\n* .build();\n* ```\n*/\nconst cacheAnnotation = withTypedMeta(defineAnnotation()({\n\tnamespace: \"cache\",\n\tapplicableTo: [\"read\"]\n}));\nfunction withTypedMeta(handle) {\n\tfunction cacheAnnotationWithMeta(options) {\n\t\treturn handle(options);\n\t}\n\treturn Object.freeze(Object.assign(cacheAnnotationWithMeta, {\n\t\tnamespace: handle.namespace,\n\t\tapplicableTo: handle.applicableTo,\n\t\tread: handle.read\n\t}));\n}\n//#endregion\n//#region src/cache-store.ts\nfunction metaUnsupported() {\n\treturn structuredError(\"RUNTIME.CACHE_STORE_META_UNSUPPORTED\", \"The in-memory cache store cannot remove entries by meta because it does not index meta\", { fix: \"Invalidate by keys, or supply a CacheStore that indexes meta in set and unset.\" });\n}\nfunction invalidOption(argument, received, expected) {\n\treturn structuredError(\"RUNTIME.ARGUMENT_INVALID\", `createInMemoryCacheStore: ${argument} must be ${expected}`, {\n\t\tfix: `Pass ${argument} as ${expected}, or leave it unset for the default.`,\n\t\tmeta: {\n\t\t\thelper: \"createInMemoryCacheStore\",\n\t\t\targument,\n\t\t\treceived\n\t\t}\n\t});\n}\n/**\n* The default cache store: a least-recently-used map with one lifetime for every value, local to\n* the process. It holds `CachedRows` unless given another `TValue`. A key's version is kept for\n* `ttlMs` after the `unset` that moved it (see `InMemoryCacheStoreOptions`). It ignores `meta` in\n* `get` and `set`, and its `unset` rejects any `meta`, including `null`, before changing anything.\n* It throws `RUNTIME.ARGUMENT_INVALID` for a `maxEntries` or `ttlMs` outside the ranges above.\n*/\nfunction createInMemoryCacheStore(options) {\n\tconst maxEntries = options?.maxEntries ?? 1e3;\n\tconst ttlMs = options?.ttlMs ?? 6e4;\n\tconst clock = options?.clock ?? Date.now;\n\tif (!Number.isInteger(maxEntries) || maxEntries <= 0) throw invalidOption(\"maxEntries\", maxEntries, \"a positive integer\");\n\tif (!(ttlMs > 0)) throw invalidOption(\"ttlMs\", ttlMs, \"a positive number of milliseconds, or Infinity\");\n\tconst records = /* @__PURE__ */ new Map();\n\tconst versions = /* @__PURE__ */ new Map();\n\tfunction forgetExpiredVersions() {\n\t\tconst now = clock();\n\t\tfor (const [key, record] of versions) {\n\t\t\tif (now < record.expiresAt) return;\n\t\t\tversions.delete(key);\n\t\t}\n\t}\n\tfunction versionOf(key) {\n\t\tforgetExpiredVersions();\n\t\treturn versions.get(key)?.version ?? 0;\n\t}\n\tfunction liveRecord(key) {\n\t\tconst record = records.get(key);\n\t\tif (record === void 0) return;\n\t\trecords.delete(key);\n\t\tif (clock() >= record.expiresAt) return;\n\t\trecords.set(key, record);\n\t\treturn record;\n\t}\n\tasync function get(target) {\n\t\tconst record = liveRecord(target.key);\n\t\treturn {\n\t\t\tkey: target.key,\n\t\t\tmeta: target.meta,\n\t\t\tversion: versionOf(target.key),\n\t\t\tdata: record === void 0 ? { empty: true } : {\n\t\t\t\tempty: false,\n\t\t\t\tvalue: record.value\n\t\t\t}\n\t\t};\n\t}\n\tasync function set(entry, value) {\n\t\tif (entry.version !== versionOf(entry.key)) return false;\n\t\trecords.delete(entry.key);\n\t\trecords.set(entry.key, {\n\t\t\tvalue,\n\t\t\texpiresAt: clock() + ttlMs\n\t\t});\n\t\tfor (const leastRecentlyUsed of records.keys()) {\n\t\t\tif (records.size <= maxEntries) break;\n\t\t\trecords.delete(leastRecentlyUsed);\n\t\t}\n\t\treturn true;\n\t}\n\tasync function unset(target) {\n\t\tif (target.meta !== void 0) throw metaUnsupported();\n\t\tfor (const key of target.keys ?? []) {\n\t\t\trecords.delete(key);\n\t\t\tconst version = versionOf(key) + 1;\n\t\t\tversions.delete(key);\n\t\t\tversions.set(key, {\n\t\t\t\tversion,\n\t\t\t\texpiresAt: clock() + ttlMs\n\t\t\t});\n\t\t}\n\t}\n\treturn {\n\t\tget,\n\t\tset,\n\t\tunset\n\t};\n}\n//#endregion\n//#region src/cache-middleware.ts\n/**\n* The default `deriveKey`: the family runtime's content hash of the plan, which covers the\n* statement, its parameters and the storage hash.\n*/\nfunction deriveKeyFromContentHash(exec, ctx) {\n\treturn ctx.contentHash(exec);\n}\nconst pendingMiss = Symbol(\"cache.pendingMiss\");\nfunction pendingOf(ctx) {\n\tconst miss = ctx.state(pendingMiss)[\"miss\"];\n\treturn isPendingMiss(miss) ? miss : void 0;\n}\nfunction isPendingMiss(value) {\n\treturn typeof value === \"object\" && value !== null && \"entry\" in value && \"buffer\" in value;\n}\n/**\n* Creates a read-through cache middleware that works with every family runtime.\n*\n* It caches a read when the plan carries `cacheAnnotation`, the annotation does not set\n* `bypass`, and the read runs in runtime scope (not inside a connection or transaction). The key\n* is the annotation's `key`, else `deriveKey(exec, ctx)`.\n*\n* - `intercept.onQuery` — on a hit, returns the stored rows and the driver does not run. On a\n* miss, starts collecting rows.\n* - `onRow` — collects each row of a miss.\n* - `activityEnded` — when the driver completed the read, calls `store.set(entry, rows)` with the\n* entry `store.get` returned, which stores only if the key's version has not moved. If an\n* `unset` moved it meanwhile, `set` returns `false` and the middleware logs\n* `middleware.cache.store-skipped`.\n*\n* @example\n* ```typescript\n* const cache = createCacheMiddleware();\n* const db = postgres<Contract>({ contractJson, url, middleware: [cache] });\n*\n* const user = await db.orm.public.User.first(\n* { id: 1 },\n* (meta) => meta.annotate(cacheAnnotation({ key: 'user-1' })),\n* );\n* await db.orm.public.User.where({ id: 1 }).update({ name: 'Alicia' });\n* await cache.invalidate({ keys: ['user-1'] });\n* ```\n*/\nfunction createCacheMiddleware(options) {\n\tconst store = options?.store ?? createInMemoryCacheStore();\n\tconst deriveKey = options?.deriveKey ?? deriveKeyFromContentHash;\n\tasync function onQuery(exec, ctx) {\n\t\tif (ctx.scope !== \"runtime\") return;\n\t\tconst annotation = cacheAnnotation.read(exec);\n\t\tif (annotation === void 0 || annotation.bypass === true) return;\n\t\tconst key = annotation.key ?? await deriveKey(exec, ctx);\n\t\tconst entry = await store.get({\n\t\t\tkey,\n\t\t\tmeta: annotation.meta\n\t\t});\n\t\tif (!entry.data.empty) {\n\t\t\tctx.log.debug?.({\n\t\t\t\tevent: \"middleware.cache.hit\",\n\t\t\t\tmiddleware: \"cache\",\n\t\t\t\tkey\n\t\t\t});\n\t\t\treturn { rows: entry.data.value };\n\t\t}\n\t\tctx.state(pendingMiss)[\"miss\"] = {\n\t\t\tentry,\n\t\t\tbuffer: []\n\t\t};\n\t\tctx.log.debug?.({\n\t\t\tevent: \"middleware.cache.miss\",\n\t\t\tmiddleware: \"cache\",\n\t\t\tkey\n\t\t});\n\t}\n\tasync function onRow(row, _exec, ctx) {\n\t\tpendingOf(ctx)?.buffer.push(row);\n\t}\n\tasync function activityEnded(_activity, result, ctx) {\n\t\tconst miss = pendingOf(ctx);\n\t\tif (miss === void 0) return;\n\t\tdelete ctx.state(pendingMiss)[\"miss\"];\n\t\tif (result.kind !== \"statement\" || result.outcome !== \"succeeded\" || result.source !== \"driver\") return;\n\t\tconst stored = await store.set(miss.entry, miss.buffer);\n\t\tctx.log.debug?.({\n\t\t\tevent: stored ? \"middleware.cache.store\" : \"middleware.cache.store-skipped\",\n\t\t\tmiddleware: \"cache\",\n\t\t\tkey: miss.entry.key\n\t\t});\n\t}\n\tasync function invalidate(target) {\n\t\tconst keys = target.keys !== void 0 && target.keys.length > 0 ? target.keys : void 0;\n\t\tif (keys === void 0 && target.meta === void 0) return;\n\t\tawait store.unset({\n\t\t\tkeys,\n\t\t\tmeta: target.meta\n\t\t});\n\t}\n\treturn {\n\t\tname: \"cache\",\n\t\tintercept: { onQuery },\n\t\tonRow,\n\t\tactivityEnded,\n\t\tinvalidate\n\t};\n}\n//#endregion\nexport { cacheAnnotation, createCacheMiddleware, createInMemoryCacheStore, deriveKeyFromContentHash };\n\n//# sourceMappingURL=index.mjs.map"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../3-extensions/middleware-cache/dist/index.mjs"],"sourcesContent":["import { defineAnnotation } from \"@internal/framework-components/runtime\";\nimport { blindCast } from \"@internal/utils/casts\";\nimport { structuredError } from \"@internal/utils/structured-error\";\n//#region src/cache-annotation.ts\n/**\n* Marks a read for the cache middleware. It applies to reads only: a write terminal refuses it at\n* compile time and at run time. The middleware reads it from a statement plan's `meta.annotations.cache`, or from the annotations of a client activity, such as an ORM read, which is then cached as a whole.\n*\n* `TMeta` types `meta` for this call only. Nothing ties it to the store's `TMeta`, so a store\n* package should export a wrapper typed with its own meta, such as\n* `(o: CacheAnnotationOptions<TagMeta>) => cacheAnnotation<TagMeta>(o)`.\n*\n* @example\n* ```typescript\n* import { cacheAnnotation } from '@internal/middleware-cache';\n*\n* // ORM read terminal — accepts the read-only annotation via the meta callback.\n* const user = await db.orm.public.User.first(\n* { id },\n* (meta) => meta.annotate(cacheAnnotation({ key: `user-${id}` })),\n* );\n*\n* // SQL DSL select builder — chainable.\n* const plan = db.sql\n* .from(tables.user)\n* .annotate(cacheAnnotation({}))\n* .select({ id: tables.user.columns.id })\n* .build();\n* ```\n*/\nconst cacheAnnotation = withTypedMeta(defineAnnotation()({\n\tnamespace: \"cache\",\n\tapplicableTo: [\"read\"]\n}));\nfunction withTypedMeta(handle) {\n\tfunction cacheAnnotationWithMeta(options) {\n\t\treturn handle(options);\n\t}\n\treturn Object.freeze(Object.assign(cacheAnnotationWithMeta, {\n\t\tnamespace: handle.namespace,\n\t\tapplicableTo: handle.applicableTo,\n\t\tread: handle.read\n\t}));\n}\n//#endregion\n//#region src/cache-store.ts\nconst typedArraySlice = Reflect.get(Object.getPrototypeOf(Uint8Array.prototype), \"slice\");\n/**\n* A copy of the plain structure of `value`: plain and null-prototype objects, arrays, `Map`, `Set`,\n* `Date`, `RegExp`, `ArrayBuffer`, typed arrays and `DataView` are copied, and every other value,\n* such as a class instance or a function, is kept by reference. A typed array is copied with\n* `TypedArray.prototype.slice`, which keeps its class, so a `Buffer` copies to a `Buffer` with its\n* own memory; a `DataView` copies to a plain `DataView`; an array copies to its elements only, with\n* holes read as `undefined`. Shared references and cycles are kept.\n*/\nfunction copyValue(value, copies = /* @__PURE__ */ new Map()) {\n\tif (typeof value !== \"object\" || value === null) return value;\n\tconst known = copies.get(value);\n\tif (known !== void 0) return blindCast(known);\n\tconst copy = copyObject(value, copies);\n\tcopies.set(value, copy);\n\treturn blindCast(copy);\n}\nfunction copyObject(value, copies) {\n\tif (value instanceof DataView) {\n\t\tconst start = value.byteOffset;\n\t\treturn new DataView(value.buffer.slice(start, start + value.byteLength));\n\t}\n\tif (ArrayBuffer.isView(value)) return typedArraySlice.call(value);\n\tconst prototype = Object.getPrototypeOf(value);\n\tif (prototype === ArrayBuffer.prototype && value instanceof ArrayBuffer) return value.slice(0);\n\tif (prototype === Date.prototype && value instanceof Date) return new Date(value.getTime());\n\tif (prototype === RegExp.prototype && value instanceof RegExp) return new RegExp(value.source, value.flags);\n\tif (prototype === Map.prototype && value instanceof Map) {\n\t\tconst copy = /* @__PURE__ */ new Map();\n\t\tcopies.set(value, copy);\n\t\tfor (const [key, entry] of value) copy.set(copyValue(key, copies), copyValue(entry, copies));\n\t\treturn copy;\n\t}\n\tif (prototype === Set.prototype && value instanceof Set) {\n\t\tconst copy = /* @__PURE__ */ new Set();\n\t\tcopies.set(value, copy);\n\t\tfor (const entry of value) copy.add(copyValue(entry, copies));\n\t\treturn copy;\n\t}\n\tif (prototype === Array.prototype && Array.isArray(value)) {\n\t\tconst copy = [];\n\t\tcopies.set(value, copy);\n\t\tfor (const entry of value) copy.push(copyValue(entry, copies));\n\t\treturn copy;\n\t}\n\tif (prototype !== Object.prototype && prototype !== null) return value;\n\tconst copy = Object.create(prototype);\n\tcopies.set(value, copy);\n\tfor (const key of Reflect.ownKeys(value)) if (Object.prototype.propertyIsEnumerable.call(value, key)) copy[key] = copyValue(Reflect.get(value, key), copies);\n\treturn copy;\n}\n/** Statement rows hold driver values, such as BSON `ObjectId`s, that this package cannot copy. So the array and each row object are copied, each row keeping its prototype, and the driver's values are kept by reference and shared with every hit. */\nfunction copyRows(rows) {\n\treturn rows.map((row) => {\n\t\tconst copy = Object.create(Object.getPrototypeOf(row));\n\t\treturn Object.assign(copy, row);\n\t});\n}\nfunction copyStored(value) {\n\tswitch (value.kind) {\n\t\tcase \"statement-rows\": return {\n\t\t\tkind: \"statement-rows\",\n\t\t\trows: copyRows(value.rows)\n\t\t};\n\t\tcase \"call-result\": return {\n\t\t\tkind: \"call-result\",\n\t\t\tresult: copyValue(value.result)\n\t\t};\n\t}\n}\nfunction metaUnsupported() {\n\treturn structuredError(\"RUNTIME.CACHE_STORE_META_UNSUPPORTED\", \"The in-memory cache store cannot remove entries by meta because it does not index meta\", { fix: \"Invalidate by keys, or supply a CacheStore that indexes meta in set and unset.\" });\n}\nfunction invalidOption(argument, received, expected) {\n\treturn structuredError(\"RUNTIME.ARGUMENT_INVALID\", `createInMemoryCacheStore: ${argument} must be ${expected}`, {\n\t\tfix: `Pass ${argument} as ${expected}, or leave it unset for the default.`,\n\t\tmeta: {\n\t\t\thelper: \"createInMemoryCacheStore\",\n\t\t\targument,\n\t\t\treceived\n\t\t}\n\t});\n}\n/**\n* The default cache store: a least-recently-used map of `CachedValue` with one lifetime for every value, local to the process. Statement rows are copied one level deep on `set` and `get`: the array, and each row object with its prototype. The driver's values in them are kept by reference and shared with every hit; the runtime decodes them on each hit, and a codec that returns its wire value unchanged, such as a date or byte-array codec, hands that shared object to the caller. A call result has its plain structure copied on `set` and on every `get`: plain and null-prototype objects, arrays (their elements only; holes read as `undefined`), `Map`, `Set`, `Date`, `RegExp`, `ArrayBuffer`, typed arrays (a subclass such as Node's `Buffer` keeps its class) and `DataView` (copied as a plain `DataView`). Every other value in it, such as a class instance (a Temporal value, a BSON value, an error) or a function, is shared by reference. So after `set` returns, a change to the plain structure of the value `set` was given, or of a value `get` returned, reaches no later `get`; a change made to a shared value, or made before `set`, does. It never refuses a value: `set` returns `false` only when the version moved. A key's version is kept for `ttlMs` after the `unset` that moved it (see `InMemoryCacheStoreOptions`). It ignores `meta` in `get` and `set`, and its `unset` rejects any `meta`, including `null`, before changing anything. It throws `RUNTIME.ARGUMENT_INVALID` for a `maxEntries` or `ttlMs` outside the ranges above.\n*/\nfunction createInMemoryCacheStore(options) {\n\tconst maxEntries = options?.maxEntries ?? 1e3;\n\tconst ttlMs = options?.ttlMs ?? 6e4;\n\tconst clock = options?.clock ?? Date.now;\n\tif (!Number.isInteger(maxEntries) || maxEntries <= 0) throw invalidOption(\"maxEntries\", maxEntries, \"a positive integer\");\n\tif (!(ttlMs > 0)) throw invalidOption(\"ttlMs\", ttlMs, \"a positive number of milliseconds, or Infinity\");\n\tconst records = /* @__PURE__ */ new Map();\n\tconst versions = /* @__PURE__ */ new Map();\n\tfunction forgetExpiredVersions() {\n\t\tconst now = clock();\n\t\tfor (const [key, record] of versions) {\n\t\t\tif (now < record.expiresAt) return;\n\t\t\tversions.delete(key);\n\t\t}\n\t}\n\tfunction versionOf(key) {\n\t\tforgetExpiredVersions();\n\t\treturn versions.get(key)?.version ?? 0;\n\t}\n\tfunction liveRecord(key) {\n\t\tconst record = records.get(key);\n\t\tif (record === void 0) return;\n\t\trecords.delete(key);\n\t\tif (clock() >= record.expiresAt) return;\n\t\trecords.set(key, record);\n\t\treturn record;\n\t}\n\tasync function get(target) {\n\t\tconst record = liveRecord(target.key);\n\t\treturn {\n\t\t\tkey: target.key,\n\t\t\tmeta: target.meta,\n\t\t\tversion: versionOf(target.key),\n\t\t\tdata: record === void 0 ? { empty: true } : {\n\t\t\t\tempty: false,\n\t\t\t\tvalue: copyStored(record.value)\n\t\t\t}\n\t\t};\n\t}\n\tasync function set(entry, value) {\n\t\tif (entry.version !== versionOf(entry.key)) return false;\n\t\trecords.delete(entry.key);\n\t\trecords.set(entry.key, {\n\t\t\tvalue: copyStored(value),\n\t\t\texpiresAt: clock() + ttlMs\n\t\t});\n\t\tfor (const leastRecentlyUsed of records.keys()) {\n\t\t\tif (records.size <= maxEntries) break;\n\t\t\trecords.delete(leastRecentlyUsed);\n\t\t}\n\t\treturn true;\n\t}\n\tasync function unset(target) {\n\t\tif (target.meta !== void 0) throw metaUnsupported();\n\t\tfor (const key of target.keys ?? []) {\n\t\t\trecords.delete(key);\n\t\t\tconst version = versionOf(key) + 1;\n\t\t\tversions.delete(key);\n\t\t\tversions.set(key, {\n\t\t\t\tversion,\n\t\t\t\texpiresAt: clock() + ttlMs\n\t\t\t});\n\t\t}\n\t}\n\treturn {\n\t\tget,\n\t\tset,\n\t\tunset\n\t};\n}\n//#endregion\n//#region src/cache-middleware.ts\n/**\n* The default `deriveKey`: the family runtime's content hash of the plan, which covers the\n* statement, its parameters and the storage hash.\n*/\nfunction deriveKeyFromContentHash(exec, ctx) {\n\treturn ctx.contentHash(exec);\n}\nconst pendingMiss = Symbol(\"cache.pendingMiss\");\nfunction pendingOf(ctx) {\n\tconst miss = ctx.state(pendingMiss)[\"miss\"];\n\treturn isPendingMiss(miss) ? miss : void 0;\n}\nfunction isPendingMiss(value) {\n\treturn typeof value === \"object\" && value !== null && \"entry\" in value && \"buffer\" in value;\n}\nfunction hasTag(value, tag) {\n\treturn value.kind === tag;\n}\nfunction cacheable(ctx) {\n\treturn ctx.scope === \"runtime\" && !ctx.inTransaction();\n}\n/** The value a pending miss stores when its activity ends this way, or `undefined` to store nothing. */\nfunction valueToStore(miss, result) {\n\tif (result.outcome !== \"succeeded\") return void 0;\n\tif (result.kind === \"statement\") return result.source === \"driver\" && miss.buffer !== void 0 ? {\n\t\tkind: \"statement-rows\",\n\t\trows: miss.buffer\n\t} : void 0;\n\tif (result.kind === \"transaction\") return void 0;\n\treturn result.source === \"client\" && result.result !== void 0 ? {\n\t\tkind: \"call-result\",\n\t\tresult: result.result\n\t} : void 0;\n}\n/**\n* Creates a read-through cache middleware that works with every family runtime.\n*\n* It caches an activity that carries `cacheAnnotation` without `bypass` and runs in runtime scope,\n* outside any transaction: never on a held connection, whose session may change what a read\n* returns, and never inside a transaction, which must read its own uncommitted writes. Two kinds\n* of activity are cached:\n*\n* - A statement whose plan carries the annotation, such as an annotated SQL builder query. Its key\n* is the annotation's `key`, else `deriveKey(exec, ctx)`; its value is the raw rows.\n* - A client activity that carries the annotation, such as an annotated ORM read. Its key is the\n* annotation's `key`, else `await activity.contentHash()`; its value is the result the client\n* ended the activity with. It is served as a whole, so a hit sends no statement.\n*\n* The middleware tags each stored value `statement-rows` or `call-result` and serves `statement-rows` only to a statement and `call-result` only to a client activity; under the same key, a value with the other tag is a miss, and the read stores its own value in its place. A key therefore belongs to one query and one terminal method: an ORM call served another method's result under a shared key fails with `ORM.INTERCEPTED_RESULT_INVALID` or returns the wrong shape.\n*\n* - `intercept.onQuery` and `intercept.onActivity` — on a hit, return the stored rows or result,\n* and nothing runs. On a miss, record the pending entry.\n* - `onRow` — collects each row of a statement miss.\n* - `activityEnded` — when the driver completed the statement, or the client ended the call\n* `succeeded` with a result (`null` is a result), calls `store.set(entry, value)` with the entry\n* `store.get` returned, which stores only if the key's version has not moved. If an `unset` moved\n* it meanwhile, `set` returns `false` and the middleware logs `middleware.cache.store-skipped`.\n*\n* @example\n* ```typescript\n* const cache = createCacheMiddleware();\n* const db = postgres<Contract>({ contractJson, url, middleware: [cache] });\n*\n* const user = await db.orm.public.User.first(\n* { id: 1 },\n* (meta) => meta.annotate(cacheAnnotation({ key: 'user-1' })),\n* );\n* await db.orm.public.User.where({ id: 1 }).update({ name: 'Alicia' });\n* await cache.invalidate({ keys: ['user-1'] });\n* ```\n*/\nfunction createCacheMiddleware(options) {\n\tconst store = options?.store ?? createInMemoryCacheStore();\n\tconst deriveKey = options?.deriveKey ?? deriveKeyFromContentHash;\n\tasync function lookUp(tag, key, meta, ctx, buffer) {\n\t\tconst entry = await store.get({\n\t\t\tkey,\n\t\t\tmeta\n\t\t});\n\t\tif (!entry.data.empty && hasTag(entry.data.value, tag)) {\n\t\t\tctx.log.debug?.({\n\t\t\t\tevent: \"middleware.cache.hit\",\n\t\t\t\tmiddleware: \"cache\",\n\t\t\t\tkey\n\t\t\t});\n\t\t\treturn entry.data.value;\n\t\t}\n\t\tctx.state(pendingMiss)[\"miss\"] = {\n\t\t\tentry,\n\t\t\tbuffer\n\t\t};\n\t\tctx.log.debug?.({\n\t\t\tevent: \"middleware.cache.miss\",\n\t\t\tmiddleware: \"cache\",\n\t\t\tkey\n\t\t});\n\t}\n\tasync function onQuery(exec, ctx) {\n\t\tif (!cacheable(ctx)) return;\n\t\tconst annotation = cacheAnnotation.read(exec);\n\t\tif (annotation === void 0 || annotation.bypass === true) return;\n\t\tconst value = await lookUp(\"statement-rows\", annotation.key ?? await deriveKey(exec, ctx), annotation.meta, ctx, []);\n\t\treturn value === void 0 ? void 0 : { rows: value.rows };\n\t}\n\tasync function onActivity(activity, ctx) {\n\t\tif (!cacheable(ctx)) return;\n\t\tconst annotation = cacheAnnotation.read(activity);\n\t\tif (annotation === void 0 || annotation.bypass === true) return;\n\t\tconst value = await lookUp(\"call-result\", annotation.key ?? await activity.contentHash(), annotation.meta, ctx, void 0);\n\t\treturn value === void 0 ? void 0 : { result: value.result };\n\t}\n\tasync function onRow(row, _exec, ctx) {\n\t\tpendingOf(ctx)?.buffer?.push(row);\n\t}\n\tasync function activityEnded(_activity, result, ctx) {\n\t\tconst miss = pendingOf(ctx);\n\t\tif (miss === void 0) return;\n\t\tdelete ctx.state(pendingMiss)[\"miss\"];\n\t\tconst value = valueToStore(miss, result);\n\t\tif (value === void 0) return;\n\t\tconst stored = await store.set(miss.entry, value);\n\t\tctx.log.debug?.({\n\t\t\tevent: stored ? \"middleware.cache.store\" : \"middleware.cache.store-skipped\",\n\t\t\tmiddleware: \"cache\",\n\t\t\tkey: miss.entry.key\n\t\t});\n\t}\n\tasync function invalidate(target) {\n\t\tconst keys = target.keys !== void 0 && target.keys.length > 0 ? target.keys : void 0;\n\t\tif (keys === void 0 && target.meta === void 0) return;\n\t\tawait store.unset({\n\t\t\tkeys,\n\t\t\tmeta: target.meta\n\t\t});\n\t}\n\treturn {\n\t\tname: \"cache\",\n\t\tintercept: {\n\t\t\tonQuery,\n\t\t\tonActivity\n\t\t},\n\t\tonRow,\n\t\tactivityEnded,\n\t\tinvalidate\n\t};\n}\n//#endregion\nexport { cacheAnnotation, createCacheMiddleware, createInMemoryCacheStore, deriveKeyFromContentHash };\n\n//# sourceMappingURL=index.mjs.map"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma/orm-extension-middleware-cache",
|
|
3
|
-
"version": "8.0.0-rc.18-dev.
|
|
3
|
+
"version": "8.0.0-rc.18-dev.18",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
"dist"
|
|
10
10
|
],
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@prisma/orm-framework": "8.0.0-rc.18-dev.
|
|
12
|
+
"@prisma/orm-framework": "8.0.0-rc.18-dev.18"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"@internal/middleware-cache": "8.0.0-rc.18-dev.
|
|
16
|
-
"@repo/tsconfig": "8.0.0-rc.18-dev.
|
|
17
|
-
"@repo/tsdown": "8.0.0-rc.18-dev.
|
|
15
|
+
"@internal/middleware-cache": "8.0.0-rc.18-dev.18",
|
|
16
|
+
"@repo/tsconfig": "8.0.0-rc.18-dev.18",
|
|
17
|
+
"@repo/tsdown": "8.0.0-rc.18-dev.18",
|
|
18
18
|
"tsdown": "0.22.14",
|
|
19
19
|
"typescript": "5.9.3"
|
|
20
20
|
},
|