hadars 0.2.1 → 0.2.2-rc.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 +177 -14
- package/cli-lib.ts +97 -5
- package/dist/{chunk-HWOLYLPF.js → chunk-H72BZXOA.js} +2 -2
- package/dist/cli.js +519 -16
- package/dist/cloudflare.cjs +2 -2
- package/dist/cloudflare.d.cts +1 -1
- package/dist/cloudflare.d.ts +1 -1
- package/dist/cloudflare.js +1 -1
- package/dist/{hadars-DEBSYAQl.d.cts → hadars-mKu5txjW.d.cts} +93 -2
- package/dist/{hadars-DEBSYAQl.d.ts → hadars-mKu5txjW.d.ts} +93 -2
- package/dist/index.cjs +11 -8
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +11 -8
- package/dist/lambda.cjs +2 -2
- package/dist/lambda.d.cts +1 -1
- package/dist/lambda.d.ts +1 -1
- package/dist/lambda.js +1 -1
- package/dist/utils/Head.tsx +17 -8
- package/dist/utils/clientScript.tsx +9 -0
- package/index.ts +3 -0
- package/package.json +1 -1
- package/src/build.ts +29 -0
- package/src/index.tsx +3 -0
- package/src/source/context.ts +113 -0
- package/src/source/graphiql.ts +96 -0
- package/src/source/inference.ts +188 -0
- package/src/source/runner.ts +114 -0
- package/src/source/store.ts +48 -0
- package/src/static.ts +106 -0
- package/src/types/hadars.ts +91 -1
- package/src/utils/Head.tsx +17 -8
- package/src/utils/clientScript.tsx +9 -0
- package/src/utils/response.tsx +4 -3
|
@@ -1,4 +1,33 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* In-process GraphQL executor passed to `getInitProps` and `paths` during
|
|
3
|
+
* `hadars export static`. Hadars is executor-agnostic — configure it in
|
|
4
|
+
* `hadars.config.ts` using any GraphQL library (e.g. `graphql-js`):
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { graphql as gql, buildSchema } from 'graphql';
|
|
8
|
+
* const schema = buildSchema(`type Query { hello: String }`);
|
|
9
|
+
* const rootValue = { hello: () => 'world' };
|
|
10
|
+
*
|
|
11
|
+
* export default {
|
|
12
|
+
* graphql: (query, variables) =>
|
|
13
|
+
* gql({ schema, rootValue, source: query, variableValues: variables }),
|
|
14
|
+
* } satisfies HadarsOptions;
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
type GraphQLExecutor = (query: string, variables?: Record<string, unknown>) => Promise<{
|
|
18
|
+
data?: any;
|
|
19
|
+
errors?: ReadonlyArray<{
|
|
20
|
+
message: string;
|
|
21
|
+
}>;
|
|
22
|
+
}>;
|
|
23
|
+
/**
|
|
24
|
+
* Context passed as the second argument to `getInitProps` and `paths`
|
|
25
|
+
* during `hadars export static`. Not present in dev/run mode.
|
|
26
|
+
*/
|
|
27
|
+
interface HadarsStaticContext {
|
|
28
|
+
graphql: GraphQLExecutor;
|
|
29
|
+
}
|
|
30
|
+
type HadarsGetInitialProps<T extends {}> = (req: HadarsRequest, ctx?: HadarsStaticContext) => Promise<T> | T;
|
|
2
31
|
type HadarsGetClientProps<T extends {}> = (props: T) => Promise<T> | T;
|
|
3
32
|
type HadarsGetFinalProps<T extends {}> = (props: HadarsProps<T>) => Promise<T> | T;
|
|
4
33
|
type HadarsApp<T extends {}> = React.FC<HadarsProps<T>>;
|
|
@@ -148,6 +177,68 @@ interface HadarsOptions {
|
|
|
148
177
|
key: string;
|
|
149
178
|
ttl?: number;
|
|
150
179
|
} | null | undefined>;
|
|
180
|
+
/**
|
|
181
|
+
* Static export path list. Required for `hadars export static`.
|
|
182
|
+
*
|
|
183
|
+
* Return an array of URL paths (e.g. `['/', '/about', '/blog/hello']`) that
|
|
184
|
+
* should be pre-rendered to HTML files. May be async.
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* paths: () => ['/', '/about', '/contact']
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* paths: async () => {
|
|
191
|
+
* const posts = await fetchBlogPosts();
|
|
192
|
+
* return ['/', ...posts.map(p => `/blog/${p.slug}`)];
|
|
193
|
+
* }
|
|
194
|
+
*/
|
|
195
|
+
/**
|
|
196
|
+
* In-process GraphQL executor. Supply this to use the GraphQL data layer
|
|
197
|
+
* in `paths` and `getInitProps` during `hadars export static`.
|
|
198
|
+
* Has no effect in `dev` / `run` mode.
|
|
199
|
+
*/
|
|
200
|
+
graphql?: GraphQLExecutor;
|
|
201
|
+
paths?: (ctx: HadarsStaticContext) => Promise<string[]> | string[];
|
|
202
|
+
/**
|
|
203
|
+
* Gatsby-compatible source plugins to run before `hadars export static`.
|
|
204
|
+
*
|
|
205
|
+
* Each entry mirrors Gatsby's `gatsby-config.js` plugin object format:
|
|
206
|
+
* `{ resolve: 'gatsby-source-contentful', options: { spaceId: '...', accessToken: '...' } }`
|
|
207
|
+
*
|
|
208
|
+
* The plugin must export a `sourceNodes` function with the standard Gatsby API.
|
|
209
|
+
* Hadars provides a thin shim covering the most-used surface:
|
|
210
|
+
* `actions.createNode`, `createNodeId`, `createContentDigest`, `cache`, `reporter`,
|
|
211
|
+
* `getNode`, `getNodes`, `getNodesByType`.
|
|
212
|
+
*
|
|
213
|
+
* After all sources have run, hadars auto-generates a GraphQL schema from the
|
|
214
|
+
* collected nodes and makes it available via `config.graphql` in `getInitProps`
|
|
215
|
+
* and `paths`. Requires `graphql` to be installed in the project.
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* sources: [
|
|
219
|
+
* {
|
|
220
|
+
* resolve: 'gatsby-source-filesystem',
|
|
221
|
+
* options: { name: 'posts', path: './content/posts' },
|
|
222
|
+
* },
|
|
223
|
+
* ]
|
|
224
|
+
*/
|
|
225
|
+
sources?: HadarsSourceEntry[];
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* A Gatsby-compatible source plugin entry, matching the format used in
|
|
229
|
+
* `gatsby-config.js` / `gatsby-config.ts`.
|
|
230
|
+
*/
|
|
231
|
+
interface HadarsSourceEntry {
|
|
232
|
+
/**
|
|
233
|
+
* Package name (e.g. `'gatsby-source-contentful'`) or a pre-imported module
|
|
234
|
+
* object that exports `sourceNodes`. Using a module object lets you pass
|
|
235
|
+
* local source plugins without publishing them to npm.
|
|
236
|
+
*/
|
|
237
|
+
resolve: string | {
|
|
238
|
+
sourceNodes?: (ctx: any, opts?: any) => Promise<void> | void;
|
|
239
|
+
};
|
|
240
|
+
/** Plugin options forwarded as the second argument to `sourceNodes`. */
|
|
241
|
+
options?: Record<string, unknown>;
|
|
151
242
|
}
|
|
152
243
|
type SwcPluginItem = string | [string, Record<string, unknown>] | {
|
|
153
244
|
path: string;
|
|
@@ -161,4 +252,4 @@ interface HadarsRequest extends Request {
|
|
|
161
252
|
cookies: Record<string, string>;
|
|
162
253
|
}
|
|
163
254
|
|
|
164
|
-
export type { HadarsEntryModule as H, HadarsOptions as a, HadarsApp as b, HadarsGetClientProps as c, HadarsGetFinalProps as d, HadarsGetInitialProps as e, HadarsProps as f, HadarsRequest as g };
|
|
255
|
+
export type { GraphQLExecutor as G, HadarsEntryModule as H, HadarsOptions as a, HadarsApp as b, HadarsGetClientProps as c, HadarsGetFinalProps as d, HadarsGetInitialProps as e, HadarsProps as f, HadarsRequest as g, HadarsSourceEntry as h, HadarsStaticContext as i };
|
|
@@ -1,4 +1,33 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* In-process GraphQL executor passed to `getInitProps` and `paths` during
|
|
3
|
+
* `hadars export static`. Hadars is executor-agnostic — configure it in
|
|
4
|
+
* `hadars.config.ts` using any GraphQL library (e.g. `graphql-js`):
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { graphql as gql, buildSchema } from 'graphql';
|
|
8
|
+
* const schema = buildSchema(`type Query { hello: String }`);
|
|
9
|
+
* const rootValue = { hello: () => 'world' };
|
|
10
|
+
*
|
|
11
|
+
* export default {
|
|
12
|
+
* graphql: (query, variables) =>
|
|
13
|
+
* gql({ schema, rootValue, source: query, variableValues: variables }),
|
|
14
|
+
* } satisfies HadarsOptions;
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
type GraphQLExecutor = (query: string, variables?: Record<string, unknown>) => Promise<{
|
|
18
|
+
data?: any;
|
|
19
|
+
errors?: ReadonlyArray<{
|
|
20
|
+
message: string;
|
|
21
|
+
}>;
|
|
22
|
+
}>;
|
|
23
|
+
/**
|
|
24
|
+
* Context passed as the second argument to `getInitProps` and `paths`
|
|
25
|
+
* during `hadars export static`. Not present in dev/run mode.
|
|
26
|
+
*/
|
|
27
|
+
interface HadarsStaticContext {
|
|
28
|
+
graphql: GraphQLExecutor;
|
|
29
|
+
}
|
|
30
|
+
type HadarsGetInitialProps<T extends {}> = (req: HadarsRequest, ctx?: HadarsStaticContext) => Promise<T> | T;
|
|
2
31
|
type HadarsGetClientProps<T extends {}> = (props: T) => Promise<T> | T;
|
|
3
32
|
type HadarsGetFinalProps<T extends {}> = (props: HadarsProps<T>) => Promise<T> | T;
|
|
4
33
|
type HadarsApp<T extends {}> = React.FC<HadarsProps<T>>;
|
|
@@ -148,6 +177,68 @@ interface HadarsOptions {
|
|
|
148
177
|
key: string;
|
|
149
178
|
ttl?: number;
|
|
150
179
|
} | null | undefined>;
|
|
180
|
+
/**
|
|
181
|
+
* Static export path list. Required for `hadars export static`.
|
|
182
|
+
*
|
|
183
|
+
* Return an array of URL paths (e.g. `['/', '/about', '/blog/hello']`) that
|
|
184
|
+
* should be pre-rendered to HTML files. May be async.
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* paths: () => ['/', '/about', '/contact']
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* paths: async () => {
|
|
191
|
+
* const posts = await fetchBlogPosts();
|
|
192
|
+
* return ['/', ...posts.map(p => `/blog/${p.slug}`)];
|
|
193
|
+
* }
|
|
194
|
+
*/
|
|
195
|
+
/**
|
|
196
|
+
* In-process GraphQL executor. Supply this to use the GraphQL data layer
|
|
197
|
+
* in `paths` and `getInitProps` during `hadars export static`.
|
|
198
|
+
* Has no effect in `dev` / `run` mode.
|
|
199
|
+
*/
|
|
200
|
+
graphql?: GraphQLExecutor;
|
|
201
|
+
paths?: (ctx: HadarsStaticContext) => Promise<string[]> | string[];
|
|
202
|
+
/**
|
|
203
|
+
* Gatsby-compatible source plugins to run before `hadars export static`.
|
|
204
|
+
*
|
|
205
|
+
* Each entry mirrors Gatsby's `gatsby-config.js` plugin object format:
|
|
206
|
+
* `{ resolve: 'gatsby-source-contentful', options: { spaceId: '...', accessToken: '...' } }`
|
|
207
|
+
*
|
|
208
|
+
* The plugin must export a `sourceNodes` function with the standard Gatsby API.
|
|
209
|
+
* Hadars provides a thin shim covering the most-used surface:
|
|
210
|
+
* `actions.createNode`, `createNodeId`, `createContentDigest`, `cache`, `reporter`,
|
|
211
|
+
* `getNode`, `getNodes`, `getNodesByType`.
|
|
212
|
+
*
|
|
213
|
+
* After all sources have run, hadars auto-generates a GraphQL schema from the
|
|
214
|
+
* collected nodes and makes it available via `config.graphql` in `getInitProps`
|
|
215
|
+
* and `paths`. Requires `graphql` to be installed in the project.
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* sources: [
|
|
219
|
+
* {
|
|
220
|
+
* resolve: 'gatsby-source-filesystem',
|
|
221
|
+
* options: { name: 'posts', path: './content/posts' },
|
|
222
|
+
* },
|
|
223
|
+
* ]
|
|
224
|
+
*/
|
|
225
|
+
sources?: HadarsSourceEntry[];
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* A Gatsby-compatible source plugin entry, matching the format used in
|
|
229
|
+
* `gatsby-config.js` / `gatsby-config.ts`.
|
|
230
|
+
*/
|
|
231
|
+
interface HadarsSourceEntry {
|
|
232
|
+
/**
|
|
233
|
+
* Package name (e.g. `'gatsby-source-contentful'`) or a pre-imported module
|
|
234
|
+
* object that exports `sourceNodes`. Using a module object lets you pass
|
|
235
|
+
* local source plugins without publishing them to npm.
|
|
236
|
+
*/
|
|
237
|
+
resolve: string | {
|
|
238
|
+
sourceNodes?: (ctx: any, opts?: any) => Promise<void> | void;
|
|
239
|
+
};
|
|
240
|
+
/** Plugin options forwarded as the second argument to `sourceNodes`. */
|
|
241
|
+
options?: Record<string, unknown>;
|
|
151
242
|
}
|
|
152
243
|
type SwcPluginItem = string | [string, Record<string, unknown>] | {
|
|
153
244
|
path: string;
|
|
@@ -161,4 +252,4 @@ interface HadarsRequest extends Request {
|
|
|
161
252
|
cookies: Record<string, string>;
|
|
162
253
|
}
|
|
163
254
|
|
|
164
|
-
export type { HadarsEntryModule as H, HadarsOptions as a, HadarsApp as b, HadarsGetClientProps as c, HadarsGetFinalProps as d, HadarsGetInitialProps as e, HadarsProps as f, HadarsRequest as g };
|
|
255
|
+
export type { GraphQLExecutor as G, HadarsEntryModule as H, HadarsOptions as a, HadarsApp as b, HadarsGetClientProps as c, HadarsGetFinalProps as d, HadarsGetInitialProps as e, HadarsProps as f, HadarsRequest as g, HadarsSourceEntry as h, HadarsStaticContext as i };
|
package/dist/index.cjs
CHANGED
|
@@ -226,14 +226,17 @@ function useServerData(key, fn) {
|
|
|
226
226
|
pendingDataFetch.set(pathKey, p);
|
|
227
227
|
queueMicrotask(async () => {
|
|
228
228
|
try {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
229
|
+
let json = null;
|
|
230
|
+
if (globalThis.__hadarsStatic) {
|
|
231
|
+
const sidecarUrl = pathKey.replace(/\/$/, "") + "/index.json";
|
|
232
|
+
const res = await fetch(sidecarUrl).catch(() => null);
|
|
233
|
+
if (res?.ok) json = await res.json().catch(() => null);
|
|
234
|
+
} else {
|
|
235
|
+
const res = await fetch(pathKey, { headers: { "Accept": "application/json" } });
|
|
236
|
+
if (res.ok) json = await res.json();
|
|
237
|
+
}
|
|
238
|
+
for (const [k, v] of Object.entries(json?.serverData ?? {})) {
|
|
239
|
+
clientServerDataCache.set(k, v);
|
|
237
240
|
}
|
|
238
241
|
} finally {
|
|
239
242
|
fetchedPaths.add(pathKey);
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { b as HadarsApp, H as HadarsEntryModule, c as HadarsGetClientProps, d as HadarsGetFinalProps, e as HadarsGetInitialProps, a as HadarsOptions, f as HadarsProps, g as HadarsRequest } from './hadars-
|
|
1
|
+
export { G as GraphQLExecutor, b as HadarsApp, H as HadarsEntryModule, c as HadarsGetClientProps, d as HadarsGetFinalProps, e as HadarsGetInitialProps, a as HadarsOptions, f as HadarsProps, g as HadarsRequest, h as HadarsSourceEntry, i as HadarsStaticContext } from './hadars-mKu5txjW.cjs';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
|
|
4
4
|
/** Call this before hydrating to seed the client cache from the server's data.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { b as HadarsApp, H as HadarsEntryModule, c as HadarsGetClientProps, d as HadarsGetFinalProps, e as HadarsGetInitialProps, a as HadarsOptions, f as HadarsProps, g as HadarsRequest } from './hadars-
|
|
1
|
+
export { G as GraphQLExecutor, b as HadarsApp, H as HadarsEntryModule, c as HadarsGetClientProps, d as HadarsGetFinalProps, e as HadarsGetInitialProps, a as HadarsOptions, f as HadarsProps, g as HadarsRequest, h as HadarsSourceEntry, i as HadarsStaticContext } from './hadars-mKu5txjW.js';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
|
|
4
4
|
/** Call this before hydrating to seed the client cache from the server's data.
|
package/dist/index.js
CHANGED
|
@@ -187,14 +187,17 @@ function useServerData(key, fn) {
|
|
|
187
187
|
pendingDataFetch.set(pathKey, p);
|
|
188
188
|
queueMicrotask(async () => {
|
|
189
189
|
try {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
190
|
+
let json = null;
|
|
191
|
+
if (globalThis.__hadarsStatic) {
|
|
192
|
+
const sidecarUrl = pathKey.replace(/\/$/, "") + "/index.json";
|
|
193
|
+
const res = await fetch(sidecarUrl).catch(() => null);
|
|
194
|
+
if (res?.ok) json = await res.json().catch(() => null);
|
|
195
|
+
} else {
|
|
196
|
+
const res = await fetch(pathKey, { headers: { "Accept": "application/json" } });
|
|
197
|
+
if (res.ok) json = await res.json();
|
|
198
|
+
}
|
|
199
|
+
for (const [k, v] of Object.entries(json?.serverData ?? {})) {
|
|
200
|
+
clientServerDataCache.set(k, v);
|
|
198
201
|
}
|
|
199
202
|
} finally {
|
|
200
203
|
fetchedPaths.add(pathKey);
|
package/dist/lambda.cjs
CHANGED
|
@@ -1226,7 +1226,7 @@ var getReactResponse = async (req, opts) => {
|
|
|
1226
1226
|
head: { title: "Hadars App", meta: {}, link: {}, style: {}, script: {}, status: 200 }
|
|
1227
1227
|
};
|
|
1228
1228
|
let props = {
|
|
1229
|
-
...getInitProps ? await getInitProps(req) : {},
|
|
1229
|
+
...getInitProps ? await getInitProps(req, opts.staticCtx) : {},
|
|
1230
1230
|
location: req.location,
|
|
1231
1231
|
context
|
|
1232
1232
|
};
|
|
@@ -1252,7 +1252,7 @@ var getReactResponse = async (req, opts) => {
|
|
|
1252
1252
|
}
|
|
1253
1253
|
};
|
|
1254
1254
|
const finalize = async () => {
|
|
1255
|
-
const
|
|
1255
|
+
const restProps = getFinalProps ? await getFinalProps(props) : props;
|
|
1256
1256
|
const serverData = {};
|
|
1257
1257
|
let hasServerData = false;
|
|
1258
1258
|
for (const [key, entry] of unsuspend.cache) {
|
package/dist/lambda.d.cts
CHANGED
package/dist/lambda.d.ts
CHANGED
package/dist/lambda.js
CHANGED
package/dist/utils/Head.tsx
CHANGED
|
@@ -246,14 +246,23 @@ export function useServerData<T>(key: string | string[], fn: () => Promise<T> |
|
|
|
246
246
|
// render is registered against the same deferred before the fetch starts.
|
|
247
247
|
queueMicrotask(async () => {
|
|
248
248
|
try {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
249
|
+
let json: { serverData: Record<string, unknown> } | null = null;
|
|
250
|
+
|
|
251
|
+
if ((globalThis as any).__hadarsStatic) {
|
|
252
|
+
// Static export: the __hadarsStatic flag was embedded in the
|
|
253
|
+
// page by `hadars export static`. Fetch the pre-generated
|
|
254
|
+
// index.json sidecar directly — no live SSR server exists.
|
|
255
|
+
const sidecarUrl = pathKey.replace(/\/$/, '') + '/index.json';
|
|
256
|
+
const res = await fetch(sidecarUrl).catch(() => null);
|
|
257
|
+
if (res?.ok) json = await res.json().catch(() => null);
|
|
258
|
+
} else {
|
|
259
|
+
// Live server: request the current URL with Accept: application/json.
|
|
260
|
+
const res = await fetch(pathKey, { headers: { 'Accept': 'application/json' } });
|
|
261
|
+
if (res.ok) json = await res.json();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
for (const [k, v] of Object.entries(json?.serverData ?? {})) {
|
|
265
|
+
clientServerDataCache.set(k, v);
|
|
257
266
|
}
|
|
258
267
|
} finally {
|
|
259
268
|
fetchedPaths.add(pathKey);
|
|
@@ -22,6 +22,15 @@ const getProps = () => {
|
|
|
22
22
|
const main = async () => {
|
|
23
23
|
let props = getProps();
|
|
24
24
|
|
|
25
|
+
// Extract the static-export flag before it reaches user code. When set,
|
|
26
|
+
// useServerData fetches index.json sidecars directly on client navigation
|
|
27
|
+
// instead of requesting the live SSR server with Accept: application/json.
|
|
28
|
+
if ((props as any).__hadarsStatic) {
|
|
29
|
+
(globalThis as any).__hadarsStatic = true;
|
|
30
|
+
const { __hadarsStatic: _, ...rest } = props as any;
|
|
31
|
+
props = rest;
|
|
32
|
+
}
|
|
33
|
+
|
|
25
34
|
// Seed the useServerData client cache from server-resolved values before
|
|
26
35
|
// hydration so that hooks return the same data on the first render.
|
|
27
36
|
if (props.__serverData && typeof props.__serverData === 'object') {
|
package/index.ts
CHANGED
package/package.json
CHANGED
package/src/build.ts
CHANGED
|
@@ -20,6 +20,9 @@ import {
|
|
|
20
20
|
buildSsrResponse, makePrecontentHtmlGetter,
|
|
21
21
|
type CacheFetchHandler, createRenderCache,
|
|
22
22
|
} from './utils/ssrHandler';
|
|
23
|
+
import { runSources } from './source/runner';
|
|
24
|
+
import { buildSchemaExecutor } from './source/inference';
|
|
25
|
+
import { createGraphiqlHandler, GRAPHQL_PATH } from './source/graphiql';
|
|
23
26
|
|
|
24
27
|
/**
|
|
25
28
|
* Reads an HTML template, processes any `<style>` blocks through PostCSS
|
|
@@ -295,6 +298,26 @@ export const dev = async (options: HadarsRuntimeOptions) => {
|
|
|
295
298
|
const handleWS = upgradeHandler(options);
|
|
296
299
|
const handler = options.fetch;
|
|
297
300
|
|
|
301
|
+
// Run source plugins and set up GraphiQL if config.sources is present.
|
|
302
|
+
let handleGraphiql: ((req: Request) => Promise<Response | undefined>) | null = null;
|
|
303
|
+
let devStaticCtx: { graphql: import('./types/hadars').GraphQLExecutor } | undefined;
|
|
304
|
+
if (options.sources && options.sources.length > 0) {
|
|
305
|
+
console.log(`[hadars] Running ${options.sources.length} source plugin(s)…`);
|
|
306
|
+
try {
|
|
307
|
+
const store = await runSources(options.sources);
|
|
308
|
+
const executor = await buildSchemaExecutor(store);
|
|
309
|
+
if (executor) {
|
|
310
|
+
devStaticCtx = { graphql: executor };
|
|
311
|
+
handleGraphiql = createGraphiqlHandler(executor);
|
|
312
|
+
console.log(`[hadars] GraphiQL available at http://localhost:${port}${GRAPHQL_PATH}`);
|
|
313
|
+
} else {
|
|
314
|
+
console.warn('[hadars] `graphql` package not found — GraphiQL disabled. Run: npm install graphql');
|
|
315
|
+
}
|
|
316
|
+
} catch (err) {
|
|
317
|
+
console.error('[hadars] Source plugin error:', err);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
298
321
|
const entry = pathMod.resolve(__dirname, options.entry);
|
|
299
322
|
const hmrPort = options.hmrPort ?? port + 1;
|
|
300
323
|
|
|
@@ -491,6 +514,11 @@ export const dev = async (options: HadarsRuntimeOptions) => {
|
|
|
491
514
|
}
|
|
492
515
|
if (handleWS && handleWS(request, ctx)) return undefined;
|
|
493
516
|
|
|
517
|
+
if (handleGraphiql) {
|
|
518
|
+
const graphiqlRes = await handleGraphiql(req);
|
|
519
|
+
if (graphiqlRes) return graphiqlRes;
|
|
520
|
+
}
|
|
521
|
+
|
|
494
522
|
const proxied = await handleProxy(request);
|
|
495
523
|
if (proxied) return proxied;
|
|
496
524
|
|
|
@@ -525,6 +553,7 @@ export const dev = async (options: HadarsRuntimeOptions) => {
|
|
|
525
553
|
getInitProps,
|
|
526
554
|
getFinalProps,
|
|
527
555
|
},
|
|
556
|
+
staticCtx: devStaticCtx,
|
|
528
557
|
});
|
|
529
558
|
|
|
530
559
|
// Content negotiation: if the client only accepts JSON (client-side
|
package/src/index.tsx
CHANGED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gatsby sourceNodes context shim.
|
|
3
|
+
*
|
|
4
|
+
* Gatsby passes a rich object to each source plugin's `sourceNodes` function.
|
|
5
|
+
* We implement the subset that the vast majority of CMS source plugins actually
|
|
6
|
+
* use so that existing plugins work without modification.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { createHash } from 'node:crypto';
|
|
10
|
+
import { EventEmitter } from 'node:events';
|
|
11
|
+
import type { NodeStore, HadarsNode } from './store';
|
|
12
|
+
|
|
13
|
+
// ── Types ─────────────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
export interface GatsbyCache {
|
|
16
|
+
get(key: string): Promise<unknown>;
|
|
17
|
+
set(key: string, value: unknown): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface GatsbyReporter {
|
|
21
|
+
info(message: string): void;
|
|
22
|
+
warn(message: string): void;
|
|
23
|
+
error(message: string, err?: Error): void;
|
|
24
|
+
panic(message: string, err?: Error): never;
|
|
25
|
+
activityTimer(name: string): { start(): void; end(): void };
|
|
26
|
+
verbose(message: string): void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface GatsbyActions {
|
|
30
|
+
createNode(node: HadarsNode): void;
|
|
31
|
+
deleteNode(node: { id: string }): void;
|
|
32
|
+
touchNode(node: { id: string }): void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface GatsbyNodeHelpers {
|
|
36
|
+
actions: GatsbyActions;
|
|
37
|
+
createNodeId(input: string): string;
|
|
38
|
+
createContentDigest(content: unknown): string;
|
|
39
|
+
getNode(id: string): HadarsNode | undefined;
|
|
40
|
+
getNodes(): HadarsNode[];
|
|
41
|
+
getNodesByType(type: string): HadarsNode[];
|
|
42
|
+
cache: GatsbyCache;
|
|
43
|
+
reporter: GatsbyReporter;
|
|
44
|
+
// Gatsby passes these; many plugins destructure but never call them
|
|
45
|
+
store: unknown;
|
|
46
|
+
emitter: unknown;
|
|
47
|
+
tracing: unknown;
|
|
48
|
+
schema: unknown;
|
|
49
|
+
/** Available in Gatsby 4+ — mostly unused by source plugins. */
|
|
50
|
+
parentSpan: unknown;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ── Factory ───────────────────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
export function makeGatsbyContext(
|
|
56
|
+
store: NodeStore,
|
|
57
|
+
pluginName: string,
|
|
58
|
+
pluginOptions: Record<string, unknown> = {},
|
|
59
|
+
emitter: EventEmitter = new EventEmitter(),
|
|
60
|
+
): GatsbyNodeHelpers {
|
|
61
|
+
// A simple in-memory cache per plugin instance
|
|
62
|
+
const cacheMap = new Map<string, unknown>();
|
|
63
|
+
const cache: GatsbyCache = {
|
|
64
|
+
get: (key) => Promise.resolve(cacheMap.get(key)),
|
|
65
|
+
set: (key, value) => { cacheMap.set(key, value); return Promise.resolve(); },
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const reporter: GatsbyReporter = {
|
|
69
|
+
info: (msg) => console.log(`[${pluginName}] ${msg}`),
|
|
70
|
+
warn: (msg) => console.warn(`[${pluginName}] WARN: ${msg}`),
|
|
71
|
+
error: (msg, err) => console.error(`[${pluginName}] ERROR: ${msg}`, err ?? ''),
|
|
72
|
+
panic: (msg, err) => { console.error(`[${pluginName}] PANIC: ${msg}`, err ?? ''); process.exit(1); },
|
|
73
|
+
verbose: (msg) => { if (process.env.HADARS_VERBOSE) console.log(`[${pluginName}] ${msg}`); },
|
|
74
|
+
activityTimer: (name) => ({
|
|
75
|
+
start: () => { if (process.env.HADARS_VERBOSE) console.log(`[${pluginName}] ▶ ${name}`); },
|
|
76
|
+
end: () => { if (process.env.HADARS_VERBOSE) console.log(`[${pluginName}] ■ ${name}`); },
|
|
77
|
+
}),
|
|
78
|
+
// Gatsby 4+ reporter extras — used by some plugins but safe to no-op
|
|
79
|
+
setErrorMap: () => {},
|
|
80
|
+
log: (msg: string) => console.log(`[${pluginName}] ${msg}`),
|
|
81
|
+
success: (msg: string) => console.log(`[${pluginName}] ✓ ${msg}`),
|
|
82
|
+
} as any;
|
|
83
|
+
|
|
84
|
+
const actions: GatsbyActions = {
|
|
85
|
+
createNode: (node) => store.createNode(node),
|
|
86
|
+
deleteNode: ({ id }) => {
|
|
87
|
+
// NodeStore doesn't expose delete — it's rarely needed by source plugins
|
|
88
|
+
// on first run, so we silently no-op. A full implementation would add
|
|
89
|
+
// store.deleteNode().
|
|
90
|
+
},
|
|
91
|
+
touchNode: () => { /* no-op — only relevant for Gatsby's caching layer */ },
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
actions,
|
|
96
|
+
createNodeId: (input) => createHash('sha256').update(pluginName + input).digest('hex'),
|
|
97
|
+
createContentDigest: (content) => {
|
|
98
|
+
const str = typeof content === 'string' ? content : JSON.stringify(content);
|
|
99
|
+
return createHash('md5').update(str).digest('hex');
|
|
100
|
+
},
|
|
101
|
+
getNode: (id) => store.getNode(id),
|
|
102
|
+
getNodes: () => store.getNodes(),
|
|
103
|
+
getNodesByType: (t) => store.getNodesByType(t),
|
|
104
|
+
cache,
|
|
105
|
+
reporter,
|
|
106
|
+
// Stubs for rarely-used Gatsby internals that plugins may destructure
|
|
107
|
+
store: {},
|
|
108
|
+
emitter,
|
|
109
|
+
tracing: { startSpan: () => ({ finish: () => {} }) },
|
|
110
|
+
schema: {},
|
|
111
|
+
parentSpan: null,
|
|
112
|
+
};
|
|
113
|
+
}
|