@helloleo/plugins 0.2.27 → 0.2.29-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,7 +15,7 @@
15
15
  "lint": "oxlint --fix"
16
16
  },
17
17
  "dependencies": {
18
- "@helloleo/runtime": "^0.1.4",
18
+ "@helloleo/runtime": "0.2.0-beta.0",
19
19
  "@hookform/resolvers": "^5.4.0",
20
20
  "@radix-ui/react-accordion": "^1.2.15",
21
21
  "@radix-ui/react-alert-dialog": "^1.1.18",
@@ -44,9 +44,9 @@
44
44
  "@radix-ui/react-toggle-group": "^1.1.14",
45
45
  "@radix-ui/react-tooltip": "^1.2.11",
46
46
  "@tailwindcss/vite": "^4.3.2",
47
- "@tanstack/react-query": "^5.101.2",
47
+ "@tanstack/react-query": "^5.102.8",
48
48
  "@tanstack/react-router": "^1.170.17",
49
- "@tanstack/react-router-with-query": "^1.130.17",
49
+ "@tanstack/react-router-ssr-query": "^1.167.2",
50
50
  "@tanstack/react-start": "^1.168.27",
51
51
  "class-variance-authority": "^0.7.1",
52
52
  "clsx": "^2.1.1",
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Cached reads — declare them here, never call the upstream straight from a
3
+ * loader.
4
+ *
5
+ * A heavy read (a search over thousands of rows, a report the integration takes
6
+ * seconds to build) is slow AND spends the customer's quota on that system, so a
7
+ * dashboard that re-queries on every render burns both. `withCache` puts a TTL
8
+ * in front of one read and hands back a plain function: the loader calls
9
+ * `getOdooPartners()` and nothing about the cache leaks into the call site.
10
+ *
11
+ * The function also carries .refresh() and .purge(), which is how a page offers
12
+ * the user current data without knowing anything about KV.
13
+ *
14
+ * Declare at MODULE SCOPE, in a server-side file like this one. A withCache
15
+ * call at the top of a route component file would evaluate in the browser too,
16
+ * shipping the fetcher and the integration call it makes to the client.
17
+ *
18
+ * Nothing refreshes on a schedule. What keeps a value fresh is its TTL expiring
19
+ * and the next visitor paying for one live read, plus .refresh() when someone
20
+ * presses the button for it.
21
+ *
22
+ * Rules:
23
+ * - Server-side only (loaders, server routes), like every other binding.
24
+ * - TTL is in SECONDS and must be at least 60 (KV's own floor). Pick it from
25
+ * how stale the data may be: a stock level is minutes, a price list is hours.
26
+ * - Cached data is NEVER the source of truth. Expiry is Cloudflare's own, and
27
+ * purging a key is always safe — it costs one slow read, nothing more.
28
+ * - Never cache anything user-specific under a shared key; the cache is
29
+ * per-project, not per-visitor.
30
+ *
31
+ * The example below is commented out on purpose: it names a model and fields
32
+ * that only exist if this project is connected to Odoo. Uncomment it, or write
33
+ * your own against whatever this project actually connects to.
34
+ *
35
+ * import { withCache } from '@helloleo/runtime'
36
+ * import { callIntegration } from '#/lib/proxy'
37
+ *
38
+ * export const getOdooPartners = withCache('odoo:partners', 7200, async () => {
39
+ * const res = await callIntegration({
40
+ * integration: 'odoo',
41
+ * endpoint: '/jsonrpc',
42
+ * body: {
43
+ * model: 'res.partner',
44
+ * method: 'search_read',
45
+ * args: [[['is_company', '=', true]]],
46
+ * kwargs: { fields: ['name', 'email'], limit: 500 },
47
+ * },
48
+ * })
49
+ * // Let a failed read throw: nothing is written on a rejection, so a bad
50
+ * // response is never served for the whole TTL.
51
+ * if (!res.success) throw new Error(`Odoo read failed (${res.status})`)
52
+ * return res.data
53
+ * })
54
+ *
55
+ * Then in a loader:
56
+ *
57
+ * const partners = await getOdooPartners()
58
+ */
59
+
60
+ export {}
@@ -18,6 +18,9 @@ import { kv } from '@helloleo/runtime'
18
18
  * - KV is for small, hot values. Relational data belongs in the database
19
19
  * (`#/db`); files belong in storage (`#/lib/storage`).
20
20
  * - Keys are flat strings — namespace with prefixes ('cache:', 'session:').
21
+ * - Caching a heavy read is NOT a raw kv.put: use `#/lib/cache`, which gives
22
+ * the read a TTL and a row in Settings → Cache the user can see the age of
23
+ * and purge. Hand-rolling it here hides the cache from them entirely.
21
24
  */
22
25
 
23
26
  /** JSON convenience wrappers — kv stores strings, most values are objects. */
@@ -1,31 +1,31 @@
1
1
  import { QueryClient } from '@tanstack/react-query'
2
2
  import { createRouter as createTanStackRouter } from '@tanstack/react-router'
3
- import { routerWithQueryClient } from '@tanstack/react-router-with-query'
3
+ import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query'
4
4
  import { routeTree } from './routeTree.gen'
5
5
  import { DefaultErrorComponent } from '@/components/error-component'
6
6
 
7
- // TanStack Start calls getRouter() on both server and client. routerWithQueryClient
8
- // wires the QueryClient into the router context and handles SSR dehydration /
9
- // rehydration, so loaders can call context.queryClient.ensureQueryData() and the
10
- // client picks the cache up without refetching.
7
+ // TanStack Start calls getRouter() on both server and client.
8
+ // setupRouterSsrQueryIntegration wires the QueryClient into the router context
9
+ // and handles SSR dehydration / rehydration, so loaders can call
10
+ // context.queryClient.ensureQueryData() and the client picks the cache up
11
+ // without refetching.
11
12
  export function getRouter() {
12
13
  const queryClient = new QueryClient()
13
14
 
14
- const router = routerWithQueryClient(
15
- createTanStackRouter({
16
- routeTree,
17
- context: { queryClient },
18
- scrollRestoration: true,
19
- // Catches uncaught render/loader errors on any route. Routes can still
20
- // set their own `errorComponent` to override this.
21
- defaultErrorComponent: DefaultErrorComponent,
22
- defaultPreload: 'intent',
23
- // With React Query we never want loader data to be stale -- the loader runs
24
- // on every preload/visit and React Query owns caching from there.
25
- defaultPreloadStaleTime: 0,
26
- }),
27
- queryClient,
28
- )
15
+ const router = createTanStackRouter({
16
+ routeTree,
17
+ context: { queryClient },
18
+ scrollRestoration: true,
19
+ // Catches uncaught render/loader errors on any route. Routes can still
20
+ // set their own `errorComponent` to override this.
21
+ defaultErrorComponent: DefaultErrorComponent,
22
+ defaultPreload: 'intent',
23
+ // With React Query we never want loader data to be stale -- the loader runs
24
+ // on every preload/visit and React Query owns caching from there.
25
+ defaultPreloadStaleTime: 0,
26
+ })
27
+
28
+ setupRouterSsrQueryIntegration({ router, queryClient })
29
29
 
30
30
  return router
31
31
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helloleo/plugins",
3
- "version": "0.2.27",
3
+ "version": "0.2.29-beta.0",
4
4
  "description": "OpenCode plugins for HelloLeo",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",