@ossy/sdk-react 1.40.2 → 3.0.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/README.md +86 -11
- package/build/node_modules/tslib/tslib.es6.js +1 -1
- package/build/packages/sdk-react/src/PushInvalidationSubscriber.js +1 -0
- package/build/packages/sdk-react/src/WorkspaceProvider.js +1 -1
- package/build/packages/sdk-react/src/asyncStatus.js +1 -1
- package/build/packages/sdk-react/src/cacheEntry.js +1 -1
- package/build/packages/sdk-react/src/cacheKey.js +1 -1
- package/build/packages/sdk-react/src/optimisticResource.js +1 -0
- package/build/packages/sdk-react/src/public.index.js +1 -1
- package/build/packages/sdk-react/src/react-sdk.js +1 -1
- package/build/packages/sdk-react/src/usePushInvalidation.js +1 -0
- package/build/packages/sdk-react/src/useRead.js +1 -1
- package/build/packages/sdk-react/src/useSdk.js +1 -1
- package/build/public.index.d.ts +67 -5
- package/build/types/PushInvalidationSubscriber.d.ts +8 -0
- package/build/types/WorkspaceProvider.d.ts +8 -2
- package/build/types/asyncStatus.d.ts +1 -0
- package/build/types/cacheEntry.d.ts +2 -0
- package/build/types/cacheKey.d.ts +1 -0
- package/build/types/optimisticResource.d.ts +17 -0
- package/build/types/public.index.d.ts +4 -0
- package/build/types/react-sdk.d.ts +2 -0
- package/build/types/usePushInvalidation.d.ts +11 -0
- package/build/types/useSdk.d.ts +2 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# React bindings
|
|
2
2
|
|
|
3
|
-
Thin React layer over `@ossy/sdk`. One hook — **`useSdk()`** — for reads, writes, and
|
|
3
|
+
Thin React layer over `@ossy/sdk`. One hook — **`useSdk()`** — for reads, writes, cache invalidation, and optimistic updates.
|
|
4
4
|
|
|
5
5
|
## Getting started
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install @ossy/sdk-react @ossy/sdk
|
|
8
|
+
npm install @ossy/sdk-react @ossy/sdk @ossy/fold
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
```jsx
|
|
@@ -45,17 +45,25 @@ function MyComponent() {
|
|
|
45
45
|
}
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
`WorkspaceProvider` provides the SDK and shared read cache. **SSE push invalidation is off by default** — enable it only where you need live cross-tab cache updates.
|
|
49
|
+
|
|
48
50
|
## API
|
|
49
51
|
|
|
50
52
|
| Export | Purpose |
|
|
51
53
|
|---|---|
|
|
52
|
-
| `WorkspaceProvider` | Provides SDK + shared read cache |
|
|
53
|
-
| `
|
|
54
|
+
| `WorkspaceProvider` | Provides SDK + shared read cache (optional app-wide push via `enablePushInvalidation`) |
|
|
55
|
+
| `PushInvalidationSubscriber` | Opt-in SSE invalidation for a page or subtree |
|
|
56
|
+
| `useSdk()` | Returns `{ invoke, invokeOptimistic, read, invalidate, cacheKey, sdk }` |
|
|
54
57
|
| `useRead()` | Same as `sdk.read` — hook for reactive reads |
|
|
58
|
+
| `usePushInvalidation()` | Low-level hook; prefer `PushInvalidationSubscriber` |
|
|
55
59
|
| `cacheKey(action, payload)` | Stable cache key for an action + payload |
|
|
56
|
-
| `
|
|
60
|
+
| `projectionKey(projectionId, scopeId)` | Cache key for projection reads |
|
|
61
|
+
| `applyOptimisticResource()` | Fold a pending event into cached resource state |
|
|
62
|
+
| `rollbackOptimisticResource()` | Restore cache entry after failed optimistic invoke |
|
|
63
|
+
| `useReadCacheStore()` | Access the shared read-cache store inside `<Cache>` |
|
|
64
|
+
| `ReactSdk` | Browser SDK with `invoke` and `subscribePush` |
|
|
57
65
|
| `ActionRef` | Platform action POJO type `{ id, access? }` |
|
|
58
|
-
| `resolveActionId()` | Normalize dot ids to slash (
|
|
66
|
+
| `resolveActionId()` | Normalize dot ids to slash (`@ossy.booking.actions.create` → `@ossy/booking/actions/create`) |
|
|
59
67
|
| `AsyncStatus` | Loading state constants for UI |
|
|
60
68
|
| `normalizeLocation()` | Normalize resource location paths |
|
|
61
69
|
| `stableSerialize()` | Deterministic JSON for cache keys |
|
|
@@ -71,6 +79,14 @@ const { status, data, error, refetch } = sdk.read(ListResources, { location })
|
|
|
71
79
|
// Command
|
|
72
80
|
await sdk.invoke(CreateResource, payload)
|
|
73
81
|
|
|
82
|
+
// Optimistic command — folds pending event into cached resource, rolls back on error
|
|
83
|
+
await sdk.invokeOptimistic(UpdateResource, payload, {
|
|
84
|
+
type: '@ossy/booking/schema/booking',
|
|
85
|
+
resourceId: 'booking-1',
|
|
86
|
+
event: 'Updated',
|
|
87
|
+
payload: { status: 'confirmed' },
|
|
88
|
+
})
|
|
89
|
+
|
|
74
90
|
// Invalidate cache (triggers re-fetch on next read)
|
|
75
91
|
sdk.invalidate('location:/@ossy/domains/')
|
|
76
92
|
sdk.invalidate(sdk.cacheKey(ListResources, { location }))
|
|
@@ -78,19 +94,78 @@ sdk.invalidate(sdk.cacheKey(ListResources, { location }))
|
|
|
78
94
|
|
|
79
95
|
`read` is a React hook — call it unconditionally at the top of your component (same rules as `useState`).
|
|
80
96
|
|
|
97
|
+
## Push invalidation
|
|
98
|
+
|
|
99
|
+
Push invalidation (ADR 0008 §9) opens a long-lived SSE connection to `GET /events` per subscriber. **It is disabled by default** because most pages only need fresh data after their own writes (`sdk.invalidate()` / refetch).
|
|
100
|
+
|
|
101
|
+
### When to enable
|
|
102
|
+
|
|
103
|
+
Use push only on pages or components that benefit from **background or cross-tab** updates, for example:
|
|
104
|
+
|
|
105
|
+
- Collaborative resource lists that should refresh when another user edits
|
|
106
|
+
- Long-lived dashboards or inbox views
|
|
107
|
+
- Dev tooling that watches task runs or automation
|
|
108
|
+
|
|
109
|
+
Avoid enabling it on static marketing pages, auth flows, or one-shot forms.
|
|
110
|
+
|
|
111
|
+
### Page-level (recommended)
|
|
112
|
+
|
|
113
|
+
```jsx
|
|
114
|
+
import { PushInvalidationSubscriber } from '@ossy/sdk-react'
|
|
115
|
+
|
|
116
|
+
export default function ResourcesPage() {
|
|
117
|
+
return (
|
|
118
|
+
<>
|
|
119
|
+
<PushInvalidationSubscriber />
|
|
120
|
+
{/* … */}
|
|
121
|
+
</>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Requires `workspaceId` on the SDK (or same-origin workspace cookie). Must render inside `WorkspaceProvider`.
|
|
127
|
+
|
|
128
|
+
### App-wide (optional)
|
|
129
|
+
|
|
130
|
+
In `src/config.js`:
|
|
131
|
+
|
|
132
|
+
```js
|
|
133
|
+
export default {
|
|
134
|
+
enablePushInvalidation: true,
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Or pass `enablePushInvalidation` to `WorkspaceProvider` in custom setups.
|
|
139
|
+
|
|
140
|
+
### Manual wiring
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
import { usePushInvalidation, useReadCacheStore } from '@ossy/sdk-react'
|
|
144
|
+
|
|
145
|
+
function MyBridge({ sdk }) {
|
|
146
|
+
const store = useReadCacheStore()
|
|
147
|
+
usePushInvalidation(sdk, store)
|
|
148
|
+
return null
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
See [@ossy/sdk README](../sdk/README.md#push-invalidation-adr-0008) for server-side details.
|
|
153
|
+
|
|
81
154
|
## Cache key conventions
|
|
82
155
|
|
|
83
156
|
| Action + payload | Cache key |
|
|
84
157
|
|---|---|
|
|
85
|
-
|
|
|
86
|
-
|
|
|
87
|
-
|
|
|
158
|
+
| `@ossy/resources/actions/list` + `{ location }` | `location:${normalizeLocation(location)}` |
|
|
159
|
+
| `@ossy/resources/actions/search` + query | `search:${stableSerialize(query)}` |
|
|
160
|
+
| `@ossy/resources/actions/get` + `{ resourceId \| id }` | `resource:${resourceId}` |
|
|
161
|
+
| `@ossy/booking/actions/list` | `action:@ossy/booking/actions/list` |
|
|
162
|
+
| `@ossy/platform/actions/list-task-runs` + `{ workspaceId }` | `projection:@ossy/platform/data/task-run-list:${workspaceId}` |
|
|
88
163
|
| default | `action:${actionId}` or `action:${actionId}:${stableSerialize(payload)}` |
|
|
89
164
|
|
|
90
|
-
Import action POJOs from feature packages (`@ossy/resources`, `@ossy/workspaces`, …). Action ids use
|
|
165
|
+
Import action POJOs from feature packages (`@ossy/resources`, `@ossy/workspaces`, …). Action ids use canonical `@ossy/{package}/actions/...` notation.
|
|
91
166
|
|
|
92
167
|
## Dependencies
|
|
93
168
|
|
|
94
|
-
Peer dependencies: `@ossy/sdk`, `react`, `react-dom`.
|
|
169
|
+
Peer dependencies: `@ossy/sdk`, `@ossy/fold`, `react`, `react-dom`.
|
|
95
170
|
|
|
96
171
|
No Ramda.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var t=function(){return t=Object.assign||function(t){for(var e,n=1,r=arguments.length;n<r;n++)for(var o in e=arguments[n])Object.prototype.hasOwnProperty.call(e,o)&&(t[o]=e[o]);return t},t.apply(this,arguments)};function e(t,e,n,r){return new(n||(n=Promise))(function(o,a){function u(t){try{i(r.next(t))}catch(t){a(t)}}function c(t){try{i(r.throw(t))}catch(t){a(t)}}function i(t){var e;t.done?o(t.value):(e=t.value,e instanceof n?e:new n(function(t){t(e)})).then(u,c)}i((r=r.apply(t,e||[])).next())})}function n(t,e){var n,r,o,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]},u=Object.create(("function"==typeof Iterator?Iterator:Object).prototype);return u.next=c(0),u.throw=c(1),u.return=c(2),"function"==typeof Symbol&&(u[Symbol.iterator]=function(){return this}),u;function c(c){return function(i){return function(c){if(n)throw new TypeError("Generator is already executing.");for(;u&&(u=0,c[0]&&(a=0)),a;)try{if(n=1,r&&(o=2&c[0]?r.return:c[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,c[1])).done)return o;switch(r=0,o&&(c=[2&c[0],o.value]),c[0]){case 0:case 1:o=c;break;case 4:return a.label++,{value:c[1],done:!1};case 5:a.label++,r=c[1],c=[0];continue;case 7:c=a.ops.pop(),a.trys.pop();continue;default:if(!(o=a.trys,(o=o.length>0&&o[o.length-1])||6!==c[0]&&2!==c[0])){a=0;continue}if(3===c[0]&&(!o||c[1]>o[0]&&c[1]<o[3])){a.label=c[1];break}if(6===c[0]&&a.label<o[1]){a.label=o[1],o=c;break}if(o&&a.label<o[2]){a.label=o[2],a.ops.push(c);break}o[2]&&a.ops.pop(),a.trys.pop();continue}c=e.call(t,a)}catch(t){c=[6,t],r=0}finally{n=o=0}if(5&c[0])throw c[1];return{value:c[0]?c[1]:void 0,done:!0}}([c,i])}}}"function"==typeof SuppressedError&&SuppressedError;export{t as __assign,e as __awaiter,n as __generator};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{useContext as r}from"react";import{Context as o}from"./WorkspaceProvider.js";import{usePushInvalidation as t}from"./usePushInvalidation.js";import{useReadCacheStore as i}from"./Cache.js";function m(){var m=r(o).sdk,e=i();return t(m,e),null}export{m as PushInvalidationSubscriber};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import r,{createContext as e,useMemo as t}from"react";import{createCache as o}from"./Cache.js";import{createMapCache as
|
|
1
|
+
import r,{createContext as e,useMemo as t}from"react";import{createCache as n,useReadCacheStore as o}from"./Cache.js";import{createMapCache as a}from"./cacheUtils.js";import{ReactSdk as i}from"./react-sdk.js";import{usePushInvalidation as m}from"./usePushInvalidation.js";var l=e({}),s=n(a());function c(r){var e=r.sdk,t=o();return m(e,t),null}var u=function(e){var n=e.sdk,o=e.enablePushInvalidation,a=void 0!==o&&o,m=e.children,u=t(function(){return i.from(n)},[n]);return r.createElement(s,null,r.createElement(l.Provider,{value:{sdk:u}},a&&r.createElement(c,{sdk:u}),m))};export{l as Context,u as WorkspaceProvider};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var i={NotInitialized:"NotInitialized",Loading:"Loading",Success:"Success",Error:"Error"};export{i as AsyncStatus};
|
|
1
|
+
var i={NotInitialized:"NotInitialized",Loading:"Loading",Submitting:"Submitting",Success:"Success",Error:"Error"};export{i as AsyncStatus};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
import{AsyncStatus as t}from"./asyncStatus.js";var r=Object.freeze({status:t.NotInitialized,data:void 0,error:null}),e=function(){return r};export{r as NOT_INITIALIZED_ENTRY,e as emptyEntry};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolveActionId as o}from"./action.js";import{normalizeLocation as
|
|
1
|
+
import{resolveActionId as o}from"./action.js";import{normalizeLocation as t}from"./normalizeLocation.js";import{stableSerialize as n}from"./stableSerialize.js";function r(o,t){return"projection:".concat(o,":").concat(t)}function s(s,i){var c,a=o(s);if("@ossy/resources/actions/list"===a){var e=null==i?void 0:i.location;return"location:".concat(t("string"==typeof e?e:void 0))}if("@ossy/resources/actions/search"===a)return"search:".concat(n(null!=i?i:{}));if("@ossy/resources/actions/get"===a){var l=null!==(c=null==i?void 0:i.resourceId)&&void 0!==c?c:null==i?void 0:i.id;return"resource:".concat(null!=l?l:"")}if("@ossy/booking/actions/list"===a)return"action:@ossy/booking/actions/list";if("@ossy/platform/actions/list-task-runs"===a){var u=null==i?void 0:i.workspaceId;return r("@ossy/platform/data/task-run-list",String(null!=u?u:""))}var f=n(i);return f?"action:".concat(a,":").concat(f):"action:".concat(a)}export{s as cacheKey,r as projectionKey};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{__assign as r}from"../../../node_modules/tslib/tslib.es6.js";import{fold as s}from"@ossy/fold";import{AsyncStatus as e}from"./asyncStatus.js";import{cacheKey as t}from"./cacheKey.js";function o(o,n){var u,a,i,c=t({id:"@ossy/resources/actions/get"},{resourceId:n.resourceId}),d=o.getEntry(c);if(d.status!==e.Success||null==d.data)return null;var l=d,f=s(d.data,r(r({},n),{id:n.resourceId,version:null!==(u=n.version)&&void 0!==u?u:(null!==(a=d.data.version)&&void 0!==a?a:0)+1,created:null!==(i=n.created)&&void 0!==i?i:Date.now()}));return o.setEntry(c,{status:e.Success,data:f,error:null}),l}function n(r,s,e){if(e){var o=t({id:"@ossy/resources/actions/get"},{resourceId:s});r.setEntry(o,e)}}export{o as applyOptimisticResource,n as rollbackOptimisticResource};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export{AsyncStatus}from"./asyncStatus.js";export{normalizeLocation}from"./normalizeLocation.js";export{ReactSdk}from"./react-sdk.js";export{resolveActionId,toActionRef}from"./action.js";export{Context,WorkspaceProvider}from"./WorkspaceProvider.js";export{useSdk}from"./useSdk.js";export{useRead}from"./useRead.js";export{cacheKey}from"./cacheKey.js";export{stableSerialize}from"./stableSerialize.js";
|
|
1
|
+
export{AsyncStatus}from"./asyncStatus.js";export{normalizeLocation}from"./normalizeLocation.js";export{ReactSdk}from"./react-sdk.js";export{resolveActionId,toActionRef}from"./action.js";export{Context,WorkspaceProvider}from"./WorkspaceProvider.js";export{PushInvalidationSubscriber}from"./PushInvalidationSubscriber.js";export{useSdk}from"./useSdk.js";export{useRead}from"./useRead.js";export{usePushInvalidation}from"./usePushInvalidation.js";export{applyOptimisticResource,rollbackOptimisticResource}from"./optimisticResource.js";export{cacheKey,projectionKey}from"./cacheKey.js";export{stableSerialize}from"./stableSerialize.js";export{useReadCacheStore}from"./Cache.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{SDK as e}from"@ossy/sdk";import{toActionRef as t}from"./action.js";export{resolveActionId}from"./action.js";var
|
|
1
|
+
import{SDK as e}from"@ossy/sdk";import{toActionRef as t}from"./action.js";export{resolveActionId}from"./action.js";var r=function(){function r(e){this.sdk=e}return Object.defineProperty(r.prototype,"workspaceId",{get:function(){return this.sdk.workspaceId},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"authorization",{get:function(){return this.sdk.authorization},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"baseUrl",{get:function(){return this.sdk.baseUrl},enumerable:!1,configurable:!0}),r.prototype.subscribePush=function(e){return this.sdk.subscribePush(e)},r.prototype.updateConfig=function(e){return this.sdk.updateConfig(e),this},r.prototype.invoke=function(e,r){return this.sdk.invoke(t(e),r)},Object.defineProperty(r.prototype,"resources",{get:function(){return this.sdk.resources},enumerable:!1,configurable:!0}),r.from=function(e){return e instanceof r?e:new r(e)},r.of=function(t){return new r(e.of(t))},r}();export{r as ReactSdk};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{useEffect as i}from"react";function n(n,o){i(function(){if((null==n?void 0:n.workspaceId)&&"function"==typeof n.subscribePush)return n.subscribePush({onMessage:function(i){var n;(null===(n=null==i?void 0:i.invalidate)||void 0===n?void 0:n.length)&&o.invalidate(i.invalidate)}})},[n,o])}export{n as usePushInvalidation};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useContext as t,useMemo as r,useCallback as
|
|
1
|
+
import{useContext as t,useMemo as r,useCallback as o,useSyncExternalStore as n,useEffect as e}from"react";import{toActionRef as s}from"./action.js";import{AsyncStatus as a}from"./asyncStatus.js";import{cacheKey as i}from"./cacheKey.js";import{useReadCacheStore as u}from"./Cache.js";import{Context as c}from"./WorkspaceProvider.js";import{stableSerialize as f}from"./stableSerialize.js";function m(m,p,d){var l=t(c).sdk,v=u(),h=f(null!=p?p:{}),j=r(function(){return i(m,p)},[m,h]),b=!1!==(null==d?void 0:d.enabled),k=o(function(){return v.getEntry(j)},[v,j]),y=n(v.subscribe,k,k);e(function(){b&&y.status!==a.Loading&&y.status!==a.Success&&y.status!==a.Error&&v.fetch(j,function(){return l.invoke(s(m),p)}).catch(function(){})},[b,j,y.status,v,l,m,h]);var S=o(function(){return v.invalidate(j),v.fetch(j,function(){return l.invoke(s(m),p)})},[v,j,l,m,h]);return{status:y.status,data:y.data,error:y.error,refetch:S}}export{i as cacheKey,m as useRead};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useContext as
|
|
1
|
+
import{__awaiter as e,__generator as r}from"../../../node_modules/tslib/tslib.es6.js";import{useContext as t,useRef as o,useCallback as i,useMemo as n}from"react";import{cacheKey as s}from"./cacheKey.js";import{Context as c}from"./WorkspaceProvider.js";import{useReadCacheStore as u}from"./Cache.js";import{useRead as a}from"./useRead.js";import{rollbackOptimisticResource as d,applyOptimisticResource as m}from"./optimisticResource.js";function l(){var l=this,f=t(c).sdk,p=u(),v=o(new Map),h=i(function(e){p.invalidate(e)},[p]),k=i(function(e,r){return f.invoke(e,r)},[f]),j=i(function(t,o,i){return e(l,void 0,void 0,function(){var e,n;return r(this,function(r){switch(r.label){case 0:e=null,(null==i?void 0:i.resourceId)&&(e=m(p,i))&&v.current.set(i.resourceId,e),r.label=1;case 1:return r.trys.push([1,3,,4]),[4,f.invoke(t,o)];case 2:return[2,r.sent()];case 3:throw n=r.sent(),(null==i?void 0:i.resourceId)&&(d(p,i.resourceId,e),v.current.delete(i.resourceId)),n;case 4:return[2]}})})},[f,p]);return n(function(){return{invoke:k,invokeOptimistic:j,read:a,invalidate:h,cacheKey:s,sdk:f}},[k,j,h,f])}export{l as useSdk};
|
package/build/public.index.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { SDK, SDKConfig } from '@ossy/sdk';
|
|
1
|
+
import { SDK, SDKConfig, PushMessage } from '@ossy/sdk';
|
|
2
2
|
import React, { PropsWithChildren } from 'react';
|
|
3
3
|
|
|
4
4
|
declare const AsyncStatus: {
|
|
5
5
|
readonly NotInitialized: "NotInitialized";
|
|
6
6
|
readonly Loading: "Loading";
|
|
7
|
+
readonly Submitting: "Submitting";
|
|
7
8
|
readonly Success: "Success";
|
|
8
9
|
readonly Error: "Error";
|
|
9
10
|
};
|
|
@@ -29,6 +30,8 @@ declare class ReactSdk {
|
|
|
29
30
|
constructor(sdk: SDK);
|
|
30
31
|
get workspaceId(): string | undefined;
|
|
31
32
|
get authorization(): string | undefined;
|
|
33
|
+
get baseUrl(): string;
|
|
34
|
+
subscribePush(handlers: Parameters<SDK['subscribePush']>[0]): () => void;
|
|
32
35
|
updateConfig(config: SDKConfig): this;
|
|
33
36
|
invoke<TPayload extends Record<string, unknown> = Record<string, unknown>, TResult = unknown>(action: ActionRef | string, payload?: TPayload): Promise<TResult>;
|
|
34
37
|
/** @deprecated Use `invoke(action, payload)`. Kept for transitional resource uploads. */
|
|
@@ -62,10 +65,26 @@ declare const Context: React.Context<Config>;
|
|
|
62
65
|
interface Config {
|
|
63
66
|
sdk: ReactSdk;
|
|
64
67
|
}
|
|
65
|
-
|
|
68
|
+
interface WorkspaceProviderProps {
|
|
66
69
|
sdk: SDK | ReactSdk;
|
|
67
|
-
|
|
70
|
+
/**
|
|
71
|
+
* When true, subscribe to SSE push for the whole app tree.
|
|
72
|
+
* Default false — prefer {@link PushInvalidationSubscriber} on specific pages.
|
|
73
|
+
*/
|
|
74
|
+
enablePushInvalidation?: boolean;
|
|
75
|
+
}
|
|
76
|
+
declare const WorkspaceProvider: ({ sdk, enablePushInvalidation, children, }: PropsWithChildren<WorkspaceProviderProps>) => React.JSX.Element;
|
|
68
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Opt-in SSE push invalidation for a page or subtree.
|
|
80
|
+
*
|
|
81
|
+
* Mount only where live cache invalidation matters (e.g. collaborative lists,
|
|
82
|
+
* long-lived dashboards). Most pages should rely on `sdk.invalidate()` after
|
|
83
|
+
* writes instead — see sdk-react README § Push invalidation.
|
|
84
|
+
*/
|
|
85
|
+
declare function PushInvalidationSubscriber(): null;
|
|
86
|
+
|
|
87
|
+
declare function projectionKey(projectionId: string, scopeId: string): string;
|
|
69
88
|
declare function cacheKey(action: ActionRef | string, payload?: Record<string, unknown>): string;
|
|
70
89
|
|
|
71
90
|
interface ReadOptions {
|
|
@@ -80,8 +99,41 @@ interface ReadResult<T = unknown> {
|
|
|
80
99
|
}
|
|
81
100
|
declare function useRead<T = unknown>(action: ActionRef | string, payload?: Record<string, unknown>, options?: ReadOptions): ReadResult<T>;
|
|
82
101
|
|
|
102
|
+
interface CacheEntry<T = unknown> {
|
|
103
|
+
status: AsyncStatusType;
|
|
104
|
+
data?: T;
|
|
105
|
+
error?: Error | null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
type Listener = () => void;
|
|
109
|
+
interface ReadCacheStore {
|
|
110
|
+
getEntry: (key: string) => CacheEntry;
|
|
111
|
+
setEntry: (key: string, entry: CacheEntry | ((prev: CacheEntry) => CacheEntry)) => void;
|
|
112
|
+
invalidate: (key: string | string[]) => void;
|
|
113
|
+
subscribe: (listener: Listener) => () => void;
|
|
114
|
+
fetch: <T>(key: string, fetcher: () => Promise<T>) => Promise<T>;
|
|
115
|
+
}
|
|
116
|
+
declare const useReadCacheStore: () => ReadCacheStore;
|
|
117
|
+
|
|
118
|
+
interface OptimisticResourceEvent {
|
|
119
|
+
type: string;
|
|
120
|
+
resourceId: string;
|
|
121
|
+
event: string;
|
|
122
|
+
version?: number;
|
|
123
|
+
payload?: Record<string, unknown>;
|
|
124
|
+
created?: number;
|
|
125
|
+
createdBy?: string;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Optimistically fold a pending resource event into the read cache (ADR 0008 §9).
|
|
129
|
+
* Returns rollback entry when applied.
|
|
130
|
+
*/
|
|
131
|
+
declare function applyOptimisticResource(store: ReadCacheStore, event: OptimisticResourceEvent): CacheEntry | null;
|
|
132
|
+
declare function rollbackOptimisticResource(store: ReadCacheStore, resourceId: string, rollback: CacheEntry | null): void;
|
|
133
|
+
|
|
83
134
|
interface SdkApi {
|
|
84
135
|
invoke: ReactSdk['invoke'];
|
|
136
|
+
invokeOptimistic: (action: Parameters<ReactSdk['invoke']>[0], payload?: Record<string, unknown>, optimistic?: OptimisticResourceEvent) => ReturnType<ReactSdk['invoke']>;
|
|
85
137
|
read: typeof useRead;
|
|
86
138
|
invalidate: (key: string | string[]) => void;
|
|
87
139
|
cacheKey: typeof cacheKey;
|
|
@@ -89,8 +141,18 @@ interface SdkApi {
|
|
|
89
141
|
}
|
|
90
142
|
declare function useSdk(): SdkApi;
|
|
91
143
|
|
|
144
|
+
/**
|
|
145
|
+
* ADR 0008 §9 — subscribe to SSE push and invalidate read-cache keys.
|
|
146
|
+
*/
|
|
147
|
+
declare function usePushInvalidation(sdk: {
|
|
148
|
+
subscribePush?: (handlers: {
|
|
149
|
+
onMessage: (message: PushMessage) => void;
|
|
150
|
+
}) => () => void;
|
|
151
|
+
workspaceId?: string;
|
|
152
|
+
}, store: ReadCacheStore): void;
|
|
153
|
+
|
|
92
154
|
/** Deterministic JSON for cache keys — sorted object keys, no Ramda. */
|
|
93
155
|
declare function stableSerialize(value: unknown): string;
|
|
94
156
|
|
|
95
|
-
export { AsyncStatus, Context, ReactSdk, WorkspaceProvider, cacheKey, normalizeLocation, resolveActionId, stableSerialize, toActionRef, useRead, useSdk };
|
|
96
|
-
export type { ActionRef, AsyncStatusType, Config, ReadOptions, ReadResult, SdkApi };
|
|
157
|
+
export { AsyncStatus, Context, PushInvalidationSubscriber, ReactSdk, WorkspaceProvider, applyOptimisticResource, cacheKey, normalizeLocation, projectionKey, resolveActionId, rollbackOptimisticResource, stableSerialize, toActionRef, usePushInvalidation, useRead, useReadCacheStore, useSdk };
|
|
158
|
+
export type { ActionRef, AsyncStatusType, Config, OptimisticResourceEvent, ReadOptions, ReadResult, SdkApi, WorkspaceProviderProps };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opt-in SSE push invalidation for a page or subtree.
|
|
3
|
+
*
|
|
4
|
+
* Mount only where live cache invalidation matters (e.g. collaborative lists,
|
|
5
|
+
* long-lived dashboards). Most pages should rely on `sdk.invalidate()` after
|
|
6
|
+
* writes instead — see sdk-react README § Push invalidation.
|
|
7
|
+
*/
|
|
8
|
+
export function PushInvalidationSubscriber(): null;
|
|
@@ -5,6 +5,12 @@ export declare const Context: React.Context<Config>;
|
|
|
5
5
|
export interface Config {
|
|
6
6
|
sdk: ReactSdk;
|
|
7
7
|
}
|
|
8
|
-
export
|
|
8
|
+
export interface WorkspaceProviderProps {
|
|
9
9
|
sdk: SDK | ReactSdk;
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* When true, subscribe to SSE push for the whole app tree.
|
|
12
|
+
* Default false — prefer {@link PushInvalidationSubscriber} on specific pages.
|
|
13
|
+
*/
|
|
14
|
+
enablePushInvalidation?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare const WorkspaceProvider: ({ sdk, enablePushInvalidation, children, }: PropsWithChildren<WorkspaceProviderProps>) => React.JSX.Element;
|
|
@@ -4,4 +4,6 @@ export interface CacheEntry<T = unknown> {
|
|
|
4
4
|
data?: T;
|
|
5
5
|
error?: Error | null;
|
|
6
6
|
}
|
|
7
|
+
/** Stable fallback for useSyncExternalStore — must not allocate per getSnapshot call. */
|
|
8
|
+
export declare const NOT_INITIALIZED_ENTRY: CacheEntry;
|
|
7
9
|
export declare const emptyEntry: () => CacheEntry;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ReadCacheStore } from './Cache';
|
|
2
|
+
import type { CacheEntry } from './cacheEntry';
|
|
3
|
+
export interface OptimisticResourceEvent {
|
|
4
|
+
type: string;
|
|
5
|
+
resourceId: string;
|
|
6
|
+
event: string;
|
|
7
|
+
version?: number;
|
|
8
|
+
payload?: Record<string, unknown>;
|
|
9
|
+
created?: number;
|
|
10
|
+
createdBy?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Optimistically fold a pending resource event into the read cache (ADR 0008 §9).
|
|
14
|
+
* Returns rollback entry when applied.
|
|
15
|
+
*/
|
|
16
|
+
export declare function applyOptimisticResource(store: ReadCacheStore, event: OptimisticResourceEvent): CacheEntry | null;
|
|
17
|
+
export declare function rollbackOptimisticResource(store: ReadCacheStore, resourceId: string, rollback: CacheEntry | null): void;
|
|
@@ -3,7 +3,11 @@ export * from './normalizeLocation';
|
|
|
3
3
|
export * from './react-sdk';
|
|
4
4
|
export * from './action';
|
|
5
5
|
export * from './WorkspaceProvider';
|
|
6
|
+
export { PushInvalidationSubscriber } from './PushInvalidationSubscriber.jsx';
|
|
6
7
|
export * from './useSdk';
|
|
7
8
|
export * from './useRead';
|
|
9
|
+
export * from './usePushInvalidation';
|
|
10
|
+
export * from './optimisticResource';
|
|
8
11
|
export * from './cacheKey';
|
|
9
12
|
export * from './stableSerialize';
|
|
13
|
+
export { useReadCacheStore } from './Cache';
|
|
@@ -8,6 +8,8 @@ export declare class ReactSdk {
|
|
|
8
8
|
constructor(sdk: SDK);
|
|
9
9
|
get workspaceId(): string | undefined;
|
|
10
10
|
get authorization(): string | undefined;
|
|
11
|
+
get baseUrl(): string;
|
|
12
|
+
subscribePush(handlers: Parameters<SDK['subscribePush']>[0]): () => void;
|
|
11
13
|
updateConfig(config: SDKConfig): this;
|
|
12
14
|
invoke<TPayload extends Record<string, unknown> = Record<string, unknown>, TResult = unknown>(action: ActionRef | string, payload?: TPayload): Promise<TResult>;
|
|
13
15
|
/** @deprecated Use `invoke(action, payload)`. Kept for transitional resource uploads. */
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { PushMessage } from '@ossy/sdk';
|
|
2
|
+
import type { ReadCacheStore } from './Cache';
|
|
3
|
+
/**
|
|
4
|
+
* ADR 0008 §9 — subscribe to SSE push and invalidate read-cache keys.
|
|
5
|
+
*/
|
|
6
|
+
export declare function usePushInvalidation(sdk: {
|
|
7
|
+
subscribePush?: (handlers: {
|
|
8
|
+
onMessage: (message: PushMessage) => void;
|
|
9
|
+
}) => () => void;
|
|
10
|
+
workspaceId?: string;
|
|
11
|
+
}, store: ReadCacheStore): void;
|
package/build/types/useSdk.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { cacheKey } from './cacheKey';
|
|
2
2
|
import { useRead } from './useRead';
|
|
3
3
|
import type { ReactSdk } from './react-sdk';
|
|
4
|
+
import { type OptimisticResourceEvent } from './optimisticResource';
|
|
4
5
|
export interface SdkApi {
|
|
5
6
|
invoke: ReactSdk['invoke'];
|
|
7
|
+
invokeOptimistic: (action: Parameters<ReactSdk['invoke']>[0], payload?: Record<string, unknown>, optimistic?: OptimisticResourceEvent) => ReturnType<ReactSdk['invoke']>;
|
|
6
8
|
read: typeof useRead;
|
|
7
9
|
invalidate: (key: string | string[]) => void;
|
|
8
10
|
cacheKey: typeof cacheKey;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/sdk-react",
|
|
3
3
|
"description": "Software Development Kit React",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "3.0.1",
|
|
5
5
|
"url": "git://github.com/ossy-se/packages/sdk-react",
|
|
6
6
|
"source": "src/public.index.ts",
|
|
7
7
|
"main": "./build/packages/sdk-react/src/public.index.js",
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
"@babel/preset-typescript": "^7.26.0"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"@ossy/
|
|
43
|
+
"@ossy/fold": ">=1.0.0 <4.0.0",
|
|
44
|
+
"@ossy/sdk": ">=1.0.0 <4.0.0",
|
|
44
45
|
"react": ">=19.0.0 <20.0.0",
|
|
45
46
|
"react-dom": ">=19.0.0 <20.0.0"
|
|
46
47
|
},
|
|
@@ -52,5 +53,5 @@
|
|
|
52
53
|
"/build",
|
|
53
54
|
"README.md"
|
|
54
55
|
],
|
|
55
|
-
"gitHead": "
|
|
56
|
+
"gitHead": "4a70ec216448680d2fddc57b2a8a0077489720ae"
|
|
56
57
|
}
|