@lovrozagar/flare 0.5.1 → 0.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovrozagar/flare",
3
- "version": "0.5.1",
3
+ "version": "0.7.0",
4
4
  "description": "Solid meta-framework. Server-driven, NDJSON streaming, renderToStream.",
5
5
  "keywords": [
6
6
  "flare",
@@ -130,10 +130,10 @@
130
130
  "peerDependencies": {
131
131
  "@babel/parser": ">=8.0.0",
132
132
  "@formatjs/intl-localematcher": ">=0.6.0",
133
- "@solidjs/vite-plugin": ">=3.0.0-next.0",
134
- "@solidjs/web": ">=2.0.0-rc.0",
133
+ "@solidjs/vite-plugin": ">=3.0.0-next.47",
134
+ "@solidjs/web": ">=2.0.0-rc.13",
135
135
  "@tanstack/query-broadcast-client-experimental": ">=5.0.0",
136
- "@tanstack/solid-query": ">=6.0.0-rc.0",
136
+ "@tanstack/solid-query": ">=6.0.0-rc.5",
137
137
  "isbot": ">=5.0.0",
138
138
  "magic-string": ">=0.30.0",
139
139
  "negotiator": ">=1.0.0",
@@ -141,7 +141,7 @@
141
141
  "oxc-parser": ">=0.139.0",
142
142
  "oxc-resolver": ">=11.23.0",
143
143
  "sharp": ">=0.33.0",
144
- "solid-js": ">=2.0.0-rc.0",
144
+ "solid-js": ">=2.0.0-rc.13",
145
145
  "tailwindcss": ">=4.0.0",
146
146
  "vite": ">=8.0.0"
147
147
  },
@@ -11,7 +11,7 @@ export {
11
11
  } from "./await.tsx";
12
12
  export { DirectionScript, type DirectionScriptProps } from "./direction-script.tsx";
13
13
  export { ResetCSS } from "./reset-css.tsx";
14
- export { SSRContextProvider, type SSRContextValue, setSSRContext, useSSRContext } from "./ssr-context.tsx";
14
+ export { SSRContextProvider, type SSRContextValue, useSSRContext } from "./ssr-context.tsx";
15
15
  export { ThemeScript, type ThemeScriptProps } from "./theme-script.tsx";
16
16
  export { ViewTransitionCSS, type ViewTransitionCSSProps } from "./view-transition-css.tsx";
17
17
 
@@ -18,9 +18,6 @@ export interface SSRContextValue {
18
18
 
19
19
  const SSRCtx = createContext<SSRContextValue | null>(null);
20
20
 
21
- /** @deprecated Wrap the tree with SSRContextProvider. Solid 2 has no sharedConfig.context. */
22
- export function setSSRContext(_value: SSRContextValue): void {}
23
-
24
21
  export function useSSRContext(): SSRContextValue | undefined {
25
22
  return useContext(SSRCtx) ?? undefined;
26
23
  }
@@ -1,4 +1,4 @@
1
- import { createContext, createEffect, createSignal, onSettled, sharedConfig, untrack, useContext } from "solid-js";
1
+ import { createContext, createEffect, createSignal, isHydrating, onSettled, untrack, useContext } from "solid-js";
2
2
  import { isServer, type JSX } from "@solidjs/web";
3
3
 
4
4
  export type Direction = "ltr" | "rtl";
@@ -61,7 +61,7 @@ export function DirectionProvider(props: { children: JSX.Element; config?: Direc
61
61
  storageKey: props.config?.storageKey ?? DEFAULT_CONFIG.storageKey,
62
62
  };
63
63
 
64
- const hydrating = isServer || !!sharedConfig.hydrating;
64
+ const hydrating = isServer || isHydrating();
65
65
 
66
66
  /* During SSR + hydration, start from defaultDir so the tree matches.
67
67
  DirectionScript already applied localStorage to <html> before first paint. */
@@ -1,6 +1,6 @@
1
1
  import type { JSX } from "@solidjs/web";
2
2
  import { Dynamic, isServer } from "@solidjs/web";
3
- import { type Component, createSignal, onSettled, Show, sharedConfig, untrack } from "solid-js";
3
+ import { type Component, createSignal, isHydrating, onSettled, Show, untrack } from "solid-js";
4
4
  import { retryImport } from "../internal.ts";
5
5
  import { warn } from "../logger.ts";
6
6
  import { GLOBAL_LAZY_LOADED, GLOBAL_LAZY_PENDING } from "../protocol.ts";
@@ -56,7 +56,7 @@ export function lazy<P extends Record<string, unknown>>(options: LazyOptions<P>)
56
56
  getGlobalPending().add(loadPromise);
57
57
 
58
58
  return ((props: P) => {
59
- const isSSR = isServer || !!sharedConfig.hydrating;
59
+ const isSSR = isServer || isHydrating();
60
60
 
61
61
  /* Pre-known error at render time → throw from component body */
62
62
  if (loadError) throw loadError;
@@ -116,7 +116,7 @@ export function clientLazy<P extends Record<string, unknown>>(
116
116
  }
117
117
 
118
118
  return ((props: P & { pending?: Component<P> }) => {
119
- const isSSR = isServer || !!sharedConfig.hydrating;
119
+ const isSSR = isServer || isHydrating();
120
120
 
121
121
  /* Pre-known error at render time → throw from component body */
122
122
  if (loadError) throw loadError;
@@ -605,7 +605,7 @@ export function createServerFnPlugin(_config?: { ignorePrefix?: string }): ViteP
605
605
  if (hasServerFnCalls(transformed)) {
606
606
  transformed = replaceServerFnConfigs(transformed, (content) => {
607
607
  const fnId = computeFnId(id, content);
608
- return ` __id: "${fnId}",${content}`;
608
+ return ` __client: true, __id: "${fnId}",${content}`;
609
609
  });
610
610
  }
611
611
 
@@ -1,4 +1,5 @@
1
1
  import { FORM_FN_FIELD, parseServerFnPathname, serverFnPath } from "../protocol.ts";
2
+ import { callServerFnOverHttp } from "./rpc.ts";
2
3
  import {
3
4
  isNotFoundError,
4
5
  isRedirectResponse,
@@ -26,6 +27,8 @@ import { runValidator } from "../validation/index.ts";
26
27
  export type { Validator } from "../validation/index.ts";
27
28
 
28
29
  export interface ServerFnConfig {
30
+ /** Set by the client build transform: calls go over HTTP, the handler body is gone. */
31
+ __client?: boolean;
29
32
  __id?: string;
30
33
  method?: "get" | "post";
31
34
  name: string;
@@ -87,6 +90,7 @@ export interface ServerFnStreamRegistration extends ServerFnRegistrationBase {
87
90
  export type ServerFnRegistration = ServerFnHandlerRegistration | ServerFnStreamRegistration;
88
91
 
89
92
  interface BuilderState {
93
+ __client?: boolean;
90
94
  __id?: string;
91
95
  authenticate: boolean;
92
96
  authorizeFn?: (ctx: { auth: unknown; input: unknown }) => boolean | Promise<boolean>;
@@ -139,6 +143,14 @@ function createBuilderTerminal<TAuth, TInput, TOutput>(
139
143
  name: state.name,
140
144
  };
141
145
 
146
+ if (state.__client) {
147
+ /* browser: the server validates, authenticates, and runs the handler */
148
+ const clientFn: ServerFn<TInput, TOutput> = async (input: TInput): Promise<TOutput> =>
149
+ (await callServerFnOverHttp<TOutput>(registration, input)).data;
150
+ clientFn._registration = registration;
151
+ return clientFn;
152
+ }
153
+
142
154
  const serverFn: ServerFn<TInput, TOutput> = async (_input: TInput): Promise<TOutput> => {
143
155
  if (state.authenticate) {
144
156
  throw new Error(
@@ -271,6 +283,7 @@ function createBuilder<TAuth, TInput, TOutput>(state: BuilderState): ServerFnBui
271
283
 
272
284
  export function createServerFn(config: ServerFnConfig): ServerFnBuilder<null, void, unknown> {
273
285
  return createBuilder<null, void, unknown>({
286
+ __client: config.__client,
274
287
  __id: config.__id,
275
288
  authenticate: false,
276
289
  method: config.method ?? "post",
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Browser side of a server fn call: one HTTP request to `/_flare/server-fn/{id}/{name}`.
3
+ * Client-safe — must not import server-context or anything that reaches node:*.
4
+ */
5
+
6
+ import { serverFnPath } from "../protocol.ts";
7
+ import { serverFnGetUrl } from "./get-input.ts";
8
+ import { throwServerFnHttpError } from "./http-error.ts";
9
+
10
+ export interface ServerFnTarget {
11
+ id?: string;
12
+ method?: string;
13
+ name: string;
14
+ }
15
+
16
+ export interface ServerFnEnvelope<TOutput> {
17
+ data: TOutput;
18
+ queries?: Array<{ data: unknown; key: unknown[] }>;
19
+ revalidatedTags?: string[];
20
+ }
21
+
22
+ export async function callServerFnOverHttp<TOutput>(
23
+ target: ServerFnTarget,
24
+ input: unknown,
25
+ ): Promise<ServerFnEnvelope<TOutput>> {
26
+ const url = serverFnPath(target.id ?? target.name, target.name);
27
+ const res =
28
+ target.method === "get"
29
+ ? await fetch(serverFnGetUrl(url, input))
30
+ : await fetch(url, {
31
+ body: input !== undefined ? JSON.stringify(input) : undefined,
32
+ headers: { "content-type": "application/json" },
33
+ method: "POST",
34
+ });
35
+
36
+ if (!res.ok) {
37
+ const body: unknown = await res.json().catch(() => null);
38
+ throwServerFnHttpError(body, res.status, target.name);
39
+ }
40
+ return (await res.json()) as ServerFnEnvelope<TOutput>;
41
+ }
@@ -4,9 +4,7 @@
4
4
  * to avoid pulling in node:async_hooks on the client.
5
5
  */
6
6
 
7
- import { serverFnGetUrl } from "./server-fn/get-input.ts";
8
- import { throwServerFnHttpError } from "./server-fn/http-error.ts";
9
- import { serverFnPath } from "./protocol.ts";
7
+ import { callServerFnOverHttp } from "./server-fn/rpc.ts";
10
8
 
11
9
  export interface PiggybackedQuery {
12
10
  data: unknown;
@@ -52,28 +50,7 @@ export function serverFnQueryOptions<TInput, TOutput>(
52
50
  return serverFn(config?.input as TInput);
53
51
  }
54
52
 
55
- const url = serverFnPath(id, name);
56
- const method = reg?.method ?? "post";
57
-
58
- const res =
59
- method === "get"
60
- ? await fetch(serverFnGetUrl(url, config?.input))
61
- : await fetch(url, {
62
- body: config?.input !== undefined ? JSON.stringify(config.input) : undefined,
63
- headers: { "content-type": "application/json" },
64
- method: "POST",
65
- });
66
-
67
- if (!res.ok) {
68
- const body: unknown = await res.json().catch(() => null);
69
- throwServerFnHttpError(body, res.status, name);
70
- }
71
-
72
- const json = (await res.json()) as {
73
- data: TOutput;
74
- queries?: Array<{ data: unknown; key: unknown[] }>;
75
- revalidatedTags?: string[];
76
- };
53
+ const json = await callServerFnOverHttp<TOutput>({ id, method: reg?.method ?? "post", name }, config?.input);
77
54
 
78
55
  if (json.queries && config?.queryClient) {
79
56
  for (const q of json.queries) {
@@ -117,23 +94,8 @@ export function serverFnMutationOptions<TInput, TOutput>(
117
94
  return serverFn(input);
118
95
  }
119
96
 
120
- const url = serverFnPath(id, name);
121
- const res = await fetch(url, {
122
- body: input !== undefined ? JSON.stringify(input) : undefined,
123
- headers: { "content-type": "application/json" },
124
- method: "POST",
125
- });
126
-
127
- if (!res.ok) {
128
- const body: unknown = await res.json().catch(() => null);
129
- throwServerFnHttpError(body, res.status, name);
130
- }
131
-
132
- const json = (await res.json()) as {
133
- data: TOutput;
134
- queries?: Array<{ data: unknown; key: unknown[] }>;
135
- revalidatedTags?: string[];
136
- };
97
+ /* mutations always POST, whatever the fn's own method */
98
+ const json = await callServerFnOverHttp<TOutput>({ id, method: "post", name }, input);
137
99
 
138
100
  if (json.queries && config?.queryClient) {
139
101
  for (const q of json.queries) {
@@ -3,8 +3,8 @@ import {
3
3
  createEffect,
4
4
  createMemo,
5
5
  createSignal,
6
+ isHydrating,
6
7
  onSettled,
7
- sharedConfig,
8
8
  untrack,
9
9
  useContext,
10
10
  } from "solid-js";
@@ -68,7 +68,7 @@ export function ThemeProvider(props: { children: JSX.Element; config?: ThemeConf
68
68
  themes: props.config?.themes ?? DEFAULT_CONFIG.themes,
69
69
  };
70
70
 
71
- const hydrating = isServer || !!sharedConfig.hydrating;
71
+ const hydrating = isServer || isHydrating();
72
72
 
73
73
  /* During SSR + hydration, always start from defaultTheme so the tree matches.
74
74
  ThemeScript already applied localStorage to <html> before first paint.