@dee-wan/cache 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +177 -2
- package/dist/chunk-A4WV6RY4.js +311 -0
- package/dist/chunk-A4WV6RY4.js.map +1 -0
- package/dist/chunk-AENVFT2S.js +6 -0
- package/dist/chunk-AENVFT2S.js.map +1 -0
- package/dist/chunk-D2EESWMC.js +19 -0
- package/dist/chunk-D2EESWMC.js.map +1 -0
- package/dist/chunk-E6OZZ5UX.js +133 -0
- package/dist/chunk-E6OZZ5UX.js.map +1 -0
- package/dist/chunk-HGAUU4EU.js +388 -0
- package/dist/chunk-HGAUU4EU.js.map +1 -0
- package/dist/chunk-JPK2AF7X.js +18 -0
- package/dist/chunk-JPK2AF7X.js.map +1 -0
- package/dist/chunk-LHOK33GI.js +12 -0
- package/dist/chunk-LHOK33GI.js.map +1 -0
- package/dist/chunk-MOJBHU3S.js +431 -0
- package/dist/chunk-MOJBHU3S.js.map +1 -0
- package/dist/chunk-OOPPXK7Q.js +133 -0
- package/dist/chunk-OOPPXK7Q.js.map +1 -0
- package/dist/chunk-ZCMZJKGN.js +78 -0
- package/dist/chunk-ZCMZJKGN.js.map +1 -0
- package/dist/chunk-ZREX7LLC.js +545 -0
- package/dist/chunk-ZREX7LLC.js.map +1 -0
- package/dist/client.d.ts +50 -0
- package/dist/client.js +224 -0
- package/dist/client.js.map +1 -0
- package/dist/cloudflare/index.d.ts +15 -2
- package/dist/cloudflare/index.js +413 -45
- package/dist/cloudflare/index.js.map +1 -1
- package/dist/d1/config.d.ts +41 -0
- package/dist/d1/config.js +102 -0
- package/dist/d1/config.js.map +1 -0
- package/dist/d1/index.d.ts +64 -0
- package/dist/d1/index.js +196 -0
- package/dist/d1/index.js.map +1 -0
- package/dist/d1/response-lease.d.ts +9 -0
- package/dist/d1/response-lease.js +42 -0
- package/dist/d1/response-lease.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -616
- package/dist/index.js.map +1 -1
- package/dist/profile--LBtbQI_.d.ts +30 -0
- package/dist/response-lease-DeeM-ofO.d.ts +19 -0
- package/dist/response-lease.d.ts +7 -0
- package/dist/response-lease.js +9 -0
- package/dist/response-lease.js.map +1 -0
- package/dist/response-warmup-BWQvnT1G.d.ts +38 -0
- package/dist/scoped-read.d.ts +12 -0
- package/dist/scoped-read.js +30 -0
- package/dist/scoped-read.js.map +1 -0
- package/dist/{types-DwtU0KpX.d.ts → types-D-Gd11to.d.ts} +13 -1
- package/docs/cache-contract.md +49 -1
- package/package.json +31 -2
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @dee-wan/cache
|
|
2
2
|
|
|
3
3
|
Prisma read cache for Cloudflare Workers and D1. A Durable Object coordinates invalidation, fill locks
|
|
4
|
-
and cached values
|
|
4
|
+
and cached values. Default reads check the coordinator. Optional regional reads allow bounded staleness.
|
|
5
5
|
|
|
6
6
|
The contract, including every invalidation rule and limit, is in
|
|
7
7
|
[docs/cache-contract.md](docs/cache-contract.md).
|
|
@@ -74,6 +74,55 @@ They then run on every read, including hits, and the arguments they rewrite (for
|
|
|
74
74
|
filter) are part of the cache key. An extension applied after the cache is skipped on a hit. Use
|
|
75
75
|
`scope` for anything a caller changes about results without changing the arguments.
|
|
76
76
|
|
|
77
|
+
## Regional Prisma reads
|
|
78
|
+
|
|
79
|
+
`createRegionalStore` adds local Cloudflare Cache API reads to the same coordinator store. Opt in to
|
|
80
|
+
at most five seconds of stale data across isolates. Authorization and guard extensions still run.
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { createDurableObjectStore, createRegionalStore } from '@dee-wan/cache/cloudflare';
|
|
84
|
+
|
|
85
|
+
const name = 'main-db';
|
|
86
|
+
const generation = `${deploymentGeneration}:${cacheConfig.cacheVersion}`;
|
|
87
|
+
const store = createRegionalStore(createDurableObjectStore(env.CACHE_COORDINATOR, { name }), {
|
|
88
|
+
namespace: env.CACHE_COORDINATOR.idFromName(name).toString(),
|
|
89
|
+
generation,
|
|
90
|
+
ttlMs: 5000,
|
|
91
|
+
waitUntil: promise => ctx.waitUntil(promise),
|
|
92
|
+
});
|
|
93
|
+
const extension = createCacheExtension({
|
|
94
|
+
config: cacheConfig,
|
|
95
|
+
store,
|
|
96
|
+
coordinationGeneration: generation,
|
|
97
|
+
scope: callerScope,
|
|
98
|
+
decimal: Prisma.Decimal,
|
|
99
|
+
waitUntil: promise => ctx.waitUntil(promise),
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Keep namespace stable across callers and writers. It identifies one coordinator and database, not a
|
|
104
|
+
user. Keep caller permissions in `scope`. `coordinationGeneration` must match the wrapper generation.
|
|
105
|
+
Accepted fills and coordinator hits with a known fresh deadline can populate regional entries. Their
|
|
106
|
+
lifetime never exceeds the source freshness deadline. Old/custom stores without that deadline still
|
|
107
|
+
work, but their hits do not populate regional entries. Refresh tokens are never replayed regionally.
|
|
108
|
+
With `waitUntil`, hits in the latter half of the regional lease renew from the coordinator in the
|
|
109
|
+
background. Renewals are deduplicated per key and keep the same freshness bound. Read/release
|
|
110
|
+
waits are bounded; late locks are released after timeout. Failed renewal permits another attempt
|
|
111
|
+
without delaying the foreground hit; expired entries still use the coordinator.
|
|
112
|
+
Local mutations block old reads and fills. Operations sharing a write token execute in order;
|
|
113
|
+
queued operations stay fenced until the actual coordinator call settles, including after a caller
|
|
114
|
+
timeout. Cross-isolate changes converge within the regional lease.
|
|
115
|
+
After a failed final write fence or explicit invalidation, regional reads stay blocked. With
|
|
116
|
+
`waitUntil`, later reads retry the coordinator in bounded background passes. Caching resumes only
|
|
117
|
+
after acknowledgment, never merely because time passed. Failure tracking overflow or reuse of an
|
|
118
|
+
unresolved failed write token keeps the isolate disabled.
|
|
119
|
+
Cache API failures use the coordinator; coordinator read failures use the original database path.
|
|
120
|
+
|
|
121
|
+
Generated backends use this wrapper and `createResponseWarmup` from the same entry point. They warm
|
|
122
|
+
bounded generated GET routes through normal authorization, with the current caller's credentials.
|
|
123
|
+
Warming is best effort; unknown query variants and first requests can remain cold. Writes and auth
|
|
124
|
+
decisions are not cached. A warm-read target is not a latency guarantee for all network requests.
|
|
125
|
+
|
|
77
126
|
## Options
|
|
78
127
|
|
|
79
128
|
Invalid options throw a `TypeError` at construction.
|
|
@@ -133,7 +182,7 @@ Models that reference the target through a foreign key are invalidated too. Unkn
|
|
|
133
182
|
- One coordinator Durable Object has a soft limit of 1,000 requests per second. Measured on
|
|
134
183
|
2026-09-17 from one client: about 1,030 cached reads per second at 21 ms p50 with no errors, against
|
|
135
184
|
about 340 per second at 215 ms reading D1 directly. Higher rates are not measured.
|
|
136
|
-
- Each
|
|
185
|
+
- Each coordinator hit costs one call; a miss costs two; a contended miss can cost up to seven;
|
|
137
186
|
each write costs two, plus retries. On the Workers Free plan, requests that make many Prisma calls
|
|
138
187
|
can reach the subrequest limit (see the contract, "Cost per Prisma call").
|
|
139
188
|
- Every write to a model invalidates that model's cached lists, counts and relation reads.
|
|
@@ -173,3 +222,129 @@ npx wrangler delete --name <worker> && npx wrangler d1 delete <name>
|
|
|
173
222
|
```
|
|
174
223
|
|
|
175
224
|
`test/workerd/.build/schema.sql` is written by `npm run test:workerd`.
|
|
225
|
+
|
|
226
|
+
## D1 write fencing with guarded Prisma reads
|
|
227
|
+
|
|
228
|
+
`@dee-wan/cache/d1` exports `cacheD1Env`, `wrapD1Handler`, `createD1CacheRuntime`,
|
|
229
|
+
`d1PrismaCacheExtension`, and `fenceD1Writes`. A consumer provides a `D1CacheProfile`
|
|
230
|
+
with environment prefix, D1 binding names and prefixes, coordinator object-name prefix,
|
|
231
|
+
Prisma `readModels`, and explicitly proven `unfencedWriteTables`. The write adapter
|
|
232
|
+
passes reads straight through and fences raw D1 writes, including batches, exec, and
|
|
233
|
+
sessions. Unknown or ambiguous writes use the global fence. Exempt a table only when
|
|
234
|
+
its schema proves that its writes cannot change cached data through triggers or cascades.
|
|
235
|
+
|
|
236
|
+
Wrap every fetch, scheduled handler, and separate writer that can touch a coordinated
|
|
237
|
+
D1 database. Apply `d1PrismaCacheExtension` to a client only after its authorization and
|
|
238
|
+
scope extensions. The runtime returns no extension when caching or cached reads are
|
|
239
|
+
staged off. Independent control and authentication clients should remain uncached.
|
|
240
|
+
`@dee-wan/cache/d1/config` validates Worker bindings, database identity maps, and paired
|
|
241
|
+
writers, and offers generic writer configuration edits. Consumers must supply real D1
|
|
242
|
+
UUIDs, never binding object identities.
|
|
243
|
+
|
|
244
|
+
`coordinationGeneration` lets a Prisma extension share the literal coordinator generation
|
|
245
|
+
with a raw-D1 write fence during an upgrade. If omitted, the existing planner/config-derived
|
|
246
|
+
coordinator generation remains unchanged. Prisma cache keys always contain planner and
|
|
247
|
+
generated-config versions. Do not mix coordination generations on one coordinator; staged
|
|
248
|
+
rollouts must make every writer participate before enabling cached reads. External D1 writes
|
|
249
|
+
are not intercepted: disable cached reads and writers as appropriate, then use a fresh
|
|
250
|
+
generation before re-enabling caching.
|
|
251
|
+
|
|
252
|
+
The package's Prisma peer support remains Prisma 6.19.3 and later 6.x. The D1 write adapter
|
|
253
|
+
is independent of Prisma; other Prisma versions have not been validated by this package.
|
|
254
|
+
|
|
255
|
+
### Explicit authorized reads
|
|
256
|
+
|
|
257
|
+
`d1CacheEpoch(database)` returns an opaque namespace for a wrapped database's identity,
|
|
258
|
+
generation and global write epoch. Capture it before reading that database and include
|
|
259
|
+
every dependency's captured namespace in an external cache key. A missing runtime,
|
|
260
|
+
disabled reads, pending write or coordinator failure returns `null`; bypass external
|
|
261
|
+
cache reads and fills in that case. Old in-flight fills retain their old namespace.
|
|
262
|
+
This does not authorize a request or invalidate downstream browser caches.
|
|
263
|
+
|
|
264
|
+
`cacheD1Read(database, { key, scope, dependencies, ttlMs, cacheable }, load)` caches an
|
|
265
|
+
explicitly selected read after the caller has completed authorization. Include the full
|
|
266
|
+
actor, site, current permissions and all query inputs in `scope` and `key`, and list every
|
|
267
|
+
physical D1 binding read by the loader in `dependencies`. The helper inspects each unique
|
|
268
|
+
binding's global fence before lookup, includes its generation and epoch in the key, and
|
|
269
|
+
checks again before serving a hit or storing a fill. A pending write, unknown runtime,
|
|
270
|
+
older coordinator, inspection failure, or disabled reads bypasses the cache. Default TTL
|
|
271
|
+
is 30 seconds, with a maximum of five minutes. Values and keys are bounded; unsupported
|
|
272
|
+
objects bypass caching. Date, bigint and byte arrays use the existing value codec.
|
|
273
|
+
|
|
274
|
+
`cacheD1JsonRead(database, { key, scope, dependencies, ttlMs, request, buffered: true }, load)`
|
|
275
|
+
is an explicit opt-in for responses the caller knows are buffered JSON. It keeps the
|
|
276
|
+
original response on a miss and preserves response headers on a hit. Conditional, range,
|
|
277
|
+
preview and capability requests, error responses, cookies, downloads, unsafe `Vary`,
|
|
278
|
+
encoded bodies and oversized bodies bypass caching. Without `buffered: true`, responses
|
|
279
|
+
bypass; never set it for streams. Response buffering is byte-bounded and time-bounded.
|
|
280
|
+
These helpers use private Durable Object storage, not the public Cache API.
|
|
281
|
+
|
|
282
|
+
For native Prisma reads that also depend on another D1 database, use
|
|
283
|
+
`withD1PrismaCache(client, database, config, { scope, dependencies })` after the caller's
|
|
284
|
+
guards. Scope must include the complete actor, site and permission context. Per-operation
|
|
285
|
+
keys include dependency epochs and recheck dependencies on hits. The original three-argument
|
|
286
|
+
form retains its existing behavior. All participating writers must fence through the same
|
|
287
|
+
physical-database coordinator.
|
|
288
|
+
|
|
289
|
+
D1 coordinator calls have a 2-second deadline each. Reads fall back to D1 on failure;
|
|
290
|
+
writes require a successful begin. A failed end preserves the database result and keeps
|
|
291
|
+
the lease conservative. Late begins and read locks are cleaned up after they settle.
|
|
292
|
+
|
|
293
|
+
`keyArgs` optionally maps read arguments to cache-key identity. Preserve every result-changing input in that identity. Execution and invalidation use the original arguments. Callback errors bypass the cache.
|
|
294
|
+
|
|
295
|
+
### Regional read cache
|
|
296
|
+
|
|
297
|
+
`cacheD1Read` and scoped `d1PrismaCacheExtension` / `withD1PrismaCache` accept `regionalTtlMs` from 0 to 5000. The default is 0 (strict coordination). A positive value opts in to bounded stale results in the local Cloudflare Cache API; changes by other writers may remain invisible for up to that interval. Use 0 for a strict read. Cache API failure falls back to the strict path. This option caches values, not HTTP responses.
|
|
298
|
+
|
|
299
|
+
Keys include database and coordinator identities, generations, scope, and query identity. `regionalModels` optionally limits Prisma regional reads to queries whose root and all related models are listed. Callers must supply a scope that separates every authorization context affecting the result. `keyArgs` must preserve every result-changing argument. For custom `createD1CacheRuntime` stores, supply a stable, unique `regionalIdentity` per coordinator; without one the regional layer is bypassed.
|
|
300
|
+
|
|
301
|
+
`cacheRegionalResponse(request, { namespace, ttlMs, waitUntil? }, load)` is an opt-in regional cache for anonymous public GET JSON responses. Use a stable deployment/version namespace and a TTL no greater than 5000 ms. Credential, preview, conditional and cache-bypass requests reach `load`. Only explicitly public responses with positive `s-maxage` qualify. Browser responses require revalidation; the internal expiry remains bounded from the start of `load`. Cache API errors fall back to `load`. Callers must apply this only to public routes and must include every response-varying request input in the request or namespace.
|
|
302
|
+
|
|
303
|
+
`wrapD1ReadResponseLease(handler, profile)` from `@dee-wan/cache/d1/response-lease` optionally reuses authenticated GET responses. Set `readResponseLease.maxSeconds` from 1 to 300 (default 5), and explicitly set `<PREFIX>_READ_ACCESS_SECONDS` from 1 to that maximum (default 0). Wrap it inside `wrapD1Handler`. JSON response bodies are validated at fill up to 8,000,000 raw bytes; hits stream the cached body without decoding and preserve client headers. Ordinary regional read values remain capped at 1,500,000 bytes. The profile supplies the database binding, deployment environment variable, admitted paths, credential and vary headers, and a freshness-cookie name. A `*` path segment matches one nonempty segment. Keys include the full request URL, configured credential and vary headers, profile, release, and configured D1 identities. Optional `cookieNames` selects case-sensitive cookie names for the key while retaining every occurrence and its order; it requires `cookie` in `credentialHeaders` and, in generation mode, the freshness cookie in the list. Include every cookie used for authentication or response variation. Without it, the full Cookie header is used. `bypassQueryParams` optionally lists exact query keys that bypass the lease. `ignoreRequestCacheControl` optionally allows lease hits when the browser sends `no-cache` or `no-store`; it defaults to false. Set `refreshAheadSeconds` to an integer from 0 to `maxSeconds` (default 0) to refresh valid leases in the background near expiry when `waitUntil` is available. Refresh-ahead is disabled when the window exceeds the active lease duration. Hits remain immediate and refreshes coalesce per key within an isolate. A successful fresh, authorized response creates a new bounded lease; failed refreshes leave the old expiry unchanged. A configured `freshnessHeader` forces a source read. An eligible successful response replaces its ordinary lease; unsafe responses do not replace it. Expiry stays bounded by the new source request and authorization/data deadlines. Configure every credential and response-varying header. Workers require `nodejs_compat`.
|
|
304
|
+
|
|
305
|
+
After successful authentication, call `limitD1ReadLeaseUntil` with the credential's absolute expiry in epoch milliseconds. Missing, undefined or expired deadlines prevent a fill. The source receives `Cache-Control: no-cache` on a lease miss. A successful mutation sets an HttpOnly freshness cookie for the configured maximum. In the default `bypass` mode, requests carrying it bypass the lease. Optional `freshnessCookieMode: 'generation'` requires `cookie` in `credentialHeaders`: each mutation creates a new cookie value, and its first read uses fresh data before its response can be reused. Legacy or invalid markers still bypass. A reused regional query value caps the response lease at that value’s original expiry; a fresh source load is not capped by the query-cache TTL. `freshnessCookieExcludedPaths` optionally excludes known non-mutating paths from this cookie using the same whole-segment path matching. On a valid lease hit, matching `If-None-Match` or `If-Modified-Since` returns a bodyless 304; `If-None-Match` takes precedence. Other preconditions and ranges reach the handler. Client response headers remain unchanged on reads apart from the bodyless 304. Writes always execute the handler. Authorization changes by other actors can take up to the lease duration to take effect on cached reads.
|
|
306
|
+
|
|
307
|
+
When authentication runs in a separate trusted service binding, wrap that service's handler with `wrapD1ReadLeaseOrigin(handler)` and use `forwardD1ReadLease(request, service)` inside the gateway lease handler. The origin wrapper supplies the request-local deadline scope. The forwarder transfers the verified deadline only over the binding and removes its internal header before returning the response. A gateway must trust the bound service to verify credentials and protect this internal proof.
|
|
308
|
+
|
|
309
|
+
Browser clients can import `createReadFreshness(policy)` from `@dee-wan/cache/client` using the same `readResponseLease` policy. Call `observe(scope, method, url, transport?)` after a successful response, then merge `headers(scope, method)` into requests. With generation mode, pass the request origin, base URL and `withCredentials: true` to use the cookie for confirmed same-origin writes; other transports keep the fresh-header fallback. Data mutations otherwise enable fresh GET headers for the configured maximum per scope; excluded paths use the same whole-segment rules as the server.
|
|
310
|
+
|
|
311
|
+
For applications without D1 coordination, import `wrapReadResponseLease` and `limitReadResponseLeaseUntil` from `@dee-wan/cache/response-lease`. Pass the handler and an options object containing stable nonempty `namespace`, `deployment`, and `database` identities, an active `seconds` value, and the same response-lease policy fields described above (excluding `databaseBinding` and `deploymentEnv`). After authentication, call `limitReadResponseLeaseUntil` with the actual credential expiry in epoch milliseconds; without a future deadline no response is stored. The identity must change whenever the deployment or underlying data source changes. This entry uses Cache API storage and does not require D1 runtime or a Durable Object. It requires Workers `nodejs_compat` for request-local deadline scope. The optional `wrapReadResponseLeaseOrigin` and `forwardReadLease` support the same trusted service-binding deadline transport as the D1 adapter.
|
|
312
|
+
|
|
313
|
+
The standalone response lease accepts an optional `warmup` policy. With a `waitUntil` context, it starts background GET reads after an eligible cache hit or a successful JSON source response with a future authorization deadline. A first cold miss still reaches the source. After any required scope bootstrap, explicit seeds with `scope: null` run first, then learned targets, before ordinary seeds. Configure `scopeHeader` in `varyHeaders`; warming is disabled if it is missing. For example, add these fields to the standalone lease options:
|
|
314
|
+
|
|
315
|
+
```ts
|
|
316
|
+
{
|
|
317
|
+
varyHeaders: ['x-scope-id'],
|
|
318
|
+
warmup: {
|
|
319
|
+
scopeHeader: 'x-scope-id',
|
|
320
|
+
seeds: ['/api/context', '/api/items'],
|
|
321
|
+
discoverScopeFrom: '/api/context',
|
|
322
|
+
discover: (url, body) => {
|
|
323
|
+
if (url.pathname === '/api/context' && body && typeof body === 'object' && 'scope' in body && typeof body.scope === 'string') {
|
|
324
|
+
return { scope: body.scope, targets: ['/api/items/recent'] };
|
|
325
|
+
}
|
|
326
|
+
return { targets: [] };
|
|
327
|
+
},
|
|
328
|
+
portable: (url) => url.pathname === '/api/items/recent',
|
|
329
|
+
},
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
`seeds` and discovered targets must be same-origin URLs admitted by the lease paths and query policy. A terminal `**` path segment matches descendants; `*` still matches one nonempty segment. Import `matchesReadResponsePath` from `@dee-wan/cache/response-lease` to apply the same path matching in caller policies. Discovery may supply a scope when the original request has no scope header. Set `discoverScopeFrom` to an eligible seed to run it alone when the foreground response has not supplied a scope; remaining targets wait for its authorized response and valid scope. The batch stops if scope discovery fails. When scope is already known, the scope seed runs as an ordinary deduplicated seed. It counts toward the target budget when run. This option requires `discover` and an entry in `seeds`. Only mark a URL `portable` when its path and query contain no secrets and are safe to share within that scope; the default is false. The regional catalog stores only portable URLs, approximate counts, and times, not credentials or response bodies. Cached responses remain keyed by the current actor credentials and vary headers. Regional Cache API updates are approximate and may race.
|
|
334
|
+
|
|
335
|
+
For per-credential, multi-scope warming, set `catalogScope: 'credential'`. The bounded catalog is keyed by the configured credential and vary headers except `scopeHeader`, plus the lease identity. It ranks scopes by their combined URL counts, then URLs by count. Use `{ url, scope }` in `seeds` or discovered targets to set the scope header for one read; `scope: null` removes it. String targets use the current scope. The optional third `discover` argument contains the triggering request `scope` (or the scope resolved by bootstrap) and ranked `popularScopes` from that credential catalog. Set `discoveryPriority: 'next'` to schedule discovered targets ahead of queued cold seeds, after explicit unscoped seeds and catalog targets; they still share the same target budget. Each response still uses its ordinary credential and scope lease key and authorization. The scope header must not also be a credential header. Only `portable` URLs enter the catalog. The catalog credential identity omits the freshness cookie, so learned popularity survives saves; response keys and batch grouping retain that cookie.
|
|
336
|
+
|
|
337
|
+
For D1, pass `{ warmup }` as the optional third argument to `wrapD1ReadResponseLease`; keep the profile serializable.
|
|
338
|
+
|
|
339
|
+
Defaults are 48 targets per batch, 4 concurrent requests across all warmup batches in one wrapper, a 25-second deadline including slot wait, and 32 catalog entries per scope with a 24-hour TTL. The default 30-second cooldown starts after the batch deadline. A completed batch retains its cooldown; an abandoned batch with unfinished work releases its own cooldown so the next eligible foreground read can retry. During cooldown, eligible foreground visits still update popularity but do not start another batch. Override these with `maxTargets`, `concurrency`, `maxMilliseconds`, `maxEntries`, `catalogTtlMs`, and `cooldownMs`. Queued warm source calls yield to active foreground GET handlers. Warming uses the caller's credentials and ordinary lease authorization. Warm reads do not trigger further batches or popularity updates. An optional pure `isCurrent()` callback returns false when the wrapper configuration is retired; queued and running work checks it and stops, and later batches do not start. A successful write outside `freshnessCookieExcludedPaths` cancels queued work and prevents older foreground reads from filling leases or starting a batch within the current isolate; freshness generations keep subsequent response leases separate. Excluded presence, draft and preview operations keep warm batches running. Cache or warmup failures do not change the foreground response.
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
`createMutationInvalidation(policy)` from `@dee-wan/cache/client` signals successful writes between listeners and browser tabs. Call `publish(scope, method, url)` after success and `subscribe(scope, listener)` to receive `{ scope, method, pathname }`; call the returned function to unsubscribe and `dispose()` when finished. The URL's query and credentials are not sent. Use a credential-free scope. Read methods and `freshnessCookieExcludedPaths` are ignored. The channel is opened only when the factory runs in a browser.
|
|
343
|
+
|
|
344
|
+
`createRevalidationWindow(intervalMs, now?)` coalesces automatic checks per scope. `remaining(scope)` establishes a deadline without moving it on later calls; `take(scope)` permits an uninitialized scope immediately and an initialized scope at or after its deadline. Mutation notifications remain immediate and do not use this window.
|
|
345
|
+
|
|
346
|
+
`createSingleFlight<Key, Value>()` shares concurrent `run(key, load)` calls. Completed and failed calls are removed; `forget(key)` allows a new call while an older one is still running.
|
|
347
|
+
|
|
348
|
+
`createIntentWarmup(cooldownMs?, now?)` warms reads on navigation intent, without storing response values. Call `setScope(siteAndActor)` before use and `warm(key, async isCurrent => ...)` on mouse hover or keyboard focus. One task runs; only the latest waiting intent remains. Check `isCurrent()` between sequential reads. Successful tasks cool for 60 seconds by default. Scope changes and `invalidate()` clear waiting tasks and cooldowns; old completions cannot warm a new scope. Use silent, bounded, read-only requests with the same URL, credentials and headers as normal reads. Do not adopt results into active editor stores or start a startup fanout.
|
|
349
|
+
|
|
350
|
+
`cacheReadValue` from `@dee-wan/cache/scoped-read` provides a Cache API value cache without D1 or a Durable Object. Pass a nonempty `namespace`, a bounded structured `key`, and a `validate` type guard with the source loader. Include every authorization and result-varying scope in the key; do not put credentials in cached values. `ttlMs` defaults to 5000 and cannot exceed 5000. `maxBytes` defaults to 1,500,000 and can be lowered. Optional `cache` and `now` allow a Cache API implementation and clock to be supplied. Cache failures, invalid values, and expired entries fall back to the loader; loader errors propagate. Expiry starts before the loader runs.
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { createRegionalCache } from './chunk-E6OZZ5UX.js';
|
|
2
|
+
|
|
3
|
+
// src/response-warmup.ts
|
|
4
|
+
var catalog = createRegionalCache(864e5, 128e3);
|
|
5
|
+
var validScope = (value) => !!value && value.length <= 256 && ![...value].some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127);
|
|
6
|
+
function createResponseWarmup(run, canRun = () => true) {
|
|
7
|
+
let generation = 0;
|
|
8
|
+
const internal = /* @__PURE__ */ new WeakMap();
|
|
9
|
+
const active = /* @__PURE__ */ new Map();
|
|
10
|
+
const verified = /* @__PURE__ */ new WeakSet();
|
|
11
|
+
let running = 0, waiting = 0;
|
|
12
|
+
const currentValue = (check) => {
|
|
13
|
+
try {
|
|
14
|
+
return check === void 0 || check() === true;
|
|
15
|
+
} catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
const acquire = async (limit, current, end, check) => {
|
|
20
|
+
if (waiting >= 256) return null;
|
|
21
|
+
waiting++;
|
|
22
|
+
try {
|
|
23
|
+
while (current === generation && currentValue(check) && Date.now() < end) {
|
|
24
|
+
if (running < limit && canRun()) {
|
|
25
|
+
running++;
|
|
26
|
+
return () => {
|
|
27
|
+
running--;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(25, Math.max(0, end - Date.now()))));
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
} finally {
|
|
34
|
+
waiting--;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const invalidate = () => {
|
|
38
|
+
generation++;
|
|
39
|
+
active.clear();
|
|
40
|
+
};
|
|
41
|
+
const allowed = (request) => {
|
|
42
|
+
const meta = internal.get(request);
|
|
43
|
+
return !meta || meta.generation === generation && currentValue(meta.isCurrent) && !request.signal.aborted;
|
|
44
|
+
};
|
|
45
|
+
const isInternal = (request) => internal.has(request);
|
|
46
|
+
const verify = (request) => {
|
|
47
|
+
if (allowed(request)) verified.add(request);
|
|
48
|
+
};
|
|
49
|
+
async function start(options, request, response, identity, group, eligible, env, context, freshnessHeader, credentialGroup) {
|
|
50
|
+
try {
|
|
51
|
+
if (!options || isInternal(request)) return;
|
|
52
|
+
const { scopeHeader, seeds, discover, portable, discoverScopeFrom, isCurrent, catalogScope, discoveryPriority } = options;
|
|
53
|
+
if (isCurrent !== void 0 && typeof isCurrent !== "function") return;
|
|
54
|
+
if (!currentValue(isCurrent)) return;
|
|
55
|
+
const maxTargets = options.maxTargets ?? 48, maxEntries = options.maxEntries ?? 32, concurrency = options.concurrency ?? 4;
|
|
56
|
+
const cooldown = options.cooldownMs ?? 3e4, duration = options.maxMilliseconds ?? 25e3, ttl = options.catalogTtlMs ?? 864e5;
|
|
57
|
+
if (typeof scopeHeader !== "string" || !/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(scopeHeader) || !Array.isArray(seeds) || seeds.length > 256 || !seeds.every((v) => typeof v === "string" || !!v && typeof v === "object" && typeof v.url === "string" && (v.scope === null || validScope(v.scope))) || catalogScope !== void 0 && catalogScope !== "credential" || discoveryPriority !== void 0 && discoveryPriority !== "next" || catalogScope === "credential" && !credentialGroup || discoverScopeFrom !== void 0 && (typeof discoverScopeFrom !== "string" || typeof discover !== "function") || discover !== void 0 && typeof discover !== "function" || portable !== void 0 && typeof portable !== "function" || ![maxTargets, maxEntries, concurrency, cooldown, duration, ttl].every(Number.isSafeInteger) || maxTargets < 1 || maxTargets > 256 || maxEntries < 1 || maxEntries > 128 || concurrency < 1 || concurrency > 16 || cooldown < 0 || cooldown > 864e5 || duration < 1 || duration > 3e4 || ttl < 1 || ttl > 864e5) return;
|
|
58
|
+
const current = generation, end = Date.now() + duration;
|
|
59
|
+
const stillCurrent = () => current === generation && currentValue(isCurrent);
|
|
60
|
+
const prior = active.get(group);
|
|
61
|
+
const cooling = prior !== void 0 && prior.until > Date.now();
|
|
62
|
+
const marker = { until: end + cooldown };
|
|
63
|
+
if (!cooling) active.set(group, marker);
|
|
64
|
+
if (active.size > 1024) for (const [key, value] of active) {
|
|
65
|
+
if (value.until <= Date.now()) active.delete(key);
|
|
66
|
+
}
|
|
67
|
+
if (active.size > 1024) active.delete(active.keys().next().value ?? "");
|
|
68
|
+
const base = new URL(request.url);
|
|
69
|
+
let scope = request.headers.get(scopeHeader);
|
|
70
|
+
if (!validScope(scope)) scope = null;
|
|
71
|
+
const rootScope = scope;
|
|
72
|
+
const jobKey = (url, value) => JSON.stringify([url, value]);
|
|
73
|
+
const queue = [], seen = /* @__PURE__ */ new Set([jobKey(base.href, scope)]);
|
|
74
|
+
let abandoned = false;
|
|
75
|
+
let completedBootstrap;
|
|
76
|
+
const urlFor = (target) => {
|
|
77
|
+
try {
|
|
78
|
+
const url = new URL(target, base);
|
|
79
|
+
if (url.origin !== base.origin || url.username || url.password || url.hash || url.href.length > 2048 || !eligible(url)) return null;
|
|
80
|
+
return url;
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
const bootstrap = discoverScopeFrom === void 0 ? null : urlFor(discoverScopeFrom);
|
|
86
|
+
if (discoverScopeFrom !== void 0 && (!bootstrap || !seeds.some((seed) => typeof seed === "string" && urlFor(seed)?.href === bootstrap.href))) return;
|
|
87
|
+
const enqueue = (target, priority = 2) => {
|
|
88
|
+
if (typeof target !== "string" && (!target || typeof target !== "object" || typeof target.url !== "string" || !(target.scope === null || validScope(target.scope)))) return;
|
|
89
|
+
const explicit = typeof target !== "string";
|
|
90
|
+
const url = urlFor(explicit ? target.url : target);
|
|
91
|
+
const targetScope = explicit ? target.scope : scope;
|
|
92
|
+
const key = url && jobKey(url.href, targetScope);
|
|
93
|
+
if (!url || !key || key === completedBootstrap) return;
|
|
94
|
+
if (seen.has(key)) {
|
|
95
|
+
const existing = queue.findIndex((job2) => job2.url === url.href && job2.scope === targetScope);
|
|
96
|
+
const queued = existing >= 0 ? queue[existing] : void 0;
|
|
97
|
+
if (queued && queued.priority > priority) {
|
|
98
|
+
queue.splice(existing, 1);
|
|
99
|
+
queued.priority = priority;
|
|
100
|
+
const index2 = queue.findIndex((item) => item.priority > priority);
|
|
101
|
+
if (index2 === -1) queue.push(queued);
|
|
102
|
+
else queue.splice(index2, 0, queued);
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (seen.size > maxTargets && priority < 2) {
|
|
107
|
+
let index2 = queue.length - 1;
|
|
108
|
+
while (index2 >= 0 && queue[index2]?.priority !== 2) index2--;
|
|
109
|
+
if (index2 >= 0) {
|
|
110
|
+
const removed = queue.splice(index2, 1)[0];
|
|
111
|
+
if (removed) seen.delete(jobKey(removed.url, removed.scope));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (seen.size > maxTargets) return;
|
|
115
|
+
seen.add(key);
|
|
116
|
+
const job = { url: url.href, scope: targetScope, override: explicit || !request.headers.has(scopeHeader) && targetScope !== null, priority };
|
|
117
|
+
const index = queue.findIndex((item) => item.priority > priority);
|
|
118
|
+
if (index === -1) queue.push(job);
|
|
119
|
+
else queue.splice(index, 0, job);
|
|
120
|
+
};
|
|
121
|
+
const keyFor = async (value) => catalog.regionalKey(ttl, catalogScope === "credential" ? ["response-warmup-credential-v1", identity, credentialGroup] : ["response-warmup-v1", identity, value]);
|
|
122
|
+
let loadedScope;
|
|
123
|
+
const popularScopes = [];
|
|
124
|
+
const load = async () => {
|
|
125
|
+
if (catalogScope !== "credential" && !scope) return;
|
|
126
|
+
const lookup = catalogScope === "credential" ? null : scope;
|
|
127
|
+
if (loadedScope === lookup) return;
|
|
128
|
+
loadedScope = lookup;
|
|
129
|
+
const key = await keyFor(lookup);
|
|
130
|
+
const result = await catalog.regionalRead(key, ttl);
|
|
131
|
+
if (!Array.isArray(result?.value)) return;
|
|
132
|
+
for (const entry of result.value) {
|
|
133
|
+
if (!entry || typeof entry !== "object" || typeof entry.url !== "string" || typeof entry.time !== "number" || Date.now() - entry.time >= ttl) continue;
|
|
134
|
+
if (catalogScope === "credential") {
|
|
135
|
+
if (entry.scope === null || validScope(entry.scope)) {
|
|
136
|
+
if (validScope(entry.scope) && !popularScopes.includes(entry.scope) && urlFor(entry.url)) popularScopes.push(entry.scope);
|
|
137
|
+
enqueue({ url: entry.url, scope: entry.scope }, 0);
|
|
138
|
+
}
|
|
139
|
+
} else enqueue(entry.url, 0);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
const record = async (url) => {
|
|
143
|
+
const effectiveScope = rootScope ?? scope;
|
|
144
|
+
if (catalogScope !== "credential" && !effectiveScope || !portable?.(url)) return;
|
|
145
|
+
const key = await keyFor(effectiveScope), result = await catalog.regionalRead(key, ttl);
|
|
146
|
+
const entries = Array.isArray(result?.value) ? result.value.filter((v) => !!v && typeof v.url === "string" && typeof v.count === "number" && Number.isFinite(v.count) && typeof v.time === "number" && Date.now() - v.time < ttl && !!urlFor(v.url) && (catalogScope !== "credential" || v.scope === null || validScope(v.scope))).slice(0, maxEntries) : [];
|
|
147
|
+
const existing = entries.find((item) => item.url === url.href && (catalogScope !== "credential" || item.scope === effectiveScope));
|
|
148
|
+
if (existing) {
|
|
149
|
+
existing.count = Math.min(existing.count + 1, 1e6);
|
|
150
|
+
existing.time = Date.now();
|
|
151
|
+
} else entries.push({ url: url.href, count: 1, time: Date.now(), ...catalogScope === "credential" ? { scope: effectiveScope } : {} });
|
|
152
|
+
if (catalogScope === "credential") {
|
|
153
|
+
const totals = /* @__PURE__ */ new Map();
|
|
154
|
+
for (const item of entries) totals.set(item.scope ?? null, (totals.get(item.scope ?? null) ?? 0) + item.count);
|
|
155
|
+
entries.sort((a, b) => (totals.get(b.scope ?? null) ?? 0) - (totals.get(a.scope ?? null) ?? 0) || b.count - a.count || b.time - a.time);
|
|
156
|
+
} else entries.sort((a, b) => b.count - a.count || b.time - a.time);
|
|
157
|
+
if (stillCurrent()) await catalog.regionalWrite(key, ttl, Date.now(), entries.slice(0, maxEntries));
|
|
158
|
+
};
|
|
159
|
+
const inspect = async (url, value, deferred, preload = false) => {
|
|
160
|
+
let body;
|
|
161
|
+
try {
|
|
162
|
+
if (!discover) return;
|
|
163
|
+
let timer;
|
|
164
|
+
try {
|
|
165
|
+
body = await Promise.race([value.json(), new Promise((_, reject) => {
|
|
166
|
+
timer = setTimeout(() => reject(Error("warmup deadline")), Math.max(0, end - Date.now()));
|
|
167
|
+
})]);
|
|
168
|
+
} finally {
|
|
169
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
170
|
+
}
|
|
171
|
+
if (Date.now() >= end || !stillCurrent()) return;
|
|
172
|
+
} catch {
|
|
173
|
+
return;
|
|
174
|
+
} finally {
|
|
175
|
+
void value.body?.cancel().catch(() => void 0);
|
|
176
|
+
}
|
|
177
|
+
if (preload) await load();
|
|
178
|
+
if (!discover || Date.now() >= end || !stillCurrent()) return;
|
|
179
|
+
let found;
|
|
180
|
+
try {
|
|
181
|
+
found = discover(url, body, { scope: rootScope ?? scope, popularScopes: [...popularScopes] });
|
|
182
|
+
} catch {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (!found || !Array.isArray(found.targets)) return;
|
|
186
|
+
if (!request.headers.has(scopeHeader) && !scope && validScope(found.scope)) {
|
|
187
|
+
scope = found.scope;
|
|
188
|
+
if (bootstrap?.href === url.href) completedBootstrap = jobKey(url.href, scope);
|
|
189
|
+
if (!deferred) await load();
|
|
190
|
+
}
|
|
191
|
+
for (const target of found.targets.slice(0, maxTargets)) {
|
|
192
|
+
if (typeof target !== "string" && (!target || typeof target !== "object" || typeof target.url !== "string" || !(target.scope === null || validScope(target.scope)))) continue;
|
|
193
|
+
if (deferred) deferred.push(target);
|
|
194
|
+
else enqueue(target, discoveryPriority === "next" ? 1 : 2);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
const runOne = async (target) => {
|
|
198
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
199
|
+
if (!stillCurrent() || Date.now() >= end) {
|
|
200
|
+
abandoned = true;
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
let release = null;
|
|
204
|
+
while (stillCurrent() && Date.now() < end) {
|
|
205
|
+
release = await acquire(concurrency, current, end, isCurrent);
|
|
206
|
+
if (!release) {
|
|
207
|
+
abandoned = true;
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (canRun()) break;
|
|
211
|
+
release();
|
|
212
|
+
release = null;
|
|
213
|
+
}
|
|
214
|
+
if (!release) {
|
|
215
|
+
abandoned = true;
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (!stillCurrent() || Date.now() >= end) {
|
|
219
|
+
abandoned = true;
|
|
220
|
+
release();
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const releaseSlot = release;
|
|
224
|
+
let controller;
|
|
225
|
+
let timer;
|
|
226
|
+
let started = false;
|
|
227
|
+
try {
|
|
228
|
+
const headers = new Headers(request.headers);
|
|
229
|
+
for (const name of ["cache-control", "pragma", "range", "if-match", "if-unmodified-since", "if-range", "if-none-match", "if-modified-since"]) headers.delete(name);
|
|
230
|
+
if (freshnessHeader) headers.delete(freshnessHeader);
|
|
231
|
+
if (target.override) {
|
|
232
|
+
if (target.scope === null) headers.delete(scopeHeader);
|
|
233
|
+
else headers.set(scopeHeader, target.scope);
|
|
234
|
+
}
|
|
235
|
+
controller = new AbortController();
|
|
236
|
+
const abort = controller;
|
|
237
|
+
const watch = () => {
|
|
238
|
+
if (!stillCurrent() || Date.now() >= end) {
|
|
239
|
+
abort.abort();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
timer = setTimeout(watch, Math.min(25, Math.max(0, end - Date.now())));
|
|
243
|
+
};
|
|
244
|
+
timer = setTimeout(watch, Math.min(25, Math.max(0, end - Date.now())));
|
|
245
|
+
const warm = new Request(target.url, { method: "GET", headers, redirect: "manual", signal: abort.signal });
|
|
246
|
+
internal.set(warm, { generation: current, isCurrent });
|
|
247
|
+
const task = Promise.resolve().then(() => run(warm, env, context));
|
|
248
|
+
started = true;
|
|
249
|
+
void task.then((response2) => {
|
|
250
|
+
if (abort.signal.aborted) void response2.body?.cancel().catch(() => void 0);
|
|
251
|
+
releaseSlot();
|
|
252
|
+
}, () => {
|
|
253
|
+
releaseSlot();
|
|
254
|
+
});
|
|
255
|
+
const result = await Promise.race([task, new Promise((_, reject) => abort.signal.addEventListener("abort", () => reject(Error("warmup deadline")), { once: true }))]);
|
|
256
|
+
if (verified.has(warm) && result.status === 200 && stillCurrent() && Date.now() < end) await inspect(new URL(target.url), result);
|
|
257
|
+
void result.body?.cancel().catch(() => void 0);
|
|
258
|
+
} catch {
|
|
259
|
+
} finally {
|
|
260
|
+
if (controller?.signal.aborted) abandoned = true;
|
|
261
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
262
|
+
if (!started) releaseSlot();
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
try {
|
|
266
|
+
if (cooling) {
|
|
267
|
+
void response.body?.cancel().catch(() => void 0);
|
|
268
|
+
await record(base);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const unscopedSeeds = () => {
|
|
272
|
+
for (const seed of seeds) if (typeof seed !== "string" && seed.scope === null) enqueue(seed, -1);
|
|
273
|
+
};
|
|
274
|
+
if (!bootstrap || scope) unscopedSeeds();
|
|
275
|
+
const discovered = [];
|
|
276
|
+
await inspect(base, response, discovered, catalogScope === "credential" && !bootstrap);
|
|
277
|
+
if (bootstrap && !scope) {
|
|
278
|
+
enqueue(bootstrap.href);
|
|
279
|
+
const index = queue.findIndex((job2) => job2.url === bootstrap.href && job2.scope === scope);
|
|
280
|
+
const job = index === -1 ? void 0 : queue.splice(index, 1)[0];
|
|
281
|
+
if (job && stillCurrent() && Date.now() < end) await runOne(job);
|
|
282
|
+
if (!scope || !stillCurrent() || Date.now() >= end) return;
|
|
283
|
+
}
|
|
284
|
+
unscopedSeeds();
|
|
285
|
+
await load();
|
|
286
|
+
if (discoveryPriority === "next") for (const target of discovered) enqueue(target, 1);
|
|
287
|
+
for (const seed of seeds) if (typeof seed === "string" || seed.scope !== null) enqueue(seed);
|
|
288
|
+
if (discoveryPriority !== "next") for (const target of discovered) enqueue(target);
|
|
289
|
+
await record(base);
|
|
290
|
+
const worker = async () => {
|
|
291
|
+
while (queue.length && stillCurrent() && Date.now() < end) {
|
|
292
|
+
const target = queue.shift();
|
|
293
|
+
if (!target) return;
|
|
294
|
+
await runOne(target);
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
await Promise.all(Array.from({ length: concurrency }, worker));
|
|
298
|
+
} catch {
|
|
299
|
+
} finally {
|
|
300
|
+
if (!cooling && (abandoned || queue.length > 0) && active.get(group) === marker) active.delete(group);
|
|
301
|
+
}
|
|
302
|
+
} finally {
|
|
303
|
+
void response.body?.cancel().catch(() => void 0);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return { start, invalidate, allowed, isInternal, verify, snapshot: () => generation, current: (value) => value === generation };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export { createResponseWarmup };
|
|
310
|
+
//# sourceMappingURL=chunk-A4WV6RY4.js.map
|
|
311
|
+
//# sourceMappingURL=chunk-A4WV6RY4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/response-warmup.ts"],"names":["job","index","response"],"mappings":"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","file":"chunk-A4WV6RY4.js","sourcesContent":["import { createRegionalCache } from './d1/regional';\n\nexport type ReadResponseWarmupTarget = string | { url: string; scope: string | null };\n\nexport interface ReadResponseWarmupContext { scope: string | null; popularScopes: readonly string[] }\n\nexport interface ReadResponseWarmupOptions {\n scopeHeader: string;\n seeds: readonly ReadResponseWarmupTarget[];\n catalogScope?: 'credential';\n discoveryPriority?: 'next';\n discoverScopeFrom?: string;\n isCurrent?: () => boolean;\n discover?: (url: URL, body: unknown, context: ReadResponseWarmupContext) => { scope?: string; targets: readonly ReadResponseWarmupTarget[] };\n portable?: (url: URL) => boolean;\n maxTargets?: number;\n maxEntries?: number;\n concurrency?: number;\n cooldownMs?: number;\n maxMilliseconds?: number;\n catalogTtlMs?: number;\n}\n\ntype Entry = { url: string; count: number; time: number; scope?: string | null };\ntype WarmJob = { url: string; scope: string | null; override: boolean; priority: number };\nconst catalog = createRegionalCache(86_400_000, 128_000);\nconst validScope = (value: string | null | undefined): value is string => !!value && value.length <= 256 && ![...value].some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127);\n\nexport function createResponseWarmup<Env, Context>(run: (request: Request, env: Env, context: Context) => Promise<Response>, canRun: () => boolean = () => true) {\n let generation = 0;\n const internal = new WeakMap<Request, { generation: number; isCurrent?: () => boolean }>();\n const active = new Map<string, { until: number }>();\n const verified = new WeakSet<Request>();\n let running = 0, waiting = 0;\n const currentValue = (check?: () => boolean) => { try { return check === undefined || check() === true; } catch { return false; } };\n const acquire = async (limit: number, current: number, end: number, check?: () => boolean): Promise<(() => void) | null> => {\n if (waiting >= 256) return null;\n waiting++;\n try {\n while (current === generation && currentValue(check) && Date.now() < end) {\n if (running < limit && canRun()) { running++; return () => { running--; }; }\n await new Promise<void>((resolve) => setTimeout(resolve, Math.min(25, Math.max(0, end - Date.now()))));\n }\n return null;\n } finally { waiting--; }\n };\n const invalidate = () => { generation++; active.clear(); };\n const allowed = (request: Request) => {\n const meta = internal.get(request);\n return !meta || (meta.generation === generation && currentValue(meta.isCurrent) && !request.signal.aborted);\n };\n const isInternal = (request: Request) => internal.has(request);\n const verify = (request: Request) => { if (allowed(request)) verified.add(request); };\n async function start(options: ReadResponseWarmupOptions | undefined, request: Request, response: Response, identity: unknown[], group: string, eligible: (url: URL) => boolean, env: Env, context: Context, freshnessHeader?: string, credentialGroup?: string) {\n try {\n if (!options || isInternal(request)) return;\n const { scopeHeader, seeds, discover, portable, discoverScopeFrom, isCurrent, catalogScope, discoveryPriority } = options;\n if (isCurrent !== undefined && typeof isCurrent !== 'function') return;\n if (!currentValue(isCurrent)) return;\n const maxTargets = options.maxTargets ?? 48, maxEntries = options.maxEntries ?? 32, concurrency = options.concurrency ?? 4;\n const cooldown = options.cooldownMs ?? 30_000, duration = options.maxMilliseconds ?? 25_000, ttl = options.catalogTtlMs ?? 86_400_000;\n if (typeof scopeHeader !== 'string' || !/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(scopeHeader) || !Array.isArray(seeds) || seeds.length > 256 || !seeds.every((v) => typeof v === 'string' || (!!v && typeof v === 'object' && typeof v.url === 'string' && (v.scope === null || validScope(v.scope)))) || (catalogScope !== undefined && catalogScope !== 'credential') || (discoveryPriority !== undefined && discoveryPriority !== 'next') || (catalogScope === 'credential' && !credentialGroup) || (discoverScopeFrom !== undefined && (typeof discoverScopeFrom !== 'string' || typeof discover !== 'function')) || (discover !== undefined && typeof discover !== 'function') || (portable !== undefined && typeof portable !== 'function') || ![maxTargets, maxEntries, concurrency, cooldown, duration, ttl].every(Number.isSafeInteger) || maxTargets < 1 || maxTargets > 256 || maxEntries < 1 || maxEntries > 128 || concurrency < 1 || concurrency > 16 || cooldown < 0 || cooldown > 86_400_000 || duration < 1 || duration > 30_000 || ttl < 1 || ttl > 86_400_000) return;\n const current = generation, end = Date.now() + duration;\n const stillCurrent = () => current === generation && currentValue(isCurrent);\n const prior = active.get(group);\n const cooling = prior !== undefined && prior.until > Date.now();\n const marker = { until: end + cooldown };\n if (!cooling) active.set(group, marker);\n if (active.size > 1024) for (const [key, value] of active) { if (value.until <= Date.now()) active.delete(key); }\n if (active.size > 1024) active.delete(active.keys().next().value ?? '');\n const base = new URL(request.url);\n let scope = request.headers.get(scopeHeader);\n if (!validScope(scope)) scope = null;\n const rootScope = scope;\n const jobKey = (url: string, value: string | null) => JSON.stringify([url, value]);\n const queue: WarmJob[] = [], seen = new Set<string>([jobKey(base.href, scope)]);\n let abandoned = false;\n let completedBootstrap: string | undefined;\n const urlFor = (target: string): URL | null => {\n try {\n const url = new URL(target, base);\n if (url.origin !== base.origin || url.username || url.password || url.hash || url.href.length > 2048 || !eligible(url)) return null;\n return url;\n } catch { return null; }\n };\n const bootstrap = discoverScopeFrom === undefined ? null : urlFor(discoverScopeFrom);\n if (discoverScopeFrom !== undefined && (!bootstrap || !seeds.some((seed) => typeof seed === 'string' && urlFor(seed)?.href === bootstrap.href))) return;\n const enqueue = (target: ReadResponseWarmupTarget, priority = 2) => {\n if (typeof target !== 'string' && (!target || typeof target !== 'object' || typeof target.url !== 'string' || !(target.scope === null || validScope(target.scope)))) return;\n const explicit = typeof target !== 'string';\n const url = urlFor(explicit ? target.url : target);\n const targetScope = explicit ? target.scope : scope;\n const key = url && jobKey(url.href, targetScope);\n if (!url || !key || key === completedBootstrap) return;\n if (seen.has(key)) {\n const existing = queue.findIndex((job) => job.url === url.href && job.scope === targetScope);\n const queued = existing >= 0 ? queue[existing] : undefined;\n if (queued && queued.priority > priority) {\n queue.splice(existing, 1); queued.priority = priority;\n const index = queue.findIndex((item) => item.priority > priority);\n if (index === -1) queue.push(queued); else queue.splice(index, 0, queued);\n }\n return;\n }\n if (seen.size > maxTargets && priority < 2) {\n let index = queue.length - 1;\n while (index >= 0 && queue[index]?.priority !== 2) index--;\n if (index >= 0) { const removed = queue.splice(index, 1)[0]; if (removed) seen.delete(jobKey(removed.url, removed.scope)); }\n }\n if (seen.size > maxTargets) return;\n seen.add(key);\n const job = { url:url.href, scope:targetScope, override:explicit || (!request.headers.has(scopeHeader) && targetScope !== null), priority };\n const index = queue.findIndex((item) => item.priority > priority);\n if (index === -1) queue.push(job); else queue.splice(index, 0, job);\n };\n const keyFor = async (value: string | null) => catalog.regionalKey(ttl, catalogScope === 'credential' ? ['response-warmup-credential-v1', identity, credentialGroup] : ['response-warmup-v1', identity, value]);\n let loadedScope: string | null | undefined;\n const popularScopes: string[] = [];\n const load = async () => {\n if (catalogScope !== 'credential' && !scope) return;\n const lookup = catalogScope === 'credential' ? null : scope;\n if (loadedScope === lookup) return;\n loadedScope = lookup;\n const key = await keyFor(lookup);\n const result = await catalog.regionalRead(key, ttl);\n if (!Array.isArray(result?.value)) return;\n for (const entry of result.value) {\n if (!entry || typeof entry !== 'object' || typeof entry.url !== 'string' || typeof entry.time !== 'number' || Date.now() - entry.time >= ttl) continue;\n if (catalogScope === 'credential') {\n if (entry.scope === null || validScope(entry.scope)) {\n if (validScope(entry.scope) && !popularScopes.includes(entry.scope) && urlFor(entry.url)) popularScopes.push(entry.scope);\n enqueue({ url:entry.url, scope:entry.scope }, 0);\n }\n } else enqueue(entry.url, 0);\n }\n };\n const record = async (url: URL) => {\n const effectiveScope = rootScope ?? scope;\n if ((catalogScope !== 'credential' && !effectiveScope) || !portable?.(url)) return;\n const key = await keyFor(effectiveScope), result = await catalog.regionalRead(key, ttl);\n const entries: Entry[] = Array.isArray(result?.value) ? result.value.filter((v): v is Entry => !!v && typeof v.url === 'string' && typeof v.count === 'number' && Number.isFinite(v.count) && typeof v.time === 'number' && Date.now() - v.time < ttl && !!urlFor(v.url) && (catalogScope !== 'credential' || v.scope === null || validScope(v.scope))).slice(0, maxEntries) : [];\n const existing = entries.find((item) => item.url === url.href && (catalogScope !== 'credential' || item.scope === effectiveScope));\n if (existing) { existing.count = Math.min(existing.count + 1, 1_000_000); existing.time = Date.now(); }\n else entries.push({ url:url.href, count:1, time:Date.now(), ...(catalogScope === 'credential' ? { scope:effectiveScope } : {}) });\n if (catalogScope === 'credential') {\n const totals = new Map<string | null, number>();\n for (const item of entries) totals.set(item.scope ?? null, (totals.get(item.scope ?? null) ?? 0) + item.count);\n entries.sort((a, b) => (totals.get(b.scope ?? null) ?? 0) - (totals.get(a.scope ?? null) ?? 0) || b.count - a.count || b.time - a.time);\n } else entries.sort((a, b) => b.count - a.count || b.time - a.time);\n if (stillCurrent()) await catalog.regionalWrite(key, ttl, Date.now(), entries.slice(0, maxEntries));\n };\n const inspect = async (url: URL, value: Response, deferred?: ReadResponseWarmupTarget[], preload = false) => {\n let body: unknown;\n try {\n if (!discover) return;\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n body = await Promise.race([value.json(), new Promise<never>((_, reject) => { timer = setTimeout(() => reject(Error('warmup deadline')), Math.max(0, end - Date.now())); })]);\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n }\n if (Date.now() >= end || !stillCurrent()) return;\n } catch { return; }\n finally { void value.body?.cancel().catch(() => undefined); }\n if (preload) await load();\n if (!discover || Date.now() >= end || !stillCurrent()) return;\n let found: { scope?: string; targets: readonly ReadResponseWarmupTarget[] };\n try { found = discover(url, body, { scope:rootScope ?? scope, popularScopes:[...popularScopes] }); }\n catch { return; }\n if (!found || !Array.isArray(found.targets)) return;\n if (!request.headers.has(scopeHeader) && !scope && validScope(found.scope)) {\n scope = found.scope;\n if (bootstrap?.href === url.href) completedBootstrap = jobKey(url.href, scope);\n if (!deferred) await load();\n }\n for (const target of found.targets.slice(0, maxTargets)) {\n if (typeof target !== 'string' && (!target || typeof target !== 'object' || typeof target.url !== 'string' || !(target.scope === null || validScope(target.scope)))) continue;\n if (deferred) deferred.push(target);\n else enqueue(target, discoveryPriority === 'next' ? 1 : 2);\n }\n };\n const runOne = async (target: WarmJob) => {\n await new Promise<void>((resolve) => setTimeout(resolve, 0));\n if (!stillCurrent() || Date.now() >= end) { abandoned = true; return; }\n let release: (() => void) | null = null;\n while (stillCurrent() && Date.now() < end) {\n release = await acquire(concurrency, current, end, isCurrent);\n if (!release) { abandoned = true; return; }\n if (canRun()) break;\n release(); release = null;\n }\n if (!release) { abandoned = true; return; }\n if (!stillCurrent() || Date.now() >= end) { abandoned = true; release(); return; }\n const releaseSlot = release;\n let controller: AbortController | undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let started = false;\n try {\n const headers = new Headers(request.headers);\n for (const name of ['cache-control', 'pragma', 'range', 'if-match', 'if-unmodified-since', 'if-range', 'if-none-match', 'if-modified-since']) headers.delete(name);\n if (freshnessHeader) headers.delete(freshnessHeader);\n if (target.override) { if (target.scope === null) headers.delete(scopeHeader); else headers.set(scopeHeader, target.scope); }\n controller = new AbortController();\n const abort = controller;\n const watch = () => {\n if (!stillCurrent() || Date.now() >= end) { abort.abort(); return; }\n timer = setTimeout(watch, Math.min(25, Math.max(0, end - Date.now())));\n };\n timer = setTimeout(watch, Math.min(25, Math.max(0, end - Date.now())));\n const warm = new Request(target.url, { method:'GET', headers, redirect:'manual', signal:abort.signal });\n internal.set(warm, { generation:current, isCurrent });\n const task = Promise.resolve().then(() => run(warm, env, context));\n started = true;\n void task.then((response) => { if (abort.signal.aborted) void response.body?.cancel().catch(() => undefined); releaseSlot(); }, () => { releaseSlot(); });\n const result = await Promise.race([task, new Promise<never>((_, reject) => abort.signal.addEventListener('abort', () => reject(Error('warmup deadline')), { once:true }))]);\n if (verified.has(warm) && result.status === 200 && stillCurrent() && Date.now() < end) await inspect(new URL(target.url), result);\n void result.body?.cancel().catch(() => undefined);\n } catch { /* Background warming is best effort. */ }\n finally {\n if (controller?.signal.aborted) abandoned = true;\n if (timer !== undefined) clearTimeout(timer);\n if (!started) releaseSlot();\n }\n };\n try {\n if (cooling) { void response.body?.cancel().catch(() => undefined); await record(base); return; }\n const unscopedSeeds = () => {\n for (const seed of seeds) if (typeof seed !== 'string' && seed.scope === null) enqueue(seed, -1);\n };\n if (!bootstrap || scope) unscopedSeeds();\n const discovered: ReadResponseWarmupTarget[] = [];\n await inspect(base, response, discovered, catalogScope === 'credential' && !bootstrap);\n if (bootstrap && !scope) {\n enqueue(bootstrap.href);\n const index = queue.findIndex((job) => job.url === bootstrap.href && job.scope === scope);\n const job = index === -1 ? undefined : queue.splice(index, 1)[0];\n if (job && stillCurrent() && Date.now() < end) await runOne(job);\n if (!scope || !stillCurrent() || Date.now() >= end) return;\n }\n unscopedSeeds();\n await load();\n if (discoveryPriority === 'next') for (const target of discovered) enqueue(target, 1);\n for (const seed of seeds) if (typeof seed === 'string' || seed.scope !== null) enqueue(seed);\n if (discoveryPriority !== 'next') for (const target of discovered) enqueue(target);\n await record(base);\n const worker = async () => {\n while (queue.length && stillCurrent() && Date.now() < end) {\n const target = queue.shift(); if (!target) return;\n await runOne(target);\n }\n };\n await Promise.all(Array.from({ length:concurrency }, worker));\n } catch { /* Background warming is best effort. */ }\n finally {\n if (!cooling && (abandoned || queue.length > 0) && active.get(group) === marker) active.delete(group);\n }\n } finally { void response.body?.cancel().catch(() => undefined); }\n }\n return { start, invalidate, allowed, isInternal, verify, snapshot:() => generation, current:(value: number) => value === generation };\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// src/d1/profile.ts
|
|
2
|
+
var isD1BindingName = (key, profile) => profile.bindingNames.includes(key) || profile.bindingPrefixes.some((prefix) => key.startsWith(prefix) && /^[A-Za-z0-9_]{1,48}$/.test(key.slice(prefix.length)));
|
|
3
|
+
|
|
4
|
+
export { isD1BindingName };
|
|
5
|
+
//# sourceMappingURL=chunk-AENVFT2S.js.map
|
|
6
|
+
//# sourceMappingURL=chunk-AENVFT2S.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/d1/profile.ts"],"names":[],"mappings":";AA6BO,IAAM,eAAA,GAAkB,CAAC,GAAA,EAAa,OAAA,KAC3C,OAAA,CAAQ,aAAa,QAAA,CAAS,GAAG,CAAA,IAAK,OAAA,CAAQ,eAAA,CAAgB,IAAA,CAAK,CAAC,MAAA,KAAW,GAAA,CAAI,UAAA,CAAW,MAAM,CAAA,IAAK,sBAAA,CAAuB,IAAA,CAAK,GAAA,CAAI,KAAA,CAAM,MAAA,CAAO,MAAM,CAAC,CAAC","file":"chunk-AENVFT2S.js","sourcesContent":["export interface D1WritePolicy {\n unfencedWriteTables: readonly string[];\n}\n\nexport interface D1CacheProfile extends D1WritePolicy {\n readModels: readonly string[];\n envPrefix: string;\n bindingNames: readonly string[];\n bindingPrefixes: readonly string[];\n coordinatorNamePrefix: string;\n coordinatorClass: string;\n readResponseLease?: {\n databaseBinding: string;\n deploymentEnv: string;\n paths: readonly string[];\n credentialHeaders: readonly string[];\n cookieNames?: readonly string[];\n varyHeaders: readonly string[];\n bypassQueryParams?: readonly string[];\n ignoreRequestCacheControl?: boolean;\n freshnessHeader?: string;\n maxSeconds?: number;\n refreshAheadSeconds?: number;\n freshnessCookie: string;\n freshnessCookieMode?: string;\n freshnessCookieExcludedPaths?: readonly string[];\n };\n}\n\nexport const isD1BindingName = (key: string, profile: D1CacheProfile): boolean =>\n profile.bindingNames.includes(key) || profile.bindingPrefixes.some((prefix) => key.startsWith(prefix) && /^[A-Za-z0-9_]{1,48}$/.test(key.slice(prefix.length)));\n"]}
|