@objectstack/client-react 0.6.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/CHANGELOG.md +22 -0
- package/LICENSE +202 -0
- package/README.md +273 -0
- package/dist/context.d.ts +45 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +86 -0
- package/dist/data-hooks.d.ts +246 -0
- package/dist/data-hooks.d.ts.map +1 -0
- package/dist/data-hooks.js +398 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/metadata-hooks.d.ts +121 -0
- package/dist/metadata-hooks.d.ts.map +1 -0
- package/dist/metadata-hooks.js +243 -0
- package/package.json +22 -0
- package/src/context.tsx +66 -0
- package/src/data-hooks.tsx +593 -0
- package/src/index.tsx +47 -0
- package/src/metadata-hooks.tsx +312 -0
- package/tsconfig.json +12 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @objectstack/client-react
|
|
4
|
+
*
|
|
5
|
+
* React hooks for ObjectStack Client SDK
|
|
6
|
+
*
|
|
7
|
+
* Provides type-safe React hooks for:
|
|
8
|
+
* - Data queries (useQuery, useMutation, usePagination, useInfiniteQuery)
|
|
9
|
+
* - Metadata access (useObject, useView, useFields, useMetadata)
|
|
10
|
+
* - Client context (ObjectStackProvider, useClient)
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.ObjectStackClient = exports.useMetadata = exports.useFields = exports.useView = exports.useObject = exports.useInfiniteQuery = exports.usePagination = exports.useMutation = exports.useQuery = exports.useClient = exports.ObjectStackContext = exports.ObjectStackProvider = void 0;
|
|
14
|
+
// Context & Provider
|
|
15
|
+
var context_1 = require("./context");
|
|
16
|
+
Object.defineProperty(exports, "ObjectStackProvider", { enumerable: true, get: function () { return context_1.ObjectStackProvider; } });
|
|
17
|
+
Object.defineProperty(exports, "ObjectStackContext", { enumerable: true, get: function () { return context_1.ObjectStackContext; } });
|
|
18
|
+
Object.defineProperty(exports, "useClient", { enumerable: true, get: function () { return context_1.useClient; } });
|
|
19
|
+
// Data Hooks
|
|
20
|
+
var data_hooks_1 = require("./data-hooks");
|
|
21
|
+
Object.defineProperty(exports, "useQuery", { enumerable: true, get: function () { return data_hooks_1.useQuery; } });
|
|
22
|
+
Object.defineProperty(exports, "useMutation", { enumerable: true, get: function () { return data_hooks_1.useMutation; } });
|
|
23
|
+
Object.defineProperty(exports, "usePagination", { enumerable: true, get: function () { return data_hooks_1.usePagination; } });
|
|
24
|
+
Object.defineProperty(exports, "useInfiniteQuery", { enumerable: true, get: function () { return data_hooks_1.useInfiniteQuery; } });
|
|
25
|
+
// Metadata Hooks
|
|
26
|
+
var metadata_hooks_1 = require("./metadata-hooks");
|
|
27
|
+
Object.defineProperty(exports, "useObject", { enumerable: true, get: function () { return metadata_hooks_1.useObject; } });
|
|
28
|
+
Object.defineProperty(exports, "useView", { enumerable: true, get: function () { return metadata_hooks_1.useView; } });
|
|
29
|
+
Object.defineProperty(exports, "useFields", { enumerable: true, get: function () { return metadata_hooks_1.useFields; } });
|
|
30
|
+
Object.defineProperty(exports, "useMetadata", { enumerable: true, get: function () { return metadata_hooks_1.useMetadata; } });
|
|
31
|
+
// Re-export ObjectStackClient and types from @objectstack/client
|
|
32
|
+
var client_1 = require("@objectstack/client");
|
|
33
|
+
Object.defineProperty(exports, "ObjectStackClient", { enumerable: true, get: function () { return client_1.ObjectStackClient; } });
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metadata Hooks
|
|
3
|
+
*
|
|
4
|
+
* React hooks for accessing ObjectStack metadata (schemas, views, fields)
|
|
5
|
+
*/
|
|
6
|
+
import { useClient } from './context';
|
|
7
|
+
/**
|
|
8
|
+
* Metadata query options
|
|
9
|
+
*/
|
|
10
|
+
export interface UseMetadataOptions {
|
|
11
|
+
/** Enable/disable automatic query execution */
|
|
12
|
+
enabled?: boolean;
|
|
13
|
+
/** Use cached metadata if available */
|
|
14
|
+
useCache?: boolean;
|
|
15
|
+
/** ETag for conditional requests */
|
|
16
|
+
ifNoneMatch?: string;
|
|
17
|
+
/** If-Modified-Since header for conditional requests */
|
|
18
|
+
ifModifiedSince?: string;
|
|
19
|
+
/** Callback on successful query */
|
|
20
|
+
onSuccess?: (data: any) => void;
|
|
21
|
+
/** Callback on error */
|
|
22
|
+
onError?: (error: Error) => void;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Metadata query result
|
|
26
|
+
*/
|
|
27
|
+
export interface UseMetadataResult<T = any> {
|
|
28
|
+
/** Metadata data */
|
|
29
|
+
data: T | null;
|
|
30
|
+
/** Loading state */
|
|
31
|
+
isLoading: boolean;
|
|
32
|
+
/** Error state */
|
|
33
|
+
error: Error | null;
|
|
34
|
+
/** Refetch the metadata */
|
|
35
|
+
refetch: () => Promise<void>;
|
|
36
|
+
/** ETag from last fetch */
|
|
37
|
+
etag?: string;
|
|
38
|
+
/** Whether data came from cache (304 Not Modified) */
|
|
39
|
+
fromCache: boolean;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Hook for fetching object schema/metadata
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```tsx
|
|
46
|
+
* function ObjectSchemaViewer({ objectName }: { objectName: string }) {
|
|
47
|
+
* const { data: schema, isLoading, error } = useObject(objectName);
|
|
48
|
+
*
|
|
49
|
+
* if (isLoading) return <div>Loading schema...</div>;
|
|
50
|
+
* if (error) return <div>Error: {error.message}</div>;
|
|
51
|
+
*
|
|
52
|
+
* return (
|
|
53
|
+
* <div>
|
|
54
|
+
* <h2>{schema.label}</h2>
|
|
55
|
+
* <p>Fields: {Object.keys(schema.fields).length}</p>
|
|
56
|
+
* </div>
|
|
57
|
+
* );
|
|
58
|
+
* }
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export declare function useObject(objectName: string, options?: UseMetadataOptions): UseMetadataResult;
|
|
62
|
+
/**
|
|
63
|
+
* Hook for fetching view configuration
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```tsx
|
|
67
|
+
* function ViewConfiguration({ objectName }: { objectName: string }) {
|
|
68
|
+
* const { data: view, isLoading } = useView(objectName, 'list');
|
|
69
|
+
*
|
|
70
|
+
* if (isLoading) return <div>Loading view...</div>;
|
|
71
|
+
*
|
|
72
|
+
* return (
|
|
73
|
+
* <div>
|
|
74
|
+
* <h3>List View for {objectName}</h3>
|
|
75
|
+
* <p>Columns: {view?.columns?.length}</p>
|
|
76
|
+
* </div>
|
|
77
|
+
* );
|
|
78
|
+
* }
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare function useView(objectName: string, viewType?: 'list' | 'form', options?: UseMetadataOptions): UseMetadataResult;
|
|
82
|
+
/**
|
|
83
|
+
* Hook for extracting fields from object schema
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```tsx
|
|
87
|
+
* function FieldList({ objectName }: { objectName: string }) {
|
|
88
|
+
* const { data: fields, isLoading } = useFields(objectName);
|
|
89
|
+
*
|
|
90
|
+
* if (isLoading) return <div>Loading fields...</div>;
|
|
91
|
+
*
|
|
92
|
+
* return (
|
|
93
|
+
* <ul>
|
|
94
|
+
* {fields?.map(field => (
|
|
95
|
+
* <li key={field.name}>{field.label} ({field.type})</li>
|
|
96
|
+
* ))}
|
|
97
|
+
* </ul>
|
|
98
|
+
* );
|
|
99
|
+
* }
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
export declare function useFields(objectName: string, options?: UseMetadataOptions): UseMetadataResult<any[]>;
|
|
103
|
+
/**
|
|
104
|
+
* Generic metadata hook for custom metadata queries
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```tsx
|
|
108
|
+
* function CustomMetadata() {
|
|
109
|
+
* const { data, isLoading } = useMetadata(async (client) => {
|
|
110
|
+
* // Custom metadata fetching logic
|
|
111
|
+
* const object = await client.meta.getObject('custom_object');
|
|
112
|
+
* const view = await client.meta.getView('custom_object', 'list');
|
|
113
|
+
* return { object, view };
|
|
114
|
+
* });
|
|
115
|
+
*
|
|
116
|
+
* return <pre>{JSON.stringify(data, null, 2)}</pre>;
|
|
117
|
+
* }
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
export declare function useMetadata<T = any>(fetcher: (client: ReturnType<typeof useClient>) => Promise<T>, options?: Omit<UseMetadataOptions, 'useCache' | 'ifNoneMatch' | 'ifModifiedSince'>): UseMetadataResult<T>;
|
|
121
|
+
//# sourceMappingURL=metadata-hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metadata-hooks.d.ts","sourceRoot":"","sources":["../src/metadata-hooks.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,+CAA+C;IAC/C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oCAAoC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wDAAwD;IACxD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mCAAmC;IACnC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IAChC,wBAAwB;IACxB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB,CAAC,CAAC,GAAG,GAAG;IACxC,oBAAoB;IACpB,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;IACf,oBAAoB;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,kBAAkB;IAClB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,2BAA2B;IAC3B,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,2BAA2B;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,SAAS,CACvB,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,kBAAuB,GAC/B,iBAAiB,CAyEnB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,OAAO,CACrB,UAAU,EAAE,MAAM,EAClB,QAAQ,GAAE,MAAM,GAAG,MAAe,EAClC,OAAO,GAAE,kBAAuB,GAC/B,iBAAiB,CA0CnB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,SAAS,CACvB,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,kBAAuB,GAC/B,iBAAiB,CAAC,GAAG,EAAE,CAAC,CAc1B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,WAAW,CAAC,CAAC,GAAG,GAAG,EACjC,OAAO,EAAE,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,EAC7D,OAAO,GAAE,IAAI,CAAC,kBAAkB,EAAE,UAAU,GAAG,aAAa,GAAG,iBAAiB,CAAM,GACrF,iBAAiB,CAAC,CAAC,CAAC,CA0CtB"}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Metadata Hooks
|
|
4
|
+
*
|
|
5
|
+
* React hooks for accessing ObjectStack metadata (schemas, views, fields)
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.useObject = useObject;
|
|
9
|
+
exports.useView = useView;
|
|
10
|
+
exports.useFields = useFields;
|
|
11
|
+
exports.useMetadata = useMetadata;
|
|
12
|
+
const react_1 = require("react");
|
|
13
|
+
const context_1 = require("./context");
|
|
14
|
+
/**
|
|
15
|
+
* Hook for fetching object schema/metadata
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```tsx
|
|
19
|
+
* function ObjectSchemaViewer({ objectName }: { objectName: string }) {
|
|
20
|
+
* const { data: schema, isLoading, error } = useObject(objectName);
|
|
21
|
+
*
|
|
22
|
+
* if (isLoading) return <div>Loading schema...</div>;
|
|
23
|
+
* if (error) return <div>Error: {error.message}</div>;
|
|
24
|
+
*
|
|
25
|
+
* return (
|
|
26
|
+
* <div>
|
|
27
|
+
* <h2>{schema.label}</h2>
|
|
28
|
+
* <p>Fields: {Object.keys(schema.fields).length}</p>
|
|
29
|
+
* </div>
|
|
30
|
+
* );
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
function useObject(objectName, options = {}) {
|
|
35
|
+
const client = (0, context_1.useClient)();
|
|
36
|
+
const [data, setData] = (0, react_1.useState)(null);
|
|
37
|
+
const [isLoading, setIsLoading] = (0, react_1.useState)(true);
|
|
38
|
+
const [error, setError] = (0, react_1.useState)(null);
|
|
39
|
+
const [etag, setEtag] = (0, react_1.useState)();
|
|
40
|
+
const [fromCache, setFromCache] = (0, react_1.useState)(false);
|
|
41
|
+
const { enabled = true, useCache = true, ifNoneMatch, ifModifiedSince, onSuccess, onError } = options;
|
|
42
|
+
const fetchMetadata = (0, react_1.useCallback)(async () => {
|
|
43
|
+
if (!enabled)
|
|
44
|
+
return;
|
|
45
|
+
try {
|
|
46
|
+
setIsLoading(true);
|
|
47
|
+
setError(null);
|
|
48
|
+
setFromCache(false);
|
|
49
|
+
if (useCache) {
|
|
50
|
+
// Use cached metadata endpoint
|
|
51
|
+
const result = await client.meta.getCached(objectName, {
|
|
52
|
+
ifNoneMatch: ifNoneMatch || etag,
|
|
53
|
+
ifModifiedSince
|
|
54
|
+
});
|
|
55
|
+
if (result.notModified) {
|
|
56
|
+
setFromCache(true);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
setData(result.data);
|
|
60
|
+
if (result.etag) {
|
|
61
|
+
setEtag(result.etag.value);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
onSuccess?.(result.data || data);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
// Direct fetch without cache
|
|
68
|
+
const result = await client.meta.getObject(objectName);
|
|
69
|
+
setData(result);
|
|
70
|
+
onSuccess?.(result);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
const error = err instanceof Error ? err : new Error('Failed to fetch object metadata');
|
|
75
|
+
setError(error);
|
|
76
|
+
onError?.(error);
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
setIsLoading(false);
|
|
80
|
+
}
|
|
81
|
+
}, [client, objectName, enabled, useCache, ifNoneMatch, ifModifiedSince, etag, data, onSuccess, onError]);
|
|
82
|
+
(0, react_1.useEffect)(() => {
|
|
83
|
+
fetchMetadata();
|
|
84
|
+
}, [fetchMetadata]);
|
|
85
|
+
const refetch = (0, react_1.useCallback)(async () => {
|
|
86
|
+
await fetchMetadata();
|
|
87
|
+
}, [fetchMetadata]);
|
|
88
|
+
return {
|
|
89
|
+
data,
|
|
90
|
+
isLoading,
|
|
91
|
+
error,
|
|
92
|
+
refetch,
|
|
93
|
+
etag,
|
|
94
|
+
fromCache
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Hook for fetching view configuration
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```tsx
|
|
102
|
+
* function ViewConfiguration({ objectName }: { objectName: string }) {
|
|
103
|
+
* const { data: view, isLoading } = useView(objectName, 'list');
|
|
104
|
+
*
|
|
105
|
+
* if (isLoading) return <div>Loading view...</div>;
|
|
106
|
+
*
|
|
107
|
+
* return (
|
|
108
|
+
* <div>
|
|
109
|
+
* <h3>List View for {objectName}</h3>
|
|
110
|
+
* <p>Columns: {view?.columns?.length}</p>
|
|
111
|
+
* </div>
|
|
112
|
+
* );
|
|
113
|
+
* }
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
function useView(objectName, viewType = 'list', options = {}) {
|
|
117
|
+
const client = (0, context_1.useClient)();
|
|
118
|
+
const [data, setData] = (0, react_1.useState)(null);
|
|
119
|
+
const [isLoading, setIsLoading] = (0, react_1.useState)(true);
|
|
120
|
+
const [error, setError] = (0, react_1.useState)(null);
|
|
121
|
+
const { enabled = true, onSuccess, onError } = options;
|
|
122
|
+
const fetchView = (0, react_1.useCallback)(async () => {
|
|
123
|
+
if (!enabled)
|
|
124
|
+
return;
|
|
125
|
+
try {
|
|
126
|
+
setIsLoading(true);
|
|
127
|
+
setError(null);
|
|
128
|
+
const result = await client.meta.getView(objectName, viewType);
|
|
129
|
+
setData(result);
|
|
130
|
+
onSuccess?.(result);
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
const error = err instanceof Error ? err : new Error('Failed to fetch view configuration');
|
|
134
|
+
setError(error);
|
|
135
|
+
onError?.(error);
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
setIsLoading(false);
|
|
139
|
+
}
|
|
140
|
+
}, [client, objectName, viewType, enabled, onSuccess, onError]);
|
|
141
|
+
(0, react_1.useEffect)(() => {
|
|
142
|
+
fetchView();
|
|
143
|
+
}, [fetchView]);
|
|
144
|
+
const refetch = (0, react_1.useCallback)(async () => {
|
|
145
|
+
await fetchView();
|
|
146
|
+
}, [fetchView]);
|
|
147
|
+
return {
|
|
148
|
+
data,
|
|
149
|
+
isLoading,
|
|
150
|
+
error,
|
|
151
|
+
refetch,
|
|
152
|
+
fromCache: false
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Hook for extracting fields from object schema
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```tsx
|
|
160
|
+
* function FieldList({ objectName }: { objectName: string }) {
|
|
161
|
+
* const { data: fields, isLoading } = useFields(objectName);
|
|
162
|
+
*
|
|
163
|
+
* if (isLoading) return <div>Loading fields...</div>;
|
|
164
|
+
*
|
|
165
|
+
* return (
|
|
166
|
+
* <ul>
|
|
167
|
+
* {fields?.map(field => (
|
|
168
|
+
* <li key={field.name}>{field.label} ({field.type})</li>
|
|
169
|
+
* ))}
|
|
170
|
+
* </ul>
|
|
171
|
+
* );
|
|
172
|
+
* }
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
function useFields(objectName, options = {}) {
|
|
176
|
+
const objectResult = useObject(objectName, options);
|
|
177
|
+
const fields = objectResult.data?.fields
|
|
178
|
+
? Object.entries(objectResult.data.fields).map(([name, field]) => ({
|
|
179
|
+
name,
|
|
180
|
+
...field
|
|
181
|
+
}))
|
|
182
|
+
: null;
|
|
183
|
+
return {
|
|
184
|
+
...objectResult,
|
|
185
|
+
data: fields
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Generic metadata hook for custom metadata queries
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```tsx
|
|
193
|
+
* function CustomMetadata() {
|
|
194
|
+
* const { data, isLoading } = useMetadata(async (client) => {
|
|
195
|
+
* // Custom metadata fetching logic
|
|
196
|
+
* const object = await client.meta.getObject('custom_object');
|
|
197
|
+
* const view = await client.meta.getView('custom_object', 'list');
|
|
198
|
+
* return { object, view };
|
|
199
|
+
* });
|
|
200
|
+
*
|
|
201
|
+
* return <pre>{JSON.stringify(data, null, 2)}</pre>;
|
|
202
|
+
* }
|
|
203
|
+
* ```
|
|
204
|
+
*/
|
|
205
|
+
function useMetadata(fetcher, options = {}) {
|
|
206
|
+
const client = (0, context_1.useClient)();
|
|
207
|
+
const [data, setData] = (0, react_1.useState)(null);
|
|
208
|
+
const [isLoading, setIsLoading] = (0, react_1.useState)(true);
|
|
209
|
+
const [error, setError] = (0, react_1.useState)(null);
|
|
210
|
+
const { enabled = true, onSuccess, onError } = options;
|
|
211
|
+
const fetchMetadata = (0, react_1.useCallback)(async () => {
|
|
212
|
+
if (!enabled)
|
|
213
|
+
return;
|
|
214
|
+
try {
|
|
215
|
+
setIsLoading(true);
|
|
216
|
+
setError(null);
|
|
217
|
+
const result = await fetcher(client);
|
|
218
|
+
setData(result);
|
|
219
|
+
onSuccess?.(result);
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
const error = err instanceof Error ? err : new Error('Failed to fetch metadata');
|
|
223
|
+
setError(error);
|
|
224
|
+
onError?.(error);
|
|
225
|
+
}
|
|
226
|
+
finally {
|
|
227
|
+
setIsLoading(false);
|
|
228
|
+
}
|
|
229
|
+
}, [client, fetcher, enabled, onSuccess, onError]);
|
|
230
|
+
(0, react_1.useEffect)(() => {
|
|
231
|
+
fetchMetadata();
|
|
232
|
+
}, [fetchMetadata]);
|
|
233
|
+
const refetch = (0, react_1.useCallback)(async () => {
|
|
234
|
+
await fetchMetadata();
|
|
235
|
+
}, [fetchMetadata]);
|
|
236
|
+
return {
|
|
237
|
+
data,
|
|
238
|
+
isLoading,
|
|
239
|
+
error,
|
|
240
|
+
refetch,
|
|
241
|
+
fromCache: false
|
|
242
|
+
};
|
|
243
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@objectstack/client-react",
|
|
3
|
+
"version": "0.6.1",
|
|
4
|
+
"description": "React hooks for ObjectStack Client SDK",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"peerDependencies": {
|
|
8
|
+
"react": ">=18.0.0"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@objectstack/client": "0.6.1",
|
|
12
|
+
"@objectstack/spec": "0.6.1",
|
|
13
|
+
"@objectstack/core": "0.6.1"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@types/react": "^18.0.0",
|
|
17
|
+
"typescript": "^5.0.0"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc"
|
|
21
|
+
}
|
|
22
|
+
}
|
package/src/context.tsx
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectStack React Context
|
|
3
|
+
*
|
|
4
|
+
* Provides ObjectStackClient instance to React components via Context API
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as React from 'react';
|
|
8
|
+
import { createContext, useContext, ReactNode } from 'react';
|
|
9
|
+
import { ObjectStackClient } from '@objectstack/client';
|
|
10
|
+
|
|
11
|
+
export interface ObjectStackProviderProps {
|
|
12
|
+
client: ObjectStackClient;
|
|
13
|
+
children: ReactNode;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const ObjectStackContext = createContext<ObjectStackClient | null>(null);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Provider component that makes ObjectStackClient available to all child components
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```tsx
|
|
23
|
+
* const client = new ObjectStackClient({ baseUrl: 'http://localhost:3000' });
|
|
24
|
+
*
|
|
25
|
+
* function App() {
|
|
26
|
+
* return (
|
|
27
|
+
* <ObjectStackProvider client={client}>
|
|
28
|
+
* <YourComponents />
|
|
29
|
+
* </ObjectStackProvider>
|
|
30
|
+
* );
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export function ObjectStackProvider({ client, children }: ObjectStackProviderProps) {
|
|
35
|
+
return (
|
|
36
|
+
<ObjectStackContext.Provider value={client}>
|
|
37
|
+
{children}
|
|
38
|
+
</ObjectStackContext.Provider>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Hook to access the ObjectStackClient instance from context
|
|
44
|
+
*
|
|
45
|
+
* @throws Error if used outside of ObjectStackProvider
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```tsx
|
|
49
|
+
* function MyComponent() {
|
|
50
|
+
* const client = useClient();
|
|
51
|
+
* // Use client.data.find(), etc.
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export function useClient(): ObjectStackClient {
|
|
56
|
+
const client = useContext(ObjectStackContext);
|
|
57
|
+
|
|
58
|
+
if (!client) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
'useClient must be used within an ObjectStackProvider. ' +
|
|
61
|
+
'Make sure your component is wrapped with <ObjectStackProvider client={...}>.'
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return client;
|
|
66
|
+
}
|