@lovrozagar/flare 0.1.1 → 0.2.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 +44 -19
- package/package.json +10 -7
- package/src/broadcast/index.ts +1 -1
- package/src/broadcast/provider.tsx +2 -2
- package/src/caches/index.ts +26 -0
- package/src/components/await.tsx +36 -39
- package/src/components/dev-error-overlay.tsx +8 -9
- package/src/components/direction-script.tsx +2 -2
- package/src/components/locale-script.tsx +2 -2
- package/src/components/reset-css.tsx +1 -1
- package/src/components/ssr-context.tsx +7 -14
- package/src/components/theme-script.tsx +2 -2
- package/src/components/view-transition-css.tsx +1 -1
- package/src/defer/index.ts +22 -0
- package/src/direction/index.tsx +61 -36
- package/src/errors/index.ts +45 -11
- package/src/fonts/component.tsx +2 -1
- package/src/form/index.tsx +18 -12
- package/src/generators/index.ts +73 -30
- package/src/head-client/index.ts +7 -0
- package/src/history/index.ts +6 -0
- package/src/hydrate/index.tsx +90 -60
- package/src/hydration/index.ts +26 -6
- package/src/i18n/index.ts +11 -2
- package/src/image/index.tsx +7 -4
- package/src/intercept-outlet/index.tsx +6 -2
- package/src/lazy/index.tsx +50 -50
- package/src/link/index.tsx +97 -58
- package/src/loader-pipeline/index.ts +93 -35
- package/src/locale/cookie.ts +15 -0
- package/src/locale/index.tsx +24 -35
- package/src/locale.ts +1 -0
- package/src/middleware/builtins/api-proxy.ts +58 -4
- package/src/middleware/builtins/cdn-proxy.ts +24 -3
- package/src/middleware/builtins/i18n.ts +23 -25
- package/src/middleware/builtins/keepalive.ts +2 -3
- package/src/mount/index.ts +8 -1
- package/src/navigation/index.ts +575 -129
- package/src/navigation/types.ts +11 -1
- package/src/navigation-progress/index.tsx +17 -16
- package/src/ndjson-client/index.ts +42 -17
- package/src/ndjson-server/index.ts +78 -29
- package/src/outlet/index.tsx +192 -97
- package/src/outlet/types.ts +9 -1
- package/src/plugins/dev-cdn-cache.ts +18 -5
- package/src/plugins/dev-dashboard/client.tsx +130 -118
- package/src/plugins/dev-prerender.ts +6 -1
- package/src/plugins/dev-server.ts +12 -3
- package/src/plugins/image-plugin.ts +18 -4
- package/src/plugins/index.ts +10 -7
- package/src/plugins/prerender-plugin.ts +3 -3
- package/src/plugins/server-fn.ts +15 -20
- package/src/plugins/solid-jsx.d.ts +3 -3
- package/src/plugins/sx-ast/atomic.ts +5 -5
- package/src/plugins/sx-ast/index.ts +9 -0
- package/src/plugins/sx.d.ts +4 -5
- package/src/plugins/virtual.ts +11 -6
- package/src/prerender/index.ts +68 -32
- package/src/protocol.ts +65 -0
- package/src/query-client/index.tsx +73 -14
- package/src/rewrite/index.ts +10 -4
- package/src/route-builder/create-layout.ts +1 -1
- package/src/route-builder/create-root-layout.ts +1 -1
- package/src/route-builder/types.ts +5 -1
- package/src/router-primitives/types.ts +2 -0
- package/src/security/index.ts +41 -5
- package/src/server/index.ts +1 -2
- package/src/server-fn/get-input.ts +90 -0
- package/src/server-fn/http-error.ts +13 -0
- package/src/server-fn/index.ts +94 -70
- package/src/server-fn-query.ts +9 -17
- package/src/server-handler/etag.ts +4 -2
- package/src/server-handler/headers.ts +1 -2
- package/src/server-handler/index.ts +164 -56
- package/src/server-handler/types.ts +2 -0
- package/src/service-worker/template.ts +10 -42
- package/src/ssr/head-prefix.ts +2 -1
- package/src/ssr/hoist-head-markers.ts +25 -0
- package/src/ssr/index.tsx +109 -92
- package/src/state-parser/index.ts +38 -26
- package/src/styles/index.ts +89 -60
- package/src/testing/index.ts +8 -3
- package/src/theme/index.tsx +35 -32
- package/src/tracing/timing.ts +6 -1
- package/src/url/index.ts +9 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Flare
|
|
2
2
|
|
|
3
|
-
Solid + Vite meta-framework. You declare pages and layouts with a typed builder. The server streams HTML (`renderToStream`). Later navigations speak NDJSON (`
|
|
3
|
+
Solid + Vite meta-framework. You declare pages and layouts with a typed builder. The server streams HTML (`renderToStream`). Later navigations speak NDJSON (`flare-data: 1`). Prefetch, ISR, deferred loaders, and forms reuse that protocol.
|
|
4
4
|
|
|
5
5
|
This repo is the source of [`@lovrozagar/flare`](https://www.npmjs.com/package/@lovrozagar/flare) `0.1.1`. The CLI binary is `flare`.
|
|
6
6
|
|
|
@@ -123,7 +123,7 @@ npm add @lovrozagar/flare
|
|
|
123
123
|
pnpm add @lovrozagar/flare
|
|
124
124
|
```
|
|
125
125
|
|
|
126
|
-
Peers: `solid-js`,
|
|
126
|
+
Peers: `solid-js`, `@solidjs/web`, `@solidjs/vite-plugin`, `vite`. Optional: `sharp` (images), `isbot` (skip locale cookies for bots), `@tanstack/solid-query` (query), `@tanstack/query-broadcast-client-experimental` (query broadcast), `oxc-parser`, `oxc-resolver`.
|
|
127
127
|
|
|
128
128
|
The CLI is a workspace package (`@flare/cli`, binary `flare`). In this repo it is on the path via workspace linking. A published consumer uses `flare init` after adding the package.
|
|
129
129
|
|
|
@@ -132,7 +132,7 @@ The CLI is a workspace package (`@flare/cli`, binary `flare`). In this repo it i
|
|
|
132
132
|
```bash
|
|
133
133
|
mkdir my-app && cd my-app
|
|
134
134
|
bun init -y
|
|
135
|
-
bun add @lovrozagar/flare solid-js vite vite-plugin
|
|
135
|
+
bun add @lovrozagar/flare solid-js @solidjs/web vite @solidjs/vite-plugin
|
|
136
136
|
flare init
|
|
137
137
|
bun run dev
|
|
138
138
|
```
|
|
@@ -786,7 +786,7 @@ import { DirectionScript } from "@lovrozagar/flare/direction";
|
|
|
786
786
|
</head>;
|
|
787
787
|
```
|
|
788
788
|
|
|
789
|
-
- **Locale** — optional `[[locale]]` segment or prefix. Cookie `flare.locale` + `Accept-Language`. Default locale is stripped (`/en/about` → `/about`). Playwright / bot UAs skip Set-Cookie (`isbot`). Prefetch (`
|
|
789
|
+
- **Locale** — optional `[[locale]]` segment or prefix. Cookie `flare.locale` + `Accept-Language`. Default locale is stripped (`/en/about` → `/about`). Playwright / bot UAs skip Set-Cookie (`isbot`). Prefetch (`flare-prefetch: 1`) never writes the cookie.
|
|
790
790
|
- **Theme** — `data-theme`, system preference, `localStorage`.
|
|
791
791
|
- **Direction** — `dir` / `data-dir`.
|
|
792
792
|
|
|
@@ -908,7 +908,7 @@ import { BroadcastProvider } from "@lovrozagar/flare/broadcast";
|
|
|
908
908
|
createRouter({ queryClientGetter: getQueryClient });
|
|
909
909
|
```
|
|
910
910
|
|
|
911
|
-
SSR dehydrates into the stream (`
|
|
911
|
+
SSR dehydrates into the stream (`__flare_queries` / `t:"q"`). `useSuspenseQuery` suspends until the dehydrated entry is ready. Optional `BroadcastProvider` invalidates across tabs.
|
|
912
912
|
|
|
913
913
|
```ts
|
|
914
914
|
import { createQueryClientGetter } from "@lovrozagar/flare/query-client";
|
|
@@ -993,7 +993,28 @@ Playwright helpers: hydration, FlareState shape, console-error filters. Product
|
|
|
993
993
|
|
|
994
994
|
## NDJSON protocol
|
|
995
995
|
|
|
996
|
-
SPA / prefetch / data requests send `
|
|
996
|
+
SPA / prefetch / data requests send `flare-data: 1`. Prefetch also sends `flare-prefetch: 1`. Stale match skip uses `flare-stale`. Flag value is always `"1"`. Headers are lowercase (HTTP/2 / Node). NDJSON `t` codes and FlareState keys stay short.
|
|
997
|
+
|
|
998
|
+
| Request | Meaning |
|
|
999
|
+
| ----------------- | ------------------------------------------------- |
|
|
1000
|
+
| `flare-data` | NDJSON data request. `Vary` always includes this. |
|
|
1001
|
+
| `flare-prefetch` | Prefetch (no locale cookie commit) |
|
|
1002
|
+
| `flare-stale` | Comma-separated match ids the client already has |
|
|
1003
|
+
| `flare-isr` | Internal ISR background re-render |
|
|
1004
|
+
| `flare-prerender` | Build-time prerender fetch |
|
|
1005
|
+
|
|
1006
|
+
| Response | Meaning |
|
|
1007
|
+
| -------------- | ------------------------ |
|
|
1008
|
+
| `flare-cache` | `HIT` / `MISS` / `STALE` |
|
|
1009
|
+
| `flare-render` | `ISR` / `SSG` / `SSR` |
|
|
1010
|
+
|
|
1011
|
+
| Path / HTML | Meaning |
|
|
1012
|
+
| ------------------------------- | ------------------------------------------ |
|
|
1013
|
+
| `/_flare/server-fn/{id}/{name}` | Server functions |
|
|
1014
|
+
| `flare_fn` | Hidden form field for no-JS POST |
|
|
1015
|
+
| `data-flare-hydrated` | Set on `<html>` after `solidHydrate` |
|
|
1016
|
+
| `__flare_defer` | Window queue for SSR deferred chunks |
|
|
1017
|
+
| `__flare_queries` | Window queue for SSR query-cache dehydrate |
|
|
997
1018
|
|
|
998
1019
|
| `t` | Meaning |
|
|
999
1020
|
| --- | --------------- |
|
|
@@ -1053,7 +1074,7 @@ export default defineConfig({
|
|
|
1053
1074
|
| `ignorePrefix` | extra ignored folder names | |
|
|
1054
1075
|
| `port` | Vite `server.port` (overrides CLI `--port`) | |
|
|
1055
1076
|
| `alias` | Vite alias | |
|
|
1056
|
-
| `solid` | passed to
|
|
1077
|
+
| `solid` | passed to `@solidjs/vite-plugin` | `ssr: true` is forced; `start: true` is rejected |
|
|
1057
1078
|
|
|
1058
1079
|
`dev: false` turns every `dev.*` flag off. Dev dashboard is `@node-only` in e2e (not on Workers).
|
|
1059
1080
|
|
|
@@ -1141,7 +1162,7 @@ Requires [Bun](https://bun.sh) 1.3+ and TypeScript 7.
|
|
|
1141
1162
|
bun install
|
|
1142
1163
|
bun run test # core unit
|
|
1143
1164
|
bun run test:cli # CLI unit
|
|
1144
|
-
bun run test:all # unit +
|
|
1165
|
+
bun run test:all # unit + build + e2e apps on node (dev then prod)
|
|
1145
1166
|
bun run test:all -- --env bun
|
|
1146
1167
|
bun run typecheck
|
|
1147
1168
|
bun run typecheck:consumers
|
|
@@ -1149,7 +1170,9 @@ bun run lint
|
|
|
1149
1170
|
bun run fmt:check
|
|
1150
1171
|
```
|
|
1151
1172
|
|
|
1152
|
-
GitHub Actions (`.github/workflows/ci.yml`): `test` runs typecheck, fmt, lint, unit. `e2e` is a matrix of node / bun / workers / deno.
|
|
1173
|
+
GitHub Actions (`.github/workflows/ci.yml`): `test` runs typecheck, fmt, lint, unit. `e2e` is a matrix of node / bun / workers / deno, each in Vite **dev** and (except deno/firefox) **prod** (`vite preview`).
|
|
1174
|
+
|
|
1175
|
+
Release: bump `packages/core/package.json`, commit, tag `vX.Y.Z` matching that version, and push the tag. `.github/workflows/release.yml` publishes `@lovrozagar/flare` to npm via GitHub Actions OIDC (no npm token) and opens the GitHub Release. The npm trusted publisher must name this file `release.yml`.
|
|
1153
1176
|
|
|
1154
1177
|
### Checks
|
|
1155
1178
|
|
|
@@ -1170,16 +1193,18 @@ Do not weaken `strict` or add `as any` to make typecheck pass.
|
|
|
1170
1193
|
|
|
1171
1194
|
### Test matrix
|
|
1172
1195
|
|
|
1173
|
-
| Command
|
|
1174
|
-
|
|
|
1175
|
-
| `bun run test`
|
|
1176
|
-
| `bun run test:cli`
|
|
1177
|
-
| `bun run test:e2e`
|
|
1178
|
-
| `bun run test:e2e:
|
|
1179
|
-
| `bun run test:e2e:
|
|
1180
|
-
| `bun run test:e2e:
|
|
1181
|
-
| `bun run test:e2e:
|
|
1182
|
-
| `bun run test:e2e:prod`
|
|
1196
|
+
| Command | What it proves |
|
|
1197
|
+
| ------------------------------- | ------------------------------------------ |
|
|
1198
|
+
| `bun run test` | Core unit + integration |
|
|
1199
|
+
| `bun run test:cli` | CLI unit |
|
|
1200
|
+
| `bun run test:e2e` | Playwright, every app × node, Vite **dev** |
|
|
1201
|
+
| `bun run test:e2e:prod` | Same tests, `vite build` + **preview** |
|
|
1202
|
+
| `bun run test:e2e:bun` | Same tests, Bun listen (dev) |
|
|
1203
|
+
| `bun run test:e2e:bun:prod` | Bun listen, prod preview |
|
|
1204
|
+
| `bun run test:e2e:workers` | Same tests, local workerd (dev) |
|
|
1205
|
+
| `bun run test:e2e:workers:prod` | workerd, prod preview |
|
|
1206
|
+
| `bun run test:e2e:deno` | Same tests, Deno (dev) |
|
|
1207
|
+
| `bun run test:e2e:firefox` | Same tests, Firefox (**dev only**) |
|
|
1183
1208
|
|
|
1184
1209
|
```bash
|
|
1185
1210
|
bun run test:e2e -- --app product
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lovrozagar/flare",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Solid meta-framework. Server-driven, NDJSON streaming, renderToStream.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"flare",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"repository": {
|
|
18
18
|
"type": "git",
|
|
19
|
-
"url": "https://github.com/lovrozagar/flare"
|
|
19
|
+
"url": "https://github.com/lovrozagar/flare",
|
|
20
|
+
"directory": "packages/core"
|
|
20
21
|
},
|
|
21
22
|
"files": [
|
|
22
23
|
"src",
|
|
@@ -106,6 +107,8 @@
|
|
|
106
107
|
"@capsizecss/metrics": "catalog:",
|
|
107
108
|
"@formatjs/intl-localematcher": "catalog:",
|
|
108
109
|
"@solidjs/testing-library": "catalog:",
|
|
110
|
+
"@solidjs/vite-plugin": "catalog:",
|
|
111
|
+
"@solidjs/web": "catalog:",
|
|
109
112
|
"@tanstack/query-broadcast-client-experimental": "catalog:",
|
|
110
113
|
"@tanstack/solid-query": "catalog:",
|
|
111
114
|
"@types/negotiator": "catalog:",
|
|
@@ -120,14 +123,15 @@
|
|
|
120
123
|
"solid-js": "catalog:",
|
|
121
124
|
"typescript": "catalog:",
|
|
122
125
|
"vite": "catalog:",
|
|
123
|
-
"vite-plugin-solid": "catalog:",
|
|
124
126
|
"vitest": "catalog:"
|
|
125
127
|
},
|
|
126
128
|
"peerDependencies": {
|
|
127
129
|
"@babel/parser": ">=8.0.0",
|
|
128
130
|
"@formatjs/intl-localematcher": ">=0.6.0",
|
|
131
|
+
"@solidjs/vite-plugin": ">=3.0.0-next.0",
|
|
132
|
+
"@solidjs/web": ">=2.0.0-rc.0",
|
|
129
133
|
"@tanstack/query-broadcast-client-experimental": ">=5.0.0",
|
|
130
|
-
"@tanstack/solid-query": ">=
|
|
134
|
+
"@tanstack/solid-query": ">=6.0.0-rc.0",
|
|
131
135
|
"isbot": ">=5.0.0",
|
|
132
136
|
"magic-string": ">=0.30.0",
|
|
133
137
|
"negotiator": ">=1.0.0",
|
|
@@ -135,9 +139,8 @@
|
|
|
135
139
|
"oxc-parser": ">=0.139.0",
|
|
136
140
|
"oxc-resolver": ">=11.23.0",
|
|
137
141
|
"sharp": ">=0.33.0",
|
|
138
|
-
"solid-js": ">=
|
|
139
|
-
"vite": ">=8.0.0"
|
|
140
|
-
"vite-plugin-solid": ">=2.0.0"
|
|
142
|
+
"solid-js": ">=2.0.0-rc.0",
|
|
143
|
+
"vite": ">=8.0.0"
|
|
141
144
|
},
|
|
142
145
|
"peerDependenciesMeta": {
|
|
143
146
|
"@formatjs/intl-localematcher": {
|
package/src/broadcast/index.ts
CHANGED
|
@@ -42,7 +42,7 @@ export function createBroadcastSignal<T>(key: string, initialValue: T): [() => T
|
|
|
42
42
|
|
|
43
43
|
let entry = registry.get(key);
|
|
44
44
|
if (!entry) {
|
|
45
|
-
const [value, setValue] = createSignal<T>
|
|
45
|
+
const [value, setValue] = createSignal(initialValue as Exclude<T, Function>);
|
|
46
46
|
entry = {
|
|
47
47
|
refCount: 0,
|
|
48
48
|
signal: [value, setValue as (v: unknown) => void],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { JSX } from "
|
|
1
|
+
import type { JSX } from "@solidjs/web";
|
|
2
2
|
import type { InternalChannel } from "./channel.ts";
|
|
3
3
|
import { BroadcastCtx, noopChannel } from "./context.ts";
|
|
4
4
|
|
|
5
5
|
export function BroadcastProvider(props: { children: JSX.Element; value?: InternalChannel }): JSX.Element {
|
|
6
|
-
return <BroadcastCtx
|
|
6
|
+
return <BroadcastCtx value={props.value ?? noopChannel}>{props.children}</BroadcastCtx>;
|
|
7
7
|
}
|
package/src/caches/index.ts
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
import type { HeadConfig } from "../route-builder/types.ts";
|
|
2
2
|
|
|
3
|
+
export function resolveCacheTags(
|
|
4
|
+
cache:
|
|
5
|
+
| {
|
|
6
|
+
cdn?: { tags?: string[] | ((ctx: { params: Record<string, string | string[]> }) => string[]) } | false;
|
|
7
|
+
ssr?: { tags?: string[] | ((ctx: { params: Record<string, string | string[]> }) => string[]) } | false;
|
|
8
|
+
}
|
|
9
|
+
| undefined,
|
|
10
|
+
params: Record<string, string | string[]>,
|
|
11
|
+
): string[] | undefined {
|
|
12
|
+
if (!cache) return undefined;
|
|
13
|
+
const tags: string[] = [];
|
|
14
|
+
const push = (
|
|
15
|
+
src: { tags?: string[] | ((ctx: { params: Record<string, string | string[]> }) => string[]) } | false | undefined,
|
|
16
|
+
) => {
|
|
17
|
+
if (!src || !src.tags) return;
|
|
18
|
+
const list = typeof src.tags === "function" ? src.tags({ params }) : src.tags;
|
|
19
|
+
for (const t of list) {
|
|
20
|
+
if (typeof t === "string" && t.length > 0 && !tags.includes(t)) tags.push(t);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
push(cache.cdn);
|
|
24
|
+
push(cache.ssr);
|
|
25
|
+
return tags.length > 0 ? tags : undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
3
28
|
export interface CachedMatch {
|
|
4
29
|
data: unknown;
|
|
5
30
|
error?: unknown;
|
|
31
|
+
gcTime?: number;
|
|
6
32
|
hasDeferred?: boolean;
|
|
7
33
|
headConfig?: HeadConfig;
|
|
8
34
|
invalid: boolean;
|
package/src/components/await.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createEffect, createMemo, createSignal, untrack } from "solid-js";
|
|
2
|
+
import type { JSX } from "@solidjs/web";
|
|
2
3
|
|
|
3
4
|
export interface Deferred<T> {
|
|
4
5
|
__deferred: true;
|
|
@@ -47,8 +48,10 @@ export function getResolvedError(value: unknown): Error | undefined {
|
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
export function Await<T>(props: AwaitProps<T>): JSX.Element {
|
|
50
|
-
|
|
51
|
-
const
|
|
51
|
+
/* Snapshot only — `createEffect` below tracks `props.promise` for updates. */
|
|
52
|
+
const initialPromise = untrack(() => props.promise);
|
|
53
|
+
const initialResolved = getResolvedValue(initialPromise);
|
|
54
|
+
const initialError = getResolvedError(initialPromise);
|
|
52
55
|
|
|
53
56
|
function computeInitialStatus(): AwaitStatus {
|
|
54
57
|
if (initialResolved !== undefined) return "success";
|
|
@@ -57,10 +60,10 @@ export function Await<T>(props: AwaitProps<T>): JSX.Element {
|
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
const [status, setStatus] = createSignal<AwaitStatus>(computeInitialStatus());
|
|
60
|
-
const [data, setData] = createSignal<T | undefined>(initialResolved);
|
|
63
|
+
const [data, setData] = createSignal<T | undefined>(initialResolved as Exclude<T | undefined, Function>);
|
|
61
64
|
const [error, setError] = createSignal<Error | undefined>(initialError);
|
|
62
65
|
|
|
63
|
-
let currentPromise = getPromise(
|
|
66
|
+
let currentPromise = getPromise(initialPromise);
|
|
64
67
|
|
|
65
68
|
if (initialResolved === undefined && !initialError && currentPromise) {
|
|
66
69
|
trackPromise(currentPromise);
|
|
@@ -70,18 +73,14 @@ export function Await<T>(props: AwaitProps<T>): JSX.Element {
|
|
|
70
73
|
p.then(
|
|
71
74
|
(result) => {
|
|
72
75
|
if (currentPromise === p) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
setStatus("success");
|
|
76
|
-
});
|
|
76
|
+
setData(() => result);
|
|
77
|
+
setStatus("success");
|
|
77
78
|
}
|
|
78
79
|
},
|
|
79
80
|
(err: unknown) => {
|
|
80
81
|
if (currentPromise === p) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
setStatus("error");
|
|
84
|
-
});
|
|
82
|
+
setError(() => (err instanceof Error ? err : new Error(String(err))));
|
|
83
|
+
setStatus("error");
|
|
85
84
|
}
|
|
86
85
|
},
|
|
87
86
|
);
|
|
@@ -90,43 +89,41 @@ export function Await<T>(props: AwaitProps<T>): JSX.Element {
|
|
|
90
89
|
function reset(): void {
|
|
91
90
|
const p = getPromise(props.promise);
|
|
92
91
|
currentPromise = p;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
setError(undefined);
|
|
97
|
-
});
|
|
92
|
+
setStatus("pending");
|
|
93
|
+
setData(undefined);
|
|
94
|
+
setError(undefined);
|
|
98
95
|
if (p) trackPromise(p);
|
|
99
96
|
}
|
|
100
97
|
|
|
101
|
-
/* Watch for promise prop changes
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
98
|
+
/* Watch for promise prop changes. Wrap in an object so a Promise value is
|
|
99
|
+
not treated as an async computation (Solid 2 unwraps returned Promises). */
|
|
100
|
+
createEffect(
|
|
101
|
+
() => ({ promise: props.promise }),
|
|
102
|
+
({ promise }) => {
|
|
103
|
+
const newPromise = getPromise(promise);
|
|
104
|
+
if (newPromise === currentPromise) return;
|
|
105
|
+
currentPromise = newPromise;
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
const resolved = getResolvedValue(promise);
|
|
108
|
+
const resolvedError = getResolvedError(promise);
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
batch(() => {
|
|
110
|
+
if (resolved !== undefined) {
|
|
112
111
|
setData(() => resolved);
|
|
113
112
|
setStatus("success");
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (resolvedError) {
|
|
118
|
-
batch(() => {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (resolvedError) {
|
|
119
116
|
setError(() => resolvedError);
|
|
120
117
|
setStatus("error");
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
124
120
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
121
|
+
setStatus("pending");
|
|
122
|
+
if (newPromise) trackPromise(newPromise);
|
|
123
|
+
},
|
|
124
|
+
);
|
|
128
125
|
|
|
129
|
-
return (() => {
|
|
126
|
+
return createMemo(() => {
|
|
130
127
|
const s = status();
|
|
131
128
|
if (s === "success") {
|
|
132
129
|
return props.children(data() as T);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { createSignal, For,
|
|
2
|
-
import {
|
|
1
|
+
import { createSignal, For, onSettled, Show } from "solid-js";
|
|
2
|
+
import type { JSX } from "@solidjs/web";
|
|
3
|
+
import { Portal } from "@solidjs/web";
|
|
3
4
|
import { createDevErrorStore, type DevErrorStore } from "./index.ts";
|
|
4
5
|
|
|
5
6
|
let globalStore: DevErrorStore | undefined;
|
|
@@ -34,16 +35,14 @@ export function DevErrorOverlay(): JSX.Element {
|
|
|
34
35
|
refresh();
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
onSettled(() => {
|
|
38
39
|
if (typeof window === "undefined") return;
|
|
39
40
|
window.addEventListener("error", handleWindowError);
|
|
40
41
|
window.addEventListener("unhandledrejection", handleUnhandledRejection);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
window.removeEventListener("error", handleWindowError);
|
|
46
|
-
window.removeEventListener("unhandledrejection", handleUnhandledRejection);
|
|
42
|
+
return () => {
|
|
43
|
+
window.removeEventListener("error", handleWindowError);
|
|
44
|
+
window.removeEventListener("unhandledrejection", handleUnhandledRejection);
|
|
45
|
+
};
|
|
47
46
|
});
|
|
48
47
|
|
|
49
48
|
function dismiss(id: string): void {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { JSX } from "
|
|
2
|
-
import { NoHydration } from "
|
|
1
|
+
import type { JSX } from "@solidjs/web";
|
|
2
|
+
import { NoHydration } from "@solidjs/web";
|
|
3
3
|
import type { DirectionConfig } from "../direction.ts";
|
|
4
4
|
import { getDirectionScript } from "../direction.ts";
|
|
5
5
|
import { useSSRContext } from "./ssr-context.tsx";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { JSX } from "
|
|
2
|
-
import { NoHydration } from "
|
|
1
|
+
import type { JSX } from "@solidjs/web";
|
|
2
|
+
import { NoHydration } from "@solidjs/web";
|
|
3
3
|
import type { LocaleConfig } from "../locale/index.tsx";
|
|
4
4
|
import { getLocaleScript } from "../locale/index.tsx";
|
|
5
5
|
import { useSSRContext } from "./ssr-context.tsx";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { createContext,
|
|
1
|
+
import { createContext, useContext } from "solid-js";
|
|
2
|
+
import type { JSX } from "@solidjs/web";
|
|
2
3
|
import type { DirectionConfig } from "../direction.ts";
|
|
3
4
|
import type { LocaleConfig } from "../locale/index.tsx";
|
|
4
5
|
import type { HeadConfig } from "../route-builder/types.ts";
|
|
@@ -15,23 +16,15 @@ export interface SSRContextValue {
|
|
|
15
16
|
theme?: ThemeConfig;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
const SSRCtx = createContext<SSRContextValue>();
|
|
19
|
+
const SSRCtx = createContext<SSRContextValue | null>(null);
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
(sharedConfig.context as Record<string, unknown>).flare = value;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
21
|
+
/** @deprecated Wrap the tree with SSRContextProvider. Solid 2 has no sharedConfig.context. */
|
|
22
|
+
export function setSSRContext(_value: SSRContextValue): void {}
|
|
25
23
|
|
|
26
24
|
export function useSSRContext(): SSRContextValue | undefined {
|
|
27
|
-
|
|
28
|
-
if (sharedConfig.context) {
|
|
29
|
-
return (sharedConfig.context as Record<string, unknown>).flare as SSRContextValue | undefined;
|
|
30
|
-
}
|
|
31
|
-
/* Testing fallback: read from Solid context provider */
|
|
32
|
-
return useContext(SSRCtx);
|
|
25
|
+
return useContext(SSRCtx) ?? undefined;
|
|
33
26
|
}
|
|
34
27
|
|
|
35
28
|
export function SSRContextProvider(props: { children: JSX.Element; value: SSRContextValue }): JSX.Element {
|
|
36
|
-
return <SSRCtx
|
|
29
|
+
return <SSRCtx value={props.value}>{props.children}</SSRCtx>;
|
|
37
30
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { JSX } from "
|
|
2
|
-
import { NoHydration } from "
|
|
1
|
+
import type { JSX } from "@solidjs/web";
|
|
2
|
+
import { NoHydration } from "@solidjs/web";
|
|
3
3
|
import type { ThemeConfig } from "../theme.ts";
|
|
4
4
|
import { getThemeScript } from "../theme.ts";
|
|
5
5
|
import { useSSRContext } from "./ssr-context.tsx";
|
package/src/defer/index.ts
CHANGED
|
@@ -80,3 +80,25 @@ export function isDeferred(value: unknown): value is Deferred<unknown> {
|
|
|
80
80
|
(value as Record<string, unknown>).__deferred === true
|
|
81
81
|
);
|
|
82
82
|
}
|
|
83
|
+
|
|
84
|
+
/** True if `data` is or contains a deferred marker. Used to skip SSR store cache of unresolved streams. */
|
|
85
|
+
export function containsDeferred(data: unknown, seen?: WeakSet<object>): boolean {
|
|
86
|
+
if (data === null || data === undefined || typeof data !== "object") return false;
|
|
87
|
+
if (isDeferred(data)) return true;
|
|
88
|
+
|
|
89
|
+
const tracking = seen ?? new WeakSet<object>();
|
|
90
|
+
if (tracking.has(data as object)) return false;
|
|
91
|
+
tracking.add(data as object);
|
|
92
|
+
|
|
93
|
+
if (Array.isArray(data)) {
|
|
94
|
+
for (const item of data) {
|
|
95
|
+
if (containsDeferred(item, tracking)) return true;
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
for (const value of Object.values(data as Record<string, unknown>)) {
|
|
101
|
+
if (containsDeferred(value, tracking)) return true;
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|