@fragno-dev/core 0.0.1
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/.turbo/turbo-build.log +61 -0
- package/.turbo/turbo-types$colon$check.log +2 -0
- package/dist/api/api.d.ts +2 -0
- package/dist/api/api.js +3 -0
- package/dist/api-CBDGZiLC.d.ts +278 -0
- package/dist/api-CBDGZiLC.d.ts.map +1 -0
- package/dist/api-DgHfYjq2.js +54 -0
- package/dist/api-DgHfYjq2.js.map +1 -0
- package/dist/client/client.d.ts +3 -0
- package/dist/client/client.js +6 -0
- package/dist/client/client.svelte.d.ts +33 -0
- package/dist/client/client.svelte.d.ts.map +1 -0
- package/dist/client/client.svelte.js +123 -0
- package/dist/client/client.svelte.js.map +1 -0
- package/dist/client/react.d.ts +58 -0
- package/dist/client/react.d.ts.map +1 -0
- package/dist/client/react.js +80 -0
- package/dist/client/react.js.map +1 -0
- package/dist/client/vanilla.d.ts +61 -0
- package/dist/client/vanilla.d.ts.map +1 -0
- package/dist/client/vanilla.js +136 -0
- package/dist/client/vanilla.js.map +1 -0
- package/dist/client/vue.d.ts +39 -0
- package/dist/client/vue.d.ts.map +1 -0
- package/dist/client/vue.js +108 -0
- package/dist/client/vue.js.map +1 -0
- package/dist/client-DWjxKDnE.js +703 -0
- package/dist/client-DWjxKDnE.js.map +1 -0
- package/dist/client-XFdAy-IQ.d.ts +287 -0
- package/dist/client-XFdAy-IQ.d.ts.map +1 -0
- package/dist/integrations/astro.d.ts +18 -0
- package/dist/integrations/astro.d.ts.map +1 -0
- package/dist/integrations/astro.js +16 -0
- package/dist/integrations/astro.js.map +1 -0
- package/dist/integrations/next-js.d.ts +15 -0
- package/dist/integrations/next-js.d.ts.map +1 -0
- package/dist/integrations/next-js.js +17 -0
- package/dist/integrations/next-js.js.map +1 -0
- package/dist/integrations/react-ssr.d.ts +19 -0
- package/dist/integrations/react-ssr.d.ts.map +1 -0
- package/dist/integrations/react-ssr.js +38 -0
- package/dist/integrations/react-ssr.js.map +1 -0
- package/dist/integrations/svelte-kit.d.ts +21 -0
- package/dist/integrations/svelte-kit.d.ts.map +1 -0
- package/dist/integrations/svelte-kit.js +18 -0
- package/dist/integrations/svelte-kit.js.map +1 -0
- package/dist/mod.d.ts +3 -0
- package/dist/mod.js +177 -0
- package/dist/mod.js.map +1 -0
- package/dist/route-Bp6eByhz.js +331 -0
- package/dist/route-Bp6eByhz.js.map +1 -0
- package/dist/ssr-tJHqcNSw.js +48 -0
- package/dist/ssr-tJHqcNSw.js.map +1 -0
- package/package.json +127 -0
- package/src/api/api.test.ts +140 -0
- package/src/api/api.ts +106 -0
- package/src/api/error.ts +47 -0
- package/src/api/fragment.test.ts +509 -0
- package/src/api/fragment.ts +277 -0
- package/src/api/internal/path-runtime.test.ts +121 -0
- package/src/api/internal/path-type.test.ts +602 -0
- package/src/api/internal/path.ts +322 -0
- package/src/api/internal/response-stream.ts +118 -0
- package/src/api/internal/route.test.ts +56 -0
- package/src/api/internal/route.ts +9 -0
- package/src/api/request-input-context.test.ts +437 -0
- package/src/api/request-input-context.ts +201 -0
- package/src/api/request-middleware.test.ts +544 -0
- package/src/api/request-middleware.ts +126 -0
- package/src/api/request-output-context.test.ts +626 -0
- package/src/api/request-output-context.ts +175 -0
- package/src/api/route.test.ts +176 -0
- package/src/api/route.ts +152 -0
- package/src/client/client-builder.test.ts +264 -0
- package/src/client/client-error.test.ts +15 -0
- package/src/client/client-error.ts +141 -0
- package/src/client/client-types.test.ts +493 -0
- package/src/client/client.ssr.test.ts +173 -0
- package/src/client/client.svelte.test.ts +837 -0
- package/src/client/client.svelte.ts +278 -0
- package/src/client/client.test.ts +1690 -0
- package/src/client/client.ts +1035 -0
- package/src/client/component.test.svelte +21 -0
- package/src/client/internal/ndjson-streaming.test.ts +457 -0
- package/src/client/internal/ndjson-streaming.ts +248 -0
- package/src/client/react.test.ts +947 -0
- package/src/client/react.ts +241 -0
- package/src/client/vanilla.test.ts +867 -0
- package/src/client/vanilla.ts +265 -0
- package/src/client/vue.test.ts +754 -0
- package/src/client/vue.ts +242 -0
- package/src/http/http-status.ts +60 -0
- package/src/integrations/astro.ts +17 -0
- package/src/integrations/next-js.ts +31 -0
- package/src/integrations/react-ssr.ts +40 -0
- package/src/integrations/svelte-kit.ts +41 -0
- package/src/mod.ts +20 -0
- package/src/util/async.test.ts +85 -0
- package/src/util/async.ts +96 -0
- package/src/util/content-type.test.ts +136 -0
- package/src/util/content-type.ts +84 -0
- package/src/util/nanostores.test.ts +28 -0
- package/src/util/nanostores.ts +65 -0
- package/src/util/ssr.ts +75 -0
- package/src/util/types-util.ts +16 -0
- package/tsconfig.json +10 -0
- package/tsdown.config.ts +21 -0
- package/vitest.config.ts +10 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import type { FetcherValue } from "@nanostores/query";
|
|
2
|
+
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
3
|
+
import { listenKeys, type ReadableAtom, type Store, type StoreValue } from "nanostores";
|
|
4
|
+
import { useCallback, useMemo, useRef, useSyncExternalStore, type DependencyList } from "react";
|
|
5
|
+
import type { NonGetHTTPMethod } from "../api/api";
|
|
6
|
+
import type { FragnoClientMutatorData, FragnoClientHookData } from "./client";
|
|
7
|
+
import { isGetHook, isMutatorHook, isStore, type FragnoStoreData } from "./client";
|
|
8
|
+
import type { FragnoClientError } from "./client-error";
|
|
9
|
+
import { hydrateFromWindow } from "../util/ssr";
|
|
10
|
+
import type { InferOr } from "../util/types-util";
|
|
11
|
+
import type {
|
|
12
|
+
ExtractPathParamsOrWiden,
|
|
13
|
+
HasPathParams,
|
|
14
|
+
MaybeExtractPathParamsOrWiden,
|
|
15
|
+
QueryParamsHint,
|
|
16
|
+
} from "../api/internal/path";
|
|
17
|
+
import { isReadableAtom } from "../util/nanostores";
|
|
18
|
+
|
|
19
|
+
export type FragnoReactHook<
|
|
20
|
+
_TMethod extends "GET",
|
|
21
|
+
TPath extends string,
|
|
22
|
+
TOutputSchema extends StandardSchemaV1,
|
|
23
|
+
TErrorCode extends string,
|
|
24
|
+
TQueryParameters extends string,
|
|
25
|
+
> = (args?: {
|
|
26
|
+
path?: MaybeExtractPathParamsOrWiden<TPath, string | ReadableAtom<string>>;
|
|
27
|
+
query?: QueryParamsHint<TQueryParameters, string | ReadableAtom<string>>;
|
|
28
|
+
}) => FetcherValue<
|
|
29
|
+
StandardSchemaV1.InferOutput<TOutputSchema>,
|
|
30
|
+
FragnoClientError<NonNullable<TErrorCode>>
|
|
31
|
+
>;
|
|
32
|
+
|
|
33
|
+
export type FragnoReactMutator<
|
|
34
|
+
_TMethod extends NonGetHTTPMethod,
|
|
35
|
+
TPath extends string,
|
|
36
|
+
TInputSchema extends StandardSchemaV1 | undefined,
|
|
37
|
+
TOutputSchema extends StandardSchemaV1 | undefined,
|
|
38
|
+
TErrorCode extends string,
|
|
39
|
+
TQueryParameters extends string,
|
|
40
|
+
> = () => {
|
|
41
|
+
mutate: ({
|
|
42
|
+
body,
|
|
43
|
+
path,
|
|
44
|
+
query,
|
|
45
|
+
}: {
|
|
46
|
+
body?: InferOr<TInputSchema, undefined>;
|
|
47
|
+
path?: HasPathParams<TPath> extends true
|
|
48
|
+
? ExtractPathParamsOrWiden<TPath, string | ReadableAtom<string>>
|
|
49
|
+
: undefined;
|
|
50
|
+
query?: QueryParamsHint<TQueryParameters, string | ReadableAtom<string>>;
|
|
51
|
+
}) => Promise<InferOr<TOutputSchema, undefined>>;
|
|
52
|
+
loading?: boolean | undefined;
|
|
53
|
+
error?: FragnoClientError<NonNullable<TErrorCode>[number]> | undefined;
|
|
54
|
+
data?: InferOr<TOutputSchema, undefined> | undefined;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Helper function to create a React hook from a GET hook
|
|
58
|
+
function createReactHook<
|
|
59
|
+
TMethod extends "GET",
|
|
60
|
+
TPath extends string,
|
|
61
|
+
TOutputSchema extends StandardSchemaV1,
|
|
62
|
+
TErrorCode extends string,
|
|
63
|
+
TQueryParameters extends string,
|
|
64
|
+
>(
|
|
65
|
+
hook: FragnoClientHookData<TMethod, TPath, TOutputSchema, TErrorCode, TQueryParameters>,
|
|
66
|
+
): FragnoReactHook<TMethod, TPath, TOutputSchema, TErrorCode, TQueryParameters> {
|
|
67
|
+
return ({ path, query } = {}) => {
|
|
68
|
+
const pathParamValues = path ? Object.values(path) : [];
|
|
69
|
+
const queryParamValues = query ? Object.values(query) : [];
|
|
70
|
+
|
|
71
|
+
const deps = [...pathParamValues, ...queryParamValues];
|
|
72
|
+
|
|
73
|
+
const store = useMemo(() => hook.store({ path, query }), [hook, ...deps]);
|
|
74
|
+
|
|
75
|
+
if (typeof window === "undefined") {
|
|
76
|
+
// TODO(Wilco): Handle server-side rendering. In React we have to implement onShellReady
|
|
77
|
+
// and onAllReady in renderToPipable stream.
|
|
78
|
+
const serverSideData = store.get();
|
|
79
|
+
return serverSideData;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return useStore(store);
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Helper function to create a React mutator from a mutator hook
|
|
87
|
+
function createReactMutator<
|
|
88
|
+
TMethod extends NonGetHTTPMethod,
|
|
89
|
+
TPath extends string,
|
|
90
|
+
TInput extends StandardSchemaV1 | undefined,
|
|
91
|
+
TOutput extends StandardSchemaV1 | undefined,
|
|
92
|
+
TError extends string,
|
|
93
|
+
TQueryParameters extends string,
|
|
94
|
+
>(
|
|
95
|
+
hook: FragnoClientMutatorData<TMethod, TPath, TInput, TOutput, TError, TQueryParameters>,
|
|
96
|
+
): FragnoReactMutator<TMethod, TPath, TInput, TOutput, TError, TQueryParameters> {
|
|
97
|
+
return () => {
|
|
98
|
+
const store = useMemo(() => hook.mutatorStore, [hook]);
|
|
99
|
+
return useStore(store);
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Type helper that unwraps any Store fields of the object into StoreValues
|
|
105
|
+
*/
|
|
106
|
+
export type FragnoReactStore<T extends object> = () => T extends Store<infer TStore>
|
|
107
|
+
? StoreValue<TStore>
|
|
108
|
+
: {
|
|
109
|
+
[K in keyof T]: T[K] extends Store ? StoreValue<T[K]> : T[K];
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
function createReactStore<const T extends object>(hook: FragnoStoreData<T>): FragnoReactStore<T> {
|
|
113
|
+
if (isReadableAtom(hook.obj)) {
|
|
114
|
+
return () => useStore(hook.obj as Store);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return () => {
|
|
118
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
119
|
+
const result: any = {};
|
|
120
|
+
|
|
121
|
+
for (const key in hook.obj) {
|
|
122
|
+
if (!Object.prototype.hasOwnProperty.call(hook.obj, key)) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const value = hook.obj[key];
|
|
127
|
+
if (isReadableAtom(value)) {
|
|
128
|
+
result[key] = useStore(value);
|
|
129
|
+
} else {
|
|
130
|
+
result[key] = value;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return result;
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function useFragno<T extends Record<string, unknown>>(
|
|
139
|
+
clientObj: T,
|
|
140
|
+
): {
|
|
141
|
+
[K in keyof T]: T[K] extends FragnoClientHookData<
|
|
142
|
+
"GET",
|
|
143
|
+
infer TPath,
|
|
144
|
+
infer TOutputSchema,
|
|
145
|
+
infer TErrorCode,
|
|
146
|
+
infer TQueryParameters
|
|
147
|
+
>
|
|
148
|
+
? FragnoReactHook<"GET", TPath, TOutputSchema, TErrorCode, TQueryParameters>
|
|
149
|
+
: T[K] extends FragnoClientMutatorData<
|
|
150
|
+
infer TMethod,
|
|
151
|
+
infer TPath,
|
|
152
|
+
infer TInput,
|
|
153
|
+
infer TOutput,
|
|
154
|
+
infer TError,
|
|
155
|
+
infer TQueryParameters
|
|
156
|
+
>
|
|
157
|
+
? FragnoReactMutator<TMethod, TPath, TInput, TOutput, TError, TQueryParameters>
|
|
158
|
+
: T[K] extends FragnoStoreData<infer TStoreObj>
|
|
159
|
+
? FragnoReactStore<TStoreObj>
|
|
160
|
+
: T[K];
|
|
161
|
+
} {
|
|
162
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
163
|
+
const result = {} as any; // We need one any cast here due to TypeScript's limitations with mapped types
|
|
164
|
+
|
|
165
|
+
for (const key in clientObj) {
|
|
166
|
+
if (!Object.prototype.hasOwnProperty.call(clientObj, key)) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const hook = clientObj[key];
|
|
171
|
+
if (isGetHook(hook)) {
|
|
172
|
+
result[key] = createReactHook(hook);
|
|
173
|
+
} else if (isMutatorHook(hook)) {
|
|
174
|
+
result[key] = createReactMutator(hook);
|
|
175
|
+
} else if (isStore(hook)) {
|
|
176
|
+
result[key] = createReactStore(hook);
|
|
177
|
+
} else {
|
|
178
|
+
// Pass through non-hook values unchanged
|
|
179
|
+
result[key] = hook;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
type StoreKeys<T> = T extends { setKey: (k: infer K, v: unknown) => unknown } ? K : never;
|
|
187
|
+
|
|
188
|
+
export interface UseStoreOptions<SomeStore> {
|
|
189
|
+
/**
|
|
190
|
+
* @default
|
|
191
|
+
* ```ts
|
|
192
|
+
* [store, options.keys]
|
|
193
|
+
* ```
|
|
194
|
+
*/
|
|
195
|
+
deps?: DependencyList;
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Will re-render components only on specific key changes.
|
|
199
|
+
*/
|
|
200
|
+
keys?: StoreKeys<SomeStore>[];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function useStore<SomeStore extends Store>(
|
|
204
|
+
store: SomeStore,
|
|
205
|
+
options: UseStoreOptions<SomeStore> = {},
|
|
206
|
+
): StoreValue<SomeStore> {
|
|
207
|
+
const snapshotRef = useRef<StoreValue<SomeStore>>(store.get());
|
|
208
|
+
|
|
209
|
+
const { keys, deps = [store, keys] } = options;
|
|
210
|
+
|
|
211
|
+
const subscribe = useCallback((onChange: () => void) => {
|
|
212
|
+
const emitChange = (value: StoreValue<SomeStore>) => {
|
|
213
|
+
if (snapshotRef.current === value) return;
|
|
214
|
+
snapshotRef.current = value;
|
|
215
|
+
onChange();
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
emitChange(store.value);
|
|
219
|
+
if (keys?.length) {
|
|
220
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
221
|
+
return listenKeys(store as any, keys, emitChange);
|
|
222
|
+
}
|
|
223
|
+
return store.listen(emitChange);
|
|
224
|
+
}, deps);
|
|
225
|
+
|
|
226
|
+
const get = () => snapshotRef.current as StoreValue<SomeStore>;
|
|
227
|
+
|
|
228
|
+
return useSyncExternalStore(subscribe, get, () => {
|
|
229
|
+
// Server-side rendering
|
|
230
|
+
return get();
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function FragnoHydrator({ children }: { children: React.ReactNode }) {
|
|
235
|
+
// Ensure initial data is transferred from window before any hooks run
|
|
236
|
+
// Running in useMemo makes this happen during render, ahead of effects
|
|
237
|
+
useMemo(() => {
|
|
238
|
+
hydrateFromWindow();
|
|
239
|
+
}, []);
|
|
240
|
+
return children;
|
|
241
|
+
}
|