tina4-nodejs 3.13.108 → 3.13.109

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/CLAUDE.md CHANGED
@@ -1,10 +1,10 @@
1
- # CLAUDE.md - AI Developer Guide for tina4-nodejs (v3.13.108)
1
+ # CLAUDE.md - AI Developer Guide for tina4-nodejs (v3.13.109)
2
2
 
3
3
  > This file helps AI assistants (Claude, Copilot, Cursor, etc.) understand and work on this codebase effectively.
4
4
 
5
5
  ## What This Project Is
6
6
 
7
- Tina4 for Node.js/TypeScript v3.13.108 - The Intelligent Native Application 4ramework. A convention-over-configuration structural paradigm. The developer writes TypeScript; Tina4 is invisible infrastructure.
7
+ Tina4 for Node.js/TypeScript v3.13.109 - The Intelligent Native Application 4ramework. A convention-over-configuration structural paradigm. The developer writes TypeScript; Tina4 is invisible infrastructure.
8
8
 
9
9
  The philosophy: zero ceremony, batteries included, file system as source of truth.
10
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tina4-nodejs",
3
- "version": "3.13.108",
3
+ "version": "3.13.109",
4
4
  "type": "module",
5
5
  "description": "Tina4 for Node.js/TypeScript - native TypeScript conventions and shared Tina4 contracts",
6
6
  "keywords": [
@@ -17767,11 +17767,32 @@ function varyFields(raw) {
17767
17767
  const text = Array.isArray(raw) ? raw.join(",") : String(raw);
17768
17768
  return text.split(",").map((f) => f.trim().toLowerCase()).filter((f) => f !== "");
17769
17769
  }
17770
- function mayStore(req2, vary, cacheControl) {
17770
+ function cacheControlTokens(raw) {
17771
+ const text = Array.isArray(raw) ? raw.join(",") : String(raw ?? "");
17772
+ const tokens = /* @__PURE__ */ new Set();
17773
+ for (const token of text.split(",")) {
17774
+ const name = token.split("=")[0].trim().toLowerCase();
17775
+ if (name !== "") tokens.add(name);
17776
+ }
17777
+ return tokens;
17778
+ }
17779
+ function sharedCacheAllowed(cacheControl) {
17780
+ const directives = cacheControlTokens(cacheControl);
17781
+ return SHARED_CACHE_DIRECTIVES.some((directive) => directives.has(directive));
17782
+ }
17783
+ function hasSetCookie(raw) {
17784
+ if (raw === void 0 || raw === null) return false;
17785
+ if (Array.isArray(raw)) return raw.length > 0;
17786
+ return String(raw) !== "";
17787
+ }
17788
+ function mayStore(req2, vary, cacheControl, setCookie) {
17771
17789
  if (vary.includes("*")) return false;
17772
- if (requestHeader(req2, "authorization") === void 0) return true;
17773
- const cc = String(cacheControl ?? "").toLowerCase();
17774
- return SHARED_CACHE_DIRECTIVES.some((directive) => cc.includes(directive));
17790
+ const directives = cacheControlTokens(cacheControl);
17791
+ if (NO_STORE_DIRECTIVES.some((directive) => directives.has(directive))) return false;
17792
+ if (requestHeader(req2, "authorization") !== void 0) return sharedCacheAllowed(cacheControl);
17793
+ if (requestHeader(req2, "cookie") !== void 0) return sharedCacheAllowed(cacheControl);
17794
+ if (hasSetCookie(setCookie)) return sharedCacheAllowed(cacheControl);
17795
+ return true;
17775
17796
  }
17776
17797
  function varyMatches(entry, req2) {
17777
17798
  const vary = entry.vary ?? [];
@@ -17804,7 +17825,7 @@ function responseCache(config) {
17804
17825
  let captured = false;
17805
17826
  res.raw.end = function(chunk, ...args) {
17806
17827
  const vary = varyFields(res.raw.getHeader("Vary"));
17807
- if (!captured && allowedCodes.has(res.raw.statusCode) && mayStore(req2, vary, res.raw.getHeader("Cache-Control"))) {
17828
+ if (!captured && allowedCodes.has(res.raw.statusCode) && mayStore(req2, vary, res.raw.getHeader("Cache-Control"), res.raw.getHeader("Set-Cookie"))) {
17808
17829
  captured = true;
17809
17830
  const body = typeof chunk === "string" ? chunk : chunk?.toString() ?? "";
17810
17831
  const contentType = String(res.raw.getHeader("Content-Type") ?? "application/octet-stream");
@@ -17891,7 +17912,7 @@ function _resetBackend() {
17891
17912
  _explicitResponseBackends.clear();
17892
17913
  _defaultTtl = null;
17893
17914
  }
17894
- var MemoryBackend, RespClient, RedisBackend, ValkeyBackend, FileBackend, MemcachedClient, MemcachedBackend, MongoBackend, DatabaseBackend, _responseBackend, _responseBackendPromise, _explicitResponseBackends, SHARED_CACHE_DIRECTIVES, _defaultBackend, _defaultBackendPromise, _defaultTtl;
17915
+ var MemoryBackend, RespClient, RedisBackend, ValkeyBackend, FileBackend, MemcachedClient, MemcachedBackend, MongoBackend, DatabaseBackend, _responseBackend, _responseBackendPromise, _explicitResponseBackends, SHARED_CACHE_DIRECTIVES, NO_STORE_DIRECTIVES, _defaultBackend, _defaultBackendPromise, _defaultTtl;
17895
17916
  var init_cache = __esm({
17896
17917
  "../core/src/cache.ts"() {
17897
17918
  "use strict";
@@ -19057,6 +19078,7 @@ ${data}\r
19057
19078
  _responseBackendPromise = null;
19058
19079
  _explicitResponseBackends = /* @__PURE__ */ new Map();
19059
19080
  SHARED_CACHE_DIRECTIVES = ["public", "s-maxage", "must-revalidate"];
19081
+ NO_STORE_DIRECTIVES = ["no-store", "private", "no-cache"];
19060
19082
  _defaultBackend = null;
19061
19083
  _defaultBackendPromise = null;
19062
19084
  _defaultTtl = null;
@@ -17766,11 +17766,32 @@ function varyFields(raw) {
17766
17766
  const text = Array.isArray(raw) ? raw.join(",") : String(raw);
17767
17767
  return text.split(",").map((f) => f.trim().toLowerCase()).filter((f) => f !== "");
17768
17768
  }
17769
- function mayStore(req2, vary, cacheControl) {
17769
+ function cacheControlTokens(raw) {
17770
+ const text = Array.isArray(raw) ? raw.join(",") : String(raw ?? "");
17771
+ const tokens = /* @__PURE__ */ new Set();
17772
+ for (const token of text.split(",")) {
17773
+ const name = token.split("=")[0].trim().toLowerCase();
17774
+ if (name !== "") tokens.add(name);
17775
+ }
17776
+ return tokens;
17777
+ }
17778
+ function sharedCacheAllowed(cacheControl) {
17779
+ const directives = cacheControlTokens(cacheControl);
17780
+ return SHARED_CACHE_DIRECTIVES.some((directive) => directives.has(directive));
17781
+ }
17782
+ function hasSetCookie(raw) {
17783
+ if (raw === void 0 || raw === null) return false;
17784
+ if (Array.isArray(raw)) return raw.length > 0;
17785
+ return String(raw) !== "";
17786
+ }
17787
+ function mayStore(req2, vary, cacheControl, setCookie) {
17770
17788
  if (vary.includes("*")) return false;
17771
- if (requestHeader(req2, "authorization") === void 0) return true;
17772
- const cc = String(cacheControl ?? "").toLowerCase();
17773
- return SHARED_CACHE_DIRECTIVES.some((directive) => cc.includes(directive));
17789
+ const directives = cacheControlTokens(cacheControl);
17790
+ if (NO_STORE_DIRECTIVES.some((directive) => directives.has(directive))) return false;
17791
+ if (requestHeader(req2, "authorization") !== void 0) return sharedCacheAllowed(cacheControl);
17792
+ if (requestHeader(req2, "cookie") !== void 0) return sharedCacheAllowed(cacheControl);
17793
+ if (hasSetCookie(setCookie)) return sharedCacheAllowed(cacheControl);
17794
+ return true;
17774
17795
  }
17775
17796
  function varyMatches(entry, req2) {
17776
17797
  const vary = entry.vary ?? [];
@@ -17803,7 +17824,7 @@ function responseCache(config) {
17803
17824
  let captured = false;
17804
17825
  res.raw.end = function(chunk, ...args) {
17805
17826
  const vary = varyFields(res.raw.getHeader("Vary"));
17806
- if (!captured && allowedCodes.has(res.raw.statusCode) && mayStore(req2, vary, res.raw.getHeader("Cache-Control"))) {
17827
+ if (!captured && allowedCodes.has(res.raw.statusCode) && mayStore(req2, vary, res.raw.getHeader("Cache-Control"), res.raw.getHeader("Set-Cookie"))) {
17807
17828
  captured = true;
17808
17829
  const body = typeof chunk === "string" ? chunk : chunk?.toString() ?? "";
17809
17830
  const contentType = String(res.raw.getHeader("Content-Type") ?? "application/octet-stream");
@@ -17890,7 +17911,7 @@ function _resetBackend() {
17890
17911
  _explicitResponseBackends.clear();
17891
17912
  _defaultTtl = null;
17892
17913
  }
17893
- var MemoryBackend, RespClient, RedisBackend, ValkeyBackend, FileBackend, MemcachedClient, MemcachedBackend, MongoBackend, DatabaseBackend, _responseBackend, _responseBackendPromise, _explicitResponseBackends, SHARED_CACHE_DIRECTIVES, _defaultBackend, _defaultBackendPromise, _defaultTtl;
17914
+ var MemoryBackend, RespClient, RedisBackend, ValkeyBackend, FileBackend, MemcachedClient, MemcachedBackend, MongoBackend, DatabaseBackend, _responseBackend, _responseBackendPromise, _explicitResponseBackends, SHARED_CACHE_DIRECTIVES, NO_STORE_DIRECTIVES, _defaultBackend, _defaultBackendPromise, _defaultTtl;
17894
17915
  var init_cache = __esm({
17895
17916
  "src/cache.ts"() {
17896
17917
  "use strict";
@@ -19056,6 +19077,7 @@ ${data}\r
19056
19077
  _responseBackendPromise = null;
19057
19078
  _explicitResponseBackends = /* @__PURE__ */ new Map();
19058
19079
  SHARED_CACHE_DIRECTIVES = ["public", "s-maxage", "must-revalidate"];
19080
+ NO_STORE_DIRECTIVES = ["no-store", "private", "no-cache"];
19059
19081
  _defaultBackend = null;
19060
19082
  _defaultBackendPromise = null;
19061
19083
  _defaultTtl = null;
@@ -1629,6 +1629,14 @@ export function _getResponseBackend(config?: ResponseCacheConfig): Promise<Cache
1629
1629
  */
1630
1630
  const SHARED_CACHE_DIRECTIVES = ["public", "s-maxage", "must-revalidate"];
1631
1631
 
1632
+ /**
1633
+ * Response directives that forbid storing the response here (RFC 9111 s3):
1634
+ * `no-store` forbids storage in any cache, `private` and `no-cache` in a shared
1635
+ * one. Before this was honoured a handler had no way to keep a response out of
1636
+ * the cache — setting the correct standard header did nothing.
1637
+ */
1638
+ const NO_STORE_DIRECTIVES = ["no-store", "private", "no-cache"];
1639
+
1632
1640
  /** Case-insensitive request header lookup. */
1633
1641
  function requestHeader(req: { headers?: Record<string, unknown> }, name: string): string | undefined {
1634
1642
  const headers = req?.headers;
@@ -1650,22 +1658,67 @@ function varyFields(raw: unknown): string[] {
1650
1658
  }
1651
1659
 
1652
1660
  /**
1653
- * May a SHARED cache store this response? (RFC 9111 s3, s4.1)
1661
+ * The lower-cased Cache-Control directive NAMES on a header value, as a set.
1654
1662
  *
1655
- * s3 "if the cache is shared: the Authorization header field is not present
1656
- * in the request ... or a response directive is present that explicitly allows
1657
- * shared caching". The key is method + URL only, and on Node the cache answers
1658
- * BEFORE the auth gate, so without this an anonymous caller was served an
1659
- * authenticated caller's body with a 200.
1663
+ * Parsed as comma-separated tokens with any `=value` stripped, rather than by
1664
+ * substring search, so `no-cache="Set-Cookie"` is recognised as `no-cache` and
1665
+ * a directive name never matches as a fragment of a longer one.
1666
+ */
1667
+ function cacheControlTokens(raw: unknown): Set<string> {
1668
+ const text = Array.isArray(raw) ? raw.join(",") : String(raw ?? "");
1669
+ const tokens = new Set<string>();
1670
+ for (const token of text.split(",")) {
1671
+ const name = token.split("=")[0].trim().toLowerCase();
1672
+ if (name !== "") tokens.add(name);
1673
+ }
1674
+ return tokens;
1675
+ }
1676
+
1677
+ /** Does the response Cache-Control carry a directive that lets a SHARED cache store it? */
1678
+ function sharedCacheAllowed(cacheControl: unknown): boolean {
1679
+ const directives = cacheControlTokens(cacheControl);
1680
+ return SHARED_CACHE_DIRECTIVES.some((directive) => directives.has(directive));
1681
+ }
1682
+
1683
+ /** Is a Set-Cookie response header present? Any non-empty value (it may be an array). */
1684
+ function hasSetCookie(raw: unknown): boolean {
1685
+ if (raw === undefined || raw === null) return false;
1686
+ if (Array.isArray(raw)) return raw.length > 0;
1687
+ return String(raw) !== "";
1688
+ }
1689
+
1690
+ /**
1691
+ * May a SHARED cache store this response? (RFC 9111 s3, s4.1)
1660
1692
  *
1661
1693
  * s4.1 — a stored response whose Vary contains "*" "always fails to match", so
1662
1694
  * storing one is pointless.
1695
+ *
1696
+ * s3 — a shared cache may store a response only when it is not marked
1697
+ * un-storable and is not built for one specific caller:
1698
+ *
1699
+ * - `no-store`/`private`/`no-cache` on the response forbid storing it here, so a
1700
+ * handler can always opt a body out of the cache with the standard header.
1701
+ * - The key is method + URL only, and on Node the cache answers BEFORE the auth
1702
+ * gate, so a response built for one caller replays to whoever asks for that URL
1703
+ * next. Authorization marks such a caller — and so does a session Cookie on the
1704
+ * request (Tina4's own session mechanism IS a cookie), and a Set-Cookie on the
1705
+ * response (it installs a per-caller session). All three are storable only when
1706
+ * the response opts in with an explicit shared-cache directive, which keeps a
1707
+ * genuinely public page cacheable for cookie-bearing browsers.
1663
1708
  */
1664
- function mayStore(req: { headers?: Record<string, unknown> }, vary: string[], cacheControl: unknown): boolean {
1709
+ function mayStore(
1710
+ req: { headers?: Record<string, unknown> },
1711
+ vary: string[],
1712
+ cacheControl: unknown,
1713
+ setCookie: unknown,
1714
+ ): boolean {
1665
1715
  if (vary.includes("*")) return false;
1666
- if (requestHeader(req, "authorization") === undefined) return true;
1667
- const cc = String(cacheControl ?? "").toLowerCase();
1668
- return SHARED_CACHE_DIRECTIVES.some((directive) => cc.includes(directive));
1716
+ const directives = cacheControlTokens(cacheControl);
1717
+ if (NO_STORE_DIRECTIVES.some((directive) => directives.has(directive))) return false;
1718
+ if (requestHeader(req, "authorization") !== undefined) return sharedCacheAllowed(cacheControl);
1719
+ if (requestHeader(req, "cookie") !== undefined) return sharedCacheAllowed(cacheControl);
1720
+ if (hasSetCookie(setCookie)) return sharedCacheAllowed(cacheControl);
1721
+ return true;
1669
1722
  }
1670
1723
 
1671
1724
  /**
@@ -1722,7 +1775,7 @@ export function responseCache(config?: ResponseCacheConfig): Middleware {
1722
1775
  res.raw.end = function (chunk?: any, ...args: any[]) {
1723
1776
  const vary = varyFields(res.raw.getHeader("Vary"));
1724
1777
  if (!captured && allowedCodes.has(res.raw.statusCode)
1725
- && mayStore(req as any, vary, res.raw.getHeader("Cache-Control"))) {
1778
+ && mayStore(req as any, vary, res.raw.getHeader("Cache-Control"), res.raw.getHeader("Set-Cookie"))) {
1726
1779
  captured = true;
1727
1780
  const body = typeof chunk === "string" ? chunk : chunk?.toString() ?? "";
1728
1781
  const contentType = String(res.raw.getHeader("Content-Type") ?? "application/octet-stream");
@@ -6909,11 +6909,32 @@ function varyFields(raw) {
6909
6909
  const text = Array.isArray(raw) ? raw.join(",") : String(raw);
6910
6910
  return text.split(",").map((f) => f.trim().toLowerCase()).filter((f) => f !== "");
6911
6911
  }
6912
- function mayStore(req2, vary, cacheControl) {
6912
+ function cacheControlTokens(raw) {
6913
+ const text = Array.isArray(raw) ? raw.join(",") : String(raw ?? "");
6914
+ const tokens = /* @__PURE__ */ new Set();
6915
+ for (const token of text.split(",")) {
6916
+ const name = token.split("=")[0].trim().toLowerCase();
6917
+ if (name !== "") tokens.add(name);
6918
+ }
6919
+ return tokens;
6920
+ }
6921
+ function sharedCacheAllowed(cacheControl) {
6922
+ const directives = cacheControlTokens(cacheControl);
6923
+ return SHARED_CACHE_DIRECTIVES.some((directive) => directives.has(directive));
6924
+ }
6925
+ function hasSetCookie(raw) {
6926
+ if (raw === void 0 || raw === null) return false;
6927
+ if (Array.isArray(raw)) return raw.length > 0;
6928
+ return String(raw) !== "";
6929
+ }
6930
+ function mayStore(req2, vary, cacheControl, setCookie) {
6913
6931
  if (vary.includes("*")) return false;
6914
- if (requestHeader(req2, "authorization") === void 0) return true;
6915
- const cc = String(cacheControl ?? "").toLowerCase();
6916
- return SHARED_CACHE_DIRECTIVES.some((directive) => cc.includes(directive));
6932
+ const directives = cacheControlTokens(cacheControl);
6933
+ if (NO_STORE_DIRECTIVES.some((directive) => directives.has(directive))) return false;
6934
+ if (requestHeader(req2, "authorization") !== void 0) return sharedCacheAllowed(cacheControl);
6935
+ if (requestHeader(req2, "cookie") !== void 0) return sharedCacheAllowed(cacheControl);
6936
+ if (hasSetCookie(setCookie)) return sharedCacheAllowed(cacheControl);
6937
+ return true;
6917
6938
  }
6918
6939
  function varyMatches(entry, req2) {
6919
6940
  const vary = entry.vary ?? [];
@@ -6946,7 +6967,7 @@ function responseCache(config) {
6946
6967
  let captured = false;
6947
6968
  res.raw.end = function(chunk, ...args) {
6948
6969
  const vary = varyFields(res.raw.getHeader("Vary"));
6949
- if (!captured && allowedCodes.has(res.raw.statusCode) && mayStore(req2, vary, res.raw.getHeader("Cache-Control"))) {
6970
+ if (!captured && allowedCodes.has(res.raw.statusCode) && mayStore(req2, vary, res.raw.getHeader("Cache-Control"), res.raw.getHeader("Set-Cookie"))) {
6950
6971
  captured = true;
6951
6972
  const body = typeof chunk === "string" ? chunk : chunk?.toString() ?? "";
6952
6973
  const contentType = String(res.raw.getHeader("Content-Type") ?? "application/octet-stream");
@@ -7033,7 +7054,7 @@ function _resetBackend() {
7033
7054
  _explicitResponseBackends.clear();
7034
7055
  _defaultTtl = null;
7035
7056
  }
7036
- var MemoryBackend, RespClient, RedisBackend, ValkeyBackend, FileBackend, MemcachedClient, MemcachedBackend, MongoBackend, DatabaseBackend, _responseBackend, _responseBackendPromise, _explicitResponseBackends, SHARED_CACHE_DIRECTIVES, _defaultBackend, _defaultBackendPromise, _defaultTtl;
7057
+ var MemoryBackend, RespClient, RedisBackend, ValkeyBackend, FileBackend, MemcachedClient, MemcachedBackend, MongoBackend, DatabaseBackend, _responseBackend, _responseBackendPromise, _explicitResponseBackends, SHARED_CACHE_DIRECTIVES, NO_STORE_DIRECTIVES, _defaultBackend, _defaultBackendPromise, _defaultTtl;
7037
7058
  var init_cache = __esm({
7038
7059
  "../core/src/cache.ts"() {
7039
7060
  "use strict";
@@ -8199,6 +8220,7 @@ ${data}\r
8199
8220
  _responseBackendPromise = null;
8200
8221
  _explicitResponseBackends = /* @__PURE__ */ new Map();
8201
8222
  SHARED_CACHE_DIRECTIVES = ["public", "s-maxage", "must-revalidate"];
8223
+ NO_STORE_DIRECTIVES = ["no-store", "private", "no-cache"];
8202
8224
  _defaultBackend = null;
8203
8225
  _defaultBackendPromise = null;
8204
8226
  _defaultTtl = null;