hadars 0.2.2-rc.1 → 0.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.
- package/README.md +130 -14
- package/cli-lib.ts +54 -3
- package/dist/cli.js +123 -18
- package/dist/cloudflare.d.cts +1 -1
- package/dist/cloudflare.d.ts +1 -1
- package/dist/{hadars-mKu5txjW.d.cts → hadars-CSWWhlQC.d.cts} +37 -3
- package/dist/{hadars-mKu5txjW.d.ts → hadars-CSWWhlQC.d.ts} +37 -3
- package/dist/index.cjs +30 -1
- package/dist/index.d.cts +29 -2
- package/dist/index.d.ts +29 -2
- package/dist/index.js +29 -1
- package/dist/lambda.d.cts +1 -1
- package/dist/lambda.d.ts +1 -1
- package/dist/utils/Head.tsx +95 -2
- package/package.json +1 -1
- package/src/build.ts +3 -0
- package/src/index.tsx +2 -1
- package/src/source/context.ts +5 -5
- package/src/source/graphiql.ts +8 -3
- package/src/source/inference.ts +92 -20
- package/src/source/runner.ts +29 -5
- package/src/source/store.ts +2 -0
- package/src/static.ts +3 -0
- package/src/types/hadars.ts +43 -4
- package/src/utils/Head.tsx +95 -2
package/src/types/hadars.ts
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
import type { LinkHTMLAttributes, MetaHTMLAttributes, ScriptHTMLAttributes, StyleHTMLAttributes } from "react";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Minimal structural representation of a typed GraphQL document node.
|
|
5
|
+
*
|
|
6
|
+
* Compatible with `TypedDocumentNode` from `@graphql-typed-document-node/core`
|
|
7
|
+
* and the documents emitted by graphql-codegen's `client` preset — so passing
|
|
8
|
+
* a generated document object gives you fully-inferred result and variable types
|
|
9
|
+
* without writing explicit generics.
|
|
10
|
+
*
|
|
11
|
+
* hadars intentionally avoids importing from `graphql` or
|
|
12
|
+
* `@graphql-typed-document-node/core` so that neither is a required dependency.
|
|
13
|
+
*/
|
|
14
|
+
export interface HadarsDocumentNode<
|
|
15
|
+
TResult = Record<string, unknown>,
|
|
16
|
+
TVariables = Record<string, unknown>,
|
|
17
|
+
> {
|
|
18
|
+
/** @internal Used by TypeScript to carry the result type. */
|
|
19
|
+
readonly __apiType?: (variables: TVariables) => TResult;
|
|
20
|
+
/** At least one definition — ensures a plain string is not assignable. */
|
|
21
|
+
readonly definitions: ReadonlyArray<{ readonly kind: string }>;
|
|
22
|
+
}
|
|
23
|
+
|
|
3
24
|
/**
|
|
4
25
|
* In-process GraphQL executor passed to `getInitProps` and `paths` during
|
|
5
26
|
* `hadars export static`. Hadars is executor-agnostic — configure it in
|
|
@@ -15,11 +36,29 @@ import type { LinkHTMLAttributes, MetaHTMLAttributes, ScriptHTMLAttributes, Styl
|
|
|
15
36
|
* gql({ schema, rootValue, source: query, variableValues: variables }),
|
|
16
37
|
* } satisfies HadarsOptions;
|
|
17
38
|
* ```
|
|
39
|
+
*
|
|
40
|
+
* The executor is generic — call it with explicit type parameters or pass a
|
|
41
|
+
* `TypedDocumentNode` / codegen-generated document to get inferred types:
|
|
42
|
+
*
|
|
43
|
+
* ```ts
|
|
44
|
+
* // Explicit generics:
|
|
45
|
+
* const { data } = await ctx.graphql<GetPostQuery, GetPostQueryVariables>(
|
|
46
|
+
* `query GetPost($slug: String) { blogPost(slug: $slug) { title } }`,
|
|
47
|
+
* { slug },
|
|
48
|
+
* );
|
|
49
|
+
*
|
|
50
|
+
* // Inferred via TypedDocumentNode (graphql-codegen client preset):
|
|
51
|
+
* import { GetPostDocument } from './gql';
|
|
52
|
+
* const { data } = await ctx.graphql(GetPostDocument, { slug });
|
|
53
|
+
* ```
|
|
18
54
|
*/
|
|
19
|
-
export type GraphQLExecutor =
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
55
|
+
export type GraphQLExecutor = <
|
|
56
|
+
TData = any,
|
|
57
|
+
TVariables extends Record<string, unknown> = Record<string, unknown>,
|
|
58
|
+
>(
|
|
59
|
+
query: string | HadarsDocumentNode<TData, TVariables>,
|
|
60
|
+
variables?: TVariables,
|
|
61
|
+
) => Promise<{ data?: TData; errors?: ReadonlyArray<{ message: string }> }>;
|
|
23
62
|
|
|
24
63
|
/**
|
|
25
64
|
* Context passed as the second argument to `getInitProps` and `paths`
|
package/src/utils/Head.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { AppHead, AppUnsuspend, LinkProps, MetaProps, ScriptProps, StyleProps } from '../types/hadars'
|
|
2
|
+
import type { AppHead, AppUnsuspend, HadarsDocumentNode, LinkProps, MetaProps, ScriptProps, StyleProps } from '../types/hadars'
|
|
3
3
|
|
|
4
4
|
interface InnerContext {
|
|
5
5
|
setTitle: (title: string) => void;
|
|
@@ -357,6 +357,98 @@ export function useServerData<T>(key: string | string[], fn: () => Promise<T> |
|
|
|
357
357
|
}
|
|
358
358
|
|
|
359
359
|
|
|
360
|
+
// ── useGraphQL ────────────────────────────────────────────────────────────────
|
|
361
|
+
//
|
|
362
|
+
// Execute a GraphQL query during SSR via the hadars data layer. The executor
|
|
363
|
+
// is stored in globalThis.__hadarsGraphQL by the framework before each render.
|
|
364
|
+
// On the client, useServerData handles hydration + client-side navigation.
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Derive a stable cache key string from a query argument.
|
|
368
|
+
* For string queries the key is the trimmed query string.
|
|
369
|
+
* For document nodes we use the operation name when available (fast, concise)
|
|
370
|
+
* and fall back to stringifying the definitions (always stable).
|
|
371
|
+
* The key is ONLY used for cache lookup — the original document is passed to
|
|
372
|
+
* the executor so it can call print() itself.
|
|
373
|
+
*/
|
|
374
|
+
function toCacheKey(doc: any): string {
|
|
375
|
+
if (typeof doc === 'string') return doc.trim();
|
|
376
|
+
// Use the operation name for named operations — compact and stable.
|
|
377
|
+
for (const def of doc?.definitions ?? []) {
|
|
378
|
+
if (def.kind === 'OperationDefinition' && def.name?.value) {
|
|
379
|
+
return `op:${def.name.value}`;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// Anonymous operation — fall back to stringifying definitions.
|
|
383
|
+
return JSON.stringify(doc?.definitions ?? doc);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Execute a GraphQL query server-side and return the result.
|
|
388
|
+
*
|
|
389
|
+
* Wraps `useServerData` — on the client the pre-resolved value is read from
|
|
390
|
+
* the hydration cache. During client-side navigation hadars automatically
|
|
391
|
+
* fires a data-only request to the server so the query re-executes there.
|
|
392
|
+
*
|
|
393
|
+
* Throws if the executor returns GraphQL errors, so the page is correctly
|
|
394
|
+
* marked as failed during `hadars export static`.
|
|
395
|
+
*
|
|
396
|
+
* ```tsx
|
|
397
|
+
* // Typed via codegen document — TData and TVariables are inferred:
|
|
398
|
+
* const result = useGraphQL(GetPostDocument, { slug });
|
|
399
|
+
* const post = result?.data?.blogPost; // fully typed
|
|
400
|
+
*
|
|
401
|
+
* // Plain string query — untyped:
|
|
402
|
+
* const result = useGraphQL(`{ allBlogPost { slug title } }`);
|
|
403
|
+
* if (!result) return null; // undefined while pending on first SSR pass
|
|
404
|
+
* ```
|
|
405
|
+
*/
|
|
406
|
+
// Overload 1: TypedDocumentNode — TData and TVariables are inferred from the document.
|
|
407
|
+
export function useGraphQL<
|
|
408
|
+
TData,
|
|
409
|
+
TVariables extends Record<string, unknown>,
|
|
410
|
+
>(
|
|
411
|
+
query: HadarsDocumentNode<TData, TVariables>,
|
|
412
|
+
variables?: TVariables,
|
|
413
|
+
): { data?: TData } | undefined;
|
|
414
|
+
// Overload 2: plain string query — untyped result.
|
|
415
|
+
export function useGraphQL(
|
|
416
|
+
query: string,
|
|
417
|
+
variables?: Record<string, unknown>,
|
|
418
|
+
): { data?: Record<string, unknown> } | undefined;
|
|
419
|
+
// Implementation
|
|
420
|
+
export function useGraphQL(
|
|
421
|
+
query: string | HadarsDocumentNode<unknown, Record<string, unknown>>,
|
|
422
|
+
variables?: Record<string, unknown>,
|
|
423
|
+
): { data?: unknown } | undefined {
|
|
424
|
+
const key = ['__gql', toCacheKey(query), JSON.stringify(variables ?? {})];
|
|
425
|
+
|
|
426
|
+
return useServerData(key, async () => {
|
|
427
|
+
const executor: ((q: any, v?: Record<string, unknown>) => Promise<any>) | undefined =
|
|
428
|
+
(globalThis as any).__hadarsGraphQL;
|
|
429
|
+
|
|
430
|
+
if (!executor) {
|
|
431
|
+
throw new Error(
|
|
432
|
+
'[hadars] useGraphQL: no GraphQL executor is available for this request. ' +
|
|
433
|
+
'Make sure you have `sources` or a `graphql` executor configured in hadars.config.ts.',
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Pass the original query (string or document object) — the executor
|
|
438
|
+
// calls print() itself so codegen documents without loc.source.body work.
|
|
439
|
+
const result = await executor(query, variables);
|
|
440
|
+
|
|
441
|
+
if (result.errors?.length) {
|
|
442
|
+
const messages = result.errors.map((e: { message: string }) => e.message).join(', ');
|
|
443
|
+
throw new Error(`[hadars] GraphQL error: ${messages}`);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return result;
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// ── HadarsHead ────────────────────────────────────────────────────────────────
|
|
451
|
+
|
|
360
452
|
export const Head: React.FC<{
|
|
361
453
|
children?: React.ReactNode;
|
|
362
454
|
status?: number;
|
|
@@ -381,7 +473,8 @@ export const Head: React.FC<{
|
|
|
381
473
|
|
|
382
474
|
switch ( childType ) {
|
|
383
475
|
case 'title': {
|
|
384
|
-
|
|
476
|
+
const raw = childProps['children'];
|
|
477
|
+
setTitle(Array.isArray(raw) ? raw.join('') : String(raw ?? ''));
|
|
385
478
|
return;
|
|
386
479
|
}
|
|
387
480
|
case 'meta': {
|