@usegraft/sdk-react 0.0.0-canary-20260901211203
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/LICENSE +21 -0
- package/README.md +108 -0
- package/dist/index.d.ts +107 -0
- package/dist/index.js +166 -0
- package/package.json +66 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anderson Joseph
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# @usegraft/sdk-react
|
|
2
|
+
|
|
3
|
+
> The browser client: typed content reads and hooks over Graft's read-only content API.
|
|
4
|
+
|
|
5
|
+
Part of [Graft](https://github.com/AndersonDesign1/graft), a CMS built so an AI agent is the primary operator.
|
|
6
|
+
|
|
7
|
+
Every other Graft SDK reads on a server. This one reads in the browser, for the places a loader cannot help: a search box, a client-side editor preview, a widget on a page the server rendered an hour ago.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm i @usegraft/sdk-react
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`react` is a peer dependency, 18 or 19.
|
|
16
|
+
|
|
17
|
+
## Types do not cross the wire
|
|
18
|
+
|
|
19
|
+
Your app imports its own `collections` from `graft.config.ts` at compile time, exactly as a server adapter does. The endpoint supplies data at runtime. Nothing is generated, nothing is fetched to be typed, and `getContent("docs", slug)` returns the document type your schema declares.
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
// src/graft.ts
|
|
23
|
+
import { createGraft, createGraftHooks } from "@usegraft/sdk-react";
|
|
24
|
+
import { collections } from "../graft.config";
|
|
25
|
+
|
|
26
|
+
export const graft = createGraft({
|
|
27
|
+
endpoint: "https://cms.example.com/api/content/v1",
|
|
28
|
+
collections,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export const { GraftProvider, useGraft, useContent, useContentList, useContentSearch } =
|
|
32
|
+
createGraftHooks(graft);
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The hooks come out of a factory rather than being importable directly, and that is what keeps the reads typed. A hook reading an untyped context could only promise `Document<AnyCollection>` — `data.title` would be `unknown` and a misspelled collection name would be a runtime surprise instead of a compile error. Binding the factory to your collections once buys typed reads everywhere.
|
|
36
|
+
|
|
37
|
+
## Serve the endpoint
|
|
38
|
+
|
|
39
|
+
`endpoint` points at a mount of Graft's read-only content API — what `graft serve` exposes at `/api/content/v1`, or your own mount of `createContentApiHandler` from [`@usegraft/content-api`](https://www.npmjs.com/package/@usegraft/content-api). It answers document reads and search, nothing else: no functions, no writes, no schema.
|
|
40
|
+
|
|
41
|
+
An endpoint is pinned to one branch on the server side, which is why there is no `branch` option to pass with `endpoint`. Point at the preview deployment's endpoint to read a preview branch.
|
|
42
|
+
|
|
43
|
+
## Read
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
import { useContent, useContentSearch } from "../graft";
|
|
47
|
+
|
|
48
|
+
function Doc({ slug }: { slug: string }) {
|
|
49
|
+
const { data, error, loading } = useContent("docs", slug);
|
|
50
|
+
|
|
51
|
+
if (loading) return <Spinner />;
|
|
52
|
+
if (error) return <Problem error={error} />;
|
|
53
|
+
if (!data) return <NotFound />;
|
|
54
|
+
return <article>{data.data.title}</article>;
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Each hook reports `{ data, error, loading, refresh }`. `data` answers the current arguments or it is `undefined`: changing the slug clears it rather than showing the previous document while the next one loads.
|
|
59
|
+
|
|
60
|
+
`useContentList(collection, options?)` and `useContentSearch(collection, query, options?)` are the same shape over `listContent` and `searchContent`.
|
|
61
|
+
|
|
62
|
+
## What this is not
|
|
63
|
+
|
|
64
|
+
It is not a cache. There is no deduplication, no retry, no background refresh, no stale-while-revalidate — a read starts when its arguments change and reports one answer. Your app almost certainly has TanStack Query or SWR already, both of which do that properly, and `graft.getContent` is a plain async function that composes with either:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
useQuery({
|
|
68
|
+
queryKey: ["docs", slug],
|
|
69
|
+
queryFn: () => graft.getContent("docs", slug),
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Shipping a worse copy of a query client inside an SDK is the wrong trade, so the hooks stay a binding and the caching stays yours.
|
|
74
|
+
|
|
75
|
+
It is also not a server renderer. `useEffect` does not run during server rendering, so on the server these hooks report their loading state and nothing else. Content that has to be in the HTML belongs in a loader or a server adapter: [`@usegraft/sdk-next`](https://www.npmjs.com/package/@usegraft/sdk-next), [`sdk-astro`](https://www.npmjs.com/package/@usegraft/sdk-astro), [`sdk-sveltekit`](https://www.npmjs.com/package/@usegraft/sdk-sveltekit), [`sdk-react-router`](https://www.npmjs.com/package/@usegraft/sdk-react-router), [`sdk-tanstack-start`](https://www.npmjs.com/package/@usegraft/sdk-tanstack-start).
|
|
76
|
+
|
|
77
|
+
## Point a subtree somewhere else
|
|
78
|
+
|
|
79
|
+
`GraftProvider` is optional — the hooks fall back to the handle the factory was built with. Mount one to override it for a subtree: a preview branch's endpoint, or a fake in tests.
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
<GraftProvider graft={previewGraft}>
|
|
83
|
+
<Preview />
|
|
84
|
+
</GraftProvider>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Without hooks
|
|
88
|
+
|
|
89
|
+
`createGraft` is useful on its own. It returns `getContent` / `listContent` / `searchContent` and the underlying `client`, all typed the same way, for event handlers, a query client, or anywhere React is not involved.
|
|
90
|
+
|
|
91
|
+
## Bring your own transport
|
|
92
|
+
|
|
93
|
+
Pass `index` instead of `endpoint` to read through any `ContentIndexReader` — a configured `createContentApiReader`, a service-worker-backed reader, or a fake in a test.
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { createContentApiReader } from "@usegraft/sdk-react";
|
|
97
|
+
|
|
98
|
+
export const graft = createGraft({
|
|
99
|
+
index: createContentApiReader({ endpoint, headers: { Authorization: `Bearer ${token}` } }),
|
|
100
|
+
collections,
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
There is no `db` option, and no `graftRoute`. A database handle in a browser bundle is a database URL in a browser bundle, and mounting a request handler is a server's job.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
MIT. [Repository](https://github.com/AndersonDesign1/graft) · [Changelog](https://github.com/AndersonDesign1/graft/blob/main/packages/sdk-react/CHANGELOG.md)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { AnyCollection, ReadOptions, Document, ListOptions, SearchOptions, SearchHit, GraftClient, ClientOptions } from '@usegraft/sdk-core';
|
|
2
|
+
export { AnyCollection, ChangeSet, ClientOptions, Document, GraftClient, ListOptions, ReadOptions, SearchHit, SearchOptions, TAG_NAMESPACE, collectionTag, createClient, documentTag, tagsFor, tagsForChanges, toDocument } from '@usegraft/sdk-core';
|
|
3
|
+
import { ReactNode, ReactElement } from 'react';
|
|
4
|
+
export { ContentApiReaderOptions, createContentApiReader } from '@usegraft/content-api';
|
|
5
|
+
|
|
6
|
+
interface GraftOptions<TCollections extends Record<string, AnyCollection>> extends Omit<ClientOptions<TCollections>, "index"> {
|
|
7
|
+
/**
|
|
8
|
+
* Your own reader, when `endpoint` is not enough. Optional here because
|
|
9
|
+
* `endpoint` is the ordinary way to configure this package, and exactly one
|
|
10
|
+
* of the two is required.
|
|
11
|
+
*/
|
|
12
|
+
index?: ClientOptions<TCollections>["index"];
|
|
13
|
+
/** Content API base URL, e.g. `https://cms.example.com/api/content/v1`. */
|
|
14
|
+
endpoint?: string | URL;
|
|
15
|
+
/** Static headers sent with every read, such as `Authorization`. */
|
|
16
|
+
headers?: Record<string, string>;
|
|
17
|
+
/** Fetch implementation, for tests, instrumentation, or a non-browser runtime. */
|
|
18
|
+
fetch?: typeof globalThis.fetch;
|
|
19
|
+
}
|
|
20
|
+
interface Graft<TCollections extends Record<string, AnyCollection>> {
|
|
21
|
+
/** Typed getDocument. */
|
|
22
|
+
getContent<K extends keyof TCollections & string>(collection: K, slug: string, options?: ReadOptions): Promise<Document<TCollections[K]> | null>;
|
|
23
|
+
/** Typed listDocuments. */
|
|
24
|
+
listContent<K extends keyof TCollections & string>(collection: K, options?: ListOptions): Promise<Document<TCollections[K]>[]>;
|
|
25
|
+
/** Typed searchDocuments (full-text, best-ranked first). */
|
|
26
|
+
searchContent<K extends keyof TCollections & string>(collection: K, query: string, options?: SearchOptions): Promise<SearchHit<TCollections[K]>[]>;
|
|
27
|
+
/** The underlying sdk-core client, for anything the helpers don't cover. */
|
|
28
|
+
client: GraftClient<TCollections>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Build the read handle. Give it your `collections` and the endpoint your
|
|
32
|
+
* content API is mounted at; reads are typed from the schema, not from the
|
|
33
|
+
* response.
|
|
34
|
+
*/
|
|
35
|
+
declare function createGraft<TCollections extends Record<string, AnyCollection>>(options: GraftOptions<TCollections>): Graft<TCollections>;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* React ergonomics over the read handle: a provider and three hooks that run
|
|
39
|
+
* the async reads and report their state.
|
|
40
|
+
*
|
|
41
|
+
* Deliberately not a cache. Every re-read starts from nothing and reports
|
|
42
|
+
* exactly one answer for the current arguments, because an SDK that invented
|
|
43
|
+
* its own stale-while-revalidate would be a worse copy of the query client the
|
|
44
|
+
* app already has. Wrap `graft.getContent` in TanStack Query or SWR when you
|
|
45
|
+
* want caching, retries, deduplication or background refresh — the handle is a
|
|
46
|
+
* plain async function and composes with either of them.
|
|
47
|
+
*
|
|
48
|
+
* Browser-side. `useEffect` does not run during server rendering, so on the
|
|
49
|
+
* server these hooks render their loading state and nothing else. Data that
|
|
50
|
+
* has to be in the HTML belongs in a loader or a server adapter
|
|
51
|
+
* (@usegraft/sdk-react-router, @usegraft/sdk-tanstack-start, and the rest).
|
|
52
|
+
*
|
|
53
|
+
* The hooks come out of a factory rather than being importable directly. That
|
|
54
|
+
* is what keeps the no-codegen contract: a hook reading an untyped context
|
|
55
|
+
* could only return `Document<AnyCollection>`, so `data.title` would be
|
|
56
|
+
* `unknown` and an unknown collection name would be a runtime surprise instead
|
|
57
|
+
* of a compile error. Binding the factory to your `collections` once is what
|
|
58
|
+
* buys typed reads everywhere the hooks are used.
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
/** What a hook reports about the read it is running. */
|
|
62
|
+
interface AsyncState<TData> {
|
|
63
|
+
/** The answer for the current arguments, or undefined until one arrives. */
|
|
64
|
+
data: TData | undefined;
|
|
65
|
+
/** Why the read failed, usually a GraftError. */
|
|
66
|
+
error: Error | undefined;
|
|
67
|
+
/** True while a read is in flight, the first one included. */
|
|
68
|
+
loading: boolean;
|
|
69
|
+
/** Run the read again. */
|
|
70
|
+
refresh: () => void;
|
|
71
|
+
}
|
|
72
|
+
interface GraftHooks<TCollections extends Record<string, AnyCollection>> {
|
|
73
|
+
/**
|
|
74
|
+
* Supplies the handle the hooks read. Optional — they fall back to the one
|
|
75
|
+
* the factory was built with. Use it to point a subtree somewhere else: a
|
|
76
|
+
* preview branch's endpoint, or a fake in tests.
|
|
77
|
+
*/
|
|
78
|
+
GraftProvider: (props: {
|
|
79
|
+
graft: Graft<TCollections>;
|
|
80
|
+
children: ReactNode;
|
|
81
|
+
}) => ReactElement;
|
|
82
|
+
/** The handle in scope, for reads the hooks don't cover. */
|
|
83
|
+
useGraft: () => Graft<TCollections>;
|
|
84
|
+
/** One document, or null when the collection has no such slug. */
|
|
85
|
+
useContent: <K extends keyof TCollections & string>(collection: K, slug: string, options?: ReadOptions) => AsyncState<Document<TCollections[K]> | null>;
|
|
86
|
+
/** A collection, in index order. */
|
|
87
|
+
useContentList: <K extends keyof TCollections & string>(collection: K, options?: ListOptions) => AsyncState<Document<TCollections[K]>[]>;
|
|
88
|
+
/** Full-text search within one collection, best-ranked first. */
|
|
89
|
+
useContentSearch: <K extends keyof TCollections & string>(collection: K, query: string, options?: SearchOptions) => AsyncState<SearchHit<TCollections[K]>[]>;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Bind the provider and hooks to one handle, and to the collections that
|
|
93
|
+
* handle was built with.
|
|
94
|
+
*
|
|
95
|
+
* ```ts
|
|
96
|
+
* // src/graft.ts
|
|
97
|
+
* import { createGraft, createGraftHooks } from "@usegraft/sdk-react";
|
|
98
|
+
* import { collections } from "../graft.config";
|
|
99
|
+
*
|
|
100
|
+
* export const graft = createGraft({ endpoint: "/api/content/v1", collections });
|
|
101
|
+
* export const { GraftProvider, useContent, useContentList, useContentSearch } =
|
|
102
|
+
* createGraftHooks(graft);
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
declare function createGraftHooks<TCollections extends Record<string, AnyCollection>>(graft: Graft<TCollections>): GraftHooks<TCollections>;
|
|
106
|
+
|
|
107
|
+
export { type AsyncState, type Graft, type GraftHooks, type GraftOptions, createGraft, createGraftHooks };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// src/graft.ts
|
|
2
|
+
import { createContentApiReader } from "@usegraft/content-api";
|
|
3
|
+
import { GraftError } from "@usegraft/contracts";
|
|
4
|
+
import {
|
|
5
|
+
createClient
|
|
6
|
+
} from "@usegraft/sdk-core";
|
|
7
|
+
function readerFor(options) {
|
|
8
|
+
if (options.endpoint === void 0) {
|
|
9
|
+
if (options.index === void 0) {
|
|
10
|
+
throw new GraftError({
|
|
11
|
+
code: "CONFIG_INVALID",
|
|
12
|
+
message: "createGraft needs somewhere to read from: pass `endpoint` or `index`.",
|
|
13
|
+
fix: "Pass `endpoint` (the content API a `graft serve` mounts, e.g. https://cms.example.com/api/content/v1) or `index` (your own ContentIndexReader). There is no `db` option in the browser."
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return options.index;
|
|
17
|
+
}
|
|
18
|
+
if (options.branch !== void 0) {
|
|
19
|
+
throw new GraftError({
|
|
20
|
+
code: "CONFIG_INVALID",
|
|
21
|
+
message: "`branch` cannot be combined with `endpoint`.",
|
|
22
|
+
fix: "Each content API endpoint serves exactly one branch, fixed by the server. Point `endpoint` at the branch's own deployment instead of asking for one here.",
|
|
23
|
+
details: { branch: options.branch, endpoint: options.endpoint.toString() }
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return createContentApiReader({
|
|
27
|
+
endpoint: options.endpoint,
|
|
28
|
+
headers: options.headers,
|
|
29
|
+
fetch: options.fetch
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function createGraft(options) {
|
|
33
|
+
const client = createClient({
|
|
34
|
+
index: readerFor(options),
|
|
35
|
+
collections: options.collections,
|
|
36
|
+
branch: options.branch
|
|
37
|
+
});
|
|
38
|
+
const endpointPinned = options.endpoint !== void 0;
|
|
39
|
+
function assertNoBranch(opts) {
|
|
40
|
+
if (!endpointPinned || opts?.branch === void 0) return;
|
|
41
|
+
throw new GraftError({
|
|
42
|
+
code: "CONFIG_INVALID",
|
|
43
|
+
message: "`branch` cannot be passed to a read on an endpoint-backed handle.",
|
|
44
|
+
fix: "Each content API endpoint serves exactly one branch, fixed by the server, and the branch query param is refused. Point a second handle at the preview's own endpoint instead of asking this one for a branch.",
|
|
45
|
+
details: { branch: opts.branch, endpoint: String(options.endpoint) }
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
client,
|
|
50
|
+
async getContent(collection, slug, opts) {
|
|
51
|
+
assertNoBranch(opts);
|
|
52
|
+
return client.getDocument(collection, slug, opts);
|
|
53
|
+
},
|
|
54
|
+
async listContent(collection, opts) {
|
|
55
|
+
assertNoBranch(opts);
|
|
56
|
+
return client.listDocuments(collection, opts);
|
|
57
|
+
},
|
|
58
|
+
async searchContent(collection, query, opts) {
|
|
59
|
+
assertNoBranch(opts);
|
|
60
|
+
return client.searchDocuments(collection, query, opts);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/hooks.tsx
|
|
66
|
+
import {
|
|
67
|
+
createContext,
|
|
68
|
+
useCallback,
|
|
69
|
+
useContext,
|
|
70
|
+
useEffect,
|
|
71
|
+
useState
|
|
72
|
+
} from "react";
|
|
73
|
+
import { jsx } from "react/jsx-runtime";
|
|
74
|
+
var PENDING = { data: void 0, error: void 0, loading: true };
|
|
75
|
+
function asError(cause) {
|
|
76
|
+
return cause instanceof Error ? cause : new Error(String(cause));
|
|
77
|
+
}
|
|
78
|
+
function useRead(read) {
|
|
79
|
+
const [attempt, setAttempt] = useState(0);
|
|
80
|
+
const [state, setState] = useState(PENDING);
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
let live = true;
|
|
83
|
+
setState(PENDING);
|
|
84
|
+
read().then(
|
|
85
|
+
(data) => {
|
|
86
|
+
if (live) setState({ data, error: void 0, loading: false });
|
|
87
|
+
},
|
|
88
|
+
(cause) => {
|
|
89
|
+
if (live) setState({ data: void 0, error: asError(cause), loading: false });
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
return () => {
|
|
93
|
+
live = false;
|
|
94
|
+
};
|
|
95
|
+
}, [read, attempt]);
|
|
96
|
+
const refresh = useCallback(() => setAttempt((previous) => previous + 1), []);
|
|
97
|
+
return { ...state, refresh };
|
|
98
|
+
}
|
|
99
|
+
function createGraftHooks(graft) {
|
|
100
|
+
const GraftContext = createContext(graft);
|
|
101
|
+
function GraftProvider({
|
|
102
|
+
graft: value,
|
|
103
|
+
children
|
|
104
|
+
}) {
|
|
105
|
+
return /* @__PURE__ */ jsx(GraftContext.Provider, { value, children });
|
|
106
|
+
}
|
|
107
|
+
const useGraft = () => useContext(GraftContext);
|
|
108
|
+
return {
|
|
109
|
+
GraftProvider,
|
|
110
|
+
useGraft,
|
|
111
|
+
useContent(collection, slug, options) {
|
|
112
|
+
const handle = useGraft();
|
|
113
|
+
const branch = options?.branch;
|
|
114
|
+
const read = useCallback(
|
|
115
|
+
() => handle.getContent(collection, slug, { branch }),
|
|
116
|
+
[handle, collection, slug, branch]
|
|
117
|
+
);
|
|
118
|
+
return useRead(read);
|
|
119
|
+
},
|
|
120
|
+
useContentList(collection, options) {
|
|
121
|
+
const handle = useGraft();
|
|
122
|
+
const branch = options?.branch;
|
|
123
|
+
const limit = options?.limit;
|
|
124
|
+
const offset = options?.offset;
|
|
125
|
+
const read = useCallback(
|
|
126
|
+
() => handle.listContent(collection, { branch, limit, offset }),
|
|
127
|
+
[handle, collection, branch, limit, offset]
|
|
128
|
+
);
|
|
129
|
+
return useRead(read);
|
|
130
|
+
},
|
|
131
|
+
useContentSearch(collection, query, options) {
|
|
132
|
+
const handle = useGraft();
|
|
133
|
+
const branch = options?.branch;
|
|
134
|
+
const limit = options?.limit;
|
|
135
|
+
const read = useCallback(
|
|
136
|
+
() => handle.searchContent(collection, query, { branch, limit }),
|
|
137
|
+
[handle, collection, query, branch, limit]
|
|
138
|
+
);
|
|
139
|
+
return useRead(read);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// src/index.ts
|
|
145
|
+
import {
|
|
146
|
+
createClient as createClient2,
|
|
147
|
+
toDocument,
|
|
148
|
+
collectionTag,
|
|
149
|
+
documentTag,
|
|
150
|
+
tagsFor,
|
|
151
|
+
tagsForChanges,
|
|
152
|
+
TAG_NAMESPACE
|
|
153
|
+
} from "@usegraft/sdk-core";
|
|
154
|
+
import { createContentApiReader as createContentApiReader2 } from "@usegraft/content-api";
|
|
155
|
+
export {
|
|
156
|
+
TAG_NAMESPACE,
|
|
157
|
+
collectionTag,
|
|
158
|
+
createClient2 as createClient,
|
|
159
|
+
createContentApiReader2 as createContentApiReader,
|
|
160
|
+
createGraft,
|
|
161
|
+
createGraftHooks,
|
|
162
|
+
documentTag,
|
|
163
|
+
tagsFor,
|
|
164
|
+
tagsForChanges,
|
|
165
|
+
toDocument
|
|
166
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@usegraft/sdk-react",
|
|
3
|
+
"version": "0.0.0-canary-20260901211203",
|
|
4
|
+
"description": "React browser client: typed content reads and hooks over Graft's read-only content API.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agent",
|
|
7
|
+
"ai",
|
|
8
|
+
"cms",
|
|
9
|
+
"content-api",
|
|
10
|
+
"graft",
|
|
11
|
+
"headless-cms",
|
|
12
|
+
"hooks",
|
|
13
|
+
"react",
|
|
14
|
+
"spa",
|
|
15
|
+
"typescript"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://github.com/AndersonDesign1/graft#readme",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/AndersonDesign1/graft.git",
|
|
22
|
+
"directory": "packages/sdk-react"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"type": "module",
|
|
28
|
+
"main": "./dist/index.js",
|
|
29
|
+
"module": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"import": "./dist/index.js"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@usegraft/content-api": "0.0.0-canary-20260901211203",
|
|
42
|
+
"@usegraft/contracts": "0.0.0-canary-20260901211203",
|
|
43
|
+
"@usegraft/sdk-core": "0.0.0-canary-20260901211203"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@testing-library/react": "^16.3.0",
|
|
47
|
+
"@types/react": "^19.0.0",
|
|
48
|
+
"@types/react-dom": "^19.0.0",
|
|
49
|
+
"@usegraft/core": "0.0.0-canary-20260901211203",
|
|
50
|
+
"jsdom": "^25.0.1",
|
|
51
|
+
"react": "^19.0.0",
|
|
52
|
+
"react-dom": "^19.0.0"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"react": ">=18.0.0"
|
|
56
|
+
},
|
|
57
|
+
"engines": {
|
|
58
|
+
"node": ">=22.16"
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "tsup src/index.ts --format esm --dts --clean --external react --external react/jsx-runtime",
|
|
62
|
+
"dev": "tsup src/index.ts --format esm --watch --external react --external react/jsx-runtime",
|
|
63
|
+
"typecheck": "tsc --noEmit",
|
|
64
|
+
"test": "vitest run --passWithNoTests"
|
|
65
|
+
}
|
|
66
|
+
}
|