mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-11 17:46:41 -05:00
Model store hooked up
This commit is contained in:
@@ -50,7 +50,7 @@
|
||||
"fuzzbunny": "^1.0.1",
|
||||
"hexy": "^0.3.5",
|
||||
"history": "^5.3.0",
|
||||
"jotai": "^2.12.2",
|
||||
"jotai": "^2.18.0",
|
||||
"js-md5": "^0.8.3",
|
||||
"lucide-react": "^0.525.0",
|
||||
"mime": "^4.0.4",
|
||||
|
||||
@@ -1,20 +1,30 @@
|
||||
import "./main.css";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { type } from "@tauri-apps/plugin-os";
|
||||
import { Button, HeaderSize } from "@yaakapp-internal/ui";
|
||||
import classNames from "classnames";
|
||||
import { StrictMode } from "react";
|
||||
import { useState } from "react";
|
||||
import { createStore, Provider, useAtomValue } from "jotai";
|
||||
import { StrictMode, useState } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { rpc } from "./rpc";
|
||||
import "./main.css";
|
||||
import { listen, rpc } from "./rpc";
|
||||
import { applyChange, dataAtom, httpExchangesAtom } from "./store";
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
const jotaiStore = createStore();
|
||||
|
||||
// Subscribe to model change events from the backend
|
||||
listen("model_write", (payload) => {
|
||||
jotaiStore.set(dataAtom, (prev) =>
|
||||
applyChange(prev, "http_exchange", payload.model, payload.change),
|
||||
);
|
||||
});
|
||||
|
||||
function App() {
|
||||
const [status, setStatus] = useState("Idle");
|
||||
const [port, setPort] = useState<number | null>(null);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const osType = type();
|
||||
const exchanges = useAtomValue(httpExchangesAtom);
|
||||
|
||||
async function startProxy() {
|
||||
setBusy(true);
|
||||
@@ -45,7 +55,12 @@ function App() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames('h-full w-full grid grid-rows-[auto_1fr]', osType === 'linux' && 'border border-border-subtle')}>
|
||||
<div
|
||||
className={classNames(
|
||||
"h-full w-full grid grid-rows-[auto_1fr]",
|
||||
osType === "linux" && "border border-border-subtle",
|
||||
)}
|
||||
>
|
||||
<HeaderSize
|
||||
size="lg"
|
||||
osType={osType}
|
||||
@@ -56,41 +71,54 @@ function App() {
|
||||
>
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className="flex items-center h-full px-2 text-sm font-semibold text-text-subtle"
|
||||
className="flex items-center px-2 text-sm font-semibold text-text-subtle"
|
||||
>
|
||||
Yaak Proxy
|
||||
</div>
|
||||
</HeaderSize>
|
||||
<main className="overflow-auto p-6">
|
||||
<section className="flex items-start">
|
||||
<div className="flex w-full max-w-xl flex-col gap-4">
|
||||
<div>
|
||||
<p className="text-sm text-text-subtle">Status: {status}</p>
|
||||
<p className="mt-1 text-sm text-text-subtle">
|
||||
Port: {port ?? "Not running"}
|
||||
</p>
|
||||
</div>
|
||||
<main className="overflow-auto p-4">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<Button disabled={busy} onClick={startProxy} size="sm" tone="primary">
|
||||
Start Proxy
|
||||
</Button>
|
||||
<Button
|
||||
disabled={busy}
|
||||
onClick={stopProxy}
|
||||
size="sm"
|
||||
variant="border"
|
||||
>
|
||||
Stop Proxy
|
||||
</Button>
|
||||
<span className="text-xs text-text-subtlest">
|
||||
{status}
|
||||
{port != null && ` · :${port}`}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<Button
|
||||
disabled={busy}
|
||||
onClick={startProxy}
|
||||
size="sm"
|
||||
tone="primary"
|
||||
>
|
||||
Start Proxy
|
||||
</Button>
|
||||
<Button
|
||||
disabled={busy}
|
||||
onClick={stopProxy}
|
||||
size="sm"
|
||||
variant="border"
|
||||
>
|
||||
Stop Proxy
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div className="text-xs font-mono">
|
||||
{exchanges.length === 0 ? (
|
||||
<p className="text-text-subtlest">No traffic yet</p>
|
||||
) : (
|
||||
<table className="w-full text-left">
|
||||
<thead>
|
||||
<tr className="text-text-subtlest border-b border-border-subtle">
|
||||
<th className="py-1 pr-3 font-medium">Method</th>
|
||||
<th className="py-1 pr-3 font-medium">URL</th>
|
||||
<th className="py-1 pr-3 font-medium">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{exchanges.map((ex) => (
|
||||
<tr key={ex.id} className="border-b border-border-subtle">
|
||||
<td className="py-1 pr-3">{ex.method}</td>
|
||||
<td className="py-1 pr-3 truncate max-w-md">{ex.url}</td>
|
||||
<td className="py-1 pr-3">{ex.resStatus ?? "—"}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
@@ -99,7 +127,9 @@ function App() {
|
||||
createRoot(document.getElementById("root") as HTMLElement).render(
|
||||
<StrictMode>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<App />
|
||||
<Provider store={jotaiStore}>
|
||||
<App />
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</StrictMode>,
|
||||
);
|
||||
|
||||
@@ -13,8 +13,10 @@
|
||||
"@tauri-apps/api": "^2.9.1",
|
||||
"@tauri-apps/plugin-os": "^2.3.2",
|
||||
"@yaakapp-internal/theme": "^1.0.0",
|
||||
"@yaakapp-internal/model-store": "^1.0.0",
|
||||
"@yaakapp-internal/ui": "^1.0.0",
|
||||
"classnames": "^2.5.1",
|
||||
"jotai": "^2.18.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0"
|
||||
},
|
||||
|
||||
11
apps/yaak-proxy/store.ts
Normal file
11
apps/yaak-proxy/store.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { createModelStore } from "@yaakapp-internal/model-store";
|
||||
import type { HttpExchange } from "../../crates-proxy/yaak-proxy-lib/bindings/gen_models";
|
||||
|
||||
export const { dataAtom, applyChange, listAtom, orderedListAtom } =
|
||||
createModelStore<HttpExchange>();
|
||||
|
||||
export const httpExchangesAtom = orderedListAtom(
|
||||
"http_exchange",
|
||||
"createdAt",
|
||||
"desc",
|
||||
);
|
||||
3
crates-proxy/yaak-proxy-lib/bindings/ModelChangeEvent.ts
generated
Normal file
3
crates-proxy/yaak-proxy-lib/bindings/ModelChangeEvent.ts
generated
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ModelChangeEvent = { "type": "upsert", created: boolean, } | { "type": "delete" };
|
||||
@@ -1,9 +1,8 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ModelChangeEvent } from "./ModelChangeEvent";
|
||||
|
||||
export type ModelChangeEvent = { "type": "upsert" } | { "type": "delete" };
|
||||
export type HttpExchange = { id: string, createdAt: string, updatedAt: string, url: string, method: string, reqHeaders: Array<ProxyHeader>, reqBody: Array<number> | null, resStatus: number | null, resHeaders: Array<ProxyHeader>, resBody: Array<number> | null, error: string | null, };
|
||||
|
||||
export type ModelPayload = { model: ProxyEntry, change: ModelChangeEvent, };
|
||||
|
||||
export type ProxyEntry = { id: string, createdAt: string, updatedAt: string, url: string, method: string, reqHeaders: Array<ProxyHeader>, reqBody: Array<number> | null, resStatus: number | null, resHeaders: Array<ProxyHeader>, resBody: Array<number> | null, error: string | null, };
|
||||
export type ModelPayload = { model: HttpExchange, change: ModelChangeEvent, };
|
||||
|
||||
export type ProxyHeader = { name: string, value: string, };
|
||||
|
||||
198
package-lock.json
generated
198
package-lock.json
generated
@@ -11,6 +11,7 @@
|
||||
"packages/ui",
|
||||
"packages/theme",
|
||||
"packages/tailwind-config",
|
||||
"packages/model-store",
|
||||
"packages/common-lib",
|
||||
"packages/plugin-runtime",
|
||||
"packages/plugin-runtime-types",
|
||||
@@ -132,7 +133,7 @@
|
||||
"fuzzbunny": "^1.0.1",
|
||||
"hexy": "^0.3.5",
|
||||
"history": "^5.3.0",
|
||||
"jotai": "^2.12.2",
|
||||
"jotai": "^2.18.0",
|
||||
"js-md5": "^0.8.3",
|
||||
"lucide-react": "^0.525.0",
|
||||
"mime": "^4.0.4",
|
||||
@@ -186,9 +187,10 @@
|
||||
}
|
||||
},
|
||||
"apps/yaak-client/node_modules/@types/node": {
|
||||
"version": "24.10.4",
|
||||
"version": "24.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz",
|
||||
"integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.16.0"
|
||||
}
|
||||
@@ -227,8 +229,11 @@
|
||||
"@tanstack/react-query": "^5.90.5",
|
||||
"@tauri-apps/api": "^2.9.1",
|
||||
"@tauri-apps/plugin-os": "^2.3.2",
|
||||
"@yaakapp-internal/model-store": "^1.0.0",
|
||||
"@yaakapp-internal/theme": "^1.0.0",
|
||||
"@yaakapp-internal/ui": "^1.0.0",
|
||||
"classnames": "^2.5.1",
|
||||
"jotai": "^2.18.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0"
|
||||
},
|
||||
@@ -3976,15 +3981,20 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "25.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz",
|
||||
"integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==",
|
||||
"version": "25.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.5.tgz",
|
||||
"integrity": "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.16.0"
|
||||
"undici-types": "~7.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node/node_modules/undici-types": {
|
||||
"version": "7.18.2",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
||||
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/openapi-to-postmanv2": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/openapi-to-postmanv2/-/openapi-to-postmanv2-5.0.0.tgz",
|
||||
@@ -4419,6 +4429,10 @@
|
||||
"resolved": "crates-tauri/yaak-mac-window",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@yaakapp-internal/model-store": {
|
||||
"resolved": "packages/model-store",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@yaakapp-internal/models": {
|
||||
"resolved": "crates/yaak-models",
|
||||
"link": true
|
||||
@@ -5669,10 +5683,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/codemirror-json-schema/node_modules/@types/node": {
|
||||
"version": "20.19.27",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz",
|
||||
"integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==",
|
||||
"license": "MIT",
|
||||
"version": "20.19.37",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz",
|
||||
"integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
@@ -5957,11 +5970,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cpx2/node_modules/brace-expansion": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz",
|
||||
"integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==",
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
|
||||
"integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
@@ -6562,11 +6574,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "17.2.3",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
|
||||
"integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==",
|
||||
"version": "17.3.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz",
|
||||
"integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -7486,15 +7497,6 @@
|
||||
"node": ">=10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/format-graphql/node_modules/graphql": {
|
||||
"version": "15.10.1",
|
||||
"resolved": "https://registry.npmjs.org/graphql/-/graphql-15.10.1.tgz",
|
||||
"integrity": "sha512-BL/Xd/T9baO6NFzoMpiMD7YUZ62R6viR5tp/MULVEnbYJXZA//kRNW7J0j1w/wXArgL0sCxhDfK5dczSKn3+cg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 10.x"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
@@ -7812,11 +7814,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/glob/node_modules/brace-expansion": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz",
|
||||
"integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==",
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
|
||||
"integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
@@ -7928,13 +7929,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/graphql": {
|
||||
"version": "16.12.0",
|
||||
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz",
|
||||
"integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"version": "15.10.1",
|
||||
"resolved": "https://registry.npmjs.org/graphql/-/graphql-15.10.1.tgz",
|
||||
"integrity": "sha512-BL/Xd/T9baO6NFzoMpiMD7YUZ62R6viR5tp/MULVEnbYJXZA//kRNW7J0j1w/wXArgL0sCxhDfK5dczSKn3+cg==",
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
|
||||
"node": ">= 10.x"
|
||||
}
|
||||
},
|
||||
"node_modules/graphql-language-service": {
|
||||
@@ -9090,10 +9089,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jotai": {
|
||||
"version": "2.16.1",
|
||||
"resolved": "https://registry.npmjs.org/jotai/-/jotai-2.16.1.tgz",
|
||||
"integrity": "sha512-vrHcAbo3P7Br37C8Bv6JshMtlKMPqqmx0DDREtTjT4nf3QChDrYdbH+4ik/9V0cXA57dK28RkJ5dctYvavcIlg==",
|
||||
"license": "MIT",
|
||||
"version": "2.18.0",
|
||||
"resolved": "https://registry.npmjs.org/jotai/-/jotai-2.18.0.tgz",
|
||||
"integrity": "sha512-XI38kGWAvtxAZ+cwHcTgJsd+kJOJGf3OfL4XYaXWZMZ7IIY8e53abpIHvtVn1eAgJ5dlgwlGFnP4psrZ/vZbtA==",
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
},
|
||||
@@ -9229,18 +9227,6 @@
|
||||
"valid-url": "^1.0.9"
|
||||
}
|
||||
},
|
||||
"node_modules/json-schema-library/node_modules/smtp-address-parser": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/smtp-address-parser/-/smtp-address-parser-1.0.10.tgz",
|
||||
"integrity": "sha512-Osg9LmvGeAG/hyao4mldbflLOkkr3a+h4m1lwKCK5U8M6ZAr7tdXEz/+/vr752TSGE4MNUlUl9cIK2cB8cgzXg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nearley": "^2.20.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/json-schema-merge-allof": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-merge-allof/-/json-schema-merge-allof-0.8.1.tgz",
|
||||
@@ -9334,10 +9320,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jsonwebtoken/node_modules/semver": {
|
||||
"version": "7.7.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
|
||||
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
|
||||
"license": "ISC",
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
@@ -10731,11 +10716,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/minipass": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
||||
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
|
||||
"integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
@@ -11943,28 +11927,26 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/path-scurry": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz",
|
||||
"integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
|
||||
"integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"lru-cache": "^11.0.0",
|
||||
"minipass": "^7.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
"node": "18 || 20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/path-scurry/node_modules/lru-cache": {
|
||||
"version": "11.2.4",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz",
|
||||
"integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==",
|
||||
"version": "11.2.6",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz",
|
||||
"integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
@@ -13129,11 +13111,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf/node_modules/brace-expansion": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz",
|
||||
"integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==",
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
|
||||
"integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
@@ -13142,18 +13123,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf/node_modules/glob": {
|
||||
"version": "13.0.0",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz",
|
||||
"integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==",
|
||||
"version": "13.0.6",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
|
||||
"integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"minimatch": "^10.1.1",
|
||||
"minipass": "^7.1.2",
|
||||
"path-scurry": "^2.0.0"
|
||||
"minimatch": "^10.2.2",
|
||||
"minipass": "^7.1.3",
|
||||
"path-scurry": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
"node": "18 || 20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
@@ -13777,10 +13757,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/smtp-address-parser": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/smtp-address-parser/-/smtp-address-parser-1.1.0.tgz",
|
||||
"integrity": "sha512-Gz11jbNU0plrReU9Sj7fmshSBxxJ9ShdD2q4ktHIHo/rpTH6lFyQoYHYKINPJtPe8aHFnsbtW46Ls0tCCBsIZg==",
|
||||
"license": "MIT",
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/smtp-address-parser/-/smtp-address-parser-1.0.10.tgz",
|
||||
"integrity": "sha512-Osg9LmvGeAG/hyao4mldbflLOkkr3a+h4m1lwKCK5U8M6ZAr7tdXEz/+/vr752TSGE4MNUlUl9cIK2cB8cgzXg==",
|
||||
"dependencies": {
|
||||
"nearley": "^2.20.1"
|
||||
},
|
||||
@@ -14149,13 +14128,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
|
||||
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
|
||||
"integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^6.0.1"
|
||||
"ansi-regex": "^6.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
@@ -16110,6 +16088,13 @@
|
||||
"name": "@yaakapp-internal/lib",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"packages/model-store": {
|
||||
"name": "@yaakapp-internal/model-store",
|
||||
"version": "1.0.0",
|
||||
"peerDependencies": {
|
||||
"jotai": "^2.18.0"
|
||||
}
|
||||
},
|
||||
"packages/plugin-runtime": {
|
||||
"name": "@yaakapp-internal/plugin-runtime",
|
||||
"dependencies": {
|
||||
@@ -16130,8 +16115,9 @@
|
||||
}
|
||||
},
|
||||
"packages/plugin-runtime-types/node_modules/@types/node": {
|
||||
"version": "24.10.4",
|
||||
"license": "MIT",
|
||||
"version": "24.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz",
|
||||
"integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.16.0"
|
||||
}
|
||||
@@ -16155,20 +16141,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@yaakapp-internal/ui",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "^5.90.5",
|
||||
"@tauri-apps/api": "^2.9.1",
|
||||
"@yaakapp-internal/lib": "^1.0.0",
|
||||
"classnames": "^2.5.1",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-use": "^17.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0"
|
||||
}
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"plugins-external/faker": {
|
||||
"name": "@yaak/faker",
|
||||
@@ -16209,11 +16182,10 @@
|
||||
}
|
||||
},
|
||||
"plugins-external/httpsnippet/node_modules/@types/node": {
|
||||
"version": "22.19.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.9.tgz",
|
||||
"integrity": "sha512-PD03/U8g1F9T9MI+1OBisaIARhSzeidsUjQaf51fOxrfjeiKN9bLVO06lHuHYjxdnqLWJijJHfqXPSJri2EM2A==",
|
||||
"version": "22.19.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz",
|
||||
"integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"packages/ui",
|
||||
"packages/theme",
|
||||
"packages/tailwind-config",
|
||||
"packages/model-store",
|
||||
"packages/common-lib",
|
||||
"packages/plugin-runtime",
|
||||
"packages/plugin-runtime-types",
|
||||
|
||||
11
packages/model-store/package.json
Normal file
11
packages/model-store/package.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "@yaakapp-internal/model-store",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"peerDependencies": {
|
||||
"jotai": "^2.18.0"
|
||||
}
|
||||
}
|
||||
71
packages/model-store/src/index.ts
Normal file
71
packages/model-store/src/index.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { atom } from "jotai";
|
||||
import { selectAtom } from "jotai/utils";
|
||||
|
||||
/** Any model must at least have an id. */
|
||||
type BaseModel = { id: string };
|
||||
|
||||
/** The raw store shape: model type string → id → model instance. */
|
||||
type StoreData<M extends BaseModel> = Record<string, Record<string, M>>;
|
||||
|
||||
export type ModelChange = { type: "upsert" } | { type: "delete" };
|
||||
|
||||
export function createModelStore<M extends BaseModel>() {
|
||||
const dataAtom = atom<StoreData<M>>({});
|
||||
|
||||
/** Apply a single upsert or delete to the store. */
|
||||
function applyChange(
|
||||
prev: StoreData<M>,
|
||||
modelType: string,
|
||||
model: M,
|
||||
change: ModelChange,
|
||||
): StoreData<M> {
|
||||
if (change.type === "upsert") {
|
||||
return {
|
||||
...prev,
|
||||
[modelType]: { ...prev[modelType], [model.id]: model },
|
||||
};
|
||||
} else {
|
||||
const bucket = { ...prev[modelType] };
|
||||
delete bucket[model.id];
|
||||
return { ...prev, [modelType]: bucket };
|
||||
}
|
||||
}
|
||||
|
||||
/** Atom that selects all models of a given type as an array. */
|
||||
function listAtom(modelType: string) {
|
||||
return selectAtom(
|
||||
dataAtom,
|
||||
(data) => Object.values(data[modelType] ?? {}),
|
||||
shallowEqual,
|
||||
);
|
||||
}
|
||||
|
||||
/** Atom that selects all models of a given type, sorted by a field. */
|
||||
function orderedListAtom<K extends keyof M>(
|
||||
modelType: string,
|
||||
field: K,
|
||||
order: "asc" | "desc",
|
||||
) {
|
||||
return selectAtom(
|
||||
dataAtom,
|
||||
(data) => {
|
||||
const vals = Object.values(data[modelType] ?? {});
|
||||
return vals.sort((a, b) => {
|
||||
const n = a[field] > b[field] ? 1 : -1;
|
||||
return order === "desc" ? -n : n;
|
||||
});
|
||||
},
|
||||
shallowEqual,
|
||||
);
|
||||
}
|
||||
|
||||
return { dataAtom, applyChange, listAtom, orderedListAtom };
|
||||
}
|
||||
|
||||
function shallowEqual<T>(a: T[], b: T[]): boolean {
|
||||
if (a.length !== b.length) return false;
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (a[i] !== b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Reference in New Issue
Block a user