@prisma/orm-extension-middleware-cache 8.0.0-rc.2-dev.3 → 8.0.0-rc.20-dev.1

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.mjs CHANGED
@@ -1,243 +1,384 @@
1
1
  import { defineAnnotation } from "@prisma/orm-framework/components/runtime";
2
+ import { blindCast } from "@prisma/orm-framework/utils/casts";
3
+ import { structuredError } from "@prisma/orm-framework/utils/structured-error";
2
4
  //#region ../../../3-extensions/middleware-cache/dist/index.mjs
3
5
  /**
4
- * Read-only annotation handle for the cache middleware.
6
+ * Marks a read for the cache middleware. It applies to reads only: a write terminal refuses it at
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.
5
8
  *
6
- * Declared with `applicableTo: ['read']`. Write terminals supply
7
- * `K = 'write'` to the type-level `ValidAnnotations<'write', As>` gate
8
- * (and the runtime `assertAnnotationsApplicable(annotations, 'write', ...)`
9
- * check); the join `K extends Kinds` fails for this annotation, making
10
- * "cache a mutation" structurally impossible without an `as any` cast
11
- * bypass at both type *and* runtime levels.
12
- *
13
- * Stored under namespace `'cache'` in `plan.meta.annotations`. The cache
14
- * middleware reads it via `cacheAnnotation.read(plan)`.
9
+ * `TMeta` types `meta` for this call only. Nothing ties it to the store's `TMeta`, so a store
10
+ * package should export a wrapper typed with its own meta, such as
11
+ * `(o: CacheAnnotationOptions<TagMeta>) => cacheAnnotation<TagMeta>(o)`.
15
12
  *
16
13
  * @example
17
14
  * ```typescript
18
15
  * import { cacheAnnotation } from '@internal/middleware-cache';
19
16
  *
20
17
  * // ORM read terminal — accepts the read-only annotation via the meta callback.
21
- * const user = await db.User.first(
18
+ * const user = await db.orm.public.User.first(
22
19
  * { id },
23
- * (meta) => meta.annotate(cacheAnnotation({ ttl: 60_000 })),
20
+ * (meta) => meta.annotate(cacheAnnotation({ key: `user-${id}` })),
24
21
  * );
25
22
  *
26
23
  * // SQL DSL select builder — chainable.
27
24
  * const plan = db.sql
28
25
  * .from(tables.user)
29
- * .annotate(cacheAnnotation({ ttl: 60_000 }))
26
+ * .annotate(cacheAnnotation({}))
30
27
  * .select({ id: tables.user.columns.id })
31
28
  * .build();
32
29
  * ```
33
30
  */
34
- const cacheAnnotation = defineAnnotation()({
31
+ const cacheAnnotation = withTypedMeta(defineAnnotation()({
35
32
  namespace: "cache",
36
33
  applicableTo: ["read"]
37
- });
34
+ }));
35
+ function withTypedMeta(handle) {
36
+ function cacheAnnotationWithMeta(options) {
37
+ return handle(options);
38
+ }
39
+ return Object.freeze(Object.assign(cacheAnnotationWithMeta, {
40
+ namespace: handle.namespace,
41
+ applicableTo: handle.applicableTo,
42
+ read: handle.read
43
+ }));
44
+ }
38
45
  /**
39
- * Default cache backend. An LRU with per-entry TTL, backed by a `Map`.
40
- *
41
- * Eviction policy:
42
- *
43
- * - On `set` of a fresh key whose insertion would push the live count
44
- * above `maxEntries`, the least recently used entry is evicted.
45
- * Setting an existing key updates the entry in place and refreshes its
46
- * recency without changing the live count.
47
- * - On `get` of an existing key, recency is bumped (so the entry is no
48
- * longer the LRU candidate).
49
- * - On `get` of an expired entry, the entry is removed from the map and
50
- * `undefined` is returned. The slot becomes available for new writes
51
- * without counting against `maxEntries`.
46
+ * Marks a write whose cache entries the cache middleware invalidates once the write's effects are final: right after the write outside a transaction, or when the outermost enclosing transaction ends. It does not invalidate when the write is rolled back, and does when the outcome is unknown. It applies to writes only: a read terminal refuses it at compile time and at run time.
52
47
  *
53
- * `Map` insertion order is the LRU order: the first key is the LRU
54
- * candidate; the last key is the most recently used. Bumping recency is
55
- * a delete-then-set on the underlying map.
48
+ * `TMeta` types `meta` for this call only. Nothing ties it to the store's `TMeta`, so a store package should export a wrapper typed with its own meta, such as `(o: InvalidationTarget<TagMeta>) => invalidateAnnotation<TagMeta>(o)`.
56
49
  *
57
- * The default store is **not** coherent across processes or replicas —
58
- * each process holds its own Map. Users who need a shared cache supply
59
- * their own `CacheStore` (Redis, Memcached, etc.).
50
+ * @example
51
+ * ```typescript
52
+ * await db.orm.public.User.where({ id: 1 }).update({ name: 'Alicia' }, (meta) =>
53
+ * meta.annotate(invalidateAnnotation({ keys: ['user-1'] })),
54
+ * );
55
+ * ```
56
+ */
57
+ const invalidateAnnotation = withTypedInvalidateMeta(defineAnnotation()({
58
+ namespace: "cache-invalidate",
59
+ applicableTo: ["write"]
60
+ }));
61
+ function withTypedInvalidateMeta(handle) {
62
+ function invalidateAnnotationWithMeta(options) {
63
+ return handle(options);
64
+ }
65
+ return Object.freeze(Object.assign(invalidateAnnotationWithMeta, {
66
+ namespace: handle.namespace,
67
+ applicableTo: handle.applicableTo,
68
+ read: handle.read
69
+ }));
70
+ }
71
+ const typedArraySlice = Reflect.get(Object.getPrototypeOf(Uint8Array.prototype), "slice");
72
+ /**
73
+ * A copy of the plain structure of `value`: plain and null-prototype objects, arrays, `Map`, `Set`,
74
+ * `Date`, `RegExp`, `ArrayBuffer`, typed arrays and `DataView` are copied, and every other value,
75
+ * such as a class instance or a function, is kept by reference. A typed array is copied with
76
+ * `TypedArray.prototype.slice`, which keeps its class, so a `Buffer` copies to a `Buffer` with its
77
+ * own memory; a `DataView` copies to a plain `DataView`; an array copies to its elements only, with
78
+ * holes read as `undefined`. Shared references and cycles are kept.
79
+ */
80
+ function copyValue(value, copies = /* @__PURE__ */ new Map()) {
81
+ if (typeof value !== "object" || value === null) return value;
82
+ const known = copies.get(value);
83
+ if (known !== void 0) return blindCast(known);
84
+ const copy = copyObject(value, copies);
85
+ copies.set(value, copy);
86
+ return blindCast(copy);
87
+ }
88
+ function copyObject(value, copies) {
89
+ if (value instanceof DataView) {
90
+ const start = value.byteOffset;
91
+ return new DataView(value.buffer.slice(start, start + value.byteLength));
92
+ }
93
+ if (ArrayBuffer.isView(value)) return typedArraySlice.call(value);
94
+ const prototype = Object.getPrototypeOf(value);
95
+ if (prototype === ArrayBuffer.prototype && value instanceof ArrayBuffer) return value.slice(0);
96
+ if (prototype === Date.prototype && value instanceof Date) return new Date(value.getTime());
97
+ if (prototype === RegExp.prototype && value instanceof RegExp) return new RegExp(value.source, value.flags);
98
+ if (prototype === Map.prototype && value instanceof Map) {
99
+ const copy = /* @__PURE__ */ new Map();
100
+ copies.set(value, copy);
101
+ for (const [key, entry] of value) copy.set(copyValue(key, copies), copyValue(entry, copies));
102
+ return copy;
103
+ }
104
+ if (prototype === Set.prototype && value instanceof Set) {
105
+ const copy = /* @__PURE__ */ new Set();
106
+ copies.set(value, copy);
107
+ for (const entry of value) copy.add(copyValue(entry, copies));
108
+ return copy;
109
+ }
110
+ if (prototype === Array.prototype && Array.isArray(value)) {
111
+ const copy = [];
112
+ copies.set(value, copy);
113
+ for (const entry of value) copy.push(copyValue(entry, copies));
114
+ return copy;
115
+ }
116
+ if (prototype !== Object.prototype && prototype !== null) return value;
117
+ const copy = Object.create(prototype);
118
+ copies.set(value, copy);
119
+ for (const key of Reflect.ownKeys(value)) if (Object.prototype.propertyIsEnumerable.call(value, key)) copy[key] = copyValue(Reflect.get(value, key), copies);
120
+ return copy;
121
+ }
122
+ /** 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. */
123
+ function copyRows(rows) {
124
+ return rows.map((row) => {
125
+ const copy = Object.create(Object.getPrototypeOf(row));
126
+ return Object.assign(copy, row);
127
+ });
128
+ }
129
+ function copyStored(value) {
130
+ switch (value.kind) {
131
+ case "statement-rows": return {
132
+ kind: "statement-rows",
133
+ rows: copyRows(value.rows)
134
+ };
135
+ case "call-result": return {
136
+ kind: "call-result",
137
+ result: copyValue(value.result)
138
+ };
139
+ }
140
+ }
141
+ function metaUnsupported() {
142
+ return structuredError("RUNTIME.CACHE_STORE_META_UNSUPPORTED", "The in-memory cache store cannot remove entries by meta because it does not index meta, so it removed nothing, not even the keys passed with it", { fix: "Use a CacheStore that indexes meta in set and unset, or pass only keys." });
143
+ }
144
+ function invalidOption(argument, received, expected) {
145
+ return structuredError("RUNTIME.ARGUMENT_INVALID", `createInMemoryCacheStore: ${argument} must be ${expected}`, {
146
+ fix: `Pass ${argument} as ${expected}, or leave it unset for the default.`,
147
+ meta: {
148
+ helper: "createInMemoryCacheStore",
149
+ argument,
150
+ received
151
+ }
152
+ });
153
+ }
154
+ /**
155
+ * 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.
60
156
  */
61
157
  function createInMemoryCacheStore(options) {
62
- const maxEntries = options.maxEntries;
63
- const clock = options.clock ?? Date.now;
64
- const map = /* @__PURE__ */ new Map();
65
- function get(key) {
66
- const record = map.get(key);
67
- if (record === void 0) return Promise.resolve(void 0);
68
- if (clock() >= record.expiresAt) {
69
- map.delete(key);
70
- return Promise.resolve(void 0);
158
+ const maxEntries = options?.maxEntries ?? 1e3;
159
+ const ttlMs = options?.ttlMs ?? 6e4;
160
+ const clock = options?.clock ?? Date.now;
161
+ if (!Number.isInteger(maxEntries) || maxEntries <= 0) throw invalidOption("maxEntries", maxEntries, "a positive integer");
162
+ if (!(ttlMs > 0)) throw invalidOption("ttlMs", ttlMs, "a positive number of milliseconds, or Infinity");
163
+ const records = /* @__PURE__ */ new Map();
164
+ const versions = /* @__PURE__ */ new Map();
165
+ function forgetExpiredVersions() {
166
+ const now = clock();
167
+ for (const [key, record] of versions) {
168
+ if (now < record.expiresAt) return;
169
+ versions.delete(key);
71
170
  }
72
- map.delete(key);
73
- map.set(key, record);
74
- return Promise.resolve(record.entry);
75
- }
76
- function set(key, entry, ttlMs) {
77
- const expiresAt = clock() + ttlMs;
78
- if (map.has(key)) map.delete(key);
79
- map.set(key, {
80
- entry,
81
- expiresAt
171
+ }
172
+ function versionOf(key) {
173
+ forgetExpiredVersions();
174
+ return versions.get(key)?.version ?? 0;
175
+ }
176
+ function liveRecord(key) {
177
+ const record = records.get(key);
178
+ if (record === void 0) return;
179
+ records.delete(key);
180
+ if (clock() >= record.expiresAt) return;
181
+ records.set(key, record);
182
+ return record;
183
+ }
184
+ async function get(target) {
185
+ const record = liveRecord(target.key);
186
+ return {
187
+ key: target.key,
188
+ meta: target.meta,
189
+ version: versionOf(target.key),
190
+ data: record === void 0 ? { empty: true } : {
191
+ empty: false,
192
+ value: copyStored(record.value)
193
+ }
194
+ };
195
+ }
196
+ async function set(entry, value) {
197
+ if (entry.version !== versionOf(entry.key)) return false;
198
+ records.delete(entry.key);
199
+ records.set(entry.key, {
200
+ value: copyStored(value),
201
+ expiresAt: clock() + ttlMs
82
202
  });
83
- while (map.size > maxEntries) {
84
- const oldest = map.keys().next();
85
- if (oldest.done) break;
86
- map.delete(oldest.value);
203
+ for (const leastRecentlyUsed of records.keys()) {
204
+ if (records.size <= maxEntries) break;
205
+ records.delete(leastRecentlyUsed);
206
+ }
207
+ return true;
208
+ }
209
+ async function unset(target) {
210
+ if (target.meta !== void 0) throw metaUnsupported();
211
+ for (const key of target.keys ?? []) {
212
+ records.delete(key);
213
+ const version = versionOf(key) + 1;
214
+ versions.delete(key);
215
+ versions.set(key, {
216
+ version,
217
+ expiresAt: clock() + ttlMs
218
+ });
87
219
  }
88
- return Promise.resolve();
89
220
  }
90
221
  return {
91
222
  get,
92
- set
223
+ set,
224
+ unset
93
225
  };
94
226
  }
95
227
  /**
96
- * Default `maxEntries` for the built-in in-memory store. Bounded so a
97
- * runaway producer cannot exhaust process memory; users who need
98
- * different bounds supply a custom `CacheStore`.
99
- */
100
- const DEFAULT_MAX_ENTRIES = 1e3;
101
- /**
102
- * Reads the cache payload from the plan, if present and branded.
103
- *
104
- * Returns `undefined` when:
105
- * - the plan has no `meta.annotations`, or
106
- * - the `cache` namespace key is absent, or
107
- * - the value under `cache` is not a branded `AnnotationValue` (the
108
- * `cacheAnnotation.read` defensive check covers this).
109
- */
110
- function readCachePayload(plan) {
111
- return cacheAnnotation.read(plan);
112
- }
113
- /**
114
- * Computes the cache key for an execution.
115
- *
116
- * Two-tier resolution:
117
- *
118
- * 1. Per-query override: `cacheAnnotation({ key })` — the supplied
119
- * string is used verbatim. Not rehashed; the user is responsible for
120
- * keeping the string bounded and free of sensitive data.
121
- * 2. Default: `ctx.contentHash(exec)` — the family runtime owns this and
122
- * returns an opaque, bounded digest (SHA-512 in the SQL and Mongo
123
- * runtimes today).
124
- *
125
- * The returned string is consumed directly as the `Map<string, …>` key
126
- * by the underlying `CacheStore`; the cache middleware does not perform
127
- * any further transformation.
228
+ * The default `deriveKey`: the family runtime's content hash of the plan, which covers the
229
+ * statement, its parameters and the storage hash.
128
230
  */
129
- async function resolveCacheKey(payload, exec, ctx) {
130
- if (payload.key !== void 0) return payload.key;
231
+ function deriveKeyFromContentHash(exec, ctx) {
131
232
  return ctx.contentHash(exec);
132
233
  }
234
+ const pendingMiss = Symbol("cache.pendingMiss");
235
+ function pendingOf(ctx) {
236
+ const miss = ctx.state(pendingMiss)["miss"];
237
+ return isPendingMiss(miss) ? miss : void 0;
238
+ }
239
+ function isPendingMiss(value) {
240
+ return typeof value === "object" && value !== null && "entry" in value && "buffer" in value;
241
+ }
242
+ function hasTag(value, tag) {
243
+ return value.kind === tag;
244
+ }
245
+ function cacheable(ctx) {
246
+ return ctx.scope === "runtime" && !ctx.inTransaction();
247
+ }
248
+ /** The value a pending miss stores when its activity ends this way, or `undefined` to store nothing. */
249
+ function valueToStore(miss, result) {
250
+ if (result.outcome !== "succeeded") return void 0;
251
+ if (result.kind === "statement") return result.source === "driver" && miss.buffer !== void 0 ? {
252
+ kind: "statement-rows",
253
+ rows: miss.buffer
254
+ } : void 0;
255
+ if (result.kind === "transaction") return void 0;
256
+ return result.source === "client" && result.result !== void 0 ? {
257
+ kind: "call-result",
258
+ result: result.result
259
+ } : void 0;
260
+ }
133
261
  /**
134
- * Creates a family-agnostic caching middleware.
262
+ * Creates a read-through cache middleware that works with every family runtime. It also invalidates entries for writes annotated with `invalidateAnnotation`.
135
263
  *
136
- * The middleware uses three hooks:
264
+ * It caches an activity that carries `cacheAnnotation` without `bypass` and runs in runtime scope,
265
+ * outside any transaction: never on a held connection, whose session may change what a read
266
+ * returns, and never inside a transaction, which must read its own uncommitted writes. Two kinds
267
+ * of activity are cached:
137
268
  *
138
- * - `interceptQuery` — on each execution, checks the cache. On a hit, returns
139
- * the cached raw rows; the runtime skips `runDriver` and `onRow`
140
- * (`beforeQuery` is not affected — it has already run for every
141
- * middleware before any `interceptQuery` is consulted) and yields the
142
- * cached rows to the consumer (which, in the SQL runtime, sees them
143
- * after the standard `decodeRow` pass — i.e. the cache stores
144
- * wire-format values). On a miss, records a pending buffer keyed on
145
- * the `exec` object identity and returns `undefined` (passthrough).
146
- * - `onRow` — on the miss path, appends each row yielded by the driver
147
- * to the pending buffer.
148
- * - `afterQuery` — on the miss path, commits the buffer to the store
149
- * if and only if `result.completed === true && result.source === 'driver'`.
150
- * Failed executions and middleware-served executions never populate
151
- * the cache. The pending buffer is cleared in all branches so a stale
152
- * `WeakMap` entry cannot leak between executions sharing an `exec`.
269
+ * - A statement whose plan carries the annotation, such as an annotated SQL builder query. Its key
270
+ * is the annotation's `key`, else `deriveKey(exec, ctx)`; its value is the raw rows.
271
+ * - A client activity that carries the annotation, such as an annotated ORM read. Its key is the
272
+ * annotation's `key`, else `await activity.contentHash()`; its value is the result the client
273
+ * ended the activity with. It is served as a whole, so a hit sends no statement.
153
274
  *
154
- * The middleware bypasses the cache entirely when:
155
- * - the plan has no `cache` annotation, or
156
- * - the annotation has `skip: true`, or
157
- * - the annotation has no `ttl`, or
158
- * - `ctx.scope !== 'runtime'` (connection / transaction scopes opt out).
275
+ * 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.
159
276
  *
160
- * Returns a cross-family `RuntimeMiddleware` (no `familyId` /
161
- * `targetId`). The package depends only on
162
- * `@internal/framework-components/runtime`; cache keys come from
163
- * `ctx.contentHash(exec)`, populated by the family runtime, so SQL and
164
- * Mongo runtimes both work out of the box.
277
+ * - `intercept.onQuery` and `intercept.onActivity` — on a hit, return the stored rows or result,
278
+ * and nothing runs. On a miss, record the pending entry.
279
+ * - `onRow` — collects each row of a statement miss.
280
+ * - `activityEnded` — when the driver completed the statement, or the client ended the call
281
+ * `succeeded` with a result (`null` is a result), calls `store.set(entry, value)` with the entry
282
+ * `store.get` returned, which stores only if the key's version has not moved. If an `unset` moved
283
+ * it meanwhile, `set` returns `false` and the middleware logs `middleware.cache.store-skipped`.
284
+ * - `activityFinalized` — for any activity that carries `invalidateAnnotation`, whatever its kind or transaction, calls `invalidate` with the annotation's `keys` and `meta`, unless the outcome is `rolled-back`. An `unknown` outcome invalidates, since the write may have landed. A store error is thrown, and the runtime logs it.
165
285
  *
166
286
  * @example
167
287
  * ```typescript
168
- * import { createCacheMiddleware, cacheAnnotation } from '@internal/middleware-cache';
169
- *
170
- * const db = postgres({
171
- * contractJson,
172
- * url: process.env['DATABASE_URL']!,
173
- * middleware: [createCacheMiddleware({ maxEntries: 1000 })],
174
- * });
288
+ * const cache = createCacheMiddleware();
289
+ * const db = postgres({ contract, url, middleware: [cache] });
175
290
  *
176
- * const user = await db.User.first(
177
- * { id },
178
- * (meta) => meta.annotate(cacheAnnotation({ ttl: 60_000 })),
291
+ * const user = await db.orm.public.User.first(
292
+ * { id: 1 },
293
+ * (meta) => meta.annotate(cacheAnnotation({ key: 'user-1' })),
294
+ * );
295
+ * await db.orm.public.User.where({ id: 1 }).update({ name: 'Alicia' }, (meta) =>
296
+ * meta.annotate(invalidateAnnotation({ keys: ['user-1'] })),
179
297
  * );
180
298
  * ```
181
299
  */
182
300
  function createCacheMiddleware(options) {
183
- const store = options?.store ?? createInMemoryCacheStore({ maxEntries: options?.maxEntries ?? DEFAULT_MAX_ENTRIES });
184
- const clock = options?.clock ?? Date.now;
185
- const pending = /* @__PURE__ */ new WeakMap();
186
- async function interceptQuery(exec, ctx) {
187
- if (ctx.scope !== "runtime") return;
188
- const payload = readCachePayload(exec);
189
- if (payload === void 0) return;
190
- if (payload.skip === true) return;
191
- if (payload.ttl === void 0) return;
192
- const key = await resolveCacheKey(payload, exec, ctx);
193
- const hit = await store.get(key);
194
- if (hit !== void 0) {
301
+ const store = options?.store ?? createInMemoryCacheStore();
302
+ const deriveKey = options?.deriveKey ?? deriveKeyFromContentHash;
303
+ async function lookUp(tag, key, meta, ctx, buffer) {
304
+ const entry = await store.get({
305
+ key,
306
+ meta
307
+ });
308
+ if (!entry.data.empty && hasTag(entry.data.value, tag)) {
195
309
  ctx.log.debug?.({
196
310
  event: "middleware.cache.hit",
197
311
  middleware: "cache",
198
312
  key
199
313
  });
200
- return { rows: hit.rows };
314
+ return entry.data.value;
201
315
  }
202
- pending.set(exec, {
203
- key,
204
- ttlMs: payload.ttl,
205
- buffer: []
206
- });
316
+ ctx.state(pendingMiss)["miss"] = {
317
+ entry,
318
+ buffer
319
+ };
207
320
  ctx.log.debug?.({
208
321
  event: "middleware.cache.miss",
209
322
  middleware: "cache",
210
323
  key
211
324
  });
212
325
  }
213
- async function onRow(row, exec, _ctx) {
214
- const slot = pending.get(exec);
215
- if (slot === void 0) return;
216
- slot.buffer.push(row);
217
- }
218
- async function afterQuery(exec, result, ctx) {
219
- const slot = pending.get(exec);
220
- if (slot === void 0) return;
221
- pending.delete(exec);
222
- if (!result.completed || result.source !== "driver") return;
223
- await store.set(slot.key, {
224
- rows: slot.buffer,
225
- storedAt: clock()
226
- }, slot.ttlMs);
326
+ async function onQuery(exec, ctx) {
327
+ if (!cacheable(ctx)) return;
328
+ const annotation = cacheAnnotation.read(exec);
329
+ if (annotation === void 0 || annotation.bypass === true) return;
330
+ const value = await lookUp("statement-rows", annotation.key ?? await deriveKey(exec, ctx), annotation.meta, ctx, []);
331
+ return value === void 0 ? void 0 : { rows: value.rows };
332
+ }
333
+ async function onActivity(activity, ctx) {
334
+ if (!cacheable(ctx)) return;
335
+ const annotation = cacheAnnotation.read(activity);
336
+ if (annotation === void 0 || annotation.bypass === true) return;
337
+ const value = await lookUp("call-result", annotation.key ?? await activity.contentHash(), annotation.meta, ctx, void 0);
338
+ return value === void 0 ? void 0 : { result: value.result };
339
+ }
340
+ async function onRow(row, _exec, ctx) {
341
+ pendingOf(ctx)?.buffer?.push(row);
342
+ }
343
+ async function activityEnded(_activity, result, ctx) {
344
+ const miss = pendingOf(ctx);
345
+ if (miss === void 0) return;
346
+ delete ctx.state(pendingMiss)["miss"];
347
+ const value = valueToStore(miss, result);
348
+ if (value === void 0) return;
349
+ const stored = await store.set(miss.entry, value);
227
350
  ctx.log.debug?.({
228
- event: "middleware.cache.store",
351
+ event: stored ? "middleware.cache.store" : "middleware.cache.store-skipped",
229
352
  middleware: "cache",
230
- key: slot.key
353
+ key: miss.entry.key
354
+ });
355
+ }
356
+ async function invalidate(target) {
357
+ const keys = target.keys !== void 0 && target.keys.length > 0 ? target.keys : void 0;
358
+ if (keys === void 0 && target.meta === void 0) return;
359
+ await store.unset({
360
+ keys,
361
+ meta: target.meta
231
362
  });
232
363
  }
364
+ async function activityFinalized(activity, result) {
365
+ if (result.outcome === "rolled-back") return;
366
+ const target = invalidateAnnotation.read(activity);
367
+ if (target !== void 0) await invalidate(target);
368
+ }
233
369
  return {
234
370
  name: "cache",
235
- interceptQuery,
371
+ intercept: {
372
+ onQuery,
373
+ onActivity
374
+ },
236
375
  onRow,
237
- afterQuery
376
+ activityEnded,
377
+ activityFinalized,
378
+ invalidate
238
379
  };
239
380
  }
240
381
  //#endregion
241
- export { cacheAnnotation, createCacheMiddleware, createInMemoryCacheStore };
382
+ export { cacheAnnotation, createCacheMiddleware, createInMemoryCacheStore, deriveKeyFromContentHash, invalidateAnnotation };
242
383
 
243
384
  //# sourceMappingURL=index.mjs.map
@@ -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* - `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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,MAAM,kBAAkB,iBAAiB,CAAC,CAAC;CAC1C,WAAW;CACX,cAAc,CAAC,MAAM;AACtB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;AA0BD,SAAS,yBAAyB,SAAS;CAC1C,MAAM,aAAa,QAAQ;CAC3B,MAAM,QAAQ,QAAQ,SAAS,KAAK;CACpC,MAAM,sBAAsB,IAAI,IAAI;CACpC,SAAS,IAAI,KAAK;EACjB,MAAM,SAAS,IAAI,IAAI,GAAG;EAC1B,IAAI,WAAW,KAAK,GAAG,OAAO,QAAQ,QAAQ,KAAK,CAAC;EACpD,IAAI,MAAM,KAAK,OAAO,WAAW;GAChC,IAAI,OAAO,GAAG;GACd,OAAO,QAAQ,QAAQ,KAAK,CAAC;EAC9B;EACA,IAAI,OAAO,GAAG;EACd,IAAI,IAAI,KAAK,MAAM;EACnB,OAAO,QAAQ,QAAQ,OAAO,KAAK;CACpC;CACA,SAAS,IAAI,KAAK,OAAO,OAAO;EAC/B,MAAM,YAAY,MAAM,IAAI;EAC5B,IAAI,IAAI,IAAI,GAAG,GAAG,IAAI,OAAO,GAAG;EAChC,IAAI,IAAI,KAAK;GACZ;GACA;EACD,CAAC;EACD,OAAO,IAAI,OAAO,YAAY;GAC7B,MAAM,SAAS,IAAI,KAAK,CAAC,CAAC,KAAK;GAC/B,IAAI,OAAO,MAAM;GACjB,IAAI,OAAO,OAAO,KAAK;EACxB;EACA,OAAO,QAAQ,QAAQ;CACxB;CACA,OAAO;EACN;EACA;CACD;AACD;;;;;;AAQA,MAAM,sBAAsB;;;;;;;;;;AAU5B,SAAS,iBAAiB,MAAM;CAC/B,OAAO,gBAAgB,KAAK,IAAI;AACjC;;;;;;;;;;;;;;;;;AAiBA,eAAe,gBAAgB,SAAS,MAAM,KAAK;CAClD,IAAI,QAAQ,QAAQ,KAAK,GAAG,OAAO,QAAQ;CAC3C,OAAO,IAAI,YAAY,IAAI;AAC5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,SAAS,sBAAsB,SAAS;CACvC,MAAM,QAAQ,SAAS,SAAS,yBAAyB,EAAE,YAAY,SAAS,cAAc,oBAAoB,CAAC;CACnH,MAAM,QAAQ,SAAS,SAAS,KAAK;CACrC,MAAM,0BAA0B,IAAI,QAAQ;CAC5C,eAAe,eAAe,MAAM,KAAK;EACxC,IAAI,IAAI,UAAU,WAAW;EAC7B,MAAM,UAAU,iBAAiB,IAAI;EACrC,IAAI,YAAY,KAAK,GAAG;EACxB,IAAI,QAAQ,SAAS,MAAM;EAC3B,IAAI,QAAQ,QAAQ,KAAK,GAAG;EAC5B,MAAM,MAAM,MAAM,gBAAgB,SAAS,MAAM,GAAG;EACpD,MAAM,MAAM,MAAM,MAAM,IAAI,GAAG;EAC/B,IAAI,QAAQ,KAAK,GAAG;GACnB,IAAI,IAAI,QAAQ;IACf,OAAO;IACP,YAAY;IACZ;GACD,CAAC;GACD,OAAO,EAAE,MAAM,IAAI,KAAK;EACzB;EACA,QAAQ,IAAI,MAAM;GACjB;GACA,OAAO,QAAQ;GACf,QAAQ,CAAC;EACV,CAAC;EACD,IAAI,IAAI,QAAQ;GACf,OAAO;GACP,YAAY;GACZ;EACD,CAAC;CACF;CACA,eAAe,MAAM,KAAK,MAAM,MAAM;EACrC,MAAM,OAAO,QAAQ,IAAI,IAAI;EAC7B,IAAI,SAAS,KAAK,GAAG;EACrB,KAAK,OAAO,KAAK,GAAG;CACrB;CACA,eAAe,WAAW,MAAM,QAAQ,KAAK;EAC5C,MAAM,OAAO,QAAQ,IAAI,IAAI;EAC7B,IAAI,SAAS,KAAK,GAAG;EACrB,QAAQ,OAAO,IAAI;EACnB,IAAI,CAAC,OAAO,aAAa,OAAO,WAAW,UAAU;EACrD,MAAM,MAAM,IAAI,KAAK,KAAK;GACzB,MAAM,KAAK;GACX,UAAU,MAAM;EACjB,GAAG,KAAK,KAAK;EACb,IAAI,IAAI,QAAQ;GACf,OAAO;GACP,YAAY;GACZ,KAAK,KAAK;EACX,CAAC;CACF;CACA,OAAO;EACN,MAAM;EACN;EACA;EACA;CACD;AACD"}
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/**\n* Marks a write whose cache entries the cache middleware invalidates once the write's effects are final: right after the write outside a transaction, or when the outermost enclosing transaction ends. It does not invalidate when the write is rolled back, and does when the outcome is unknown. It applies to writes only: a read terminal refuses it at compile time and at run time.\n*\n* `TMeta` types `meta` for this call only. Nothing ties it to the store's `TMeta`, so a store package should export a wrapper typed with its own meta, such as `(o: InvalidationTarget<TagMeta>) => invalidateAnnotation<TagMeta>(o)`.\n*\n* @example\n* ```typescript\n* await db.orm.public.User.where({ id: 1 }).update({ name: 'Alicia' }, (meta) =>\n* meta.annotate(invalidateAnnotation({ keys: ['user-1'] })),\n* );\n* ```\n*/\nconst invalidateAnnotation = withTypedInvalidateMeta(defineAnnotation()({\n\tnamespace: \"cache-invalidate\",\n\tapplicableTo: [\"write\"]\n}));\nfunction withTypedInvalidateMeta(handle) {\n\tfunction invalidateAnnotationWithMeta(options) {\n\t\treturn handle(options);\n\t}\n\treturn Object.freeze(Object.assign(invalidateAnnotationWithMeta, {\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, so it removed nothing, not even the keys passed with it\", { fix: \"Use a CacheStore that indexes meta in set and unset, or pass only keys.\" });\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. It also invalidates entries for writes annotated with `invalidateAnnotation`.\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* - `activityFinalized` — for any activity that carries `invalidateAnnotation`, whatever its kind or transaction, calls `invalidate` with the annotation's `keys` and `meta`, unless the outcome is `rolled-back`. An `unknown` outcome invalidates, since the write may have landed. A store error is thrown, and the runtime logs it.\n*\n* @example\n* ```typescript\n* const cache = createCacheMiddleware();\n* const db = postgres({ contract, 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' }, (meta) =>\n* meta.annotate(invalidateAnnotation({ keys: ['user-1'] })),\n* );\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\tasync function activityFinalized(activity, result) {\n\t\tif (result.outcome === \"rolled-back\") return;\n\t\tconst target = invalidateAnnotation.read(activity);\n\t\tif (target !== void 0) await invalidate(target);\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\tactivityFinalized,\n\t\tinvalidate\n\t};\n}\n//#endregion\nexport { cacheAnnotation, createCacheMiddleware, createInMemoryCacheStore, deriveKeyFromContentHash, invalidateAnnotation };\n\n//# sourceMappingURL=index.mjs.map"],"mappings":"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"}