@nifrajs/web 2.2.0 → 2.3.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.
Files changed (163) hide show
  1. package/README.md +3 -3
  2. package/dist/build-vite.d.ts +4 -4
  3. package/dist/build-vite.d.ts.map +1 -1
  4. package/dist/build-vite.js +20 -74
  5. package/dist/build-vite.js.map +1 -1
  6. package/dist/build.d.ts +64 -42
  7. package/dist/build.d.ts.map +1 -1
  8. package/dist/build.js +100 -78
  9. package/dist/build.js.map +1 -1
  10. package/dist/bun-dev-entry.d.ts +1 -1
  11. package/dist/bun-dev-entry.js +1 -1
  12. package/dist/chunk-names.js +1 -1
  13. package/dist/client.d.ts +3 -9
  14. package/dist/client.d.ts.map +1 -1
  15. package/dist/client.js +201 -29
  16. package/dist/client.js.map +1 -1
  17. package/dist/conformance.d.ts +2 -2
  18. package/dist/conformance.js +3 -3
  19. package/dist/deferred.d.ts +11 -11
  20. package/dist/deferred.d.ts.map +1 -1
  21. package/dist/deferred.js +16 -16
  22. package/dist/dev-error.d.ts +2 -2
  23. package/dist/dev-error.js +3 -3
  24. package/dist/dev-port.d.ts +2 -2
  25. package/dist/dev-port.js +1 -1
  26. package/dist/dev.d.ts +3 -3
  27. package/dist/dev.js +9 -9
  28. package/dist/draft.d.ts +12 -12
  29. package/dist/draft.js +11 -11
  30. package/dist/fn-state.d.ts +56 -0
  31. package/dist/fn-state.d.ts.map +1 -0
  32. package/dist/fn-state.js +77 -0
  33. package/dist/fn-state.js.map +1 -0
  34. package/dist/fn.d.ts +111 -0
  35. package/dist/fn.d.ts.map +1 -0
  36. package/dist/fn.js +125 -0
  37. package/dist/fn.js.map +1 -0
  38. package/dist/fonts-google.d.ts +9 -9
  39. package/dist/fonts-google.js +10 -10
  40. package/dist/fonts.d.ts +7 -7
  41. package/dist/fonts.js +1 -1
  42. package/dist/forms.d.ts +8 -8
  43. package/dist/fs.d.ts +1 -1
  44. package/dist/fs.js +1 -1
  45. package/dist/index.d.ts +151 -70
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +317 -139
  48. package/dist/index.js.map +1 -1
  49. package/dist/internal/head-attributes.d.ts +15 -0
  50. package/dist/internal/head-attributes.d.ts.map +1 -0
  51. package/dist/internal/head-attributes.js +99 -0
  52. package/dist/internal/head-attributes.js.map +1 -0
  53. package/dist/internal/script-types.d.ts +14 -0
  54. package/dist/internal/script-types.d.ts.map +1 -0
  55. package/dist/internal/script-types.js +17 -0
  56. package/dist/internal/script-types.js.map +1 -0
  57. package/dist/internal/server-boundary.d.ts +60 -0
  58. package/dist/internal/server-boundary.d.ts.map +1 -0
  59. package/dist/internal/server-boundary.js +174 -0
  60. package/dist/internal/server-boundary.js.map +1 -0
  61. package/dist/internal/timing-safe-equal.d.ts +1 -1
  62. package/dist/internal/timing-safe-equal.js +1 -1
  63. package/dist/internal/vite-import.d.ts +63 -0
  64. package/dist/internal/vite-import.d.ts.map +1 -0
  65. package/dist/internal/vite-import.js +94 -0
  66. package/dist/internal/vite-import.js.map +1 -0
  67. package/dist/islands.d.ts +5 -5
  68. package/dist/islands.js +5 -5
  69. package/dist/isr.d.ts +15 -15
  70. package/dist/isr.js +7 -7
  71. package/dist/manifest.d.ts +73 -35
  72. package/dist/manifest.d.ts.map +1 -1
  73. package/dist/manifest.js +10 -16
  74. package/dist/manifest.js.map +1 -1
  75. package/dist/module-graph.d.ts +5 -5
  76. package/dist/module-graph.js +4 -4
  77. package/dist/navigation.d.ts +120 -5
  78. package/dist/navigation.d.ts.map +1 -1
  79. package/dist/navigation.js +37 -5
  80. package/dist/navigation.js.map +1 -1
  81. package/dist/plugins/css-modules.d.ts +3 -3
  82. package/dist/plugins/css-modules.js +0 -0
  83. package/dist/plugins/kit.d.ts +8 -8
  84. package/dist/plugins/kit.js +9 -9
  85. package/dist/plugins/scss.js +3 -3
  86. package/dist/plugins/vite-leak-guard.d.ts +1 -1
  87. package/dist/plugins/vite-leak-guard.js +1 -1
  88. package/dist/plugins/vite-server-fn.d.ts +23 -0
  89. package/dist/plugins/vite-server-fn.d.ts.map +1 -0
  90. package/dist/plugins/vite-server-fn.js +51 -0
  91. package/dist/plugins/vite-server-fn.js.map +1 -0
  92. package/dist/plugins/vite-server-only.d.ts +49 -0
  93. package/dist/plugins/vite-server-only.d.ts.map +1 -0
  94. package/dist/plugins/vite-server-only.js +57 -0
  95. package/dist/plugins/vite-server-only.js.map +1 -0
  96. package/dist/prerender.d.ts +5 -5
  97. package/dist/prerender.js +7 -7
  98. package/dist/query.d.ts +10 -10
  99. package/dist/query.js +15 -15
  100. package/dist/route-manifest.d.ts +1 -1
  101. package/dist/route-manifest.js +2 -2
  102. package/dist/router.d.ts +30 -21
  103. package/dist/router.d.ts.map +1 -1
  104. package/dist/router.js +57 -28
  105. package/dist/router.js.map +1 -1
  106. package/dist/search.d.ts +94 -0
  107. package/dist/search.d.ts.map +1 -0
  108. package/dist/search.js +232 -0
  109. package/dist/search.js.map +1 -0
  110. package/dist/server-only.d.ts +6 -6
  111. package/dist/server-only.js +7 -7
  112. package/dist/service-worker.d.ts +56 -0
  113. package/dist/service-worker.d.ts.map +1 -0
  114. package/dist/service-worker.js +127 -0
  115. package/dist/service-worker.js.map +1 -0
  116. package/dist/vite.d.ts +15 -13
  117. package/dist/vite.d.ts.map +1 -1
  118. package/dist/vite.js +31 -21
  119. package/dist/vite.js.map +1 -1
  120. package/package.json +33 -4
  121. package/server-manifest.d.ts +31 -0
  122. package/src/build-vite.ts +24 -77
  123. package/src/build.ts +137 -105
  124. package/src/bun-dev-entry.ts +1 -1
  125. package/src/chunk-names.ts +1 -1
  126. package/src/client.ts +215 -32
  127. package/src/conformance.ts +3 -3
  128. package/src/deferred.ts +17 -17
  129. package/src/dev-error.ts +3 -3
  130. package/src/dev-port.ts +2 -2
  131. package/src/dev.ts +12 -12
  132. package/src/draft.ts +15 -15
  133. package/src/fn-state.ts +107 -0
  134. package/src/fn.ts +199 -0
  135. package/src/fonts-google.ts +13 -13
  136. package/src/fonts.ts +7 -7
  137. package/src/forms.ts +8 -8
  138. package/src/fs.ts +2 -2
  139. package/src/index.ts +460 -181
  140. package/src/internal/head-attributes.ts +109 -0
  141. package/src/internal/script-types.ts +18 -0
  142. package/src/internal/server-boundary.ts +187 -0
  143. package/src/internal/timing-safe-equal.ts +1 -1
  144. package/src/internal/vite-import.ts +92 -0
  145. package/src/islands.ts +7 -7
  146. package/src/isr.ts +17 -17
  147. package/src/manifest.ts +83 -42
  148. package/src/module-graph.ts +6 -6
  149. package/src/navigation.ts +151 -5
  150. package/src/plugins/css-modules.ts +0 -0
  151. package/src/plugins/kit.ts +11 -11
  152. package/src/plugins/scss.ts +3 -3
  153. package/src/plugins/vite-leak-guard.ts +2 -2
  154. package/src/plugins/vite-server-fn.ts +67 -0
  155. package/src/plugins/vite-server-only.ts +75 -0
  156. package/src/prerender.ts +10 -10
  157. package/src/query.ts +22 -22
  158. package/src/route-manifest.ts +3 -3
  159. package/src/router.ts +91 -41
  160. package/src/search.ts +297 -0
  161. package/src/server-only.ts +7 -7
  162. package/src/service-worker.ts +165 -0
  163. package/src/vite.ts +41 -29
package/dist/search.js ADDED
@@ -0,0 +1,232 @@
1
+ /** Conservative defaults: a 4 KB query, 64 keys, 6 levels of nesting. */
2
+ export const DEFAULT_SEARCH_LIMITS = { maxLength: 4096, maxKeys: 64, maxDepth: 6 };
3
+ // Keys that must never be written from untrusted input: a bracket-assignment to `__proto__` (or the
4
+ // others) can pollute Object.prototype. Dropped at every level of a decoded value.
5
+ const FORBIDDEN_KEYS = new Set(["__proto__", "prototype", "constructor"]);
6
+ function isPlainObject(value) {
7
+ return typeof value === "object" && value !== null && !Array.isArray(value);
8
+ }
9
+ // True when `value` nests deeper than `remaining` levels (a scalar never does). Used to reject an
10
+ // over-deep decoded JSON value back to its raw string rather than store the graph.
11
+ function exceedsDepth(value, remaining) {
12
+ if (Array.isArray(value)) {
13
+ if (remaining <= 0)
14
+ return true;
15
+ return value.some((item) => exceedsDepth(item, remaining - 1));
16
+ }
17
+ if (isPlainObject(value)) {
18
+ if (remaining <= 0)
19
+ return true;
20
+ return Object.values(value).some((item) => exceedsDepth(item, remaining - 1));
21
+ }
22
+ return false;
23
+ }
24
+ // Recursively strip forbidden keys from a decoded value. `JSON.parse('{"__proto__":1}')` yields an OWN
25
+ // enumerable `__proto__` property (it does not set the prototype), but a later spread/assign of it would,
26
+ // so it is removed here, at the source.
27
+ function sanitize(value) {
28
+ if (Array.isArray(value))
29
+ return value.map(sanitize);
30
+ if (isPlainObject(value)) {
31
+ const out = {};
32
+ for (const [key, item] of Object.entries(value)) {
33
+ if (FORBIDDEN_KEYS.has(key))
34
+ continue;
35
+ out[key] = sanitize(item);
36
+ }
37
+ return out;
38
+ }
39
+ return value;
40
+ }
41
+ // One query value: JSON-decode it (so `2` is a number, `true` a boolean, `{...}` an object), falling back
42
+ // to the raw string when it is not JSON or nests past the depth cap.
43
+ function decodeValue(raw, limits) {
44
+ let parsed;
45
+ try {
46
+ parsed = JSON.parse(raw);
47
+ }
48
+ catch {
49
+ return raw;
50
+ }
51
+ if ((isPlainObject(parsed) || Array.isArray(parsed)) && exceedsDepth(parsed, limits.maxDepth)) {
52
+ return raw;
53
+ }
54
+ return sanitize(parsed);
55
+ }
56
+ function encodeValue(value) {
57
+ if (typeof value !== "string")
58
+ return JSON.stringify(value);
59
+ // The codec is JSON-first on decode, so a raw string such as `"2"`, `"true"`, or `"null"`
60
+ // would otherwise change type on a serialize → parse round-trip. Quote only ambiguous strings;
61
+ // ordinary text stays readable (`?q=hello`, not `?q=%22hello%22`).
62
+ try {
63
+ JSON.parse(value);
64
+ return JSON.stringify(value);
65
+ }
66
+ catch {
67
+ return value;
68
+ }
69
+ }
70
+ /** The default JSON-first codec. Repeated keys (`?t=a&t=b`) decode to an array; single keys to a value. */
71
+ const jsonCodec = {
72
+ parse(raw, limits) {
73
+ const usp = new URLSearchParams(raw.startsWith("?") ? raw.slice(1) : raw);
74
+ const out = {};
75
+ let kept = 0;
76
+ for (const key of new Set(usp.keys())) {
77
+ if (FORBIDDEN_KEYS.has(key))
78
+ continue;
79
+ if (++kept > limits.maxKeys)
80
+ break;
81
+ const decoded = usp.getAll(key).map((value) => decodeValue(value, limits));
82
+ out[key] = decoded.length > 1 ? decoded : decoded[0];
83
+ }
84
+ return out;
85
+ },
86
+ serialize(value) {
87
+ const usp = new URLSearchParams();
88
+ for (const [key, item] of Object.entries(value)) {
89
+ if (item === undefined)
90
+ continue;
91
+ if (Array.isArray(item) && item.length > 1) {
92
+ for (const element of item)
93
+ usp.append(key, encodeValue(element));
94
+ }
95
+ else {
96
+ // Repeated query keys naturally preserve arrays of length >= 2. Empty and singleton arrays
97
+ // need one JSON value or they collapse to a missing key / scalar during decoding.
98
+ usp.set(key, encodeValue(item));
99
+ }
100
+ }
101
+ const query = usp.toString();
102
+ return query === "" ? "" : `?${query}`;
103
+ },
104
+ };
105
+ /**
106
+ * Parse a raw URL query into a structured object. Fails closed to `{}` when the raw string exceeds
107
+ * `limits.maxLength` (before any parsing work is done).
108
+ */
109
+ export function parseSearch(raw, codec = jsonCodec, limits = DEFAULT_SEARCH_LIMITS) {
110
+ if (raw.length > limits.maxLength)
111
+ return {};
112
+ return codec.parse(raw, limits);
113
+ }
114
+ /** Serialize a structured search object back to a query string (leading `?`, or `""` when empty). */
115
+ export function serializeSearch(value, codec = jsonCodec) {
116
+ return codec.serialize(value);
117
+ }
118
+ /**
119
+ * The search a route sees for a raw URL query: parsed, then validated against a single `searchSchema`
120
+ * when the route declares one (failing closed to its defaults), or the raw parsed query otherwise. A
121
+ * one-link {@link searchOfChain}; use that directly for a layout+page chain. Both the server (`renderPage`,
122
+ * loader ctx) and the client (the adapter mount) derive search this way, from the same URL + schema, so
123
+ * they produce the identical value - SSR-correct by construction, no serialization.
124
+ */
125
+ export function searchOf(searchSchema, rawSearch) {
126
+ return searchOfChain([searchSchema], rawSearch);
127
+ }
128
+ /**
129
+ * The search for a route whose effective schema is a CHAIN - a `_layout` may declare `searchSchema` for
130
+ * shared keys (`?org`, `?theme`) and each page declares its own. The raw query is validated against every
131
+ * schema in the chain (outermost layout first, page last) and their outputs are merged, page-wins on a key
132
+ * conflict (nearest-wins, like `mergeHeads`): validate-each-then-combine, since Standard Schema has no
133
+ * `.merge()`. `undefined` links (a layout with no `searchSchema`) contribute nothing; when the whole chain
134
+ * is empty, the raw parsed query is returned. Fails closed per schema, same as {@link searchOf} (which is
135
+ * this with a one-link chain). The server and the client build the identical chain, so the value matches.
136
+ */
137
+ export function searchOfChain(schemas, rawSearch) {
138
+ const parsed = parseSearch(rawSearch);
139
+ const defined = schemas.filter((s) => s !== undefined);
140
+ if (defined.length === 0)
141
+ return parsed;
142
+ let out = {};
143
+ for (const schema of defined) {
144
+ const validated = validateSearch(schema, parsed);
145
+ if (!isPlainObject(validated)) {
146
+ throw new TypeError("[nifra/web] a search schema must produce an object");
147
+ }
148
+ out = { ...out, ...validated };
149
+ }
150
+ return out;
151
+ }
152
+ /**
153
+ * Validate a parsed search object against a route's Standard Schema, returning the typed output. Fails
154
+ * CLOSED: on validation issues it retries against an empty object so the schema's per-field defaults apply,
155
+ * and degrades to `{}` only if even that fails - it never throws on hostile input. An async validator is a
156
+ * configuration error (search must validate synchronously, in the render/nav path) and throws loudly.
157
+ */
158
+ export function validateSearch(schema, parsed) {
159
+ const result = schema["~standard"].validate(parsed);
160
+ if (result instanceof Promise) {
161
+ throw new TypeError("[nifra/web] a search schema must validate synchronously");
162
+ }
163
+ if (result.issues === undefined)
164
+ return result.value;
165
+ const fallback = schema["~standard"].validate({});
166
+ if (fallback instanceof Promise) {
167
+ throw new TypeError("[nifra/web] a search schema must validate synchronously");
168
+ }
169
+ return (fallback.issues === undefined ? fallback.value : {});
170
+ }
171
+ function deepEqual(a, b) {
172
+ if (Object.is(a, b))
173
+ return true;
174
+ if (Array.isArray(a) && Array.isArray(b)) {
175
+ return a.length === b.length && a.every((item, index) => deepEqual(item, b[index]));
176
+ }
177
+ if (isPlainObject(a) && isPlainObject(b)) {
178
+ const aKeys = Object.keys(a);
179
+ return (aKeys.length === Object.keys(b).length &&
180
+ aKeys.every((key) => key in b && deepEqual(a[key], b[key])));
181
+ }
182
+ return false;
183
+ }
184
+ /**
185
+ * Structural sharing (shallow-first): return `next`, but reuse `prev`'s reference for every top-level key
186
+ * whose value is deep-equal. When nothing changed, `prev` itself is returned (stable identity), so a
187
+ * selector like `useSearch(s => s.filters)` does not see a change - and a filters-only component does not
188
+ * re-render when an unrelated key (`page`) changes.
189
+ */
190
+ export function shareSearch(prev, next) {
191
+ if (Object.is(prev, next))
192
+ return prev;
193
+ if (!isPlainObject(prev) || !isPlainObject(next))
194
+ return next;
195
+ const out = {};
196
+ let identical = Object.keys(prev).length === Object.keys(next).length;
197
+ for (const [key, after] of Object.entries(next)) {
198
+ if (key in prev && deepEqual(prev[key], after)) {
199
+ out[key] = prev[key];
200
+ }
201
+ else {
202
+ out[key] = after;
203
+ identical = false;
204
+ }
205
+ }
206
+ return identical ? prev : out;
207
+ }
208
+ /**
209
+ * True when two raw queries differ ONLY in keys a route declared client-only (`searchClientKeys`) - the
210
+ * signal a client navigation can update search WITHOUT re-running the loader (re-render, not revalidate:
211
+ * `?tab`, a client-side `?sort` over already-loaded rows, `?modal`). Returns false when a key OUTSIDE the
212
+ * allow-list changed (that key drives data, so the nav must revalidate) and when nothing changed at all (a
213
+ * same-URL nav still revalidates). Fail-safe by construction: an empty allow-list can never return true,
214
+ * so a route that declares no client keys always revalidates - the correct, never-stale default.
215
+ */
216
+ export function isClientOnlySearchChange(prevRaw, nextRaw, clientKeys) {
217
+ if (clientKeys.length === 0)
218
+ return false;
219
+ const prev = parseSearch(prevRaw);
220
+ const next = parseSearch(nextRaw);
221
+ const allow = new Set(clientKeys);
222
+ let changed = false;
223
+ for (const key of new Set([...Object.keys(prev), ...Object.keys(next)])) {
224
+ if (deepEqual(prev[key], next[key]))
225
+ continue;
226
+ if (!allow.has(key))
227
+ return false; // a loader-affecting key changed - must revalidate
228
+ changed = true;
229
+ }
230
+ return changed; // only a real change to a client-only key skips the fetch (else revalidate)
231
+ }
232
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"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"}
@@ -1,21 +1,21 @@
1
1
  /**
2
- * `@nifrajs/web/server-only` — the **explicit, opt-in poison-import marker** (Next's `import
3
- * "server-only"`). A module of PURE server logic that carries no `node:` import — a secret-bearing
4
- * constant, a server-only API call — can't be caught by nifra's other two client-leak guards: the
2
+ * `@nifrajs/web/server-only` - the **explicit, opt-in poison-import marker** (Next's `import
3
+ * "server-only"`). A module of PURE server logic that carries no `node:` import - a secret-bearing
4
+ * constant, a server-only API call - can't be caught by nifra's other two client-leak guards: the
5
5
  * `.server` convention (which empties `*.server` modules in the client build) needs the file to be
6
6
  * NAMED `*.server`, and the node-builtin guard only fires when a `node:` import actually reaches a
7
- * client chunk. This marker closes that gap: drop a side-effect import at the top of the module —
7
+ * client chunk. This marker closes that gap: drop a side-effect import at the top of the module -
8
8
  *
9
9
  * import "@nifrajs/web/server-only"
10
10
  *
11
- * — and the CLIENT build ({@link buildClient}) FAILS LOUD, naming the import chain, if that module
11
+ * - and the CLIENT build ({@link buildClient}) FAILS LOUD, naming the import chain, if that module
12
12
  * ever lands in a browser chunk. On the SERVER build/runtime it's a harmless no-op (this empty
13
13
  * module), so the same file runs untouched server-side.
14
14
  *
15
15
  * Pair it with the type-level {@link ServerOnly} brand (from `@nifrajs/web`) on the module's exports
16
16
  * to signal intent to readers + the compiler; this runtime import is what actually fails the build.
17
17
  *
18
- * @see ServerOnly — the type-level intent marker that complements this runtime import.
18
+ * @see ServerOnly - the type-level intent marker that complements this runtime import.
19
19
  */
20
20
  export {};
21
21
  //# sourceMappingURL=server-only.d.ts.map
@@ -1,25 +1,25 @@
1
1
  /**
2
- * `@nifrajs/web/server-only` — the **explicit, opt-in poison-import marker** (Next's `import
3
- * "server-only"`). A module of PURE server logic that carries no `node:` import — a secret-bearing
4
- * constant, a server-only API call — can't be caught by nifra's other two client-leak guards: the
2
+ * `@nifrajs/web/server-only` - the **explicit, opt-in poison-import marker** (Next's `import
3
+ * "server-only"`). A module of PURE server logic that carries no `node:` import - a secret-bearing
4
+ * constant, a server-only API call - can't be caught by nifra's other two client-leak guards: the
5
5
  * `.server` convention (which empties `*.server` modules in the client build) needs the file to be
6
6
  * NAMED `*.server`, and the node-builtin guard only fires when a `node:` import actually reaches a
7
- * client chunk. This marker closes that gap: drop a side-effect import at the top of the module —
7
+ * client chunk. This marker closes that gap: drop a side-effect import at the top of the module -
8
8
  *
9
9
  * import "@nifrajs/web/server-only"
10
10
  *
11
- * — and the CLIENT build ({@link buildClient}) FAILS LOUD, naming the import chain, if that module
11
+ * - and the CLIENT build ({@link buildClient}) FAILS LOUD, naming the import chain, if that module
12
12
  * ever lands in a browser chunk. On the SERVER build/runtime it's a harmless no-op (this empty
13
13
  * module), so the same file runs untouched server-side.
14
14
  *
15
15
  * Pair it with the type-level {@link ServerOnly} brand (from `@nifrajs/web`) on the module's exports
16
16
  * to signal intent to readers + the compiler; this runtime import is what actually fails the build.
17
17
  *
18
- * @see ServerOnly — the type-level intent marker that complements this runtime import.
18
+ * @see ServerOnly - the type-level intent marker that complements this runtime import.
19
19
  */
20
20
  // Intentionally empty. The marker is the IMPORT itself: `buildClient` detects any module that imports
21
21
  // this specifier and lands in a client chunk (via the same Bun metafile graph the node-builtin guard
22
- // walks) and fails the build. There is no value to export and nothing to run — on the server it's a
22
+ // walks) and fails the build. There is no value to export and nothing to run - on the server it's a
23
23
  // no-op so the marked module behaves normally; on the client the build never completes.
24
24
  export {};
25
25
  //# sourceMappingURL=server-only.js.map
@@ -0,0 +1,56 @@
1
+ /**
2
+ * `@nifrajs/web/service-worker` - generate a service worker from a build manifest.
3
+ *
4
+ * A service worker is the one piece of an app that outlives a deploy, keeps serving after you have
5
+ * stopped, and can hand one user a response produced for another. So the generated worker is
6
+ * deliberately narrow, and every rule below exists because the permissive version of it is a bug:
7
+ *
8
+ * - **Only content-hashed assets are precached.** A hashed URL names its bytes, so serving it from
9
+ * cache forever is correct by construction. Anything unhashed is left to the network.
10
+ * - **Documents are never cached.** Only navigations that FAIL are answered, and only with the
11
+ * offline page you nominate. A cached HTML document is how a service worker serves one signed-in
12
+ * user the page rendered for another; there is no per-user story here worth that risk.
13
+ * - **GET, same-origin, `ok`, and not `no-store`.** Anything else goes straight to the network.
14
+ * - **The cache name carries the build id.** A deploy that changed the assets gets a new cache, and
15
+ * activation deletes every older one, so a stale worker cannot pin an old build indefinitely.
16
+ *
17
+ * It is opt-in and generated at build time; an app that does not call this ships nothing.
18
+ */
19
+ /** The parts of a `BuildManifest` a worker needs. Structural, so a caller can pass extra fields. */
20
+ export interface ServiceWorkerManifest {
21
+ readonly entry: string;
22
+ readonly assets: readonly string[];
23
+ readonly css?: readonly string[];
24
+ }
25
+ export interface ServiceWorkerOptions {
26
+ /**
27
+ * Distinguishes this build's cache from the last one. Use something that changes exactly when the
28
+ * assets do - a content hash, a commit sha, a release version.
29
+ */
30
+ readonly buildId: string;
31
+ /**
32
+ * URL of a page to serve when a navigation fails and the network is unreachable. It must be a
33
+ * static, user-independent document (a prerendered `/offline`), because every visitor gets the same
34
+ * bytes. Omit it and failed navigations simply fail, which is the honest default.
35
+ */
36
+ readonly offlineUrl?: string;
37
+ /** Cache name prefix. Default `nifra`. */
38
+ readonly cacheName?: string;
39
+ /** Extra same-origin URLs to precache. Only pass immutable ones. */
40
+ readonly additionalPrecache?: readonly string[];
41
+ }
42
+ /**
43
+ * Generate the service worker source for a build.
44
+ *
45
+ * Write the result to a file served from the ORIGIN ROOT (`/sw.js`): a worker's default scope is its
46
+ * own directory, so one served from `/assets/` could never control the pages it exists for.
47
+ */
48
+ export declare function generateServiceWorker(manifest: ServiceWorkerManifest, options: ServiceWorkerOptions): string;
49
+ /**
50
+ * The registration snippet, for a `<script>` in your document shell.
51
+ *
52
+ * Registration is deliberately deferred to `load`: a worker installing during first paint competes
53
+ * with the page it is meant to speed up.
54
+ */
55
+ export declare function serviceWorkerRegistration(scriptUrl?: string): string;
56
+ //# sourceMappingURL=service-worker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-worker.d.ts","sourceRoot":"","sources":["../src/service-worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,oGAAoG;AACpG,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;IAClC,QAAQ,CAAC,GAAG,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CACjC;AAED,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,0CAA0C;IAC1C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,oEAAoE;IACpE,QAAQ,CAAC,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAChD;AAqBD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,qBAAqB,EAC/B,OAAO,EAAE,oBAAoB,GAC5B,MAAM,CAiFR;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,SAAS,SAAW,GAAG,MAAM,CAGtE"}
@@ -0,0 +1,127 @@
1
+ /**
2
+ * `@nifrajs/web/service-worker` - generate a service worker from a build manifest.
3
+ *
4
+ * A service worker is the one piece of an app that outlives a deploy, keeps serving after you have
5
+ * stopped, and can hand one user a response produced for another. So the generated worker is
6
+ * deliberately narrow, and every rule below exists because the permissive version of it is a bug:
7
+ *
8
+ * - **Only content-hashed assets are precached.** A hashed URL names its bytes, so serving it from
9
+ * cache forever is correct by construction. Anything unhashed is left to the network.
10
+ * - **Documents are never cached.** Only navigations that FAIL are answered, and only with the
11
+ * offline page you nominate. A cached HTML document is how a service worker serves one signed-in
12
+ * user the page rendered for another; there is no per-user story here worth that risk.
13
+ * - **GET, same-origin, `ok`, and not `no-store`.** Anything else goes straight to the network.
14
+ * - **The cache name carries the build id.** A deploy that changed the assets gets a new cache, and
15
+ * activation deletes every older one, so a stale worker cannot pin an old build indefinitely.
16
+ *
17
+ * It is opt-in and generated at build time; an app that does not call this ships nothing.
18
+ */
19
+ const SW_URL = /^\/[\w\-./@%]*$/;
20
+ /** A precache URL has to be same-origin, rooted, and free of anything that could break out of a list. */
21
+ function assertPrecachable(url, label) {
22
+ if (typeof url !== "string" || url === "") {
23
+ throw new Error(`[nifra/service-worker] ${label} must be a non-empty string`);
24
+ }
25
+ if (!url.startsWith("/") || url.startsWith("//")) {
26
+ throw new Error(`[nifra/service-worker] ${label} must be a root-relative same-origin path, got ${JSON.stringify(url)}`);
27
+ }
28
+ if (!SW_URL.test(url)) {
29
+ throw new Error(`[nifra/service-worker] ${label} contains characters that are not valid in an asset path: ${JSON.stringify(url)}`);
30
+ }
31
+ }
32
+ /**
33
+ * Generate the service worker source for a build.
34
+ *
35
+ * Write the result to a file served from the ORIGIN ROOT (`/sw.js`): a worker's default scope is its
36
+ * own directory, so one served from `/assets/` could never control the pages it exists for.
37
+ */
38
+ export function generateServiceWorker(manifest, options) {
39
+ if (typeof options.buildId !== "string" || options.buildId.trim() === "") {
40
+ throw new Error("[nifra/service-worker] buildId is required and must be non-empty");
41
+ }
42
+ if (/[^\w.-]/.test(options.buildId)) {
43
+ throw new Error(`[nifra/service-worker] buildId must be word characters, dots or hyphens, got ${JSON.stringify(options.buildId)}`);
44
+ }
45
+ const prefix = options.cacheName ?? "nifra";
46
+ if (/[^\w.-]/.test(prefix)) {
47
+ throw new Error(`[nifra/service-worker] cacheName must be word characters, dots or hyphens`);
48
+ }
49
+ const precache = [
50
+ manifest.entry,
51
+ ...manifest.assets,
52
+ ...(manifest.css ?? []),
53
+ ...(options.additionalPrecache ?? []),
54
+ ];
55
+ for (const url of precache)
56
+ assertPrecachable(url, "precache url");
57
+ const offline = options.offlineUrl;
58
+ if (offline !== undefined)
59
+ assertPrecachable(offline, "offlineUrl");
60
+ // De-duped and sorted so an unchanged build produces an unchanged worker - a byte-identical file
61
+ // means the browser does not treat it as an update.
62
+ const urls = [...new Set(offline === undefined ? precache : [...precache, offline])].sort();
63
+ return `// Generated by @nifrajs/web/service-worker. Do not edit.
64
+ const CACHE = ${JSON.stringify(`${prefix}-${options.buildId}`)}
65
+ const PRECACHE = ${JSON.stringify(urls)}
66
+ const OFFLINE = ${offline === undefined ? "null" : JSON.stringify(offline)}
67
+
68
+ self.addEventListener("install", (event) => {
69
+ // Take over as soon as the new assets are stored; the activate handler drops the old cache, so
70
+ // waiting would only serve stale bytes for longer.
71
+ event.waitUntil(caches.open(CACHE).then((c) => c.addAll(PRECACHE)).then(() => self.skipWaiting()))
72
+ })
73
+
74
+ self.addEventListener("activate", (event) => {
75
+ event.waitUntil(
76
+ caches
77
+ .keys()
78
+ .then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k))))
79
+ .then(() => self.clients.claim()),
80
+ )
81
+ })
82
+
83
+ self.addEventListener("fetch", (event) => {
84
+ const request = event.request
85
+ if (request.method !== "GET") return
86
+ const url = new URL(request.url)
87
+ if (url.origin !== self.location.origin) return
88
+
89
+ // Navigations: network only, with the offline document as a last resort. A cached HTML response is
90
+ // how one user gets served the page rendered for another, so nothing is stored here.
91
+ if (request.mode === "navigate") {
92
+ if (OFFLINE === null) return
93
+ event.respondWith(fetch(request).catch(() => caches.match(OFFLINE).then((r) => r ?? Response.error())))
94
+ return
95
+ }
96
+
97
+ // Assets: cache first, because every precached URL is content-hashed and therefore immutable.
98
+ event.respondWith(
99
+ caches.match(request).then((hit) => {
100
+ if (hit !== undefined) return hit
101
+ return fetch(request).then((response) => {
102
+ const cacheable =
103
+ response.ok &&
104
+ response.type === "basic" &&
105
+ !(response.headers.get("cache-control") ?? "").includes("no-store")
106
+ if (cacheable && PRECACHE.includes(url.pathname)) {
107
+ const copy = response.clone()
108
+ void caches.open(CACHE).then((c) => c.put(request, copy))
109
+ }
110
+ return response
111
+ })
112
+ }),
113
+ )
114
+ })
115
+ `;
116
+ }
117
+ /**
118
+ * The registration snippet, for a `<script>` in your document shell.
119
+ *
120
+ * Registration is deliberately deferred to `load`: a worker installing during first paint competes
121
+ * with the page it is meant to speed up.
122
+ */
123
+ export function serviceWorkerRegistration(scriptUrl = "/sw.js") {
124
+ assertPrecachable(scriptUrl, "scriptUrl");
125
+ return `if("serviceWorker"in navigator){addEventListener("load",function(){navigator.serviceWorker.register(${JSON.stringify(scriptUrl)}).catch(function(){})})}`;
126
+ }
127
+ //# sourceMappingURL=service-worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-worker.js","sourceRoot":"","sources":["../src/service-worker.ts"],"names":[],"mappings":"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"}
package/dist/vite.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- /** Minimal app surface — `createWebApp(...)` satisfies it. */
1
+ /** Minimal app surface - `createWebApp(...)` satisfies it. */
2
2
  interface FetchApp {
3
3
  fetch(request: Request): Response | Promise<Response>;
4
4
  }
@@ -17,10 +17,10 @@ export interface ViteDevServerOptions {
17
17
  * crash. Vite re-evaluates on change, so no `importQuery` cache-buster is needed alongside it.
18
18
  */
19
19
  readonly createApp: (clientEntry: string, load: (absolutePath: string) => Promise<unknown>) => FetchApp | Promise<FetchApp>;
20
- /** Vite plugins — inject your framework's official plugin, e.g. `[react()]`. */
20
+ /** Vite plugins - inject your framework's official plugin, e.g. `[react()]`. */
21
21
  readonly plugins?: readonly unknown[];
22
22
  /**
23
- * Extra `resolve.conditions` prepended ahead of nifra's defaults — some frameworks need their own
23
+ * Extra `resolve.conditions` prepended ahead of nifra's defaults - some frameworks need their own
24
24
  * (e.g. Solid's `"solid"` condition routes `solid-js` to its source/JSX-dev build).
25
25
  */
26
26
  readonly conditions?: readonly string[];
@@ -35,18 +35,20 @@ export interface ViteDevServerOptions {
35
35
  readonly port?: number;
36
36
  /**
37
37
  * Use polling for the file watcher. Native fs events (fsevents/inotify) are unreliable inside
38
- * containers, networked filesystems, and some sandboxes — there, HMR silently never fires. Set
38
+ * containers, networked filesystems, and some sandboxes - there, HMR silently never fires. Set
39
39
  * `true` (or the env var `CHOKIDAR_USEPOLLING=1`) to poll instead. Default: off (native events).
40
40
  */
41
41
  readonly poll?: boolean;
42
42
  /** Vite public directory. Defaults to `<root>/public`; `false` disables it. */
43
43
  readonly publicDir?: string | false;
44
+ /** Client-visible environment prefix (default `"PUBLIC_"`; empty disables exposure). */
45
+ readonly publicEnvPrefix?: string;
44
46
  }
45
47
  export interface ViteDevServer {
46
48
  readonly port: number;
47
49
  stop(): Promise<void>;
48
50
  }
49
- /** The bits of a Node ServerResponse `pipeWebBodyToNode` touches — structural, to avoid a node:http dep here. */
51
+ /** The bits of a Node ServerResponse `pipeWebBodyToNode` touches - structural, to avoid a node:http dep here. */
50
52
  interface NodeResLike {
51
53
  flushHeaders?(): void;
52
54
  on(event: "close", cb: () => void): void;
@@ -59,30 +61,30 @@ interface NodeHeaderSink {
59
61
  }
60
62
  /**
61
63
  * Copy a Web `Response`'s headers onto a Node response, emitting EACH `Set-Cookie` as its own header. The
62
- * `Headers` iterator (and `.get`) join multiple set-cookie values with ", ", which corrupts cookies — e.g.
64
+ * `Headers` iterator (and `.get`) join multiple set-cookie values with ", ", which corrupts cookies - e.g.
63
65
  * better-auth's `session_token` + `session_data` collapse into one unparseable cookie and the session is
64
66
  * silently lost. `getSetCookie()` returns them split; Node's `setHeader` emits one header per array element.
65
67
  */
66
68
  export declare function applyResponseHeaders(headers: Headers, res: NodeHeaderSink): void;
67
69
  /**
68
70
  * Stream a Web `Response` body to a Node response chunk-by-chunk. Buffering the whole body (e.g.
69
- * `arrayBuffer()`) waits for the stream to END — which an open-ended SSE (`text/event-stream`) body never
71
+ * `arrayBuffer()`) waits for the stream to END - which an open-ended SSE (`text/event-stream`) body never
70
72
  * does, so it hung `nifra dev` (the Bun production server streamed it fine). This flushes each chunk as it
71
73
  * arrives and cancels the reader if the client disconnects; a finite body just streams its chunk(s) + ends.
72
74
  */
73
75
  export declare function pipeWebBodyToNode(body: ReadableStream<Uint8Array> | null, res: NodeResLike): Promise<void>;
74
76
  /**
75
77
  * Strip `optimizeDeps.rollupOptions.jsx` from a plugin's `config` hook output when running under
76
- * rolldown-vite — the source of the scary, harmless `Warning: Invalid input options … "jsx" Invalid
78
+ * rolldown-vite - the source of the scary, harmless `Warning: Invalid input options … "jsx" Invalid
77
79
  * key: Expected never but received "jsx"` on `nifra dev`.
78
80
  *
79
81
  * Why it happens: `@vitejs/plugin-react@4.x` (and peers) target an *older* rolldown-vite optimizeDeps
80
- * API — they inject `optimizeDeps.rollupOptions.jsx` to tell the dep pre-bundler to transform JSX. Vite
82
+ * API - they inject `optimizeDeps.rollupOptions.jsx` to tell the dep pre-bundler to transform JSX. Vite
81
83
  * 8's rolldown dep-optimizer renamed that surface to `optimizeDeps.rolldownOptions` (and moved jsx under
82
84
  * `transform.jsx`), so the stale `rollupOptions.jsx` is an unrecognized input option → the warning. It's
83
85
  * a version-skew artifact, not a real misconfig: the route source JSX transform runs through the
84
86
  * plugin's own `transform` hook (untouched here), and node_modules deps that get pre-bundled almost
85
- * never contain raw JSX — so dropping the dead key changes no behavior and keeps HMR/Fast Refresh
87
+ * never contain raw JSX - so dropping the dead key changes no behavior and keeps HMR/Fast Refresh
86
88
  * intact. We *strip* (rather than translate to `rolldownOptions`) so the fix is version-agnostic: a
87
89
  * plugin already emitting the correct `rolldownOptions` is left untouched, and a future plugin bump that
88
90
  * stops emitting `rollupOptions.jsx` makes this a no-op.
@@ -90,12 +92,12 @@ export declare function pipeWebBodyToNode(body: ReadableStream<Uint8Array> | nul
90
92
  * Scoped narrowly: only the `optimizeDeps.rollupOptions.jsx` key is removed, only under rolldown-vite,
91
93
  * and only from the value a plugin's `config` hook returns. Non-rolldown Vite is passed through verbatim.
92
94
  *
93
- * FLATTEN FIRST: a Vite plugin factory may return an ARRAY of plugins — `@vitejs/plugin-react`'s `react()`
95
+ * FLATTEN FIRST: a Vite plugin factory may return an ARRAY of plugins - `@vitejs/plugin-react`'s `react()`
94
96
  * returns `[vite:react-babel, vite:react-refresh]`, and it's `react:react-babel`'s `config` hook that emits
95
97
  * the offending `optimizeDeps.rollupOptions.jsx`. `nifra.config.ts` writes `vitePlugins = [react()]`, so the
96
98
  * plugin list arrives NESTED (`[[babel, refresh]]`). Without flattening, `.map` sees the inner array (which
97
- * has no `config`), leaves it untouched, and Vite — which flattens plugin arrays itself before running them
98
- * — then executes the un-stripped babel hook, so the warning survives. Flattening here (Vite accepts a flat
99
+ * has no `config`), leaves it untouched, and Vite - which flattens plugin arrays itself before running them
100
+ * - then executes the un-stripped babel hook, so the warning survives. Flattening here (Vite accepts a flat
99
101
  * list identically) is what lets the strip reach every real plugin.
100
102
  */
101
103
  export declare function normalizeRolldownPlugins(plugins: readonly unknown[], isRolldown: boolean): readonly unknown[];
@@ -1 +1 @@
1
- {"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AAwBA,8DAA8D;AAC9D,UAAU,QAAQ;IAChB,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;CACtD;AAuBD,MAAM,WAAW,oBAAoB;IACnC,4DAA4D;IAC5D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,0FAA0F;IAC1F,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B;;;;;;;;OAQG;IACH,QAAQ,CAAC,SAAS,EAAE,CAClB,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,KAC7C,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IACjC,gFAAgF;IAChF,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,OAAO,EAAE,CAAA;IACrC;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACvC;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAClD,mDAAmD;IACnD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,+CAA+C;IAC/C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;IACvB,+EAA+E;IAC/E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;CACpC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACtB;AA6CD,iHAAiH;AACjH,UAAU,WAAW;IACnB,YAAY,CAAC,IAAI,IAAI,CAAA;IACrB,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAAA;IACxC,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAA;IACjC,GAAG,IAAI,IAAI,CAAA;CACZ;AAED,8DAA8D;AAC9D,UAAU,cAAc;IACtB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,IAAI,CAAA;CACjE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,IAAI,CAOhF;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,EACvC,GAAG,EAAE,WAAW,GACf,OAAO,CAAC,IAAI,CAAC,CAkBf;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,SAAS,OAAO,EAAE,EAC3B,UAAU,EAAE,OAAO,GAClB,SAAS,OAAO,EAAE,CAkCpB;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,aAAa,CAAC,CAwI/F"}
1
+ {"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AA4BA,8DAA8D;AAC9D,UAAU,QAAQ;IAChB,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;CACtD;AAuBD,MAAM,WAAW,oBAAoB;IACnC,4DAA4D;IAC5D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,0FAA0F;IAC1F,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B;;;;;;;;OAQG;IACH,QAAQ,CAAC,SAAS,EAAE,CAClB,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,KAC7C,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IACjC,gFAAgF;IAChF,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,OAAO,EAAE,CAAA;IACrC;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACvC;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAClD,mDAAmD;IACnD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,+CAA+C;IAC/C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;IACvB,+EAA+E;IAC/E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IACnC,wFAAwF;IACxF,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAClC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACtB;AA6CD,iHAAiH;AACjH,UAAU,WAAW;IACnB,YAAY,CAAC,IAAI,IAAI,CAAA;IACrB,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAAA;IACxC,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAA;IACjC,GAAG,IAAI,IAAI,CAAA;CACZ;AAED,8DAA8D;AAC9D,UAAU,cAAc;IACtB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,IAAI,CAAA;CACjE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,IAAI,CAOhF;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,EACvC,GAAG,EAAE,WAAW,GACf,OAAO,CAAC,IAAI,CAAC,CAkBf;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,SAAS,OAAO,EAAE,EAC3B,UAAU,EAAE,OAAO,GAClB,SAAS,OAAO,EAAE,CAkCpB;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,aAAa,CAAC,CA8I/F"}