@sylphx/lens-solid 2.0.4 → 2.1.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 +59 -10
- package/dist/create.d.ts +149 -0
- package/dist/create.d.ts.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1272 -17
- package/dist/primitives.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/create.ts +472 -0
- package/src/index.ts +34 -1
- package/src/primitives.ts +39 -6
package/dist/primitives.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["../src/primitives.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,EAAE,KAAK,QAAQ,
|
|
1
|
+
{"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["../src/primitives.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,EAAE,KAAK,QAAQ,EAAyC,MAAM,UAAU,CAAC;AAMhF,4EAA4E;AAC5E,MAAM,MAAM,UAAU,CAAC,CAAC,IACrB,WAAW,CAAC,CAAC,CAAC,GACd,IAAI,GACJ,SAAS,GACT,CAAC,MAAM,WAAW,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;AAW7C,yCAAyC;AACzC,MAAM,WAAW,iBAAiB,CAAC,CAAC;IACnC,6BAA6B;IAC7B,IAAI,EAAE,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACzB,6BAA6B;IAC7B,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC3B,2BAA2B;IAC3B,KAAK,EAAE,QAAQ,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IAC9B,wBAAwB;IACxB,OAAO,EAAE,MAAM,IAAI,CAAC;CACpB;AAED,4CAA4C;AAC5C,MAAM,WAAW,oBAAoB,CAAC,MAAM,EAAE,OAAO;IACpD,6BAA6B;IAC7B,IAAI,EAAE,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IAC/B,6BAA6B;IAC7B,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC3B,2BAA2B;IAC3B,KAAK,EAAE,QAAQ,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IAC9B,2BAA2B;IAC3B,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAC5D,kBAAkB;IAClB,KAAK,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,wBAAwB;AACxB,MAAM,WAAW,qBAAqB,CAAC,CAAC;IACvC,6BAA6B;IAC7B,IAAI,EAAE,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACzB,6BAA6B;IAC7B,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC3B,2BAA2B;IAC3B,KAAK,EAAE,QAAQ,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IAC9B,wBAAwB;IACxB,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC;IAC1B,kBAAkB;IAClB,KAAK,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,oBAAoB;AACpB,MAAM,WAAW,kBAAkB;IAClC,qCAAqC;IACrC,IAAI,CAAC,EAAE,OAAO,CAAC;CACf;AAED,6BAA6B;AAC7B,MAAM,MAAM,UAAU,CAAC,MAAM,EAAE,OAAO,IAAI,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;AAM9F;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAC5B,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,EACzB,OAAO,CAAC,EAAE,kBAAkB,GAC1B,iBAAiB,CAAC,CAAC,CAAC,CAuGtB;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,EAC7C,UAAU,EAAE,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,GACrC,oBAAoB,CAAC,MAAM,EAAE,OAAO,CAAC,CAmCvC;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,qBAAqB,CAAC,CAAC,CAAC,CA2CtF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sylphx/lens-solid",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "SolidJS bindings for Lens API framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
"author": "SylphxAI",
|
|
32
32
|
"license": "MIT",
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@sylphx/lens-client": "^2.0
|
|
34
|
+
"@sylphx/lens-client": "^2.1.0",
|
|
35
|
+
"@sylphx/lens-core": "^2.0.1"
|
|
35
36
|
},
|
|
36
37
|
"peerDependencies": {
|
|
37
38
|
"solid-js": ">=1.8.0"
|
package/src/create.ts
ADDED
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sylphx/lens-solid - Create Client
|
|
3
|
+
*
|
|
4
|
+
* Creates a typed Lens client with SolidJS primitives.
|
|
5
|
+
* Each endpoint can be called directly as a primitive or via .fetch() for promises.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* // lib/client.ts
|
|
10
|
+
* import { createClient } from '@sylphx/lens-solid';
|
|
11
|
+
* import { httpTransport } from '@sylphx/lens-client';
|
|
12
|
+
* import type { AppRouter } from '@/server/router';
|
|
13
|
+
*
|
|
14
|
+
* export const client = createClient<AppRouter>({
|
|
15
|
+
* transport: httpTransport({ url: '/api/lens' }),
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* // In component
|
|
19
|
+
* const { data, loading } = client.user.get({ input: { id } });
|
|
20
|
+
*
|
|
21
|
+
* // In SSR
|
|
22
|
+
* const user = await client.user.get.fetch({ input: { id } });
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import {
|
|
27
|
+
createClient as createBaseClient,
|
|
28
|
+
type LensClientConfig,
|
|
29
|
+
type QueryResult,
|
|
30
|
+
type SelectionObject,
|
|
31
|
+
type TypedClientConfig,
|
|
32
|
+
} from "@sylphx/lens-client";
|
|
33
|
+
import type { MutationDef, QueryDef, RouterDef, RouterRoutes } from "@sylphx/lens-core";
|
|
34
|
+
import { type Accessor, createEffect, createSignal, onCleanup } from "solid-js";
|
|
35
|
+
|
|
36
|
+
// =============================================================================
|
|
37
|
+
// Types
|
|
38
|
+
// =============================================================================
|
|
39
|
+
|
|
40
|
+
/** Query hook options */
|
|
41
|
+
export interface QueryHookOptions<TInput> {
|
|
42
|
+
/** Query input parameters */
|
|
43
|
+
input?: TInput;
|
|
44
|
+
/** Field selection */
|
|
45
|
+
select?: SelectionObject;
|
|
46
|
+
/** Skip query execution */
|
|
47
|
+
skip?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Query hook result */
|
|
51
|
+
export interface QueryHookResult<T> {
|
|
52
|
+
/** Reactive data accessor */
|
|
53
|
+
data: Accessor<T | null>;
|
|
54
|
+
/** Reactive loading state */
|
|
55
|
+
loading: Accessor<boolean>;
|
|
56
|
+
/** Reactive error state */
|
|
57
|
+
error: Accessor<Error | null>;
|
|
58
|
+
/** Refetch the query */
|
|
59
|
+
refetch: () => void;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Mutation hook options */
|
|
63
|
+
export interface MutationHookOptions<TOutput> {
|
|
64
|
+
/** Called on successful mutation */
|
|
65
|
+
onSuccess?: (data: TOutput) => void;
|
|
66
|
+
/** Called on mutation error */
|
|
67
|
+
onError?: (error: Error) => void;
|
|
68
|
+
/** Called when mutation settles (success or error) */
|
|
69
|
+
onSettled?: () => void;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Mutation hook result */
|
|
73
|
+
export interface MutationHookResult<TInput, TOutput> {
|
|
74
|
+
/** Execute the mutation */
|
|
75
|
+
mutate: (options: { input: TInput; select?: SelectionObject }) => Promise<TOutput>;
|
|
76
|
+
/** Reactive loading state */
|
|
77
|
+
loading: Accessor<boolean>;
|
|
78
|
+
/** Reactive error state */
|
|
79
|
+
error: Accessor<Error | null>;
|
|
80
|
+
/** Reactive last mutation result */
|
|
81
|
+
data: Accessor<TOutput | null>;
|
|
82
|
+
/** Reset mutation state */
|
|
83
|
+
reset: () => void;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Query endpoint type */
|
|
87
|
+
export interface QueryEndpoint<TInput, TOutput> {
|
|
88
|
+
/** Primitive call (in component) */
|
|
89
|
+
<_TSelect extends SelectionObject = Record<string, never>>(
|
|
90
|
+
options: TInput extends void ? QueryHookOptions<void> | void : QueryHookOptions<TInput>,
|
|
91
|
+
): QueryHookResult<TOutput>;
|
|
92
|
+
|
|
93
|
+
/** Promise call (SSR) */
|
|
94
|
+
fetch: <TSelect extends SelectionObject = Record<string, never>>(
|
|
95
|
+
options: TInput extends void
|
|
96
|
+
? { input?: void; select?: TSelect } | void
|
|
97
|
+
: { input: TInput; select?: TSelect },
|
|
98
|
+
) => Promise<TOutput>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Mutation endpoint type */
|
|
102
|
+
export interface MutationEndpoint<TInput, TOutput> {
|
|
103
|
+
/** Primitive call (in component) */
|
|
104
|
+
(options?: MutationHookOptions<TOutput>): MutationHookResult<TInput, TOutput>;
|
|
105
|
+
|
|
106
|
+
/** Promise call (SSR) */
|
|
107
|
+
fetch: <TSelect extends SelectionObject = Record<string, never>>(options: {
|
|
108
|
+
input: TInput;
|
|
109
|
+
select?: TSelect;
|
|
110
|
+
}) => Promise<TOutput>;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Infer client type from router routes */
|
|
114
|
+
type InferTypedClient<TRoutes extends RouterRoutes> = {
|
|
115
|
+
[K in keyof TRoutes]: TRoutes[K] extends RouterDef<infer TNestedRoutes>
|
|
116
|
+
? InferTypedClient<TNestedRoutes>
|
|
117
|
+
: TRoutes[K] extends QueryDef<infer TInput, infer TOutput>
|
|
118
|
+
? QueryEndpoint<TInput, TOutput>
|
|
119
|
+
: TRoutes[K] extends MutationDef<infer TInput, infer TOutput>
|
|
120
|
+
? MutationEndpoint<TInput, TOutput>
|
|
121
|
+
: never;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/** Typed client from router */
|
|
125
|
+
export type TypedClient<TRouter extends RouterDef> =
|
|
126
|
+
TRouter extends RouterDef<infer TRoutes> ? InferTypedClient<TRoutes> : never;
|
|
127
|
+
|
|
128
|
+
// =============================================================================
|
|
129
|
+
// Hook Factories
|
|
130
|
+
// =============================================================================
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Create a query primitive for a specific endpoint
|
|
134
|
+
*/
|
|
135
|
+
function createQueryHook<TInput, TOutput>(
|
|
136
|
+
baseClient: unknown,
|
|
137
|
+
path: string,
|
|
138
|
+
): QueryEndpoint<TInput, TOutput> {
|
|
139
|
+
const getEndpoint = (p: string) => {
|
|
140
|
+
const parts = p.split(".");
|
|
141
|
+
let current: unknown = baseClient;
|
|
142
|
+
for (const part of parts) {
|
|
143
|
+
current = (current as Record<string, unknown>)[part];
|
|
144
|
+
}
|
|
145
|
+
return current as (options: unknown) => QueryResult<TOutput>;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const useQueryPrimitive = (options?: QueryHookOptions<TInput>): QueryHookResult<TOutput> => {
|
|
149
|
+
const [data, setData] = createSignal<TOutput | null>(null);
|
|
150
|
+
const [loading, setLoading] = createSignal(!options?.skip);
|
|
151
|
+
const [error, setError] = createSignal<Error | null>(null);
|
|
152
|
+
|
|
153
|
+
let unsubscribe: (() => void) | null = null;
|
|
154
|
+
|
|
155
|
+
const executeQuery = () => {
|
|
156
|
+
if (unsubscribe) {
|
|
157
|
+
unsubscribe();
|
|
158
|
+
unsubscribe = null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (options?.skip) {
|
|
162
|
+
setData(null);
|
|
163
|
+
setLoading(false);
|
|
164
|
+
setError(null);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const endpoint = getEndpoint(path);
|
|
169
|
+
const query = endpoint({ input: options?.input, select: options?.select });
|
|
170
|
+
|
|
171
|
+
setLoading(true);
|
|
172
|
+
setError(null);
|
|
173
|
+
|
|
174
|
+
unsubscribe = query.subscribe((value) => {
|
|
175
|
+
setData(() => value);
|
|
176
|
+
setLoading(false);
|
|
177
|
+
setError(null);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
query.then(
|
|
181
|
+
(value) => {
|
|
182
|
+
setData(() => value);
|
|
183
|
+
setLoading(false);
|
|
184
|
+
setError(null);
|
|
185
|
+
},
|
|
186
|
+
(err) => {
|
|
187
|
+
const queryError = err instanceof Error ? err : new Error(String(err));
|
|
188
|
+
setError(queryError);
|
|
189
|
+
setLoading(false);
|
|
190
|
+
},
|
|
191
|
+
);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// Execute initial query synchronously
|
|
195
|
+
executeQuery();
|
|
196
|
+
|
|
197
|
+
// Use createEffect for reactive updates when options change
|
|
198
|
+
createEffect(() => {
|
|
199
|
+
// Access options to track them (Solid will re-run when they change)
|
|
200
|
+
const _ = JSON.stringify(options);
|
|
201
|
+
executeQuery();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
onCleanup(() => {
|
|
205
|
+
if (unsubscribe) {
|
|
206
|
+
unsubscribe();
|
|
207
|
+
unsubscribe = null;
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const refetch = () => {
|
|
212
|
+
if (unsubscribe) {
|
|
213
|
+
unsubscribe();
|
|
214
|
+
unsubscribe = null;
|
|
215
|
+
}
|
|
216
|
+
setLoading(true);
|
|
217
|
+
setError(null);
|
|
218
|
+
const endpoint = getEndpoint(path);
|
|
219
|
+
const query = endpoint({ input: options?.input, select: options?.select });
|
|
220
|
+
if (query) {
|
|
221
|
+
unsubscribe = query.subscribe((value) => {
|
|
222
|
+
setData(() => value);
|
|
223
|
+
setLoading(false);
|
|
224
|
+
setError(null);
|
|
225
|
+
});
|
|
226
|
+
query.then(
|
|
227
|
+
(value) => {
|
|
228
|
+
setData(() => value);
|
|
229
|
+
setLoading(false);
|
|
230
|
+
},
|
|
231
|
+
(err) => {
|
|
232
|
+
const queryError = err instanceof Error ? err : new Error(String(err));
|
|
233
|
+
setError(queryError);
|
|
234
|
+
setLoading(false);
|
|
235
|
+
},
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
return { data, loading, error, refetch };
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
// Fetch method for promises (SSR)
|
|
244
|
+
const fetch = async (options?: {
|
|
245
|
+
input?: TInput;
|
|
246
|
+
select?: SelectionObject;
|
|
247
|
+
}): Promise<TOutput> => {
|
|
248
|
+
const endpoint = getEndpoint(path);
|
|
249
|
+
const queryResult = endpoint({ input: options?.input, select: options?.select });
|
|
250
|
+
return queryResult.then((data) => data);
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
// Attach fetch method to the hook function
|
|
254
|
+
useQueryPrimitive.fetch = fetch;
|
|
255
|
+
|
|
256
|
+
return useQueryPrimitive as unknown as QueryEndpoint<TInput, TOutput>;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Create a mutation primitive for a specific endpoint
|
|
261
|
+
*/
|
|
262
|
+
function createMutationHook<TInput, TOutput>(
|
|
263
|
+
baseClient: unknown,
|
|
264
|
+
path: string,
|
|
265
|
+
): MutationEndpoint<TInput, TOutput> {
|
|
266
|
+
const getEndpoint = (p: string) => {
|
|
267
|
+
const parts = p.split(".");
|
|
268
|
+
let current: unknown = baseClient;
|
|
269
|
+
for (const part of parts) {
|
|
270
|
+
current = (current as Record<string, unknown>)[part];
|
|
271
|
+
}
|
|
272
|
+
return current as (options: unknown) => QueryResult<{ data: TOutput }>;
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const useMutationPrimitive = (
|
|
276
|
+
hookOptions?: MutationHookOptions<TOutput>,
|
|
277
|
+
): MutationHookResult<TInput, TOutput> => {
|
|
278
|
+
const [data, setData] = createSignal<TOutput | null>(null);
|
|
279
|
+
const [loading, setLoading] = createSignal(false);
|
|
280
|
+
const [error, setError] = createSignal<Error | null>(null);
|
|
281
|
+
|
|
282
|
+
const mutate = async (options: {
|
|
283
|
+
input: TInput;
|
|
284
|
+
select?: SelectionObject;
|
|
285
|
+
}): Promise<TOutput> => {
|
|
286
|
+
setLoading(true);
|
|
287
|
+
setError(null);
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
const endpoint = getEndpoint(path);
|
|
291
|
+
const result = await endpoint({ input: options.input, select: options.select });
|
|
292
|
+
const mutationResult = result as unknown as { data: TOutput };
|
|
293
|
+
|
|
294
|
+
setData(() => mutationResult.data);
|
|
295
|
+
setLoading(false);
|
|
296
|
+
|
|
297
|
+
hookOptions?.onSuccess?.(mutationResult.data);
|
|
298
|
+
hookOptions?.onSettled?.();
|
|
299
|
+
|
|
300
|
+
return mutationResult.data;
|
|
301
|
+
} catch (err) {
|
|
302
|
+
const mutationError = err instanceof Error ? err : new Error(String(err));
|
|
303
|
+
setError(mutationError);
|
|
304
|
+
setLoading(false);
|
|
305
|
+
|
|
306
|
+
hookOptions?.onError?.(mutationError);
|
|
307
|
+
hookOptions?.onSettled?.();
|
|
308
|
+
|
|
309
|
+
throw mutationError;
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const reset = () => {
|
|
314
|
+
setData(null);
|
|
315
|
+
setLoading(false);
|
|
316
|
+
setError(null);
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
return { mutate, loading, error, data, reset };
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
// Fetch method for promises (SSR)
|
|
323
|
+
const fetch = async (options: { input: TInput; select?: SelectionObject }): Promise<TOutput> => {
|
|
324
|
+
const endpoint = getEndpoint(path);
|
|
325
|
+
const result = await endpoint({ input: options.input, select: options.select });
|
|
326
|
+
const mutationResult = result as unknown as { data: TOutput };
|
|
327
|
+
return mutationResult.data;
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
// Attach fetch method to the hook function
|
|
331
|
+
useMutationPrimitive.fetch = fetch;
|
|
332
|
+
|
|
333
|
+
return useMutationPrimitive as unknown as MutationEndpoint<TInput, TOutput>;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// =============================================================================
|
|
337
|
+
// Create Client
|
|
338
|
+
// =============================================================================
|
|
339
|
+
|
|
340
|
+
// Cache for hook functions
|
|
341
|
+
const hookCache = new Map<string, unknown>();
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Create a Lens client with SolidJS primitives.
|
|
345
|
+
*
|
|
346
|
+
* Each endpoint can be called:
|
|
347
|
+
* - Directly as a primitive: `client.user.get({ input: { id } })`
|
|
348
|
+
* - Via .fetch() for promises: `await client.user.get.fetch({ input: { id } })`
|
|
349
|
+
*
|
|
350
|
+
* @example
|
|
351
|
+
* ```tsx
|
|
352
|
+
* // lib/client.ts
|
|
353
|
+
* import { createClient } from '@sylphx/lens-solid';
|
|
354
|
+
* import { httpTransport } from '@sylphx/lens-client';
|
|
355
|
+
* import type { AppRouter } from '@/server/router';
|
|
356
|
+
*
|
|
357
|
+
* export const client = createClient<AppRouter>({
|
|
358
|
+
* transport: httpTransport({ url: '/api/lens' }),
|
|
359
|
+
* });
|
|
360
|
+
*
|
|
361
|
+
* // Component usage
|
|
362
|
+
* function UserProfile(props: { id: string }) {
|
|
363
|
+
* const { data, loading, error } = client.user.get({
|
|
364
|
+
* input: { id: props.id },
|
|
365
|
+
* select: { name: true },
|
|
366
|
+
* });
|
|
367
|
+
*
|
|
368
|
+
* const { mutate, loading: saving } = client.user.update({
|
|
369
|
+
* onSuccess: () => console.log('Updated!'),
|
|
370
|
+
* });
|
|
371
|
+
*
|
|
372
|
+
* return (
|
|
373
|
+
* <Show when={!loading()} fallback={<Spinner />}>
|
|
374
|
+
* <h1>{data()?.name}</h1>
|
|
375
|
+
* <button
|
|
376
|
+
* onClick={() => mutate({ input: { id: props.id, name: 'New' } })}
|
|
377
|
+
* disabled={saving()}
|
|
378
|
+
* >
|
|
379
|
+
* Update
|
|
380
|
+
* </button>
|
|
381
|
+
* </Show>
|
|
382
|
+
* );
|
|
383
|
+
* }
|
|
384
|
+
* ```
|
|
385
|
+
*/
|
|
386
|
+
export function createClient<TRouter extends RouterDef>(
|
|
387
|
+
config: LensClientConfig | TypedClientConfig<{ router: TRouter }>,
|
|
388
|
+
): TypedClient<TRouter> {
|
|
389
|
+
const baseClient = createBaseClient(config as LensClientConfig);
|
|
390
|
+
|
|
391
|
+
function createProxy(path: string): unknown {
|
|
392
|
+
const handler: ProxyHandler<(...args: unknown[]) => unknown> = {
|
|
393
|
+
get(_target, prop) {
|
|
394
|
+
if (typeof prop === "symbol") return undefined;
|
|
395
|
+
const key = prop as string;
|
|
396
|
+
|
|
397
|
+
if (key === "fetch") {
|
|
398
|
+
return async (options: unknown) => {
|
|
399
|
+
const parts = path.split(".");
|
|
400
|
+
let current: unknown = baseClient;
|
|
401
|
+
for (const part of parts) {
|
|
402
|
+
current = (current as Record<string, unknown>)[part];
|
|
403
|
+
}
|
|
404
|
+
const endpointFn = current as (opts: unknown) => QueryResult<unknown>;
|
|
405
|
+
const queryResult = endpointFn(options);
|
|
406
|
+
const result = await queryResult;
|
|
407
|
+
|
|
408
|
+
if (
|
|
409
|
+
result &&
|
|
410
|
+
typeof result === "object" &&
|
|
411
|
+
"data" in result &&
|
|
412
|
+
Object.keys(result).length === 1
|
|
413
|
+
) {
|
|
414
|
+
return (result as { data: unknown }).data;
|
|
415
|
+
}
|
|
416
|
+
return result;
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (key === "then") return undefined;
|
|
421
|
+
if (key.startsWith("_")) return undefined;
|
|
422
|
+
|
|
423
|
+
const newPath = path ? `${path}.${key}` : key;
|
|
424
|
+
return createProxy(newPath);
|
|
425
|
+
},
|
|
426
|
+
|
|
427
|
+
apply(_target, _thisArg, args) {
|
|
428
|
+
const options = args[0] as Record<string, unknown> | undefined;
|
|
429
|
+
|
|
430
|
+
const isQueryOptions =
|
|
431
|
+
options && ("input" in options || "select" in options || "skip" in options);
|
|
432
|
+
|
|
433
|
+
const isMutationOptions =
|
|
434
|
+
!options ||
|
|
435
|
+
(!isQueryOptions &&
|
|
436
|
+
(Object.keys(options).length === 0 ||
|
|
437
|
+
"onSuccess" in options ||
|
|
438
|
+
"onError" in options ||
|
|
439
|
+
"onSettled" in options));
|
|
440
|
+
|
|
441
|
+
const cacheKeyQuery = `${path}:query`;
|
|
442
|
+
const cacheKeyMutation = `${path}:mutation`;
|
|
443
|
+
|
|
444
|
+
if (isQueryOptions) {
|
|
445
|
+
if (!hookCache.has(cacheKeyQuery)) {
|
|
446
|
+
hookCache.set(cacheKeyQuery, createQueryHook(baseClient, path));
|
|
447
|
+
}
|
|
448
|
+
const hook = hookCache.get(cacheKeyQuery) as (opts: unknown) => unknown;
|
|
449
|
+
return hook(options);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (isMutationOptions) {
|
|
453
|
+
if (!hookCache.has(cacheKeyMutation)) {
|
|
454
|
+
hookCache.set(cacheKeyMutation, createMutationHook(baseClient, path));
|
|
455
|
+
}
|
|
456
|
+
const hook = hookCache.get(cacheKeyMutation) as (opts: unknown) => unknown;
|
|
457
|
+
return hook(options);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (!hookCache.has(cacheKeyQuery)) {
|
|
461
|
+
hookCache.set(cacheKeyQuery, createQueryHook(baseClient, path));
|
|
462
|
+
}
|
|
463
|
+
const hook = hookCache.get(cacheKeyQuery) as (opts: unknown) => unknown;
|
|
464
|
+
return hook(options);
|
|
465
|
+
},
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
return new Proxy((() => {}) as (...args: unknown[]) => unknown, handler);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return createProxy("") as TypedClient<TRouter>;
|
|
472
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -3,10 +3,43 @@
|
|
|
3
3
|
*
|
|
4
4
|
* SolidJS bindings for Lens API framework.
|
|
5
5
|
* Reactive primitives that integrate with SolidJS fine-grained reactivity.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* // lib/client.ts
|
|
10
|
+
* import { createClient } from '@sylphx/lens-solid';
|
|
11
|
+
* import { httpTransport } from '@sylphx/lens-client';
|
|
12
|
+
* import type { AppRouter } from '@/server/router';
|
|
13
|
+
*
|
|
14
|
+
* export const client = createClient<AppRouter>({
|
|
15
|
+
* transport: httpTransport({ url: '/api/lens' }),
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* // Component usage
|
|
19
|
+
* const { data, loading } = client.user.get({ input: { id } });
|
|
20
|
+
*
|
|
21
|
+
* // SSR usage
|
|
22
|
+
* const user = await client.user.get.fetch({ input: { id } });
|
|
23
|
+
* ```
|
|
6
24
|
*/
|
|
7
25
|
|
|
8
26
|
// =============================================================================
|
|
9
|
-
//
|
|
27
|
+
// New API (v4) - Recommended
|
|
28
|
+
// =============================================================================
|
|
29
|
+
|
|
30
|
+
export {
|
|
31
|
+
createClient,
|
|
32
|
+
type MutationEndpoint,
|
|
33
|
+
type MutationHookOptions,
|
|
34
|
+
type MutationHookResult,
|
|
35
|
+
type QueryEndpoint,
|
|
36
|
+
type QueryHookOptions,
|
|
37
|
+
type QueryHookResult,
|
|
38
|
+
type TypedClient,
|
|
39
|
+
} from "./create.js";
|
|
40
|
+
|
|
41
|
+
// =============================================================================
|
|
42
|
+
// Legacy API (v3) - Deprecated
|
|
10
43
|
// =============================================================================
|
|
11
44
|
|
|
12
45
|
export { LensProvider, type LensProviderProps, useLensClient } from "./context.js";
|
package/src/primitives.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { MutationResult, QueryResult } from "@sylphx/lens-client";
|
|
9
|
-
import { type Accessor, createSignal, onCleanup } from "solid-js";
|
|
9
|
+
import { type Accessor, createEffect, createSignal, onCleanup } from "solid-js";
|
|
10
10
|
|
|
11
11
|
// =============================================================================
|
|
12
12
|
// Query Input Types
|
|
@@ -120,8 +120,12 @@ export function createQuery<T>(
|
|
|
120
120
|
|
|
121
121
|
let unsubscribe: (() => void) | null = null;
|
|
122
122
|
|
|
123
|
-
const executeQuery = () => {
|
|
124
|
-
|
|
123
|
+
const executeQuery = (queryResult: QueryResult<T> | null | undefined) => {
|
|
124
|
+
// Cleanup previous subscription
|
|
125
|
+
if (unsubscribe) {
|
|
126
|
+
unsubscribe();
|
|
127
|
+
unsubscribe = null;
|
|
128
|
+
}
|
|
125
129
|
|
|
126
130
|
// Handle null/undefined query or skip
|
|
127
131
|
if (options?.skip || queryResult == null) {
|
|
@@ -156,8 +160,19 @@ export function createQuery<T>(
|
|
|
156
160
|
);
|
|
157
161
|
};
|
|
158
162
|
|
|
159
|
-
// Execute query
|
|
160
|
-
|
|
163
|
+
// Execute initial query synchronously
|
|
164
|
+
const initialQuery = resolveQuery(queryInput);
|
|
165
|
+
executeQuery(initialQuery);
|
|
166
|
+
|
|
167
|
+
// Use createEffect to track reactive dependencies for re-execution
|
|
168
|
+
// This ensures the query re-runs when signals used in the accessor change
|
|
169
|
+
createEffect(() => {
|
|
170
|
+
const queryResult = resolveQuery(queryInput); // Solid tracks reactive deps here
|
|
171
|
+
// Only re-execute if query changed (not on initial mount)
|
|
172
|
+
if (queryResult !== initialQuery) {
|
|
173
|
+
executeQuery(queryResult);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
161
176
|
|
|
162
177
|
// Cleanup on unmount
|
|
163
178
|
onCleanup(() => {
|
|
@@ -174,7 +189,25 @@ export function createQuery<T>(
|
|
|
174
189
|
}
|
|
175
190
|
setLoading(true);
|
|
176
191
|
setError(null);
|
|
177
|
-
|
|
192
|
+
const queryResult = resolveQuery(queryInput);
|
|
193
|
+
if (queryResult) {
|
|
194
|
+
unsubscribe = queryResult.subscribe((value) => {
|
|
195
|
+
setData(() => value);
|
|
196
|
+
setLoading(false);
|
|
197
|
+
setError(null);
|
|
198
|
+
});
|
|
199
|
+
queryResult.then(
|
|
200
|
+
(value) => {
|
|
201
|
+
setData(() => value);
|
|
202
|
+
setLoading(false);
|
|
203
|
+
},
|
|
204
|
+
(err) => {
|
|
205
|
+
const queryError = err instanceof Error ? err : new Error(String(err));
|
|
206
|
+
setError(queryError);
|
|
207
|
+
setLoading(false);
|
|
208
|
+
},
|
|
209
|
+
);
|
|
210
|
+
}
|
|
178
211
|
};
|
|
179
212
|
|
|
180
213
|
return {
|