@prisma/orm-extension-middleware-cache 8.0.0-rc.1-dev.41 → 8.0.0-rc.1-dev.43
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/dist/index.d.mts +4 -4
- package/dist/index.mjs +8 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
package/dist/index.d.mts
CHANGED
|
@@ -169,17 +169,17 @@ interface CacheMiddlewareOptions {
|
|
|
169
169
|
*
|
|
170
170
|
* The middleware uses three hooks:
|
|
171
171
|
*
|
|
172
|
-
* - `
|
|
172
|
+
* - `interceptQuery` — on each execution, checks the cache. On a hit, returns
|
|
173
173
|
* the cached raw rows; the runtime skips `runDriver` and `onRow`
|
|
174
|
-
* (`
|
|
175
|
-
* middleware before any `
|
|
174
|
+
* (`beforeQuery` is not affected — it has already run for every
|
|
175
|
+
* middleware before any `interceptQuery` is consulted) and yields the
|
|
176
176
|
* cached rows to the consumer (which, in the SQL runtime, sees them
|
|
177
177
|
* after the standard `decodeRow` pass — i.e. the cache stores
|
|
178
178
|
* wire-format values). On a miss, records a pending buffer keyed on
|
|
179
179
|
* the `exec` object identity and returns `undefined` (passthrough).
|
|
180
180
|
* - `onRow` — on the miss path, appends each row yielded by the driver
|
|
181
181
|
* to the pending buffer.
|
|
182
|
-
* - `
|
|
182
|
+
* - `afterQuery` — on the miss path, commits the buffer to the store
|
|
183
183
|
* if and only if `result.completed === true && result.source === 'driver'`.
|
|
184
184
|
* Failed executions and middleware-served executions never populate
|
|
185
185
|
* the cache. The pending buffer is cleared in all branches so a stale
|
package/dist/index.mjs
CHANGED
|
@@ -135,17 +135,17 @@ async function resolveCacheKey(payload, exec, ctx) {
|
|
|
135
135
|
*
|
|
136
136
|
* The middleware uses three hooks:
|
|
137
137
|
*
|
|
138
|
-
* - `
|
|
138
|
+
* - `interceptQuery` — on each execution, checks the cache. On a hit, returns
|
|
139
139
|
* the cached raw rows; the runtime skips `runDriver` and `onRow`
|
|
140
|
-
* (`
|
|
141
|
-
* middleware before any `
|
|
140
|
+
* (`beforeQuery` is not affected — it has already run for every
|
|
141
|
+
* middleware before any `interceptQuery` is consulted) and yields the
|
|
142
142
|
* cached rows to the consumer (which, in the SQL runtime, sees them
|
|
143
143
|
* after the standard `decodeRow` pass — i.e. the cache stores
|
|
144
144
|
* wire-format values). On a miss, records a pending buffer keyed on
|
|
145
145
|
* the `exec` object identity and returns `undefined` (passthrough).
|
|
146
146
|
* - `onRow` — on the miss path, appends each row yielded by the driver
|
|
147
147
|
* to the pending buffer.
|
|
148
|
-
* - `
|
|
148
|
+
* - `afterQuery` — on the miss path, commits the buffer to the store
|
|
149
149
|
* if and only if `result.completed === true && result.source === 'driver'`.
|
|
150
150
|
* Failed executions and middleware-served executions never populate
|
|
151
151
|
* the cache. The pending buffer is cleared in all branches so a stale
|
|
@@ -183,7 +183,7 @@ function createCacheMiddleware(options) {
|
|
|
183
183
|
const store = options?.store ?? createInMemoryCacheStore({ maxEntries: options?.maxEntries ?? DEFAULT_MAX_ENTRIES });
|
|
184
184
|
const clock = options?.clock ?? Date.now;
|
|
185
185
|
const pending = /* @__PURE__ */ new WeakMap();
|
|
186
|
-
async function
|
|
186
|
+
async function interceptQuery(exec, ctx) {
|
|
187
187
|
if (ctx.scope !== "runtime") return;
|
|
188
188
|
const payload = readCachePayload(exec);
|
|
189
189
|
if (payload === void 0) return;
|
|
@@ -215,7 +215,7 @@ function createCacheMiddleware(options) {
|
|
|
215
215
|
if (slot === void 0) return;
|
|
216
216
|
slot.buffer.push(row);
|
|
217
217
|
}
|
|
218
|
-
async function
|
|
218
|
+
async function afterQuery(exec, result, ctx) {
|
|
219
219
|
const slot = pending.get(exec);
|
|
220
220
|
if (slot === void 0) return;
|
|
221
221
|
pending.delete(exec);
|
|
@@ -232,9 +232,9 @@ function createCacheMiddleware(options) {
|
|
|
232
232
|
}
|
|
233
233
|
return {
|
|
234
234
|
name: "cache",
|
|
235
|
-
|
|
235
|
+
interceptQuery,
|
|
236
236
|
onRow,
|
|
237
|
-
|
|
237
|
+
afterQuery
|
|
238
238
|
};
|
|
239
239
|
}
|
|
240
240
|
//#endregion
|
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\";\n//#region src/cache-annotation.ts\n/**\n* Read-only annotation handle for the cache middleware.\n*\n* Declared with `applicableTo: ['read']`. Write terminals supply\n* `K = 'write'` to the type-level `ValidAnnotations<'write', As>` gate\n* (and the runtime `assertAnnotationsApplicable(annotations, 'write', ...)`\n* check); the join `K extends Kinds` fails for this annotation, making\n* \"cache a mutation\" structurally impossible without an `as any` cast\n* bypass at both type *and* runtime levels.\n*\n* Stored under namespace `'cache'` in `plan.meta.annotations`. The cache\n* middleware reads it via `cacheAnnotation.read(plan)`.\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.User.first(\n* { id },\n* (meta) => meta.annotate(cacheAnnotation({ ttl: 60_000 })),\n* );\n*\n* // SQL DSL select builder — chainable.\n* const plan = db.sql\n* .from(tables.user)\n* .annotate(cacheAnnotation({ ttl: 60_000 }))\n* .select({ id: tables.user.columns.id })\n* .build();\n* ```\n*/\nconst cacheAnnotation = defineAnnotation()({\n\tnamespace: \"cache\",\n\tapplicableTo: [\"read\"]\n});\n//#endregion\n//#region src/cache-store.ts\n/**\n* Default cache backend. An LRU with per-entry TTL, backed by a `Map`.\n*\n* Eviction policy:\n*\n* - On `set` of a fresh key whose insertion would push the live count\n* above `maxEntries`, the least recently used entry is evicted.\n* Setting an existing key updates the entry in place and refreshes its\n* recency without changing the live count.\n* - On `get` of an existing key, recency is bumped (so the entry is no\n* longer the LRU candidate).\n* - On `get` of an expired entry, the entry is removed from the map and\n* `undefined` is returned. The slot becomes available for new writes\n* without counting against `maxEntries`.\n*\n* `Map` insertion order is the LRU order: the first key is the LRU\n* candidate; the last key is the most recently used. Bumping recency is\n* a delete-then-set on the underlying map.\n*\n* The default store is **not** coherent across processes or replicas —\n* each process holds its own Map. Users who need a shared cache supply\n* their own `CacheStore` (Redis, Memcached, etc.).\n*/\nfunction createInMemoryCacheStore(options) {\n\tconst maxEntries = options.maxEntries;\n\tconst clock = options.clock ?? Date.now;\n\tconst map = /* @__PURE__ */ new Map();\n\tfunction get(key) {\n\t\tconst record = map.get(key);\n\t\tif (record === void 0) return Promise.resolve(void 0);\n\t\tif (clock() >= record.expiresAt) {\n\t\t\tmap.delete(key);\n\t\t\treturn Promise.resolve(void 0);\n\t\t}\n\t\tmap.delete(key);\n\t\tmap.set(key, record);\n\t\treturn Promise.resolve(record.entry);\n\t}\n\tfunction set(key, entry, ttlMs) {\n\t\tconst expiresAt = clock() + ttlMs;\n\t\tif (map.has(key)) map.delete(key);\n\t\tmap.set(key, {\n\t\t\tentry,\n\t\t\texpiresAt\n\t\t});\n\t\twhile (map.size > maxEntries) {\n\t\t\tconst oldest = map.keys().next();\n\t\t\tif (oldest.done) break;\n\t\t\tmap.delete(oldest.value);\n\t\t}\n\t\treturn Promise.resolve();\n\t}\n\treturn {\n\t\tget,\n\t\tset\n\t};\n}\n//#endregion\n//#region src/cache-middleware.ts\n/**\n* Default `maxEntries` for the built-in in-memory store. Bounded so a\n* runaway producer cannot exhaust process memory; users who need\n* different bounds supply a custom `CacheStore`.\n*/\nconst DEFAULT_MAX_ENTRIES = 1e3;\n/**\n* Reads the cache payload from the plan, if present and branded.\n*\n* Returns `undefined` when:\n* - the plan has no `meta.annotations`, or\n* - the `cache` namespace key is absent, or\n* - the value under `cache` is not a branded `AnnotationValue` (the\n* `cacheAnnotation.read` defensive check covers this).\n*/\nfunction readCachePayload(plan) {\n\treturn cacheAnnotation.read(plan);\n}\n/**\n* Computes the cache key for an execution.\n*\n* Two-tier resolution:\n*\n* 1. Per-query override: `cacheAnnotation({ key })` — the supplied\n* string is used verbatim. Not rehashed; the user is responsible for\n* keeping the string bounded and free of sensitive data.\n* 2. Default: `ctx.contentHash(exec)` — the family runtime owns this and\n* returns an opaque, bounded digest (SHA-512 in the SQL and Mongo\n* runtimes today).\n*\n* The returned string is consumed directly as the `Map<string, …>` key\n* by the underlying `CacheStore`; the cache middleware does not perform\n* any further transformation.\n*/\nasync function resolveCacheKey(payload, exec, ctx) {\n\tif (payload.key !== void 0) return payload.key;\n\treturn ctx.contentHash(exec);\n}\n/**\n* Creates a family-agnostic caching middleware.\n*\n* The middleware uses three hooks:\n*\n* - `intercept` — on each execution, checks the cache. On a hit, returns\n* the cached raw rows; the runtime skips `runDriver` and `onRow`\n* (`beforeExecute` is not affected — it has already run for every\n* middleware before any `intercept` is consulted) and yields the\n* cached rows to the consumer (which, in the SQL runtime, sees them\n* after the standard `decodeRow` pass — i.e. the cache stores\n* wire-format values). On a miss, records a pending buffer keyed on\n* the `exec` object identity and returns `undefined` (passthrough).\n* - `onRow` — on the miss path, appends each row yielded by the driver\n* to the pending buffer.\n* - `afterExecute` — on the miss path, commits the buffer to the store\n* if and only if `result.completed === true && result.source === 'driver'`.\n* Failed executions and middleware-served executions never populate\n* the cache. The pending buffer is cleared in all branches so a stale\n* `WeakMap` entry cannot leak between executions sharing an `exec`.\n*\n* The middleware bypasses the cache entirely when:\n* - the plan has no `cache` annotation, or\n* - the annotation has `skip: true`, or\n* - the annotation has no `ttl`, or\n* - `ctx.scope !== 'runtime'` (connection / transaction scopes opt out).\n*\n* Returns a cross-family `RuntimeMiddleware` (no `familyId` /\n* `targetId`). The package depends only on\n* `@internal/framework-components/runtime`; cache keys come from\n* `ctx.contentHash(exec)`, populated by the family runtime, so SQL and\n* Mongo runtimes both work out of the box.\n*\n* @example\n* ```typescript\n* import { createCacheMiddleware, cacheAnnotation } from '@internal/middleware-cache';\n*\n* const db = postgres({\n* contractJson,\n* url: process.env['DATABASE_URL']!,\n* middleware: [createCacheMiddleware({ maxEntries: 1000 })],\n* });\n*\n* const user = await db.User.first(\n* { id },\n* (meta) => meta.annotate(cacheAnnotation({ ttl: 60_000 })),\n* );\n* ```\n*/\nfunction createCacheMiddleware(options) {\n\tconst store = options?.store ?? createInMemoryCacheStore({ maxEntries: options?.maxEntries ?? DEFAULT_MAX_ENTRIES });\n\tconst clock = options?.clock ?? Date.now;\n\tconst pending = /* @__PURE__ */ new WeakMap();\n\tasync function intercept(exec, ctx) {\n\t\tif (ctx.scope !== \"runtime\") return;\n\t\tconst payload = readCachePayload(exec);\n\t\tif (payload === void 0) return;\n\t\tif (payload.skip === true) return;\n\t\tif (payload.ttl === void 0) return;\n\t\tconst key = await resolveCacheKey(payload, exec, ctx);\n\t\tconst hit = await store.get(key);\n\t\tif (hit !== void 0) {\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: hit.rows };\n\t\t}\n\t\tpending.set(exec, {\n\t\t\tkey,\n\t\t\tttlMs: payload.ttl,\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\tconst slot = pending.get(exec);\n\t\tif (slot === void 0) return;\n\t\tslot.buffer.push(row);\n\t}\n\tasync function afterExecute(exec, result, ctx) {\n\t\tconst slot = pending.get(exec);\n\t\tif (slot === void 0) return;\n\t\tpending.delete(exec);\n\t\tif (!result.completed || result.source !== \"driver\") return;\n\t\tawait store.set(slot.key, {\n\t\t\trows: slot.buffer,\n\t\t\tstoredAt: clock()\n\t\t}, slot.ttlMs);\n\t\tctx.log.debug?.({\n\t\t\tevent: \"middleware.cache.store\",\n\t\t\tmiddleware: \"cache\",\n\t\t\tkey: slot.key\n\t\t});\n\t}\n\treturn {\n\t\tname: \"cache\",\n\t\tintercept,\n\t\tonRow,\n\t\tafterExecute\n\t};\n}\n//#endregion\nexport { cacheAnnotation, createCacheMiddleware, createInMemoryCacheStore };\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\";\n//#region src/cache-annotation.ts\n/**\n* Read-only annotation handle for the cache middleware.\n*\n* Declared with `applicableTo: ['read']`. Write terminals supply\n* `K = 'write'` to the type-level `ValidAnnotations<'write', As>` gate\n* (and the runtime `assertAnnotationsApplicable(annotations, 'write', ...)`\n* check); the join `K extends Kinds` fails for this annotation, making\n* \"cache a mutation\" structurally impossible without an `as any` cast\n* bypass at both type *and* runtime levels.\n*\n* Stored under namespace `'cache'` in `plan.meta.annotations`. The cache\n* middleware reads it via `cacheAnnotation.read(plan)`.\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.User.first(\n* { id },\n* (meta) => meta.annotate(cacheAnnotation({ ttl: 60_000 })),\n* );\n*\n* // SQL DSL select builder — chainable.\n* const plan = db.sql\n* .from(tables.user)\n* .annotate(cacheAnnotation({ ttl: 60_000 }))\n* .select({ id: tables.user.columns.id })\n* .build();\n* ```\n*/\nconst cacheAnnotation = defineAnnotation()({\n\tnamespace: \"cache\",\n\tapplicableTo: [\"read\"]\n});\n//#endregion\n//#region src/cache-store.ts\n/**\n* Default cache backend. An LRU with per-entry TTL, backed by a `Map`.\n*\n* Eviction policy:\n*\n* - On `set` of a fresh key whose insertion would push the live count\n* above `maxEntries`, the least recently used entry is evicted.\n* Setting an existing key updates the entry in place and refreshes its\n* recency without changing the live count.\n* - On `get` of an existing key, recency is bumped (so the entry is no\n* longer the LRU candidate).\n* - On `get` of an expired entry, the entry is removed from the map and\n* `undefined` is returned. The slot becomes available for new writes\n* without counting against `maxEntries`.\n*\n* `Map` insertion order is the LRU order: the first key is the LRU\n* candidate; the last key is the most recently used. Bumping recency is\n* a delete-then-set on the underlying map.\n*\n* The default store is **not** coherent across processes or replicas —\n* each process holds its own Map. Users who need a shared cache supply\n* their own `CacheStore` (Redis, Memcached, etc.).\n*/\nfunction createInMemoryCacheStore(options) {\n\tconst maxEntries = options.maxEntries;\n\tconst clock = options.clock ?? Date.now;\n\tconst map = /* @__PURE__ */ new Map();\n\tfunction get(key) {\n\t\tconst record = map.get(key);\n\t\tif (record === void 0) return Promise.resolve(void 0);\n\t\tif (clock() >= record.expiresAt) {\n\t\t\tmap.delete(key);\n\t\t\treturn Promise.resolve(void 0);\n\t\t}\n\t\tmap.delete(key);\n\t\tmap.set(key, record);\n\t\treturn Promise.resolve(record.entry);\n\t}\n\tfunction set(key, entry, ttlMs) {\n\t\tconst expiresAt = clock() + ttlMs;\n\t\tif (map.has(key)) map.delete(key);\n\t\tmap.set(key, {\n\t\t\tentry,\n\t\t\texpiresAt\n\t\t});\n\t\twhile (map.size > maxEntries) {\n\t\t\tconst oldest = map.keys().next();\n\t\t\tif (oldest.done) break;\n\t\t\tmap.delete(oldest.value);\n\t\t}\n\t\treturn Promise.resolve();\n\t}\n\treturn {\n\t\tget,\n\t\tset\n\t};\n}\n//#endregion\n//#region src/cache-middleware.ts\n/**\n* Default `maxEntries` for the built-in in-memory store. Bounded so a\n* runaway producer cannot exhaust process memory; users who need\n* different bounds supply a custom `CacheStore`.\n*/\nconst DEFAULT_MAX_ENTRIES = 1e3;\n/**\n* Reads the cache payload from the plan, if present and branded.\n*\n* Returns `undefined` when:\n* - the plan has no `meta.annotations`, or\n* - the `cache` namespace key is absent, or\n* - the value under `cache` is not a branded `AnnotationValue` (the\n* `cacheAnnotation.read` defensive check covers this).\n*/\nfunction readCachePayload(plan) {\n\treturn cacheAnnotation.read(plan);\n}\n/**\n* Computes the cache key for an execution.\n*\n* Two-tier resolution:\n*\n* 1. Per-query override: `cacheAnnotation({ key })` — the supplied\n* string is used verbatim. Not rehashed; the user is responsible for\n* keeping the string bounded and free of sensitive data.\n* 2. Default: `ctx.contentHash(exec)` — the family runtime owns this and\n* returns an opaque, bounded digest (SHA-512 in the SQL and Mongo\n* runtimes today).\n*\n* The returned string is consumed directly as the `Map<string, …>` key\n* by the underlying `CacheStore`; the cache middleware does not perform\n* any further transformation.\n*/\nasync function resolveCacheKey(payload, exec, ctx) {\n\tif (payload.key !== void 0) return payload.key;\n\treturn ctx.contentHash(exec);\n}\n/**\n* Creates a family-agnostic caching middleware.\n*\n* The middleware uses three hooks:\n*\n* - `interceptQuery` — on each execution, checks the cache. On a hit, returns\n* the cached raw rows; the runtime skips `runDriver` and `onRow`\n* (`beforeQuery` is not affected — it has already run for every\n* middleware before any `interceptQuery` is consulted) and yields the\n* cached rows to the consumer (which, in the SQL runtime, sees them\n* after the standard `decodeRow` pass — i.e. the cache stores\n* wire-format values). On a miss, records a pending buffer keyed on\n* the `exec` object identity and returns `undefined` (passthrough).\n* - `onRow` — on the miss path, appends each row yielded by the driver\n* to the pending buffer.\n* - `afterQuery` — on the miss path, commits the buffer to the store\n* if and only if `result.completed === true && result.source === 'driver'`.\n* Failed executions and middleware-served executions never populate\n* the cache. The pending buffer is cleared in all branches so a stale\n* `WeakMap` entry cannot leak between executions sharing an `exec`.\n*\n* The middleware bypasses the cache entirely when:\n* - the plan has no `cache` annotation, or\n* - the annotation has `skip: true`, or\n* - the annotation has no `ttl`, or\n* - `ctx.scope !== 'runtime'` (connection / transaction scopes opt out).\n*\n* Returns a cross-family `RuntimeMiddleware` (no `familyId` /\n* `targetId`). The package depends only on\n* `@internal/framework-components/runtime`; cache keys come from\n* `ctx.contentHash(exec)`, populated by the family runtime, so SQL and\n* Mongo runtimes both work out of the box.\n*\n* @example\n* ```typescript\n* import { createCacheMiddleware, cacheAnnotation } from '@internal/middleware-cache';\n*\n* const db = postgres({\n* contractJson,\n* url: process.env['DATABASE_URL']!,\n* middleware: [createCacheMiddleware({ maxEntries: 1000 })],\n* });\n*\n* const user = await db.User.first(\n* { id },\n* (meta) => meta.annotate(cacheAnnotation({ ttl: 60_000 })),\n* );\n* ```\n*/\nfunction createCacheMiddleware(options) {\n\tconst store = options?.store ?? createInMemoryCacheStore({ maxEntries: options?.maxEntries ?? DEFAULT_MAX_ENTRIES });\n\tconst clock = options?.clock ?? Date.now;\n\tconst pending = /* @__PURE__ */ new WeakMap();\n\tasync function interceptQuery(exec, ctx) {\n\t\tif (ctx.scope !== \"runtime\") return;\n\t\tconst payload = readCachePayload(exec);\n\t\tif (payload === void 0) return;\n\t\tif (payload.skip === true) return;\n\t\tif (payload.ttl === void 0) return;\n\t\tconst key = await resolveCacheKey(payload, exec, ctx);\n\t\tconst hit = await store.get(key);\n\t\tif (hit !== void 0) {\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: hit.rows };\n\t\t}\n\t\tpending.set(exec, {\n\t\t\tkey,\n\t\t\tttlMs: payload.ttl,\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\tconst slot = pending.get(exec);\n\t\tif (slot === void 0) return;\n\t\tslot.buffer.push(row);\n\t}\n\tasync function afterQuery(exec, result, ctx) {\n\t\tconst slot = pending.get(exec);\n\t\tif (slot === void 0) return;\n\t\tpending.delete(exec);\n\t\tif (!result.completed || result.source !== \"driver\") return;\n\t\tawait store.set(slot.key, {\n\t\t\trows: slot.buffer,\n\t\t\tstoredAt: clock()\n\t\t}, slot.ttlMs);\n\t\tctx.log.debug?.({\n\t\t\tevent: \"middleware.cache.store\",\n\t\t\tmiddleware: \"cache\",\n\t\t\tkey: slot.key\n\t\t});\n\t}\n\treturn {\n\t\tname: \"cache\",\n\t\tinterceptQuery,\n\t\tonRow,\n\t\tafterQuery\n\t};\n}\n//#endregion\nexport { cacheAnnotation, createCacheMiddleware, createInMemoryCacheStore };\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.1-dev.
|
|
3
|
+
"version": "8.0.0-rc.1-dev.43",
|
|
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.1-dev.
|
|
12
|
+
"@prisma/orm-framework": "8.0.0-rc.1-dev.43"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"@internal/middleware-cache": "8.0.0-rc.1-dev.
|
|
16
|
-
"@repo/tsconfig": "8.0.0-rc.1-dev.
|
|
17
|
-
"@repo/tsdown": "8.0.0-rc.1-dev.
|
|
15
|
+
"@internal/middleware-cache": "8.0.0-rc.1-dev.43",
|
|
16
|
+
"@repo/tsconfig": "8.0.0-rc.1-dev.43",
|
|
17
|
+
"@repo/tsdown": "8.0.0-rc.1-dev.43",
|
|
18
18
|
"tsdown": "0.22.14",
|
|
19
19
|
"typescript": "5.9.3"
|
|
20
20
|
},
|