@osdk/react 0.8.0 → 0.9.0-beta.10
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/AGENTS.md +253 -0
- package/CHANGELOG.md +105 -24
- package/build/browser/intellisense.test.helpers/useOsdkObjectsWithProperties.js +1 -1
- package/build/browser/intellisense.test.helpers/useOsdkObjectsWithProperties.js.map +1 -1
- package/build/browser/intellisense.test.js +1 -1
- package/build/browser/intellisense.test.js.map +1 -1
- package/build/browser/new/platform-apis/admin/useCurrentFoundryUser.js +44 -0
- package/build/browser/new/platform-apis/admin/useCurrentFoundryUser.js.map +1 -0
- package/build/browser/new/platform-apis/admin/useFoundryUser.js +50 -0
- package/build/browser/new/platform-apis/admin/useFoundryUser.js.map +1 -0
- package/build/browser/new/platform-apis/admin/useFoundryUsersList.js +54 -0
- package/build/browser/new/platform-apis/admin/useFoundryUsersList.js.map +1 -0
- package/build/browser/new/useLinks.js +15 -8
- package/build/browser/new/useLinks.js.map +1 -1
- package/build/browser/new/useObjectSet.js +3 -2
- package/build/browser/new/useObjectSet.js.map +1 -1
- package/build/browser/new/useOsdkAction.js.map +1 -1
- package/build/browser/new/useOsdkAggregation.js +1 -1
- package/build/browser/new/useOsdkAggregation.js.map +1 -1
- package/build/browser/new/useOsdkFunction.js +101 -0
- package/build/browser/new/useOsdkFunction.js.map +1 -0
- package/build/browser/new/useOsdkObject.js +1 -1
- package/build/browser/new/useOsdkObject.js.map +1 -1
- package/build/browser/new/useOsdkObjects.js +4 -3
- package/build/browser/new/useOsdkObjects.js.map +1 -1
- package/build/browser/public/experimental.js +4 -0
- package/build/browser/public/experimental.js.map +1 -1
- package/build/browser/utils/usePlatformQuery.js +74 -0
- package/build/browser/utils/usePlatformQuery.js.map +1 -0
- package/build/cjs/{chunk-OVBG5VXE.cjs → chunk-V32JHU3O.cjs} +8 -3
- package/build/cjs/chunk-V32JHU3O.cjs.map +1 -0
- package/build/cjs/index.cjs +4 -4
- package/build/cjs/public/experimental.cjs +399 -66
- package/build/cjs/public/experimental.cjs.map +1 -1
- package/build/cjs/public/experimental.d.cts +279 -32
- package/build/esm/intellisense.test.helpers/useOsdkObjectsWithProperties.js +1 -1
- package/build/esm/intellisense.test.helpers/useOsdkObjectsWithProperties.js.map +1 -1
- package/build/esm/intellisense.test.js +1 -1
- package/build/esm/intellisense.test.js.map +1 -1
- package/build/esm/new/platform-apis/admin/useCurrentFoundryUser.js +44 -0
- package/build/esm/new/platform-apis/admin/useCurrentFoundryUser.js.map +1 -0
- package/build/esm/new/platform-apis/admin/useFoundryUser.js +50 -0
- package/build/esm/new/platform-apis/admin/useFoundryUser.js.map +1 -0
- package/build/esm/new/platform-apis/admin/useFoundryUsersList.js +54 -0
- package/build/esm/new/platform-apis/admin/useFoundryUsersList.js.map +1 -0
- package/build/esm/new/useLinks.js +15 -8
- package/build/esm/new/useLinks.js.map +1 -1
- package/build/esm/new/useObjectSet.js +3 -2
- package/build/esm/new/useObjectSet.js.map +1 -1
- package/build/esm/new/useOsdkAction.js.map +1 -1
- package/build/esm/new/useOsdkAggregation.js +1 -1
- package/build/esm/new/useOsdkAggregation.js.map +1 -1
- package/build/esm/new/useOsdkFunction.js +101 -0
- package/build/esm/new/useOsdkFunction.js.map +1 -0
- package/build/esm/new/useOsdkObject.js +1 -1
- package/build/esm/new/useOsdkObject.js.map +1 -1
- package/build/esm/new/useOsdkObjects.js +4 -3
- package/build/esm/new/useOsdkObjects.js.map +1 -1
- package/build/esm/public/experimental.js +4 -0
- package/build/esm/public/experimental.js.map +1 -1
- package/build/esm/utils/usePlatformQuery.js +74 -0
- package/build/esm/utils/usePlatformQuery.js.map +1 -0
- package/build/types/new/platform-apis/admin/useCurrentFoundryUser.d.ts +28 -0
- package/build/types/new/platform-apis/admin/useCurrentFoundryUser.d.ts.map +1 -0
- package/build/types/new/platform-apis/admin/useFoundryUser.d.ts +36 -0
- package/build/types/new/platform-apis/admin/useFoundryUser.d.ts.map +1 -0
- package/build/types/new/platform-apis/admin/useFoundryUsersList.d.ts +52 -0
- package/build/types/new/platform-apis/admin/useFoundryUsersList.d.ts.map +1 -0
- package/build/types/new/useLinks.d.ts +5 -5
- package/build/types/new/useLinks.d.ts.map +1 -1
- package/build/types/new/useObjectSet.d.ts +4 -0
- package/build/types/new/useObjectSet.d.ts.map +1 -1
- package/build/types/new/useOsdkAction.d.ts +3 -3
- package/build/types/new/useOsdkAction.d.ts.map +1 -1
- package/build/types/new/useOsdkAggregation.d.ts +10 -12
- package/build/types/new/useOsdkAggregation.d.ts.map +1 -1
- package/build/types/new/useOsdkFunction.d.ts +112 -0
- package/build/types/new/useOsdkFunction.d.ts.map +1 -0
- package/build/types/new/useOsdkObjects.d.ts +31 -13
- package/build/types/new/useOsdkObjects.d.ts.map +1 -1
- package/build/types/public/experimental.d.ts +5 -0
- package/build/types/public/experimental.d.ts.map +1 -1
- package/build/types/utils/usePlatformQuery.d.ts +25 -0
- package/build/types/utils/usePlatformQuery.d.ts.map +1 -0
- package/docs/actions.md +414 -0
- package/docs/advanced-queries.md +663 -0
- package/docs/cache-management.md +213 -0
- package/docs/getting-started.md +382 -0
- package/docs/platform-apis.md +203 -0
- package/docs/querying-data.md +648 -0
- package/package.json +10 -6
- package/build/browser/new/types.js +0 -2
- package/build/browser/new/types.js.map +0 -1
- package/build/cjs/chunk-OVBG5VXE.cjs.map +0 -1
- package/build/esm/new/types.js +0 -2
- package/build/esm/new/types.js.map +0 -1
- package/build/types/new/types.d.ts +0 -5
- package/build/types/new/types.d.ts.map +0 -1
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import type { DerivedProperty,
|
|
2
|
-
import type { InferRdpTypes } from "./types.js";
|
|
1
|
+
import type { DerivedProperty, LinkedType, LinkNames, ObjectOrInterfaceDefinition, Osdk, PropertyKeys, SimplePropertyDef, WhereClause } from "@osdk/api";
|
|
3
2
|
export interface UseOsdkObjectsOptions<
|
|
4
|
-
T extends
|
|
5
|
-
|
|
3
|
+
T extends ObjectOrInterfaceDefinition,
|
|
4
|
+
RDPs extends Record<string, SimplePropertyDef> = {}
|
|
6
5
|
> {
|
|
7
6
|
/**
|
|
8
7
|
* Standard OSDK Where with RDP support
|
|
9
8
|
*/
|
|
10
|
-
where?: WhereClause<T,
|
|
9
|
+
where?: WhereClause<T, RDPs>;
|
|
11
10
|
/**
|
|
12
11
|
* The preferred page size for the list.
|
|
13
12
|
*/
|
|
@@ -18,14 +17,14 @@ export interface UseOsdkObjectsOptions<
|
|
|
18
17
|
* Define derived properties (RDPs) to be computed server-side and attached to each object.
|
|
19
18
|
* These properties will be available on the returned objects alongside their regular properties.
|
|
20
19
|
*/
|
|
21
|
-
withProperties?:
|
|
20
|
+
withProperties?: { [K in keyof RDPs] : DerivedProperty.Creator<T, RDPs[K]> };
|
|
22
21
|
/**
|
|
23
22
|
* Intersect the results with additional object sets.
|
|
24
23
|
* Each element defines a where clause for an object set to intersect with.
|
|
25
24
|
* The final result will only include objects that match ALL conditions.
|
|
26
25
|
*/
|
|
27
26
|
intersectWith?: Array<{
|
|
28
|
-
where: WhereClause<T,
|
|
27
|
+
where: WhereClause<T, RDPs>
|
|
29
28
|
}>;
|
|
30
29
|
/**
|
|
31
30
|
* Pivot to related objects through a link.
|
|
@@ -106,10 +105,25 @@ export interface UseOsdkObjectsOptions<
|
|
|
106
105
|
*/
|
|
107
106
|
enabled?: boolean;
|
|
108
107
|
}
|
|
109
|
-
export interface UseOsdkListResult<
|
|
108
|
+
export interface UseOsdkListResult<
|
|
109
|
+
T extends ObjectOrInterfaceDefinition,
|
|
110
|
+
RDPs extends Record<string, SimplePropertyDef> = {}
|
|
111
|
+
> {
|
|
112
|
+
/**
|
|
113
|
+
* Function to fetch more pages (undefined if no more pages)
|
|
114
|
+
*/
|
|
110
115
|
fetchMore: (() => Promise<void>) | undefined;
|
|
111
|
-
|
|
116
|
+
/**
|
|
117
|
+
* The fetched data with derived properties
|
|
118
|
+
*/
|
|
119
|
+
data: Osdk.Instance<T, "$allBaseProperties", PropertyKeys<T>, RDPs>[] | undefined;
|
|
120
|
+
/**
|
|
121
|
+
* Whether data is currently being loaded
|
|
122
|
+
*/
|
|
112
123
|
isLoading: boolean;
|
|
124
|
+
/**
|
|
125
|
+
* Any error that occurred during fetching
|
|
126
|
+
*/
|
|
113
127
|
error: Error | undefined;
|
|
114
128
|
/**
|
|
115
129
|
* Refers to whether the ordered list of objects (only considering the $primaryKey)
|
|
@@ -119,14 +133,18 @@ export interface UseOsdkListResult<T extends ObjectTypeDefinition | InterfaceDef
|
|
|
119
133
|
* do that on a per object basis with useOsdkObject
|
|
120
134
|
*/
|
|
121
135
|
isOptimistic: boolean;
|
|
136
|
+
/**
|
|
137
|
+
* The total count of objects matching the query (if available from the API)
|
|
138
|
+
*/
|
|
139
|
+
totalCount?: string;
|
|
122
140
|
}
|
|
123
141
|
export declare function useOsdkObjects<
|
|
124
|
-
Q extends
|
|
142
|
+
Q extends ObjectOrInterfaceDefinition,
|
|
125
143
|
L extends LinkNames<Q>
|
|
126
144
|
>(type: Q, options: UseOsdkObjectsOptions<Q> & {
|
|
127
145
|
pivotTo: L
|
|
128
146
|
}): UseOsdkListResult<LinkedType<Q, L>>;
|
|
129
147
|
export declare function useOsdkObjects<
|
|
130
|
-
Q extends
|
|
131
|
-
|
|
132
|
-
>(type: Q, options?: UseOsdkObjectsOptions<Q,
|
|
148
|
+
Q extends ObjectOrInterfaceDefinition,
|
|
149
|
+
RDPs extends Record<string, SimplePropertyDef> = {}
|
|
150
|
+
>(type: Q, options?: UseOsdkObjectsOptions<Q, RDPs>): UseOsdkListResult<Q, RDPs>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"mappings":"AAgBA,cACE,iBACA,
|
|
1
|
+
{"mappings":"AAgBA,cACE,iBACA,YACA,WACA,6BACA,MACA,cACA,mBACA,mBACK,WAAY;AAMnB,iBAAiB;CACf,UAAU;CACV,aAAa,eAAe,qBAAqB,CAAE;EACnD;;;;CAIA,QAAQ,YAAY,GAAG;;;;CAKvB;;CAGA,aACG,KAAK,aAAa,OAAM,QAAQ;;;;;CAOnC,oBAAoB,WAAW,QAAO,gBAAgB,QAAQ,GAAG,KAAK;;;;;;CAOtE,gBAAgB,MAAM;EACpB,OAAO,YAAY,GAAG;CACvB;;;;;CAMD,UAAU,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;CA0BpB;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BA;CAEA;;;;;;;;;;;;;;;;;;;;;CAsBA;AACD;AAED,iBAAiB;CACf,UAAU;CACV,aAAa,eAAe,qBAAqB,CAAE;EACnD;;;;CAIA,kBAAkB;;;;CAKlB,MACI,KAAK,SAAS,GAAG,sBAAsB,aAAa,IAAI;;;;CAM5D;;;;CAKA,OAAO;;;;;;;;CASP;;;;CAKA;AACD;AAQD,OAAO,iBAAS;CACd,UAAU;CACV,UAAU,UAAU;EAEpBA,MAAM,GACNC,SAAS,sBAAsB,KAAK;CAAE,SAAS;AAAG,IACjD,kBAAkB,WAAW,GAAG;AAEnC,OAAO,iBAAS;CACd,UAAU;CACV,aAAa,eAAe,qBAAqB,CAAE;EAEnDD,MAAM,GACNE,UAAU,sBAAsB,GAAG,QAClC,kBAAkB,GAAG","names":["type: Q","options: UseOsdkObjectsOptions<Q> & { pivotTo: L }","options?: UseOsdkObjectsOptions<Q, RDPs>"],"sources":["../../../src/new/useOsdkObjects.ts"],"version":3,"file":"useOsdkObjects.d.ts"}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
export { OsdkProvider2 } from "../new/OsdkProvider2.js";
|
|
2
|
+
export { useCurrentFoundryUser } from "../new/platform-apis/admin/useCurrentFoundryUser.js";
|
|
3
|
+
export { useFoundryUser } from "../new/platform-apis/admin/useFoundryUser.js";
|
|
4
|
+
export { useFoundryUsersList } from "../new/platform-apis/admin/useFoundryUsersList.js";
|
|
2
5
|
export { useLinks } from "../new/useLinks.js";
|
|
3
6
|
export { useObjectSet } from "../new/useObjectSet.js";
|
|
4
7
|
export { useOsdkAction } from "../new/useOsdkAction.js";
|
|
5
8
|
export type { UseOsdkAggregationResult } from "../new/useOsdkAggregation.js";
|
|
6
9
|
export { useOsdkAggregation } from "../new/useOsdkAggregation.js";
|
|
10
|
+
export type { UseOsdkFunctionOptions, UseOsdkFunctionResult } from "../new/useOsdkFunction.js";
|
|
11
|
+
export { useOsdkFunction } from "../new/useOsdkFunction.js";
|
|
7
12
|
export { useOsdkObject } from "../new/useOsdkObject.js";
|
|
8
13
|
export type { UseOsdkListResult } from "../new/useOsdkObjects.js";
|
|
9
14
|
export { useOsdkObjects } from "../new/useOsdkObjects.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"mappings":"AAgBA,SAAS,qBAAqB;AAC9B,SAAS,gBAAgB;AACzB,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB;AAC9B,cAAc,gCAAgC;AAC9C,SAAS,0BAA0B;AACnC,SAAS,qBAAqB;AAC9B,cAAc,yBAAyB;AACvC,SAAS,sBAAsB;AAC/B,SAAS,qBAAqB;AAC9B,SAAS,uBAAuB;AAChC,cAAc,6BAA6B;AAC3C,SAAS,4BAA4B","names":[],"sources":["../../../src/public/experimental.ts"],"version":3,"file":"experimental.d.ts"}
|
|
1
|
+
{"mappings":"AAgBA,SAAS,qBAAqB;AAC9B,SAAS,6BAA6B;AACtC,SAAS,sBAAsB;AAC/B,SAAS,2BAA2B;AACpC,SAAS,gBAAgB;AACzB,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB;AAC9B,cAAc,gCAAgC;AAC9C,SAAS,0BAA0B;AACnC,cACE,wBACA,6BACK;AACP,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAC9B,cAAc,yBAAyB;AACvC,SAAS,sBAAsB;AAC/B,SAAS,qBAAqB;AAC9B,SAAS,uBAAuB;AAChC,cAAc,6BAA6B;AAC3C,SAAS,4BAA4B","names":[],"sources":["../../../src/public/experimental.ts"],"version":3,"file":"experimental.d.ts"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface UseQueryOptions<T> {
|
|
2
|
+
/**
|
|
3
|
+
* Enable or disable the query.
|
|
4
|
+
*
|
|
5
|
+
* When `false`, the query will not automatically execute.
|
|
6
|
+
*
|
|
7
|
+
* This is useful for:
|
|
8
|
+
* - Lazy/on-demand queries that should wait for user interaction
|
|
9
|
+
* - Dependent queries that need data from another query first
|
|
10
|
+
* - Conditional queries based on component state
|
|
11
|
+
*
|
|
12
|
+
* @default true
|
|
13
|
+
* });
|
|
14
|
+
*/
|
|
15
|
+
enabled?: boolean;
|
|
16
|
+
queryName: string;
|
|
17
|
+
query: () => Promise<T>;
|
|
18
|
+
}
|
|
19
|
+
export interface QueryResult<T> {
|
|
20
|
+
data: T | undefined;
|
|
21
|
+
isLoading: boolean;
|
|
22
|
+
error: Error | undefined;
|
|
23
|
+
refetch: () => void;
|
|
24
|
+
}
|
|
25
|
+
export declare function usePlatformQuery<T>({ query, queryName, enabled }: UseQueryOptions<T>): QueryResult<T>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"mappings":"AAoBA,iBAAiB,gBAAgB,GAAG;;;;;;;;;;;;;;CAclC;CACA;CACA,aAAa,QAAQ;AACtB;AAED,iBAAiB,YAAY,GAAG;CAC9B,MAAM;CACN;CACA,OAAO;CACP;AACD;AAOD,OAAO,iBAAS,iBAAiB,GAC/B,EAAE,OAAO,WAAW,SAAoC,EAAlB,gBAAgB,KACrD,YAAY","names":[],"sources":["../../../src/utils/usePlatformQuery.ts"],"version":3,"file":"usePlatformQuery.d.ts"}
|
package/docs/actions.md
ADDED
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 3
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Actions
|
|
6
|
+
|
|
7
|
+
This guide covers executing actions, validation, optimistic updates, and debouncing patterns.
|
|
8
|
+
|
|
9
|
+
## useOsdkAction
|
|
10
|
+
|
|
11
|
+
*Experimental - import from `@osdk/react/experimental`*
|
|
12
|
+
|
|
13
|
+
Execute and validate actions with automatic state management.
|
|
14
|
+
|
|
15
|
+
### Basic Usage
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { $Actions, Todo } from "@my/osdk";
|
|
19
|
+
import { useOsdkAction, useOsdkObject } from "@osdk/react/experimental";
|
|
20
|
+
import { useCallback } from "react";
|
|
21
|
+
|
|
22
|
+
function TodoView({ todo }: { todo: Todo.OsdkInstance }) {
|
|
23
|
+
const { isLoading } = useOsdkObject(todo);
|
|
24
|
+
const { applyAction, data, error, isPending } = useOsdkAction(
|
|
25
|
+
$Actions.completeTodo,
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const onClick = useCallback(() => {
|
|
29
|
+
applyAction({
|
|
30
|
+
todo: todo,
|
|
31
|
+
isComplete: true,
|
|
32
|
+
});
|
|
33
|
+
}, [applyAction, todo]);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div>
|
|
37
|
+
<div>
|
|
38
|
+
{todo.title}
|
|
39
|
+
{todo.isComplete === false && (
|
|
40
|
+
<button onClick={onClick} disabled={isPending}>
|
|
41
|
+
Mark Complete
|
|
42
|
+
</button>
|
|
43
|
+
)}
|
|
44
|
+
{isPending && "(Applying)"}
|
|
45
|
+
{data && "(Action completed successfully)"}
|
|
46
|
+
</div>
|
|
47
|
+
{error && (
|
|
48
|
+
<div>
|
|
49
|
+
An error occurred:
|
|
50
|
+
<pre>{JSON.stringify(error, null, 2)}</pre>
|
|
51
|
+
</div>
|
|
52
|
+
)}
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Return Values
|
|
59
|
+
|
|
60
|
+
- `applyAction` - Function to execute the action (accepts single args object or array for batch)
|
|
61
|
+
- `data` - Return value from the last successful action execution
|
|
62
|
+
- `error` - Error object (see error handling below)
|
|
63
|
+
- `isPending` - True while action is executing
|
|
64
|
+
- `isValidating` - True while validation is in progress
|
|
65
|
+
- `validateAction` - Function to validate without executing
|
|
66
|
+
- `validationResult` - Result of last validation
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Error Handling
|
|
71
|
+
|
|
72
|
+
The `error` object has the following structure:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
{
|
|
76
|
+
actionValidation?: ActionValidationError;
|
|
77
|
+
unknown?: unknown;
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`ActionValidationError` extends `Error` and has:
|
|
82
|
+
- `message` - Error message string
|
|
83
|
+
- `validation` - Full validation response from server
|
|
84
|
+
|
|
85
|
+
Example:
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
import { $Actions, Todo } from "@my/osdk";
|
|
89
|
+
import { useOsdkAction } from "@osdk/react/experimental";
|
|
90
|
+
|
|
91
|
+
function TodoActionWithErrorHandling({ todo }: { todo: Todo.OsdkInstance }) {
|
|
92
|
+
const { applyAction, error, isPending } = useOsdkAction($Actions.completeTodo);
|
|
93
|
+
|
|
94
|
+
const onClick = async () => {
|
|
95
|
+
try {
|
|
96
|
+
await applyAction({ todo, isComplete: true });
|
|
97
|
+
} catch (e) {
|
|
98
|
+
console.error("Action failed", e);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<div>
|
|
104
|
+
<button onClick={onClick} disabled={isPending}>
|
|
105
|
+
Complete Todo
|
|
106
|
+
</button>
|
|
107
|
+
|
|
108
|
+
{error?.actionValidation && (
|
|
109
|
+
<div style={{ color: "red" }}>
|
|
110
|
+
Validation failed: {JSON.stringify(error.actionValidation.validation)}
|
|
111
|
+
</div>
|
|
112
|
+
)}
|
|
113
|
+
|
|
114
|
+
{error?.unknown && (
|
|
115
|
+
<div style={{ color: "red" }}>
|
|
116
|
+
An unexpected error occurred: {String(error.unknown)}
|
|
117
|
+
</div>
|
|
118
|
+
)}
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Validation
|
|
127
|
+
|
|
128
|
+
Validate action parameters without executing using `validateAction`.
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
import { $Actions } from "@my/osdk";
|
|
132
|
+
import { useOsdkAction } from "@osdk/react/experimental";
|
|
133
|
+
import { useState } from "react";
|
|
134
|
+
|
|
135
|
+
function TodoForm() {
|
|
136
|
+
const [title, setTitle] = useState("");
|
|
137
|
+
const [assignee, setAssignee] = useState("");
|
|
138
|
+
|
|
139
|
+
const {
|
|
140
|
+
applyAction,
|
|
141
|
+
validateAction,
|
|
142
|
+
isValidating,
|
|
143
|
+
validationResult,
|
|
144
|
+
isPending,
|
|
145
|
+
error,
|
|
146
|
+
} = useOsdkAction($Actions.createTodo);
|
|
147
|
+
|
|
148
|
+
const handleTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
149
|
+
const newTitle = e.target.value;
|
|
150
|
+
setTitle(newTitle);
|
|
151
|
+
validateAction({ title: newTitle, assignee });
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const handleAssigneeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
155
|
+
const newAssignee = e.target.value;
|
|
156
|
+
setAssignee(newAssignee);
|
|
157
|
+
validateAction({ title, assignee: newAssignee });
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
161
|
+
e.preventDefault();
|
|
162
|
+
if (validationResult?.result === "VALID") {
|
|
163
|
+
await applyAction({ title, assignee });
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<form onSubmit={handleSubmit}>
|
|
169
|
+
<input
|
|
170
|
+
type="text"
|
|
171
|
+
value={title}
|
|
172
|
+
onChange={handleTitleChange}
|
|
173
|
+
placeholder="Todo title"
|
|
174
|
+
/>
|
|
175
|
+
<input
|
|
176
|
+
type="text"
|
|
177
|
+
value={assignee}
|
|
178
|
+
onChange={handleAssigneeChange}
|
|
179
|
+
placeholder="Assignee"
|
|
180
|
+
/>
|
|
181
|
+
|
|
182
|
+
{isValidating && <span>Validating...</span>}
|
|
183
|
+
|
|
184
|
+
{validationResult?.result === "INVALID" && (
|
|
185
|
+
<div style={{ color: "red" }}>
|
|
186
|
+
Invalid: {JSON.stringify(validationResult)}
|
|
187
|
+
</div>
|
|
188
|
+
)}
|
|
189
|
+
|
|
190
|
+
<button
|
|
191
|
+
type="submit"
|
|
192
|
+
disabled={isPending || isValidating || validationResult?.result !== "VALID"}
|
|
193
|
+
>
|
|
194
|
+
Create Todo
|
|
195
|
+
</button>
|
|
196
|
+
|
|
197
|
+
{error?.actionValidation && (
|
|
198
|
+
<div style={{ color: "red" }}>
|
|
199
|
+
Validation error: {error.actionValidation.message}
|
|
200
|
+
</div>
|
|
201
|
+
)}
|
|
202
|
+
</form>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Key features:
|
|
208
|
+
|
|
209
|
+
- `validateAction` - Validates action parameters without executing
|
|
210
|
+
- `isValidating` - True while validation is in progress
|
|
211
|
+
- `validationResult` - Contains `{ result: "VALID" | "INVALID", ... }`
|
|
212
|
+
- Calling `validateAction` while a previous validation is in progress cancels the previous one
|
|
213
|
+
- Validation and execution are mutually exclusive
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Batch Actions
|
|
218
|
+
|
|
219
|
+
Apply the same action to multiple items in a single call:
|
|
220
|
+
|
|
221
|
+
```tsx
|
|
222
|
+
import { $Actions, Todo } from "@my/osdk";
|
|
223
|
+
import { useOsdkAction } from "@osdk/react/experimental";
|
|
224
|
+
import { useCallback } from "react";
|
|
225
|
+
|
|
226
|
+
function BulkCompleteButton({ todos }: { todos: Todo.OsdkInstance[] }) {
|
|
227
|
+
const { applyAction, isPending } = useOsdkAction($Actions.completeTodo);
|
|
228
|
+
|
|
229
|
+
const onClick = useCallback(() => {
|
|
230
|
+
applyAction(
|
|
231
|
+
todos.map(todo => ({
|
|
232
|
+
todo: todo,
|
|
233
|
+
isComplete: true,
|
|
234
|
+
})),
|
|
235
|
+
);
|
|
236
|
+
}, [applyAction, todos]);
|
|
237
|
+
|
|
238
|
+
return (
|
|
239
|
+
<button onClick={onClick} disabled={isPending}>
|
|
240
|
+
Complete All ({todos.length})
|
|
241
|
+
</button>
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Optimistic Updates
|
|
249
|
+
|
|
250
|
+
Apply changes to the cache immediately while waiting for the server response.
|
|
251
|
+
|
|
252
|
+
```tsx
|
|
253
|
+
import { $Actions, Todo } from "@my/osdk";
|
|
254
|
+
import { useOsdkAction, useOsdkObject } from "@osdk/react/experimental";
|
|
255
|
+
import { useCallback } from "react";
|
|
256
|
+
|
|
257
|
+
function TodoView({ todo }: { todo: Todo.OsdkInstance }) {
|
|
258
|
+
const { isLoading, isOptimistic } = useOsdkObject(todo);
|
|
259
|
+
const { applyAction, error, isPending } = useOsdkAction($Actions.completeTodo);
|
|
260
|
+
|
|
261
|
+
const onClick = useCallback(() => {
|
|
262
|
+
applyAction({
|
|
263
|
+
todo: todo,
|
|
264
|
+
isComplete: true,
|
|
265
|
+
|
|
266
|
+
$optimisticUpdate: (ou) => {
|
|
267
|
+
ou.updateObject(todo.$clone({ isComplete: true }));
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
}, [applyAction, todo]);
|
|
271
|
+
|
|
272
|
+
return (
|
|
273
|
+
<div>
|
|
274
|
+
{todo.title}
|
|
275
|
+
{todo.isComplete === false && !isOptimistic && (
|
|
276
|
+
<button onClick={onClick} disabled={isPending}>Mark Complete</button>
|
|
277
|
+
)}
|
|
278
|
+
{isPending && "(Saving)"}
|
|
279
|
+
{isLoading && "(Loading)"}
|
|
280
|
+
{isOptimistic && "(Optimistic)"}
|
|
281
|
+
{error && (
|
|
282
|
+
<div style={{ color: "red" }}>
|
|
283
|
+
{error.actionValidation?.message ?? String(error.unknown)}
|
|
284
|
+
</div>
|
|
285
|
+
)}
|
|
286
|
+
</div>
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### How Optimistic Updates Work
|
|
292
|
+
|
|
293
|
+
1. When you call `applyAction` with `$optimisticUpdate`, the cache is updated immediately
|
|
294
|
+
2. The UI shows the optimistic state (tracked via `isOptimistic`)
|
|
295
|
+
3. If the action succeeds, the cache is refreshed with server data
|
|
296
|
+
4. If the action fails, the optimistic changes are rolled back automatically
|
|
297
|
+
|
|
298
|
+
### Optimistic Update API
|
|
299
|
+
|
|
300
|
+
The `$optimisticUpdate` callback receives an object with the following methods:
|
|
301
|
+
|
|
302
|
+
```tsx
|
|
303
|
+
$optimisticUpdate: (ou) => {
|
|
304
|
+
ou.updateObject(todo.$clone({ isComplete: true }));
|
|
305
|
+
ou.updateObject(anotherObject.$clone({ ... }));
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
:::note The `$clone` method
|
|
310
|
+
Every OSDK object instance has a `$clone()` method that creates a new object with modified properties. This is essential for optimistic updates because OSDK objects are immutable.
|
|
311
|
+
|
|
312
|
+
```tsx
|
|
313
|
+
// Create a modified copy without mutating the original
|
|
314
|
+
const completedTodo = todo.$clone({ isComplete: true });
|
|
315
|
+
|
|
316
|
+
// Clone with multiple property changes
|
|
317
|
+
const updatedTodo = todo.$clone({
|
|
318
|
+
title: "New Title",
|
|
319
|
+
priority: "high",
|
|
320
|
+
});
|
|
321
|
+
```
|
|
322
|
+
:::
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## useDebouncedCallback
|
|
327
|
+
|
|
328
|
+
*Experimental - import from `@osdk/react/experimental`*
|
|
329
|
+
|
|
330
|
+
Debounce callback functions for auto-save patterns or expensive operations.
|
|
331
|
+
|
|
332
|
+
### Basic Usage
|
|
333
|
+
|
|
334
|
+
```tsx
|
|
335
|
+
import { useDebouncedCallback } from "@osdk/react/experimental";
|
|
336
|
+
import { useState } from "react";
|
|
337
|
+
|
|
338
|
+
function SearchableList({ onSearch }: { onSearch: (query: string) => void }) {
|
|
339
|
+
const [query, setQuery] = useState("");
|
|
340
|
+
|
|
341
|
+
const debouncedSearch = useDebouncedCallback((q: string) => {
|
|
342
|
+
onSearch(q);
|
|
343
|
+
}, 500);
|
|
344
|
+
|
|
345
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
346
|
+
const value = e.target.value;
|
|
347
|
+
setQuery(value);
|
|
348
|
+
debouncedSearch(value);
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
return (
|
|
352
|
+
<input
|
|
353
|
+
value={query}
|
|
354
|
+
onChange={handleChange}
|
|
355
|
+
placeholder="Search..."
|
|
356
|
+
/>
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Auto-Save Pattern
|
|
362
|
+
|
|
363
|
+
Combine with actions for auto-saving:
|
|
364
|
+
|
|
365
|
+
```tsx
|
|
366
|
+
import { $Actions, Todo } from "@my/osdk";
|
|
367
|
+
import { useDebouncedCallback, useOsdkAction } from "@osdk/react/experimental";
|
|
368
|
+
import { useState } from "react";
|
|
369
|
+
|
|
370
|
+
function AutoSaveTodo({ todo }: { todo: Todo.OsdkInstance }) {
|
|
371
|
+
const [title, setTitle] = useState(todo.title);
|
|
372
|
+
const { applyAction } = useOsdkAction($Actions.updateTodo);
|
|
373
|
+
|
|
374
|
+
const debouncedSave = useDebouncedCallback((newTitle: string) => {
|
|
375
|
+
applyAction({
|
|
376
|
+
todo,
|
|
377
|
+
title: newTitle,
|
|
378
|
+
$optimisticUpdate: (ou) => {
|
|
379
|
+
ou.updateObject(todo.$clone({ title: newTitle }));
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
}, 1000);
|
|
383
|
+
|
|
384
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
385
|
+
const newTitle = e.target.value;
|
|
386
|
+
setTitle(newTitle);
|
|
387
|
+
debouncedSave(newTitle);
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
return (
|
|
391
|
+
<input
|
|
392
|
+
value={title}
|
|
393
|
+
onChange={handleChange}
|
|
394
|
+
placeholder="Click to edit title..."
|
|
395
|
+
/>
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### Debounced Callback Methods
|
|
401
|
+
|
|
402
|
+
The returned function has utility methods:
|
|
403
|
+
|
|
404
|
+
```tsx
|
|
405
|
+
import { useDebouncedCallback } from "@osdk/react/experimental";
|
|
406
|
+
|
|
407
|
+
const debouncedFn = useDebouncedCallback((value: string) => {
|
|
408
|
+
console.log("Called with:", value);
|
|
409
|
+
}, 500);
|
|
410
|
+
|
|
411
|
+
debouncedFn("hello");
|
|
412
|
+
debouncedFn.cancel();
|
|
413
|
+
debouncedFn.flush();
|
|
414
|
+
```
|