@sayrio/public 1.0.2 → 1.0.3
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 +42 -42
- package/dist/index.cjs +35 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -12
- package/dist/index.d.ts +12 -12
- package/dist/index.js +32 -41
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +42 -54
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +10 -10
- package/dist/react/index.d.ts +10 -10
- package/dist/react/index.js +41 -53
- package/dist/react/index.js.map +1 -1
- package/package.json +2 -2
package/dist/react/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/index.ts","../../src/react/useOrg.ts","../../src/client/index.ts","../../src/api/v1/org/org.ts","../../src/shared/index.ts","../../src/api/v1/org/tasks.ts","../../src/api/v1/org/comments.ts","../../src/api/v1/org/labels.ts","../../src/api/v1/org/categories.ts","../../src/api/v1/org/index.ts","../../src/api/v1/me/index.ts","../../src/api/v1/index.ts","../../src/ws/types.ts","../../src/ws/index.ts","../../src/index.ts","../../src/react/useTasks.ts","../../src/react/useSayrWS.ts","../../src/react/useTask.ts","../../src/react/useComments.ts"],"sourcesContent":["export { useOrg } from \"./useOrg\";\r\nexport { useTasks } from \"./useTasks\";\r\nexport { useTask } from \"./useTask\";\r\nexport { useComments } from \"./useComments\";\r\nexport { useSayrWS } from \"./useSayrWS\";","import { useEffect, useRef, useState } from \"react\";\r\nimport Sayr, { Organization } from \"../index\";\r\n\r\nexport function useOrg(slug?: string) {\r\n const [data, setData] = useState<Organization | null>(null);\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const mountedRef = useRef(true);\r\n\r\n useEffect(() => {\r\n return () => {\r\n mountedRef.current = false;\r\n };\r\n }, []);\r\n\r\n useEffect(() => {\r\n if (!slug) return;\r\n\r\n setLoading(true);\r\n setError(null);\r\n\r\n Sayr.org.get(slug).then((res) => {\r\n if (!mountedRef.current) return;\r\n\r\n if (!res.success) {\r\n setError(res.error);\r\n setData(null);\r\n } else {\r\n setData(res.data);\r\n }\r\n\r\n setLoading(false);\r\n });\r\n }, [slug]);\r\n\r\n return {\r\n data,\r\n loading,\r\n error,\r\n };\r\n}","import { ApiError } from \"../types\";\r\n\r\n/* ────────────────────────────\r\n Types\r\n──────────────────────────── */\r\nexport type RequestOptions = {\r\n method?: \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\r\n headers?: Record<string, string>;\r\n body?: Record<string, string>;\r\n signal?: AbortSignal;\r\n};\r\n\r\nexport interface ApiResult<T> {\r\n success: boolean;\r\n data: T | null;\r\n error: string | null;\r\n}\r\n\r\ntype Hooks = {\r\n onRequest?: (url: string, opts: RequestOptions) => void;\r\n onResponse?: (res: Response) => void;\r\n onError?: (error: ApiError | unknown) => void;\r\n};\r\n\r\ntype ClientConfig = {\r\n token?: string;\r\n headers?: Record<string, string>;\r\n baseUrl: string;\r\n};\r\n\r\n/* ────────────────────────────\r\n Internal state\r\n──────────────────────────── */\r\nconst DEFAULT_API = \"https://api.sayr.io\";\r\n\r\nconst config: ClientConfig = {\r\n baseUrl: DEFAULT_API\r\n};\r\n\r\nconst hooks: Hooks = {};\r\n\r\n/* ────────────────────────────\r\n Public config API\r\n──────────────────────────── */\r\nexport function setToken(token?: string) {\r\n config.token = token;\r\n}\r\n\r\nexport function getToken() {\r\n return config.token;\r\n}\r\n\r\nexport function setHeaders(headers?: Record<string, string>) {\r\n config.headers = {\r\n ...config.headers,\r\n ...headers\r\n };\r\n}\r\n\r\nexport function setBaseUrl(url: string) {\r\n config.baseUrl = url.replace(/\\/$/, \"\");\r\n}\r\n\r\nexport function setHooks(h: Hooks) {\r\n Object.assign(hooks, h);\r\n}\r\nexport function resetClient() {\r\n config.token = undefined;\r\n config.headers = undefined;\r\n config.baseUrl = DEFAULT_API;\r\n}\r\n\r\n/* ────────────────────────────\r\n Request helper\r\n──────────────────────────── */\r\nexport async function request<T>(\r\n path: string,\r\n opts: RequestOptions = {}\r\n): Promise<T> {\r\n\r\n let url: string;\r\n try {\r\n url = path.startsWith(\"http\")\r\n ? path\r\n : `${config.baseUrl}${path}`;\r\n } catch (err) {\r\n throw err;\r\n }\r\n\r\n try {\r\n hooks.onRequest?.(url, opts);\r\n } catch (err) {\r\n throw err;\r\n }\r\n\r\n let res: Response;\r\n\r\n try {\r\n const method = opts.method ?? \"GET\";\r\n\r\n\r\n // 🔴 Guard: GET must not have body\r\n if (method === \"GET\" && opts.body !== undefined) {\r\n throw new Error(\"GET request cannot have a body\");\r\n }\r\n\r\n res = await fetch(url, {\r\n method,\r\n headers: {\r\n ...(config.token\r\n ? { Authorization: `Bearer ${config.token}` }\r\n : {}),\r\n ...(opts.body && method !== \"GET\"\r\n ? { \"Content-Type\": \"application/json\" }\r\n : {}),\r\n ...config.headers,\r\n ...opts.headers\r\n },\r\n body:\r\n opts.body && method !== \"GET\"\r\n ? JSON.stringify(opts.body)\r\n : undefined,\r\n signal: opts.signal\r\n });\r\n } catch (err) {\r\n const error: ApiError = {\r\n success: false,\r\n error: \"NETWORK_ERROR\",\r\n message: \"Failed to reach Sayr API\",\r\n // 👇 keep raw error so we can see it\r\n // @ts-ignore\r\n raw: err\r\n };\r\n\r\n hooks.onError?.(error);\r\n throw error;\r\n }\r\n\r\n try {\r\n hooks.onResponse?.(res);\r\n } catch (err) {\r\n throw err;\r\n }\r\n\r\n let json: any;\r\n try {\r\n json = await res.json();\r\n } catch (err) {\r\n const error: ApiError = {\r\n success: false,\r\n error: \"INVALID_RESPONSE\",\r\n message: \"Server returned invalid JSON\",\r\n status: res.status\r\n };\r\n hooks.onError?.(error);\r\n throw error;\r\n }\r\n if (!res.ok || !json.success) {\r\n const error: ApiError = {\r\n ...json,\r\n success: false,\r\n status: res.status\r\n };\r\n hooks.onError?.(error);\r\n throw error;\r\n }\r\n return json;\r\n}","import { Organization, ApiSuccess } from \"../../../types\";\r\nimport { ApiResult, request, type RequestOptions } from \"../../../client\";\r\n\r\n/**\r\n * Organization core operations.\r\n */\r\nexport default {\r\n /**\r\n * Fetches a public organization by slug.\r\n *\r\n * @since v1.0.0\r\n */\r\n async get(\r\n slug: string,\r\n opts?: RequestOptions,\r\n ): Promise<ApiResult<Organization>> {\r\n try {\r\n const r = await request<ApiSuccess<Organization>>(\r\n `/v1/organization/${slug}`,\r\n opts,\r\n );\r\n\r\n return {\r\n success: true,\r\n data: r.data,\r\n error: null,\r\n };\r\n } catch (err: any) {\r\n return {\r\n success: false,\r\n data: null,\r\n error: err?.message ?? \"Failed to fetch organization\",\r\n };\r\n }\r\n },\r\n};","/* =======================\r\n * Shared params & helpers\r\n * ======================= */\r\n\r\nexport type Order = \"asc\" | \"desc\";\r\n\r\nexport interface PaginationParams {\r\n page?: number;\r\n limit?: number;\r\n}\r\n\r\nexport interface OrderedPaginationParams extends PaginationParams {\r\n order?: Order;\r\n}\r\n\r\nexport function buildPaginationParams(\r\n params?: OrderedPaginationParams\r\n): URLSearchParams {\r\n return new URLSearchParams({\r\n order: params?.order ?? \"desc\",\r\n limit: String(params?.limit ?? 5),\r\n page: String(params?.page ?? 1)\r\n });\r\n}","import {\r\n Task,\r\n Pagination,\r\n ApiSuccess,\r\n} from \"../../../types\";\r\nimport {\r\n ApiResult,\r\n request,\r\n type RequestOptions,\r\n} from \"../../../client\";\r\nimport {\r\n type OrderedPaginationParams,\r\n buildPaginationParams,\r\n} from \"../../../shared\";\r\n\r\n/**\r\n * Organization tasks.\r\n */\r\nexport default {\r\n /**\r\n * Lists public tasks for an organization.\r\n *\r\n * @since v1.0.0\r\n */\r\n async list(\r\n slug: string,\r\n params?: OrderedPaginationParams,\r\n opts?: RequestOptions,\r\n ): Promise<\r\n ApiResult<{\r\n items: Task[];\r\n pagination: Pagination;\r\n }>\r\n > {\r\n try {\r\n const q = buildPaginationParams(params);\r\n\r\n const r = await request<\r\n ApiSuccess<Task[]> & { pagination: Pagination }\r\n >(\r\n `/v1/organization/${slug}/tasks?${q}`,\r\n opts,\r\n );\r\n\r\n return {\r\n success: true,\r\n data: {\r\n items: r.data,\r\n pagination: r.pagination,\r\n },\r\n error: null,\r\n };\r\n } catch (err: any) {\r\n return {\r\n success: false,\r\n data: null,\r\n error:\r\n err?.message ??\r\n \"Failed to fetch tasks\",\r\n };\r\n }\r\n },\r\n\r\n /**\r\n * Fetches a single public task by short ID.\r\n *\r\n * @since v1.0.0\r\n */\r\n async get(\r\n slug: string,\r\n shortId: number,\r\n opts?: RequestOptions,\r\n ): Promise<ApiResult<Task>> {\r\n try {\r\n const r = await request<ApiSuccess<Task>>(\r\n `/v1/organization/${slug}/tasks/${shortId}`,\r\n opts,\r\n );\r\n\r\n return {\r\n success: true,\r\n data: r.data,\r\n error: null,\r\n };\r\n } catch (err: any) {\r\n return {\r\n success: false,\r\n data: null,\r\n error:\r\n err?.message ??\r\n \"Failed to fetch task\",\r\n };\r\n }\r\n },\r\n};","import {\r\n Comment,\r\n Pagination,\r\n ApiSuccess,\r\n} from \"../../../types\";\r\nimport {\r\n ApiResult,\r\n request,\r\n type RequestOptions,\r\n} from \"../../../client\";\r\nimport {\r\n type OrderedPaginationParams,\r\n buildPaginationParams,\r\n} from \"../../../shared\";\r\n\r\n/**\r\n * Organization task comments.\r\n */\r\nexport default {\r\n /**\r\n * Lists public comments for a task.\r\n *\r\n * @since v1.0.0\r\n */\r\n async list(\r\n slug: string,\r\n shortId: number,\r\n params?: OrderedPaginationParams,\r\n opts?: RequestOptions,\r\n ): Promise<\r\n ApiResult<{\r\n items: Comment[];\r\n pagination: Pagination;\r\n }>\r\n > {\r\n try {\r\n const q = buildPaginationParams(params);\r\n\r\n const r = await request<\r\n ApiSuccess<Comment[]> & { pagination: Pagination }\r\n >(\r\n `/v1/organization/${slug}/tasks/${shortId}/comments?${q}`,\r\n opts,\r\n );\r\n\r\n return {\r\n success: true,\r\n data: {\r\n items: r.data,\r\n pagination: r.pagination,\r\n },\r\n error: null,\r\n };\r\n } catch (err: any) {\r\n return {\r\n success: false,\r\n data: null,\r\n error:\r\n err?.message ??\r\n \"Failed to fetch task comments\",\r\n };\r\n }\r\n },\r\n};","import { Label, ApiSuccess } from \"../../../types\";\r\nimport { ApiResult, request, type RequestOptions } from \"../../../client\";\r\n\r\n/**\r\n * Organization labels.\r\n */\r\nexport default {\r\n /**\r\n * Lists public labels for an organization.\r\n *\r\n * @since v1.0.0\r\n */\r\n async list(\r\n slug: string,\r\n opts?: RequestOptions,\r\n ): Promise<ApiResult<Label[]>> {\r\n try {\r\n const r = await request<ApiSuccess<Label[]>>(\r\n `/v1/organization/${slug}/labels`,\r\n opts,\r\n );\r\n\r\n return {\r\n success: true,\r\n data: r.data,\r\n error: null,\r\n };\r\n } catch (err: any) {\r\n return {\r\n success: false,\r\n data: null,\r\n error: err?.message ?? \"Failed to fetch labels\",\r\n };\r\n }\r\n },\r\n};","import {\r\n Category,\r\n ApiSuccess,\r\n} from \"../../../types\";\r\nimport {\r\n ApiResult,\r\n request,\r\n type RequestOptions,\r\n} from \"../../../client\";\r\nimport { type Order } from \"../../../shared\";\r\n\r\n/**\r\n * Organization categories.\r\n */\r\nexport default {\r\n /**\r\n * Lists public categories for an organization.\r\n *\r\n * @since v1.0.0\r\n */\r\n async list(\r\n slug: string,\r\n order: Order = \"desc\",\r\n opts?: RequestOptions,\r\n ): Promise<ApiResult<Category[]>> {\r\n try {\r\n const r = await request<ApiSuccess<Category[]>>(\r\n `/v1/organization/${slug}/categories?order=${order}`,\r\n opts,\r\n );\r\n\r\n return {\r\n success: true,\r\n data: r.data,\r\n error: null,\r\n };\r\n } catch (err: any) {\r\n return {\r\n success: false,\r\n data: null,\r\n error:\r\n err?.message ??\r\n \"Failed to fetch categories\",\r\n };\r\n }\r\n },\r\n};","import org from \"./org\";\r\nimport tasks from \"./tasks\";\r\nimport comments from \"./comments\";\r\nimport labels from \"./labels\";\r\nimport categories from \"./categories\";\r\n\r\n/**\r\n * Public Sayr Organization API — Version 1.\r\n *\r\n * @since v1.0.0\r\n */\r\nconst OrgAPI = {\r\n ...org,\r\n tasks,\r\n comments,\r\n labels,\r\n categories\r\n};\r\n\r\nexport default OrgAPI;","import { ApiResult, request, type RequestOptions } from \"../../../client\";\r\nimport { ApiSuccess, Organization } from \"../../../types\";\r\n\r\nexport interface Me {\r\n id: string;\r\n name: string | null;\r\n email: string | null;\r\n image: string | null;\r\n createdAt: string;\r\n}\r\n\r\n/**\r\n * Authenticated user API — Version 1.\r\n *\r\n * @since v1.0.0\r\n */\r\nexport default {\r\n async get(opts?: RequestOptions): Promise<ApiResult<Me>> {\r\n try {\r\n const r = await request<ApiSuccess<Me>>(\"/v1/me\", opts);\r\n return {\r\n success: true,\r\n data: r.data,\r\n error: null,\r\n };\r\n } catch (err: any) {\r\n return {\r\n success: false,\r\n data: null,\r\n error: err?.message ?? \"Failed to fetch user\",\r\n };\r\n }\r\n },\r\n\r\n async organizations(\r\n opts?: RequestOptions,\r\n ): Promise<ApiResult<Organization[]>> {\r\n try {\r\n const r = await request<ApiSuccess<Organization[]>>(\r\n \"/v1/me/organizations\",\r\n opts,\r\n );\r\n return {\r\n success: true,\r\n data: r.data,\r\n error: null,\r\n };\r\n } catch (err: any) {\r\n return {\r\n success: false,\r\n data: null,\r\n error: err?.message ?? \"Failed to fetch organizations\",\r\n };\r\n }\r\n },\r\n};","import org from \"./org\";\r\nimport me from \"./me\";\r\nexport const v1 = {\r\n org,\r\n me\r\n};\r\n\r\nexport default v1;","export type WSMessageType =\r\n | \"CONNECTION_STATUS\"\r\n | \"SUBSCRIBED\"\r\n | \"ERROR\"\r\n | \"PING\"\r\n | \"PONG\"\r\n | \"UPDATE_ORG\"\r\n | \"CREATE_TASK\"\r\n | \"UPDATE_TASK\"\r\n | \"UPDATE_TASK_COMMENTS\"\r\n | \"UPDATE_TASK_VOTE\"\r\n | \"UPDATE_LABELS\"\r\n | \"UPDATE_VIEWS\"\r\n | \"UPDATE_CATEGORIES\"\r\n | \"UPDATE_ISSUE_TEMPLATES\"\r\n | \"DISCONNECTED\";\r\n\r\n/**\r\n * String enum replacement for WS event names.\r\n * Use this instead of raw strings.\r\n */\r\nexport const WS_EVENTS: Record<WSMessageType, WSMessageType> = {\r\n CONNECTION_STATUS: \"CONNECTION_STATUS\",\r\n SUBSCRIBED: \"SUBSCRIBED\",\r\n ERROR: \"ERROR\",\r\n PING: \"PING\",\r\n PONG: \"PONG\",\r\n UPDATE_ORG: \"UPDATE_ORG\",\r\n CREATE_TASK: \"CREATE_TASK\",\r\n UPDATE_TASK: \"UPDATE_TASK\",\r\n UPDATE_TASK_COMMENTS: \"UPDATE_TASK_COMMENTS\",\r\n UPDATE_TASK_VOTE: \"UPDATE_TASK_VOTE\",\r\n UPDATE_LABELS: \"UPDATE_LABELS\",\r\n UPDATE_VIEWS: \"UPDATE_VIEWS\",\r\n UPDATE_CATEGORIES: \"UPDATE_CATEGORIES\",\r\n UPDATE_ISSUE_TEMPLATES: \"UPDATE_ISSUE_TEMPLATES\",\r\n DISCONNECTED: \"DISCONNECTED\"\r\n};\r\n\r\nexport interface WSMessage<T = unknown> {\r\n type: WSMessageType;\r\n scope: \"PUBLIC\";\r\n data: T;\r\n meta?: { ts: number };\r\n}","import { WS_EVENTS, type WSMessage, type WSMessageType } from \"./types\";\r\n\r\ntype Handlers = Partial<\r\n Record<WSMessageType, (data: any, msg: WSMessage) => void>\r\n>;\r\n\r\nexport function ws(url: string, handlers: Handlers = {}) {\r\n if (!url) {\r\n throw new Error(\r\n \"[Sayr.ws] WebSocket URL is required. \" +\r\n \"Did you forget to pass org.wsUrl?\"\r\n );\r\n }\r\n let socket: WebSocket;\r\n let retry = 0;\r\n let closed = false;\r\n\r\n function connect() {\r\n if (closed) return;\r\n\r\n socket = new WebSocket(url);\r\n\r\n socket.onmessage = (e) => {\r\n const msg = JSON.parse(e.data) as WSMessage;\r\n\r\n if (msg.type === WS_EVENTS.PING) {\r\n socket.send(JSON.stringify({ type: WS_EVENTS.PONG }));\r\n return;\r\n }\r\n\r\n handlers[msg.type]?.(msg.data, msg);\r\n };\r\n\r\n socket.onclose = () => {\r\n if (closed) return;\r\n setTimeout(connect, Math.min(1000 * 2 ** retry++, 30000));\r\n };\r\n\r\n socket.onerror = () => socket.close();\r\n }\r\n\r\n connect();\r\n\r\n return {\r\n close() {\r\n closed = true;\r\n socket?.close();\r\n }\r\n };\r\n}","/* ────────────────────────────\r\n API versions\r\n──────────────────────────── */\r\nimport v1 from \"./api/v1\";\r\n\r\n/* ────────────────────────────\r\n Realtime\r\n──────────────────────────── */\r\nimport { ws } from \"./ws\";\r\nimport { WS_EVENTS } from \"./ws/types\";\r\n\r\n/* ────────────────────────────\r\n Client config\r\n──────────────────────────── */\r\nimport {\r\n setToken,\r\n setHeaders,\r\n setBaseUrl,\r\n resetClient,\r\n setHooks,\r\n} from \"./client\";\r\n\r\n/* ────────────────────────────\r\n Named exports (power users)\r\n──────────────────────────── */\r\n\r\n/**\r\n * Sayr Public API — Version 1.\r\n *\r\n * @since v1.0.0\r\n */\r\nexport const SayrV1 = v1;\r\n\r\n\r\n/**\r\n * Create a WebSocket connection for public real‑time updates.\r\n */\r\nexport const SayrWS = ws;\r\n\r\n/**\r\n * Typed WebSocket event constants.\r\n */\r\nexport const SayrWSEvents = WS_EVENTS;\r\n\r\n/**\r\n * Global client configuration helpers.\r\n */\r\nexport const SayrClient = {\r\n setToken,\r\n setHeaders,\r\n setBaseUrl,\r\n resetClient,\r\n setHooks,\r\n};\r\n\r\n/* ────────────────────────────\r\n Default facade\r\n──────────────────────────── */\r\n\r\n/**\r\n * Sayr Public SDK.\r\n *\r\n * Read‑only access to public Sayr data via REST and WebSockets.\r\n *\r\n * @since v1.0.0\r\n */\r\nconst Sayr: {\r\n /**\r\n * Client configuration helpers.\r\n */\r\n client: typeof SayrClient;\r\n\r\n /**\r\n * Versioned API namespaces.\r\n */\r\n v1: typeof v1;\r\n\r\n /**\r\n * Alias for the current API version (`v1`).\r\n *\r\n * @since v1.0.0\r\n */\r\n org: typeof v1.org;\r\n me: typeof v1.me;\r\n\r\n /**\r\n * WebSocket helper for real‑time updates.\r\n */\r\n ws: typeof ws;\r\n\r\n /**\r\n * WebSocket event constants.\r\n */\r\n WS_EVENTS: typeof WS_EVENTS;\r\n} = {\r\n // client configuration\r\n client: SayrClient,\r\n\r\n // APIs\r\n v1,\r\n org: v1.org,\r\n me: v1.me,\r\n\r\n // realtime\r\n ws,\r\n WS_EVENTS\r\n};\r\n\r\nexport default Sayr;\r\n\r\n/* ────────────────────────────\r\n Types & shared helpers\r\n──────────────────────────── */\r\nexport * from \"./types\";\r\nexport * from \"./shared\";\r\nexport * from \"./ws/types\";","import { useCallback, useEffect, useRef, useState } from \"react\";\r\nimport type { Task } from \"../types\";\r\nimport { useSayrWS } from \"./useSayrWS\";\r\nimport Sayr from \"..\";\r\n\r\nexport function useTasks(\r\n slug?: string,\r\n wsUrl?: string,\r\n) {\r\n const [tasks, setTasks] = useState<Task[]>([]);\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const mountedRef = useRef(true);\r\n\r\n useEffect(() => {\r\n return () => {\r\n mountedRef.current = false;\r\n };\r\n }, []);\r\n\r\n const fetchTasks = useCallback(async () => {\r\n if (!slug) return;\r\n\r\n setLoading(true);\r\n setError(null);\r\n\r\n const res = await Sayr.org.tasks.list(slug);\r\n\r\n if (!mountedRef.current) return;\r\n\r\n if (!res.success) {\r\n setError(res.error);\r\n setTasks([]);\r\n } else {\r\n setTasks(res.data?.items ?? []);\r\n }\r\n\r\n setLoading(false);\r\n }, [slug]);\r\n\r\n useEffect(() => {\r\n fetchTasks();\r\n }, [fetchTasks]);\r\n\r\n useSayrWS(wsUrl, {\r\n [Sayr.WS_EVENTS.CREATE_TASK]: fetchTasks,\r\n [Sayr.WS_EVENTS.UPDATE_TASK]: fetchTasks,\r\n });\r\n\r\n return {\r\n tasks,\r\n loading,\r\n error,\r\n refetch: fetchTasks,\r\n };\r\n}","import { useEffect, useRef } from \"react\";\r\nimport { WSMessageType } from \"../ws/types\";\r\nimport { ws } from \"../ws\";\r\nimport type { WSMessage } from \"../ws/types\";\r\n\r\ntype Handler = (data: any, msg: WSMessage<unknown>) => void;\r\n\r\ntype Handlers = Partial<Record<WSMessageType, Handler>>;\r\n\r\nexport function useSayrWS(\r\n wsUrl?: string,\r\n handlers?: Handlers,\r\n) {\r\n const connRef = useRef<ReturnType<typeof ws> | null>(null);\r\n const handlersRef = useRef<Handlers | undefined>(handlers);\r\n\r\n // ✅ Keep latest handlers without reconnecting\r\n useEffect(() => {\r\n handlersRef.current = handlers;\r\n }, [handlers]);\r\n\r\n useEffect(() => {\r\n if (!wsUrl) return;\r\n\r\n // ✅ Adapter object: keys stay stable, functions dispatch dynamically\r\n const proxyHandlers: Handlers = new Proxy(\r\n {},\r\n {\r\n get: (_, type: string) => {\r\n return (data: any, msg: WSMessage<unknown>) => {\r\n const handler =\r\n handlersRef.current?.[type as WSMessageType];\r\n if (handler) {\r\n handler(data, msg);\r\n }\r\n };\r\n },\r\n },\r\n );\r\n\r\n connRef.current = ws(wsUrl, proxyHandlers);\r\n\r\n return () => {\r\n connRef.current?.close();\r\n connRef.current = null;\r\n };\r\n }, [wsUrl]);\r\n\r\n return connRef;\r\n}","import { useEffect, useRef, useState } from \"react\";\r\nimport type { Task } from \"../types\";\r\nimport Sayr from \"..\";\r\n\r\nexport function useTask(\r\n slug?: string,\r\n shortId?: number,\r\n) {\r\n const [task, setTask] = useState<Task | null>(null);\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const mountedRef = useRef(true);\r\n\r\n useEffect(() => {\r\n return () => {\r\n mountedRef.current = false;\r\n };\r\n }, []);\r\n\r\n useEffect(() => {\r\n if (!slug || shortId == null) return;\r\n\r\n setLoading(true);\r\n setError(null);\r\n\r\n Sayr.org.tasks.get(slug, shortId).then((res) => {\r\n if (!mountedRef.current) return;\r\n\r\n if (!res.success) {\r\n setError(res.error);\r\n setTask(null);\r\n } else {\r\n setTask(res.data);\r\n }\r\n\r\n setLoading(false);\r\n });\r\n }, [slug, shortId]);\r\n\r\n return {\r\n task,\r\n loading,\r\n error,\r\n };\r\n}","import { useCallback, useEffect, useRef, useState } from \"react\";\r\nimport type { Comment } from \"../types\";\r\nimport { useSayrWS } from \"./useSayrWS\";\r\nimport Sayr from \"..\";\r\n\r\nexport function useComments(\r\n slug?: string,\r\n shortId?: number,\r\n wsUrl?: string,\r\n) {\r\n const [comments, setComments] = useState<Comment[]>([]);\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const mountedRef = useRef(true);\r\n\r\n useEffect(() => {\r\n return () => {\r\n mountedRef.current = false;\r\n };\r\n }, []);\r\n\r\n const fetchComments = useCallback(async () => {\r\n if (!slug || shortId == null) return;\r\n\r\n setLoading(true);\r\n setError(null);\r\n\r\n const res = await Sayr.org.comments.list(\r\n slug,\r\n shortId,\r\n );\r\n\r\n if (!mountedRef.current) return;\r\n\r\n if (!res.success) {\r\n setError(res.error);\r\n setComments([]);\r\n } else {\r\n setComments(res.data?.items ?? []);\r\n }\r\n\r\n setLoading(false);\r\n }, [slug, shortId]);\r\n\r\n useEffect(() => {\r\n fetchComments();\r\n }, [fetchComments]);\r\n\r\n useSayrWS(wsUrl, {\r\n [Sayr.WS_EVENTS.UPDATE_TASK_COMMENTS]: fetchComments,\r\n });\r\n\r\n return {\r\n comments,\r\n loading,\r\n error,\r\n refetch: fetchComments,\r\n };\r\n}"],"mappings":"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","names":["org_default","org_default","import_react","import_react","import_react","import_react"]}
|
|
1
|
+
{"version":3,"sources":["../../src/react/index.ts","../../src/react/useOrg.ts","../../src/client/index.ts","../../src/api/v1/org/org.ts","../../src/shared/index.ts","../../src/api/v1/org/tasks.ts","../../src/api/v1/org/comments.ts","../../src/api/v1/org/labels.ts","../../src/api/v1/org/categories.ts","../../src/api/v1/org/index.ts","../../src/api/v1/me/index.ts","../../src/api/v1/index.ts","../../src/events/index.ts","../../src/events/types.ts","../../src/index.ts","../../src/react/useTasks.ts","../../src/react/useSayrSSE.ts","../../src/react/useTask.ts","../../src/react/useComments.ts"],"sourcesContent":["export { useOrg } from \"./useOrg\";\r\nexport { useTasks } from \"./useTasks\";\r\nexport { useTask } from \"./useTask\";\r\nexport { useComments } from \"./useComments\";\r\nexport { useSayrSSE } from \"./useSayrSSE\";\r\n// export { useSayrWS } from \"./useSayrWS\";","import { useEffect, useRef, useState } from \"react\";\r\nimport Sayr, { Organization } from \"../index\";\r\n\r\nexport function useOrg(slug?: string) {\r\n const [data, setData] = useState<Organization | null>(null);\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const mountedRef = useRef(true);\r\n\r\n useEffect(() => {\r\n return () => {\r\n mountedRef.current = false;\r\n };\r\n }, []);\r\n\r\n useEffect(() => {\r\n if (!slug) return;\r\n\r\n setLoading(true);\r\n setError(null);\r\n\r\n Sayr.org.get(slug).then((res) => {\r\n if (!mountedRef.current) return;\r\n\r\n if (!res.success) {\r\n setError(res.error);\r\n setData(null);\r\n } else {\r\n setData(res.data);\r\n }\r\n\r\n setLoading(false);\r\n });\r\n }, [slug]);\r\n\r\n return {\r\n data,\r\n loading,\r\n error,\r\n };\r\n}","import { ApiError } from \"../types\";\r\n\r\n/* ────────────────────────────\r\n Types\r\n──────────────────────────── */\r\nexport type RequestOptions = {\r\n method?: \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\r\n headers?: Record<string, string>;\r\n body?: Record<string, string>;\r\n signal?: AbortSignal;\r\n};\r\n\r\nexport interface ApiResult<T> {\r\n success: boolean;\r\n data: T | null;\r\n error: string | null;\r\n}\r\n\r\ntype Hooks = {\r\n onRequest?: (url: string, opts: RequestOptions) => void;\r\n onResponse?: (res: Response) => void;\r\n onError?: (error: ApiError | unknown) => void;\r\n};\r\n\r\ntype ClientConfig = {\r\n token?: string;\r\n headers?: Record<string, string>;\r\n baseUrl: string;\r\n};\r\n\r\n/* ────────────────────────────\r\n Internal state\r\n──────────────────────────── */\r\nconst DEFAULT_API = \"https://api.sayr.io\";\r\n\r\nconst config: ClientConfig = {\r\n baseUrl: DEFAULT_API\r\n};\r\n\r\nconst hooks: Hooks = {};\r\n\r\n/* ────────────────────────────\r\n Public config API\r\n──────────────────────────── */\r\nexport function setToken(token?: string) {\r\n config.token = token;\r\n}\r\n\r\nexport function getToken() {\r\n return config.token;\r\n}\r\n\r\nexport function setHeaders(headers?: Record<string, string>) {\r\n config.headers = {\r\n ...config.headers,\r\n ...headers\r\n };\r\n}\r\n\r\nexport function setBaseUrl(url: string) {\r\n config.baseUrl = url.replace(/\\/$/, \"\");\r\n}\r\n\r\nexport function setHooks(h: Hooks) {\r\n Object.assign(hooks, h);\r\n}\r\nexport function resetClient() {\r\n config.token = undefined;\r\n config.headers = undefined;\r\n config.baseUrl = DEFAULT_API;\r\n}\r\n\r\n/* ────────────────────────────\r\n Request helper\r\n──────────────────────────── */\r\nexport async function request<T>(\r\n path: string,\r\n opts: RequestOptions = {}\r\n): Promise<T> {\r\n\r\n let url: string;\r\n try {\r\n url = path.startsWith(\"http\")\r\n ? path\r\n : `${config.baseUrl}${path}`;\r\n } catch (err) {\r\n throw err;\r\n }\r\n\r\n try {\r\n hooks.onRequest?.(url, opts);\r\n } catch (err) {\r\n throw err;\r\n }\r\n\r\n let res: Response;\r\n\r\n try {\r\n const method = opts.method ?? \"GET\";\r\n\r\n\r\n // 🔴 Guard: GET must not have body\r\n if (method === \"GET\" && opts.body !== undefined) {\r\n throw new Error(\"GET request cannot have a body\");\r\n }\r\n\r\n res = await fetch(url, {\r\n method,\r\n headers: {\r\n ...(config.token\r\n ? { Authorization: `Bearer ${config.token}` }\r\n : {}),\r\n ...(opts.body && method !== \"GET\"\r\n ? { \"Content-Type\": \"application/json\" }\r\n : {}),\r\n ...config.headers,\r\n ...opts.headers\r\n },\r\n body:\r\n opts.body && method !== \"GET\"\r\n ? JSON.stringify(opts.body)\r\n : undefined,\r\n signal: opts.signal\r\n });\r\n } catch (err) {\r\n const error: ApiError = {\r\n success: false,\r\n error: \"NETWORK_ERROR\",\r\n message: \"Failed to reach Sayr API\",\r\n // 👇 keep raw error so we can see it\r\n // @ts-ignore\r\n raw: err\r\n };\r\n\r\n hooks.onError?.(error);\r\n throw error;\r\n }\r\n\r\n try {\r\n hooks.onResponse?.(res);\r\n } catch (err) {\r\n throw err;\r\n }\r\n\r\n let json: any;\r\n try {\r\n json = await res.json();\r\n } catch (err) {\r\n const error: ApiError = {\r\n success: false,\r\n error: \"INVALID_RESPONSE\",\r\n message: \"Server returned invalid JSON\",\r\n status: res.status\r\n };\r\n hooks.onError?.(error);\r\n throw error;\r\n }\r\n if (!res.ok || !json.success) {\r\n const error: ApiError = {\r\n ...json,\r\n success: false,\r\n status: res.status\r\n };\r\n hooks.onError?.(error);\r\n throw error;\r\n }\r\n return json;\r\n}","import { Organization, ApiSuccess } from \"../../../types\";\r\nimport { ApiResult, request, type RequestOptions } from \"../../../client\";\r\n\r\n/**\r\n * Organization core operations.\r\n */\r\nexport default {\r\n /**\r\n * Fetches a public organization by slug.\r\n *\r\n * @since v1.0.0\r\n */\r\n async get(\r\n slug: string,\r\n opts?: RequestOptions,\r\n ): Promise<ApiResult<Organization>> {\r\n try {\r\n const r = await request<ApiSuccess<Organization>>(\r\n `/v1/organization/${slug}`,\r\n opts,\r\n );\r\n\r\n return {\r\n success: true,\r\n data: r.data,\r\n error: null,\r\n };\r\n } catch (err: any) {\r\n return {\r\n success: false,\r\n data: null,\r\n error: err?.message ?? \"Failed to fetch organization\",\r\n };\r\n }\r\n },\r\n};","/* =======================\r\n * Shared params & helpers\r\n * ======================= */\r\n\r\nexport type Order = \"asc\" | \"desc\";\r\n\r\nexport interface PaginationParams {\r\n page?: number;\r\n limit?: number;\r\n}\r\n\r\nexport interface OrderedPaginationParams extends PaginationParams {\r\n order?: Order;\r\n}\r\n\r\nexport function buildPaginationParams(\r\n params?: OrderedPaginationParams\r\n): URLSearchParams {\r\n return new URLSearchParams({\r\n order: params?.order ?? \"desc\",\r\n limit: String(params?.limit ?? 5),\r\n page: String(params?.page ?? 1)\r\n });\r\n}","import {\r\n Task,\r\n Pagination,\r\n ApiSuccess,\r\n} from \"../../../types\";\r\nimport {\r\n ApiResult,\r\n request,\r\n type RequestOptions,\r\n} from \"../../../client\";\r\nimport {\r\n type OrderedPaginationParams,\r\n buildPaginationParams,\r\n} from \"../../../shared\";\r\n\r\n/**\r\n * Organization tasks.\r\n */\r\nexport default {\r\n /**\r\n * Lists public tasks for an organization.\r\n *\r\n * @since v1.0.0\r\n */\r\n async list(\r\n slug: string,\r\n params?: OrderedPaginationParams,\r\n opts?: RequestOptions,\r\n ): Promise<\r\n ApiResult<{\r\n items: Task[];\r\n pagination: Pagination;\r\n }>\r\n > {\r\n try {\r\n const q = buildPaginationParams(params);\r\n\r\n const r = await request<\r\n ApiSuccess<Task[]> & { pagination: Pagination }\r\n >(\r\n `/v1/organization/${slug}/tasks?${q}`,\r\n opts,\r\n );\r\n\r\n return {\r\n success: true,\r\n data: {\r\n items: r.data,\r\n pagination: r.pagination,\r\n },\r\n error: null,\r\n };\r\n } catch (err: any) {\r\n return {\r\n success: false,\r\n data: null,\r\n error:\r\n err?.message ??\r\n \"Failed to fetch tasks\",\r\n };\r\n }\r\n },\r\n\r\n /**\r\n * Fetches a single public task by short ID.\r\n *\r\n * @since v1.0.0\r\n */\r\n async get(\r\n slug: string,\r\n shortId: number,\r\n opts?: RequestOptions,\r\n ): Promise<ApiResult<Task>> {\r\n try {\r\n const r = await request<ApiSuccess<Task>>(\r\n `/v1/organization/${slug}/tasks/${shortId}`,\r\n opts,\r\n );\r\n\r\n return {\r\n success: true,\r\n data: r.data,\r\n error: null,\r\n };\r\n } catch (err: any) {\r\n return {\r\n success: false,\r\n data: null,\r\n error:\r\n err?.message ??\r\n \"Failed to fetch task\",\r\n };\r\n }\r\n },\r\n};","import {\r\n Comment,\r\n Pagination,\r\n ApiSuccess,\r\n} from \"../../../types\";\r\nimport {\r\n ApiResult,\r\n request,\r\n type RequestOptions,\r\n} from \"../../../client\";\r\nimport {\r\n type OrderedPaginationParams,\r\n buildPaginationParams,\r\n} from \"../../../shared\";\r\n\r\n/**\r\n * Organization task comments.\r\n */\r\nexport default {\r\n /**\r\n * Lists public comments for a task.\r\n *\r\n * @since v1.0.0\r\n */\r\n async list(\r\n slug: string,\r\n shortId: number,\r\n params?: OrderedPaginationParams,\r\n opts?: RequestOptions,\r\n ): Promise<\r\n ApiResult<{\r\n items: Comment[];\r\n pagination: Pagination;\r\n }>\r\n > {\r\n try {\r\n const q = buildPaginationParams(params);\r\n\r\n const r = await request<\r\n ApiSuccess<Comment[]> & { pagination: Pagination }\r\n >(\r\n `/v1/organization/${slug}/tasks/${shortId}/comments?${q}`,\r\n opts,\r\n );\r\n\r\n return {\r\n success: true,\r\n data: {\r\n items: r.data,\r\n pagination: r.pagination,\r\n },\r\n error: null,\r\n };\r\n } catch (err: any) {\r\n return {\r\n success: false,\r\n data: null,\r\n error:\r\n err?.message ??\r\n \"Failed to fetch task comments\",\r\n };\r\n }\r\n },\r\n};","import { Label, ApiSuccess } from \"../../../types\";\r\nimport { ApiResult, request, type RequestOptions } from \"../../../client\";\r\n\r\n/**\r\n * Organization labels.\r\n */\r\nexport default {\r\n /**\r\n * Lists public labels for an organization.\r\n *\r\n * @since v1.0.0\r\n */\r\n async list(\r\n slug: string,\r\n opts?: RequestOptions,\r\n ): Promise<ApiResult<Label[]>> {\r\n try {\r\n const r = await request<ApiSuccess<Label[]>>(\r\n `/v1/organization/${slug}/labels`,\r\n opts,\r\n );\r\n\r\n return {\r\n success: true,\r\n data: r.data,\r\n error: null,\r\n };\r\n } catch (err: any) {\r\n return {\r\n success: false,\r\n data: null,\r\n error: err?.message ?? \"Failed to fetch labels\",\r\n };\r\n }\r\n },\r\n};","import {\r\n Category,\r\n ApiSuccess,\r\n} from \"../../../types\";\r\nimport {\r\n ApiResult,\r\n request,\r\n type RequestOptions,\r\n} from \"../../../client\";\r\nimport { type Order } from \"../../../shared\";\r\n\r\n/**\r\n * Organization categories.\r\n */\r\nexport default {\r\n /**\r\n * Lists public categories for an organization.\r\n *\r\n * @since v1.0.0\r\n */\r\n async list(\r\n slug: string,\r\n order: Order = \"desc\",\r\n opts?: RequestOptions,\r\n ): Promise<ApiResult<Category[]>> {\r\n try {\r\n const r = await request<ApiSuccess<Category[]>>(\r\n `/v1/organization/${slug}/categories?order=${order}`,\r\n opts,\r\n );\r\n\r\n return {\r\n success: true,\r\n data: r.data,\r\n error: null,\r\n };\r\n } catch (err: any) {\r\n return {\r\n success: false,\r\n data: null,\r\n error:\r\n err?.message ??\r\n \"Failed to fetch categories\",\r\n };\r\n }\r\n },\r\n};","import org from \"./org\";\r\nimport tasks from \"./tasks\";\r\nimport comments from \"./comments\";\r\nimport labels from \"./labels\";\r\nimport categories from \"./categories\";\r\n\r\n/**\r\n * Public Sayr Organization API — Version 1.\r\n *\r\n * @since v1.0.0\r\n */\r\nconst OrgAPI = {\r\n ...org,\r\n tasks,\r\n comments,\r\n labels,\r\n categories\r\n};\r\n\r\nexport default OrgAPI;","import { ApiResult, request, type RequestOptions } from \"../../../client\";\r\nimport { ApiSuccess, Organization } from \"../../../types\";\r\n\r\nexport interface Me {\r\n id: string;\r\n name: string | null;\r\n email: string | null;\r\n image: string | null;\r\n createdAt: string;\r\n}\r\n\r\n/**\r\n * Authenticated user API — Version 1.\r\n *\r\n * @since v1.0.0\r\n */\r\nexport default {\r\n async get(opts?: RequestOptions): Promise<ApiResult<Me>> {\r\n try {\r\n const r = await request<ApiSuccess<Me>>(\"/v1/me\", opts);\r\n return {\r\n success: true,\r\n data: r.data,\r\n error: null,\r\n };\r\n } catch (err: any) {\r\n return {\r\n success: false,\r\n data: null,\r\n error: err?.message ?? \"Failed to fetch user\",\r\n };\r\n }\r\n },\r\n\r\n async organizations(\r\n opts?: RequestOptions,\r\n ): Promise<ApiResult<Organization[]>> {\r\n try {\r\n const r = await request<ApiSuccess<Organization[]>>(\r\n \"/v1/me/organizations\",\r\n opts,\r\n );\r\n return {\r\n success: true,\r\n data: r.data,\r\n error: null,\r\n };\r\n } catch (err: any) {\r\n return {\r\n success: false,\r\n data: null,\r\n error: err?.message ?? \"Failed to fetch organizations\",\r\n };\r\n }\r\n },\r\n};","import org from \"./org\";\r\nimport me from \"./me\";\r\nexport const v1 = {\r\n org,\r\n me\r\n};\r\n\r\nexport default v1;","import { type ServerEventMessage, type ServerEventMessageType } from \"./types\";\r\n\r\ntype Handlers = Partial<\r\n Record<ServerEventMessageType, (data: any, msg: ServerEventMessage) => void>\r\n>;\r\n\r\nexport function sse(url: string, handlers: Handlers = {}) {\r\n if (!url) {\r\n throw new Error(\r\n \"[Sayr.sse] SSE URL is required. Did you forget to pass org.eventsUrl?\"\r\n );\r\n }\r\n\r\n let source: EventSource | null = null;\r\n let closed = false;\r\n\r\n function connect() {\r\n if (closed) return;\r\n\r\n source = new EventSource(url);\r\n\r\n source.onmessage = (e) => {\r\n const msg = JSON.parse(e.data) as ServerEventMessage;\r\n handlers[msg.type]?.(msg.data, msg);\r\n };\r\n\r\n source.onerror = () => {\r\n if (closed) return;\r\n source?.close();\r\n setTimeout(connect, 2000); // simple retry\r\n };\r\n }\r\n\r\n connect();\r\n\r\n return {\r\n close() {\r\n closed = true;\r\n source?.close();\r\n }\r\n };\r\n}","export type ServerEventMessageType =\r\n | \"CONNECTION_STATUS\"\r\n | \"ERROR\"\r\n | \"UPDATE_ORG\"\r\n | \"CREATE_TASK\"\r\n | \"UPDATE_TASK\"\r\n | \"UPDATE_TASK_COMMENTS\"\r\n | \"UPDATE_TASK_VOTE\"\r\n | \"UPDATE_LABELS\"\r\n | \"UPDATE_VIEWS\"\r\n | \"UPDATE_CATEGORIES\"\r\n | \"UPDATE_ISSUE_TEMPLATES\";\r\n\r\n/**\r\n * String enum replacement for WS event names.\r\n * Use this instead of raw strings.\r\n */\r\nexport const ServerEvent_EVENTS: Record<ServerEventMessageType, ServerEventMessageType> = {\r\n CONNECTION_STATUS: \"CONNECTION_STATUS\",\r\n ERROR: \"ERROR\",\r\n UPDATE_ORG: \"UPDATE_ORG\",\r\n CREATE_TASK: \"CREATE_TASK\",\r\n UPDATE_TASK: \"UPDATE_TASK\",\r\n UPDATE_TASK_COMMENTS: \"UPDATE_TASK_COMMENTS\",\r\n UPDATE_TASK_VOTE: \"UPDATE_TASK_VOTE\",\r\n UPDATE_LABELS: \"UPDATE_LABELS\",\r\n UPDATE_VIEWS: \"UPDATE_VIEWS\",\r\n UPDATE_CATEGORIES: \"UPDATE_CATEGORIES\",\r\n UPDATE_ISSUE_TEMPLATES: \"UPDATE_ISSUE_TEMPLATES\",\r\n};\r\n\r\nexport interface ServerEventMessage<T = unknown> {\r\n type: ServerEventMessageType;\r\n scope: \"PUBLIC\";\r\n data: T;\r\n meta?: { ts: number };\r\n}","/* ────────────────────────────\r\n API versions\r\n──────────────────────────── */\r\nimport v1 from \"./api/v1\";\r\n\r\n/* ────────────────────────────\r\n Realtime\r\n──────────────────────────── */\r\nimport { ws } from \"./ws\";\r\nimport { WS_EVENTS } from \"./ws/types\";\r\nimport { sse } from \"./events\";\r\nimport { ServerEvent_EVENTS } from \"./events/types\";\r\n\r\n/* ────────────────────────────\r\n Client config\r\n──────────────────────────── */\r\nimport {\r\n setToken,\r\n setHeaders,\r\n setBaseUrl,\r\n resetClient,\r\n setHooks,\r\n} from \"./client\";\r\n\r\n/* ────────────────────────────\r\n Named exports (power users)\r\n──────────────────────────── */\r\n\r\n/**\r\n * Sayr Public API — Version 1.\r\n *\r\n * @since v1.0.0\r\n */\r\nexport const SayrV1 = v1;\r\n\r\n\r\n/**\r\n * Create a WebSocket connection for public real‑time updates.\r\n */\r\nexport const SayrSSE = sse;\r\n\r\n/**\r\n * Typed WebSocket event constants.\r\n */\r\nexport const SayrServerEvents = ServerEvent_EVENTS;\r\n\r\n/**\r\n * Global client configuration helpers.\r\n */\r\nexport const SayrClient = {\r\n setToken,\r\n setHeaders,\r\n setBaseUrl,\r\n resetClient,\r\n setHooks,\r\n};\r\n\r\n/* ────────────────────────────\r\n Default facade\r\n──────────────────────────── */\r\n\r\n/**\r\n * Sayr Public SDK.\r\n *\r\n * Read‑only access to public Sayr data via REST and WebSockets.\r\n *\r\n * @since v1.0.0\r\n */\r\nconst Sayr: {\r\n /**\r\n * Client configuration helpers.\r\n */\r\n client: typeof SayrClient;\r\n\r\n /**\r\n * Versioned API namespaces.\r\n */\r\n v1: typeof v1;\r\n\r\n /**\r\n * Alias for the current API version (`v1`).\r\n *\r\n * @since v1.0.0\r\n */\r\n org: typeof v1.org;\r\n me: typeof v1.me;\r\n\r\n /**\r\n * WebSocket helper for real‑time updates.\r\n */\r\n sse: typeof sse;\r\n\r\n /**\r\n * WebSocket event constants.\r\n */\r\n EVENTS: typeof ServerEvent_EVENTS;\r\n} = {\r\n // client configuration\r\n client: SayrClient,\r\n\r\n // APIs\r\n v1,\r\n org: v1.org,\r\n me: v1.me,\r\n\r\n // realtime\r\n sse,\r\n EVENTS: ServerEvent_EVENTS\r\n};\r\n\r\nexport default Sayr;\r\n\r\n/* ────────────────────────────\r\n Types & shared helpers\r\n──────────────────────────── */\r\nexport * from \"./types\";\r\nexport * from \"./shared\";\r\n// export * from \"./ws/types\";\r\nexport * from \"./events/types\";","import { useCallback, useEffect, useRef, useState } from \"react\";\r\nimport type { Task } from \"../types\";\r\nimport { useSayrSSE } from \"./useSayrSSE\";\r\nimport Sayr from \"..\";\r\n\r\nexport function useTasks(slug?: string, eventsUrl?: string) {\r\n const [tasks, setTasks] = useState<Task[]>([]);\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const mountedRef = useRef(true);\r\n\r\n useEffect(() => {\r\n return () => {\r\n mountedRef.current = false;\r\n };\r\n }, []);\r\n\r\n const fetchTasks = useCallback(async () => {\r\n if (!slug) return;\r\n\r\n setLoading(true);\r\n setError(null);\r\n\r\n const res = await Sayr.org.tasks.list(slug);\r\n\r\n if (!mountedRef.current) return;\r\n\r\n if (!res.success) {\r\n setError(res.error);\r\n setTasks([]);\r\n } else {\r\n setTasks(res.data?.items ?? []);\r\n }\r\n\r\n setLoading(false);\r\n }, [slug]);\r\n\r\n useEffect(() => {\r\n fetchTasks();\r\n }, [fetchTasks]);\r\n\r\n // SSE instead of WebSockets\r\n useSayrSSE(eventsUrl, {\r\n [Sayr.EVENTS.CREATE_TASK]: fetchTasks,\r\n [Sayr.EVENTS.UPDATE_TASK]: fetchTasks\r\n });\r\n\r\n return {\r\n tasks,\r\n loading,\r\n error,\r\n refetch: fetchTasks\r\n };\r\n}","import { useEffect, useRef } from \"react\";\r\nimport { ServerEventMessageType } from \"../events/types\";\r\nimport { sse } from \"../events\";\r\nimport type { ServerEventMessage } from \"../events/types\";\r\n\r\ntype Handler = (data: any, msg: ServerEventMessage) => void;\r\n\r\ntype Handlers = Partial<Record<ServerEventMessageType, Handler>>;\r\n\r\nexport function useSayrSSE(eventsUrl?: string, handlers?: Handlers) {\r\n const connRef = useRef<ReturnType<typeof sse> | null>(null);\r\n const handlersRef = useRef<Handlers | undefined>(handlers);\r\n\r\n // keep latest handlers\r\n useEffect(() => {\r\n handlersRef.current = handlers;\r\n }, [handlers]);\r\n\r\n useEffect(() => {\r\n if (!eventsUrl) return;\r\n\r\n // proxy: stable keys, dynamic handler resolution\r\n const proxyHandlers: Handlers = new Proxy(\r\n {},\r\n {\r\n get: (_, type: string) => {\r\n return (data: any, msg: ServerEventMessage) => {\r\n const handler =\r\n handlersRef.current?.[type as ServerEventMessageType];\r\n if (handler) {\r\n handler(data, msg);\r\n }\r\n };\r\n }\r\n }\r\n );\r\n\r\n connRef.current = sse(eventsUrl, proxyHandlers);\r\n\r\n return () => {\r\n connRef.current?.close();\r\n connRef.current = null;\r\n };\r\n }, [eventsUrl]);\r\n\r\n return connRef;\r\n}","import { useEffect, useRef, useState } from \"react\";\r\nimport type { Task } from \"../types\";\r\nimport Sayr from \"..\";\r\n\r\nexport function useTask(\r\n slug?: string,\r\n shortId?: number,\r\n) {\r\n const [task, setTask] = useState<Task | null>(null);\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const mountedRef = useRef(true);\r\n\r\n useEffect(() => {\r\n return () => {\r\n mountedRef.current = false;\r\n };\r\n }, []);\r\n\r\n useEffect(() => {\r\n if (!slug || shortId == null) return;\r\n\r\n setLoading(true);\r\n setError(null);\r\n\r\n Sayr.org.tasks.get(slug, shortId).then((res) => {\r\n if (!mountedRef.current) return;\r\n\r\n if (!res.success) {\r\n setError(res.error);\r\n setTask(null);\r\n } else {\r\n setTask(res.data);\r\n }\r\n\r\n setLoading(false);\r\n });\r\n }, [slug, shortId]);\r\n\r\n return {\r\n task,\r\n loading,\r\n error,\r\n };\r\n}","import { useCallback, useEffect, useRef, useState } from \"react\";\r\nimport type { Comment } from \"../types\";\r\nimport { useSayrSSE } from \"./useSayrSSE\";\r\nimport Sayr from \"..\";\r\n\r\nexport function useComments(\r\n slug?: string,\r\n shortId?: number,\r\n eventsUrl?: string\r\n) {\r\n const [comments, setComments] = useState<Comment[]>([]);\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const mountedRef = useRef(true);\r\n\r\n useEffect(() => {\r\n return () => {\r\n mountedRef.current = false;\r\n };\r\n }, []);\r\n\r\n const fetchComments = useCallback(async () => {\r\n if (!slug || shortId == null) return;\r\n\r\n setLoading(true);\r\n setError(null);\r\n\r\n const res = await Sayr.org.comments.list(slug, shortId);\r\n\r\n if (!mountedRef.current) return;\r\n\r\n if (!res.success) {\r\n setError(res.error);\r\n setComments([]);\r\n } else {\r\n setComments(res.data?.items ?? []);\r\n }\r\n\r\n setLoading(false);\r\n }, [slug, shortId]);\r\n\r\n useEffect(() => {\r\n fetchComments();\r\n }, [fetchComments]);\r\n\r\n // SSE instead of WebSockets\r\n useSayrSSE(eventsUrl, {\r\n [Sayr.EVENTS.UPDATE_TASK_COMMENTS]: fetchComments\r\n });\r\n\r\n return {\r\n comments,\r\n loading,\r\n error,\r\n refetch: fetchComments\r\n };\r\n}"],"mappings":"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","names":["org_default","org_default","import_react","import_react","import_react","import_react"]}
|
package/dist/react/index.d.cts
CHANGED
|
@@ -23,7 +23,7 @@ interface Organization {
|
|
|
23
23
|
createdAt: string;
|
|
24
24
|
updatedAt: string;
|
|
25
25
|
members: OrganizationMember[];
|
|
26
|
-
|
|
26
|
+
eventsUrl: string;
|
|
27
27
|
}
|
|
28
28
|
type TaskStatus = "backlog" | "todo" | "in-progress" | "done" | "canceled";
|
|
29
29
|
type TaskPriority = "none" | "low" | "medium" | "high" | "urgent";
|
|
@@ -69,9 +69,9 @@ interface Comment {
|
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
type
|
|
73
|
-
interface
|
|
74
|
-
type:
|
|
72
|
+
type ServerEventMessageType = "CONNECTION_STATUS" | "ERROR" | "UPDATE_ORG" | "CREATE_TASK" | "UPDATE_TASK" | "UPDATE_TASK_COMMENTS" | "UPDATE_TASK_VOTE" | "UPDATE_LABELS" | "UPDATE_VIEWS" | "UPDATE_CATEGORIES" | "UPDATE_ISSUE_TEMPLATES";
|
|
73
|
+
interface ServerEventMessage<T = unknown> {
|
|
74
|
+
type: ServerEventMessageType;
|
|
75
75
|
scope: "PUBLIC";
|
|
76
76
|
data: T;
|
|
77
77
|
meta?: {
|
|
@@ -85,7 +85,7 @@ declare function useOrg(slug?: string): {
|
|
|
85
85
|
error: string | null;
|
|
86
86
|
};
|
|
87
87
|
|
|
88
|
-
declare function useTasks(slug?: string,
|
|
88
|
+
declare function useTasks(slug?: string, eventsUrl?: string): {
|
|
89
89
|
tasks: Task[];
|
|
90
90
|
loading: boolean;
|
|
91
91
|
error: string | null;
|
|
@@ -98,17 +98,17 @@ declare function useTask(slug?: string, shortId?: number): {
|
|
|
98
98
|
error: string | null;
|
|
99
99
|
};
|
|
100
100
|
|
|
101
|
-
declare function useComments(slug?: string, shortId?: number,
|
|
101
|
+
declare function useComments(slug?: string, shortId?: number, eventsUrl?: string): {
|
|
102
102
|
comments: Comment[];
|
|
103
103
|
loading: boolean;
|
|
104
104
|
error: string | null;
|
|
105
105
|
refetch: () => Promise<void>;
|
|
106
106
|
};
|
|
107
107
|
|
|
108
|
-
type Handler = (data: any, msg:
|
|
109
|
-
type Handlers = Partial<Record<
|
|
110
|
-
declare function
|
|
108
|
+
type Handler = (data: any, msg: ServerEventMessage) => void;
|
|
109
|
+
type Handlers = Partial<Record<ServerEventMessageType, Handler>>;
|
|
110
|
+
declare function useSayrSSE(eventsUrl?: string, handlers?: Handlers): react.MutableRefObject<{
|
|
111
111
|
close(): void;
|
|
112
112
|
} | null>;
|
|
113
113
|
|
|
114
|
-
export { useComments, useOrg,
|
|
114
|
+
export { useComments, useOrg, useSayrSSE, useTask, useTasks };
|
package/dist/react/index.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ interface Organization {
|
|
|
23
23
|
createdAt: string;
|
|
24
24
|
updatedAt: string;
|
|
25
25
|
members: OrganizationMember[];
|
|
26
|
-
|
|
26
|
+
eventsUrl: string;
|
|
27
27
|
}
|
|
28
28
|
type TaskStatus = "backlog" | "todo" | "in-progress" | "done" | "canceled";
|
|
29
29
|
type TaskPriority = "none" | "low" | "medium" | "high" | "urgent";
|
|
@@ -69,9 +69,9 @@ interface Comment {
|
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
type
|
|
73
|
-
interface
|
|
74
|
-
type:
|
|
72
|
+
type ServerEventMessageType = "CONNECTION_STATUS" | "ERROR" | "UPDATE_ORG" | "CREATE_TASK" | "UPDATE_TASK" | "UPDATE_TASK_COMMENTS" | "UPDATE_TASK_VOTE" | "UPDATE_LABELS" | "UPDATE_VIEWS" | "UPDATE_CATEGORIES" | "UPDATE_ISSUE_TEMPLATES";
|
|
73
|
+
interface ServerEventMessage<T = unknown> {
|
|
74
|
+
type: ServerEventMessageType;
|
|
75
75
|
scope: "PUBLIC";
|
|
76
76
|
data: T;
|
|
77
77
|
meta?: {
|
|
@@ -85,7 +85,7 @@ declare function useOrg(slug?: string): {
|
|
|
85
85
|
error: string | null;
|
|
86
86
|
};
|
|
87
87
|
|
|
88
|
-
declare function useTasks(slug?: string,
|
|
88
|
+
declare function useTasks(slug?: string, eventsUrl?: string): {
|
|
89
89
|
tasks: Task[];
|
|
90
90
|
loading: boolean;
|
|
91
91
|
error: string | null;
|
|
@@ -98,17 +98,17 @@ declare function useTask(slug?: string, shortId?: number): {
|
|
|
98
98
|
error: string | null;
|
|
99
99
|
};
|
|
100
100
|
|
|
101
|
-
declare function useComments(slug?: string, shortId?: number,
|
|
101
|
+
declare function useComments(slug?: string, shortId?: number, eventsUrl?: string): {
|
|
102
102
|
comments: Comment[];
|
|
103
103
|
loading: boolean;
|
|
104
104
|
error: string | null;
|
|
105
105
|
refetch: () => Promise<void>;
|
|
106
106
|
};
|
|
107
107
|
|
|
108
|
-
type Handler = (data: any, msg:
|
|
109
|
-
type Handlers = Partial<Record<
|
|
110
|
-
declare function
|
|
108
|
+
type Handler = (data: any, msg: ServerEventMessage) => void;
|
|
109
|
+
type Handlers = Partial<Record<ServerEventMessageType, Handler>>;
|
|
110
|
+
declare function useSayrSSE(eventsUrl?: string, handlers?: Handlers): react.MutableRefObject<{
|
|
111
111
|
close(): void;
|
|
112
112
|
} | null>;
|
|
113
113
|
|
|
114
|
-
export { useComments, useOrg,
|
|
114
|
+
export { useComments, useOrg, useSayrSSE, useTask, useTasks };
|
package/dist/react/index.js
CHANGED
|
@@ -335,61 +335,52 @@ var v1 = {
|
|
|
335
335
|
};
|
|
336
336
|
var v1_default = v1;
|
|
337
337
|
|
|
338
|
-
// src/
|
|
339
|
-
|
|
340
|
-
CONNECTION_STATUS: "CONNECTION_STATUS",
|
|
341
|
-
SUBSCRIBED: "SUBSCRIBED",
|
|
342
|
-
ERROR: "ERROR",
|
|
343
|
-
PING: "PING",
|
|
344
|
-
PONG: "PONG",
|
|
345
|
-
UPDATE_ORG: "UPDATE_ORG",
|
|
346
|
-
CREATE_TASK: "CREATE_TASK",
|
|
347
|
-
UPDATE_TASK: "UPDATE_TASK",
|
|
348
|
-
UPDATE_TASK_COMMENTS: "UPDATE_TASK_COMMENTS",
|
|
349
|
-
UPDATE_TASK_VOTE: "UPDATE_TASK_VOTE",
|
|
350
|
-
UPDATE_LABELS: "UPDATE_LABELS",
|
|
351
|
-
UPDATE_VIEWS: "UPDATE_VIEWS",
|
|
352
|
-
UPDATE_CATEGORIES: "UPDATE_CATEGORIES",
|
|
353
|
-
UPDATE_ISSUE_TEMPLATES: "UPDATE_ISSUE_TEMPLATES",
|
|
354
|
-
DISCONNECTED: "DISCONNECTED"
|
|
355
|
-
};
|
|
356
|
-
|
|
357
|
-
// src/ws/index.ts
|
|
358
|
-
function ws(url, handlers = {}) {
|
|
338
|
+
// src/events/index.ts
|
|
339
|
+
function sse(url, handlers = {}) {
|
|
359
340
|
if (!url) {
|
|
360
341
|
throw new Error(
|
|
361
|
-
"[Sayr.
|
|
342
|
+
"[Sayr.sse] SSE URL is required. Did you forget to pass org.eventsUrl?"
|
|
362
343
|
);
|
|
363
344
|
}
|
|
364
|
-
let
|
|
365
|
-
let retry = 0;
|
|
345
|
+
let source = null;
|
|
366
346
|
let closed = false;
|
|
367
347
|
function connect() {
|
|
368
348
|
if (closed) return;
|
|
369
|
-
|
|
370
|
-
|
|
349
|
+
source = new EventSource(url);
|
|
350
|
+
source.onmessage = (e) => {
|
|
371
351
|
const msg = JSON.parse(e.data);
|
|
372
|
-
if (msg.type === WS_EVENTS.PING) {
|
|
373
|
-
socket.send(JSON.stringify({ type: WS_EVENTS.PONG }));
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
352
|
handlers[msg.type]?.(msg.data, msg);
|
|
377
353
|
};
|
|
378
|
-
|
|
354
|
+
source.onerror = () => {
|
|
379
355
|
if (closed) return;
|
|
380
|
-
|
|
356
|
+
source?.close();
|
|
357
|
+
setTimeout(connect, 2e3);
|
|
381
358
|
};
|
|
382
|
-
socket.onerror = () => socket.close();
|
|
383
359
|
}
|
|
384
360
|
connect();
|
|
385
361
|
return {
|
|
386
362
|
close() {
|
|
387
363
|
closed = true;
|
|
388
|
-
|
|
364
|
+
source?.close();
|
|
389
365
|
}
|
|
390
366
|
};
|
|
391
367
|
}
|
|
392
368
|
|
|
369
|
+
// src/events/types.ts
|
|
370
|
+
var ServerEvent_EVENTS = {
|
|
371
|
+
CONNECTION_STATUS: "CONNECTION_STATUS",
|
|
372
|
+
ERROR: "ERROR",
|
|
373
|
+
UPDATE_ORG: "UPDATE_ORG",
|
|
374
|
+
CREATE_TASK: "CREATE_TASK",
|
|
375
|
+
UPDATE_TASK: "UPDATE_TASK",
|
|
376
|
+
UPDATE_TASK_COMMENTS: "UPDATE_TASK_COMMENTS",
|
|
377
|
+
UPDATE_TASK_VOTE: "UPDATE_TASK_VOTE",
|
|
378
|
+
UPDATE_LABELS: "UPDATE_LABELS",
|
|
379
|
+
UPDATE_VIEWS: "UPDATE_VIEWS",
|
|
380
|
+
UPDATE_CATEGORIES: "UPDATE_CATEGORIES",
|
|
381
|
+
UPDATE_ISSUE_TEMPLATES: "UPDATE_ISSUE_TEMPLATES"
|
|
382
|
+
};
|
|
383
|
+
|
|
393
384
|
// src/index.ts
|
|
394
385
|
var SayrClient = {
|
|
395
386
|
setToken,
|
|
@@ -406,8 +397,8 @@ var Sayr = {
|
|
|
406
397
|
org: v1_default.org,
|
|
407
398
|
me: v1_default.me,
|
|
408
399
|
// realtime
|
|
409
|
-
|
|
410
|
-
|
|
400
|
+
sse,
|
|
401
|
+
EVENTS: ServerEvent_EVENTS
|
|
411
402
|
};
|
|
412
403
|
var index_default = Sayr;
|
|
413
404
|
|
|
@@ -447,16 +438,16 @@ function useOrg(slug) {
|
|
|
447
438
|
// src/react/useTasks.ts
|
|
448
439
|
import { useCallback, useEffect as useEffect3, useRef as useRef3, useState as useState2 } from "react";
|
|
449
440
|
|
|
450
|
-
// src/react/
|
|
441
|
+
// src/react/useSayrSSE.ts
|
|
451
442
|
import { useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
452
|
-
function
|
|
443
|
+
function useSayrSSE(eventsUrl, handlers) {
|
|
453
444
|
const connRef = useRef2(null);
|
|
454
445
|
const handlersRef = useRef2(handlers);
|
|
455
446
|
useEffect2(() => {
|
|
456
447
|
handlersRef.current = handlers;
|
|
457
448
|
}, [handlers]);
|
|
458
449
|
useEffect2(() => {
|
|
459
|
-
if (!
|
|
450
|
+
if (!eventsUrl) return;
|
|
460
451
|
const proxyHandlers = new Proxy(
|
|
461
452
|
{},
|
|
462
453
|
{
|
|
@@ -470,17 +461,17 @@ function useSayrWS(wsUrl, handlers) {
|
|
|
470
461
|
}
|
|
471
462
|
}
|
|
472
463
|
);
|
|
473
|
-
connRef.current =
|
|
464
|
+
connRef.current = sse(eventsUrl, proxyHandlers);
|
|
474
465
|
return () => {
|
|
475
466
|
connRef.current?.close();
|
|
476
467
|
connRef.current = null;
|
|
477
468
|
};
|
|
478
|
-
}, [
|
|
469
|
+
}, [eventsUrl]);
|
|
479
470
|
return connRef;
|
|
480
471
|
}
|
|
481
472
|
|
|
482
473
|
// src/react/useTasks.ts
|
|
483
|
-
function useTasks(slug,
|
|
474
|
+
function useTasks(slug, eventsUrl) {
|
|
484
475
|
const [tasks, setTasks] = useState2([]);
|
|
485
476
|
const [loading, setLoading] = useState2(false);
|
|
486
477
|
const [error, setError] = useState2(null);
|
|
@@ -507,9 +498,9 @@ function useTasks(slug, wsUrl) {
|
|
|
507
498
|
useEffect3(() => {
|
|
508
499
|
fetchTasks();
|
|
509
500
|
}, [fetchTasks]);
|
|
510
|
-
|
|
511
|
-
[index_default.
|
|
512
|
-
[index_default.
|
|
501
|
+
useSayrSSE(eventsUrl, {
|
|
502
|
+
[index_default.EVENTS.CREATE_TASK]: fetchTasks,
|
|
503
|
+
[index_default.EVENTS.UPDATE_TASK]: fetchTasks
|
|
513
504
|
});
|
|
514
505
|
return {
|
|
515
506
|
tasks,
|
|
@@ -555,7 +546,7 @@ function useTask(slug, shortId) {
|
|
|
555
546
|
|
|
556
547
|
// src/react/useComments.ts
|
|
557
548
|
import { useCallback as useCallback2, useEffect as useEffect5, useRef as useRef5, useState as useState4 } from "react";
|
|
558
|
-
function useComments(slug, shortId,
|
|
549
|
+
function useComments(slug, shortId, eventsUrl) {
|
|
559
550
|
const [comments, setComments] = useState4([]);
|
|
560
551
|
const [loading, setLoading] = useState4(false);
|
|
561
552
|
const [error, setError] = useState4(null);
|
|
@@ -569,10 +560,7 @@ function useComments(slug, shortId, wsUrl) {
|
|
|
569
560
|
if (!slug || shortId == null) return;
|
|
570
561
|
setLoading(true);
|
|
571
562
|
setError(null);
|
|
572
|
-
const res = await index_default.org.comments.list(
|
|
573
|
-
slug,
|
|
574
|
-
shortId
|
|
575
|
-
);
|
|
563
|
+
const res = await index_default.org.comments.list(slug, shortId);
|
|
576
564
|
if (!mountedRef.current) return;
|
|
577
565
|
if (!res.success) {
|
|
578
566
|
setError(res.error);
|
|
@@ -585,8 +573,8 @@ function useComments(slug, shortId, wsUrl) {
|
|
|
585
573
|
useEffect5(() => {
|
|
586
574
|
fetchComments();
|
|
587
575
|
}, [fetchComments]);
|
|
588
|
-
|
|
589
|
-
[index_default.
|
|
576
|
+
useSayrSSE(eventsUrl, {
|
|
577
|
+
[index_default.EVENTS.UPDATE_TASK_COMMENTS]: fetchComments
|
|
590
578
|
});
|
|
591
579
|
return {
|
|
592
580
|
comments,
|
|
@@ -598,7 +586,7 @@ function useComments(slug, shortId, wsUrl) {
|
|
|
598
586
|
export {
|
|
599
587
|
useComments,
|
|
600
588
|
useOrg,
|
|
601
|
-
|
|
589
|
+
useSayrSSE,
|
|
602
590
|
useTask,
|
|
603
591
|
useTasks
|
|
604
592
|
};
|