@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.
@@ -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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAA4C;;;ACiC5C,IAAM,cAAc;AAEpB,IAAM,SAAuB;AAAA,EACzB,SAAS;AACb;AAEA,IAAM,QAAe,CAAC;AAKf,SAAS,SAAS,OAAgB;AACrC,SAAO,QAAQ;AACnB;AAMO,SAAS,WAAW,SAAkC;AACzD,SAAO,UAAU;AAAA,IACb,GAAG,OAAO;AAAA,IACV,GAAG;AAAA,EACP;AACJ;AAEO,SAAS,WAAW,KAAa;AACpC,SAAO,UAAU,IAAI,QAAQ,OAAO,EAAE;AAC1C;AAEO,SAAS,SAAS,GAAU;AAC/B,SAAO,OAAO,OAAO,CAAC;AAC1B;AACO,SAAS,cAAc;AAC1B,SAAO,QAAQ;AACf,SAAO,UAAU;AACjB,SAAO,UAAU;AACrB;AAKA,eAAsB,QAClB,MACA,OAAuB,CAAC,GACd;AAEV,MAAI;AACJ,MAAI;AACA,UAAM,KAAK,WAAW,MAAM,IACtB,OACA,GAAG,OAAO,OAAO,GAAG,IAAI;AAAA,EAClC,SAAS,KAAK;AACV,UAAM;AAAA,EACV;AAEA,MAAI;AACA,UAAM,YAAY,KAAK,IAAI;AAAA,EAC/B,SAAS,KAAK;AACV,UAAM;AAAA,EACV;AAEA,MAAI;AAEJ,MAAI;AACA,UAAM,SAAS,KAAK,UAAU;AAI9B,QAAI,WAAW,SAAS,KAAK,SAAS,QAAW;AAC7C,YAAM,IAAI,MAAM,gCAAgC;AAAA,IACpD;AAEA,UAAM,MAAM,MAAM,KAAK;AAAA,MACnB;AAAA,MACA,SAAS;AAAA,QACL,GAAI,OAAO,QACL,EAAE,eAAe,UAAU,OAAO,KAAK,GAAG,IAC1C,CAAC;AAAA,QACP,GAAI,KAAK,QAAQ,WAAW,QACtB,EAAE,gBAAgB,mBAAmB,IACrC,CAAC;AAAA,QACP,GAAG,OAAO;AAAA,QACV,GAAG,KAAK;AAAA,MACZ;AAAA,MACA,MACI,KAAK,QAAQ,WAAW,QAClB,KAAK,UAAU,KAAK,IAAI,IACxB;AAAA,MACV,QAAQ,KAAK;AAAA,IACjB,CAAC;AAAA,EACL,SAAS,KAAK;AACV,UAAM,QAAkB;AAAA,MACpB,SAAS;AAAA,MACT,OAAO;AAAA,MACP,SAAS;AAAA;AAAA;AAAA,MAGT,KAAK;AAAA,IACT;AAEA,UAAM,UAAU,KAAK;AACrB,UAAM;AAAA,EACV;AAEA,MAAI;AACA,UAAM,aAAa,GAAG;AAAA,EAC1B,SAAS,KAAK;AACV,UAAM;AAAA,EACV;AAEA,MAAI;AACJ,MAAI;AACA,WAAO,MAAM,IAAI,KAAK;AAAA,EAC1B,SAAS,KAAK;AACV,UAAM,QAAkB;AAAA,MACpB,SAAS;AAAA,MACT,OAAO;AAAA,MACP,SAAS;AAAA,MACT,QAAQ,IAAI;AAAA,IAChB;AACA,UAAM,UAAU,KAAK;AACrB,UAAM;AAAA,EACV;AACA,MAAI,CAAC,IAAI,MAAM,CAAC,KAAK,SAAS;AAC1B,UAAM,QAAkB;AAAA,MACpB,GAAG;AAAA,MACH,SAAS;AAAA,MACT,QAAQ,IAAI;AAAA,IAChB;AACA,UAAM,UAAU,KAAK;AACrB,UAAM;AAAA,EACV;AACA,SAAO;AACX;;;ACjKA,IAAO,cAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMX,MAAM,IACF,MACA,MACgC;AAChC,QAAI;AACA,YAAM,IAAI,MAAM;AAAA,QACZ,oBAAoB,IAAI;AAAA,QACxB;AAAA,MACJ;AAEA,aAAO;AAAA,QACH,SAAS;AAAA,QACT,MAAM,EAAE;AAAA,QACR,OAAO;AAAA,MACX;AAAA,IACJ,SAAS,KAAU;AACf,aAAO;AAAA,QACH,SAAS;AAAA,QACT,MAAM;AAAA,QACN,OAAO,KAAK,WAAW;AAAA,MAC3B;AAAA,IACJ;AAAA,EACJ;AACJ;;;ACpBO,SAAS,sBACZ,QACe;AACf,SAAO,IAAI,gBAAgB;AAAA,IACvB,OAAO,QAAQ,SAAS;AAAA,IACxB,OAAO,OAAO,QAAQ,SAAS,CAAC;AAAA,IAChC,MAAM,OAAO,QAAQ,QAAQ,CAAC;AAAA,EAClC,CAAC;AACL;;;ACLA,IAAO,gBAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMX,MAAM,KACF,MACA,QACA,MAMF;AACE,QAAI;AACA,YAAM,IAAI,sBAAsB,MAAM;AAEtC,YAAM,IAAI,MAAM;AAAA,QAGZ,oBAAoB,IAAI,UAAU,CAAC;AAAA,QACnC;AAAA,MACJ;AAEA,aAAO;AAAA,QACH,SAAS;AAAA,QACT,MAAM;AAAA,UACF,OAAO,EAAE;AAAA,UACT,YAAY,EAAE;AAAA,QAClB;AAAA,QACA,OAAO;AAAA,MACX;AAAA,IACJ,SAAS,KAAU;AACf,aAAO;AAAA,QACH,SAAS;AAAA,QACT,MAAM;AAAA,QACN,OACI,KAAK,WACL;AAAA,MACR;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,IACF,MACA,SACA,MACwB;AACxB,QAAI;AACA,YAAM,IAAI,MAAM;AAAA,QACZ,oBAAoB,IAAI,UAAU,OAAO;AAAA,QACzC;AAAA,MACJ;AAEA,aAAO;AAAA,QACH,SAAS;AAAA,QACT,MAAM,EAAE;AAAA,QACR,OAAO;AAAA,MACX;AAAA,IACJ,SAAS,KAAU;AACf,aAAO;AAAA,QACH,SAAS;AAAA,QACT,MAAM;AAAA,QACN,OACI,KAAK,WACL;AAAA,MACR;AAAA,IACJ;AAAA,EACJ;AACJ;;;AC5EA,IAAO,mBAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMX,MAAM,KACF,MACA,SACA,QACA,MAMF;AACE,QAAI;AACA,YAAM,IAAI,sBAAsB,MAAM;AAEtC,YAAM,IAAI,MAAM;AAAA,QAGZ,oBAAoB,IAAI,UAAU,OAAO,aAAa,CAAC;AAAA,QACvD;AAAA,MACJ;AAEA,aAAO;AAAA,QACH,SAAS;AAAA,QACT,MAAM;AAAA,UACF,OAAO,EAAE;AAAA,UACT,YAAY,EAAE;AAAA,QAClB;AAAA,QACA,OAAO;AAAA,MACX;AAAA,IACJ,SAAS,KAAU;AACf,aAAO;AAAA,QACH,SAAS;AAAA,QACT,MAAM;AAAA,QACN,OACI,KAAK,WACL;AAAA,MACR;AAAA,IACJ;AAAA,EACJ;AACJ;;;ACzDA,IAAO,iBAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMX,MAAM,KACF,MACA,MAC2B;AAC3B,QAAI;AACA,YAAM,IAAI,MAAM;AAAA,QACZ,oBAAoB,IAAI;AAAA,QACxB;AAAA,MACJ;AAEA,aAAO;AAAA,QACH,SAAS;AAAA,QACT,MAAM,EAAE;AAAA,QACR,OAAO;AAAA,MACX;AAAA,IACJ,SAAS,KAAU;AACf,aAAO;AAAA,QACH,SAAS;AAAA,QACT,MAAM;AAAA,QACN,OAAO,KAAK,WAAW;AAAA,MAC3B;AAAA,IACJ;AAAA,EACJ;AACJ;;;ACrBA,IAAO,qBAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMX,MAAM,KACF,MACA,QAAe,QACf,MAC8B;AAC9B,QAAI;AACA,YAAM,IAAI,MAAM;AAAA,QACZ,oBAAoB,IAAI,qBAAqB,KAAK;AAAA,QAClD;AAAA,MACJ;AAEA,aAAO;AAAA,QACH,SAAS;AAAA,QACT,MAAM,EAAE;AAAA,QACR,OAAO;AAAA,MACX;AAAA,IACJ,SAAS,KAAU;AACf,aAAO;AAAA,QACH,SAAS;AAAA,QACT,MAAM;AAAA,QACN,OACI,KAAK,WACL;AAAA,MACR;AAAA,IACJ;AAAA,EACJ;AACJ;;;ACnCA,IAAM,SAAS;AAAA,EACX,GAAG;AAAA,EACH;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ;AAEA,IAAOA,eAAQ;;;ACHf,IAAO,aAAQ;AAAA,EACX,MAAM,IAAI,MAA+C;AACrD,QAAI;AACA,YAAM,IAAI,MAAM,QAAwB,UAAU,IAAI;AACtD,aAAO;AAAA,QACH,SAAS;AAAA,QACT,MAAM,EAAE;AAAA,QACR,OAAO;AAAA,MACX;AAAA,IACJ,SAAS,KAAU;AACf,aAAO;AAAA,QACH,SAAS;AAAA,QACT,MAAM;AAAA,QACN,OAAO,KAAK,WAAW;AAAA,MAC3B;AAAA,IACJ;AAAA,EACJ;AAAA,EAEA,MAAM,cACF,MACkC;AAClC,QAAI;AACA,YAAM,IAAI,MAAM;AAAA,QACZ;AAAA,QACA;AAAA,MACJ;AACA,aAAO;AAAA,QACH,SAAS;AAAA,QACT,MAAM,EAAE;AAAA,QACR,OAAO;AAAA,MACX;AAAA,IACJ,SAAS,KAAU;AACf,aAAO;AAAA,QACH,SAAS;AAAA,QACT,MAAM;AAAA,QACN,OAAO,KAAK,WAAW;AAAA,MAC3B;AAAA,IACJ;AAAA,EACJ;AACJ;;;ACrDO,IAAM,KAAK;AAAA,EACd,KAAAC;AAAA,EACA;AACJ;AAEA,IAAO,aAAQ;;;ACDR,SAAS,IAAI,KAAa,WAAqB,CAAC,GAAG;AACtD,MAAI,CAAC,KAAK;AACN,UAAM,IAAI;AAAA,MACN;AAAA,IACJ;AAAA,EACJ;AAEA,MAAI,SAA6B;AACjC,MAAI,SAAS;AAEb,WAAS,UAAU;AACf,QAAI,OAAQ;AAEZ,aAAS,IAAI,YAAY,GAAG;AAE5B,WAAO,YAAY,CAAC,MAAM;AACtB,YAAM,MAAM,KAAK,MAAM,EAAE,IAAI;AAC7B,eAAS,IAAI,IAAI,IAAI,IAAI,MAAM,GAAG;AAAA,IACtC;AAEA,WAAO,UAAU,MAAM;AACnB,UAAI,OAAQ;AACZ,cAAQ,MAAM;AACd,iBAAW,SAAS,GAAI;AAAA,IAC5B;AAAA,EACJ;AAEA,UAAQ;AAER,SAAO;AAAA,IACH,QAAQ;AACJ,eAAS;AACT,cAAQ,MAAM;AAAA,IAClB;AAAA,EACJ;AACJ;;;ACxBO,IAAM,qBAA6E;AAAA,EACtF,mBAAmB;AAAA,EACnB,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,aAAa;AAAA,EACb,sBAAsB;AAAA,EACtB,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf,cAAc;AAAA,EACd,mBAAmB;AAAA,EACnB,wBAAwB;AAC5B;;;ACoBO,IAAM,aAAa;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACH;AAaA,IAAM,OA4BF;AAAA;AAAA,EAED,QAAQ;AAAA;AAAA,EAGR;AAAA,EACA,KAAK,WAAG;AAAA,EACR,IAAI,WAAG;AAAA;AAAA,EAGP;AAAA,EACA,QAAQ;AACX;AAEA,IAAO,gBAAQ;;;Ab3GR,SAAS,OAAO,MAAe;AAClC,QAAM,CAAC,MAAM,OAAO,QAAI,uBAA8B,IAAI;AAC1D,QAAM,CAAC,SAAS,UAAU,QAAI,uBAAS,KAAK;AAC5C,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAwB,IAAI;AAEtD,QAAM,iBAAa,qBAAO,IAAI;AAE9B,8BAAU,MAAM;AACZ,WAAO,MAAM;AACT,iBAAW,UAAU;AAAA,IACzB;AAAA,EACJ,GAAG,CAAC,CAAC;AAEL,8BAAU,MAAM;AACZ,QAAI,CAAC,KAAM;AAEX,eAAW,IAAI;AACf,aAAS,IAAI;AAEb,kBAAK,IAAI,IAAI,IAAI,EAAE,KAAK,CAAC,QAAQ;AAC7B,UAAI,CAAC,WAAW,QAAS;AAEzB,UAAI,CAAC,IAAI,SAAS;AACd,iBAAS,IAAI,KAAK;AAClB,gBAAQ,IAAI;AAAA,MAChB,OAAO;AACH,gBAAQ,IAAI,IAAI;AAAA,MACpB;AAEA,iBAAW,KAAK;AAAA,IACpB,CAAC;AAAA,EACL,GAAG,CAAC,IAAI,CAAC;AAET,SAAO;AAAA,IACH;AAAA,IACA;AAAA,IACA;AAAA,EACJ;AACJ;;;AczCA,IAAAC,gBAAyD;;;ACAzD,IAAAC,gBAAkC;AAS3B,SAAS,WAAW,WAAoB,UAAqB;AAChE,QAAM,cAAU,sBAAsC,IAAI;AAC1D,QAAM,kBAAc,sBAA6B,QAAQ;AAGzD,+BAAU,MAAM;AACZ,gBAAY,UAAU;AAAA,EAC1B,GAAG,CAAC,QAAQ,CAAC;AAEb,+BAAU,MAAM;AACZ,QAAI,CAAC,UAAW;AAGhB,UAAM,gBAA0B,IAAI;AAAA,MAChC,CAAC;AAAA,MACD;AAAA,QACI,KAAK,CAAC,GAAG,SAAiB;AACtB,iBAAO,CAAC,MAAW,QAA4B;AAC3C,kBAAM,UACF,YAAY,UAAU,IAA8B;AACxD,gBAAI,SAAS;AACT,sBAAQ,MAAM,GAAG;AAAA,YACrB;AAAA,UACJ;AAAA,QACJ;AAAA,MACJ;AAAA,IACJ;AAEA,YAAQ,UAAU,IAAI,WAAW,aAAa;AAE9C,WAAO,MAAM;AACT,cAAQ,SAAS,MAAM;AACvB,cAAQ,UAAU;AAAA,IACtB;AAAA,EACJ,GAAG,CAAC,SAAS,CAAC;AAEd,SAAO;AACX;;;ADzCO,SAAS,SAAS,MAAe,WAAoB;AACxD,QAAM,CAAC,OAAO,QAAQ,QAAI,wBAAiB,CAAC,CAAC;AAC7C,QAAM,CAAC,SAAS,UAAU,QAAI,wBAAS,KAAK;AAC5C,QAAM,CAAC,OAAO,QAAQ,QAAI,wBAAwB,IAAI;AAEtD,QAAM,iBAAa,sBAAO,IAAI;AAE9B,+BAAU,MAAM;AACZ,WAAO,MAAM;AACT,iBAAW,UAAU;AAAA,IACzB;AAAA,EACJ,GAAG,CAAC,CAAC;AAEL,QAAM,iBAAa,2BAAY,YAAY;AACvC,QAAI,CAAC,KAAM;AAEX,eAAW,IAAI;AACf,aAAS,IAAI;AAEb,UAAM,MAAM,MAAM,cAAK,IAAI,MAAM,KAAK,IAAI;AAE1C,QAAI,CAAC,WAAW,QAAS;AAEzB,QAAI,CAAC,IAAI,SAAS;AACd,eAAS,IAAI,KAAK;AAClB,eAAS,CAAC,CAAC;AAAA,IACf,OAAO;AACH,eAAS,IAAI,MAAM,SAAS,CAAC,CAAC;AAAA,IAClC;AAEA,eAAW,KAAK;AAAA,EACpB,GAAG,CAAC,IAAI,CAAC;AAET,+BAAU,MAAM;AACZ,eAAW;AAAA,EACf,GAAG,CAAC,UAAU,CAAC;AAGf,aAAW,WAAW;AAAA,IAClB,CAAC,cAAK,OAAO,WAAW,GAAG;AAAA,IAC3B,CAAC,cAAK,OAAO,WAAW,GAAG;AAAA,EAC/B,CAAC;AAED,SAAO;AAAA,IACH;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS;AAAA,EACb;AACJ;;;AEtDA,IAAAC,gBAA4C;AAIrC,SAAS,QACZ,MACA,SACF;AACE,QAAM,CAAC,MAAM,OAAO,QAAI,wBAAsB,IAAI;AAClD,QAAM,CAAC,SAAS,UAAU,QAAI,wBAAS,KAAK;AAC5C,QAAM,CAAC,OAAO,QAAQ,QAAI,wBAAwB,IAAI;AAEtD,QAAM,iBAAa,sBAAO,IAAI;AAE9B,+BAAU,MAAM;AACZ,WAAO,MAAM;AACT,iBAAW,UAAU;AAAA,IACzB;AAAA,EACJ,GAAG,CAAC,CAAC;AAEL,+BAAU,MAAM;AACZ,QAAI,CAAC,QAAQ,WAAW,KAAM;AAE9B,eAAW,IAAI;AACf,aAAS,IAAI;AAEb,kBAAK,IAAI,MAAM,IAAI,MAAM,OAAO,EAAE,KAAK,CAAC,QAAQ;AAC5C,UAAI,CAAC,WAAW,QAAS;AAEzB,UAAI,CAAC,IAAI,SAAS;AACd,iBAAS,IAAI,KAAK;AAClB,gBAAQ,IAAI;AAAA,MAChB,OAAO;AACH,gBAAQ,IAAI,IAAI;AAAA,MACpB;AAEA,iBAAW,KAAK;AAAA,IACpB,CAAC;AAAA,EACL,GAAG,CAAC,MAAM,OAAO,CAAC;AAElB,SAAO;AAAA,IACH;AAAA,IACA;AAAA,IACA;AAAA,EACJ;AACJ;;;AC7CA,IAAAC,gBAAyD;AAKlD,SAAS,YACZ,MACA,SACA,WACF;AACE,QAAM,CAAC,UAAU,WAAW,QAAI,wBAAoB,CAAC,CAAC;AACtD,QAAM,CAAC,SAAS,UAAU,QAAI,wBAAS,KAAK;AAC5C,QAAM,CAAC,OAAO,QAAQ,QAAI,wBAAwB,IAAI;AAEtD,QAAM,iBAAa,sBAAO,IAAI;AAE9B,+BAAU,MAAM;AACZ,WAAO,MAAM;AACT,iBAAW,UAAU;AAAA,IACzB;AAAA,EACJ,GAAG,CAAC,CAAC;AAEL,QAAM,oBAAgB,2BAAY,YAAY;AAC1C,QAAI,CAAC,QAAQ,WAAW,KAAM;AAE9B,eAAW,IAAI;AACf,aAAS,IAAI;AAEb,UAAM,MAAM,MAAM,cAAK,IAAI,SAAS,KAAK,MAAM,OAAO;AAEtD,QAAI,CAAC,WAAW,QAAS;AAEzB,QAAI,CAAC,IAAI,SAAS;AACd,eAAS,IAAI,KAAK;AAClB,kBAAY,CAAC,CAAC;AAAA,IAClB,OAAO;AACH,kBAAY,IAAI,MAAM,SAAS,CAAC,CAAC;AAAA,IACrC;AAEA,eAAW,KAAK;AAAA,EACpB,GAAG,CAAC,MAAM,OAAO,CAAC;AAElB,+BAAU,MAAM;AACZ,kBAAc;AAAA,EAClB,GAAG,CAAC,aAAa,CAAC;AAGlB,aAAW,WAAW;AAAA,IAClB,CAAC,cAAK,OAAO,oBAAoB,GAAG;AAAA,EACxC,CAAC;AAED,SAAO;AAAA,IACH;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS;AAAA,EACb;AACJ;","names":["org_default","org_default","import_react","import_react","import_react","import_react"]}
@@ -23,7 +23,7 @@ interface Organization {
23
23
  createdAt: string;
24
24
  updatedAt: string;
25
25
  members: OrganizationMember[];
26
- wsUrl: string;
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 WSMessageType = "CONNECTION_STATUS" | "SUBSCRIBED" | "ERROR" | "PING" | "PONG" | "UPDATE_ORG" | "CREATE_TASK" | "UPDATE_TASK" | "UPDATE_TASK_COMMENTS" | "UPDATE_TASK_VOTE" | "UPDATE_LABELS" | "UPDATE_VIEWS" | "UPDATE_CATEGORIES" | "UPDATE_ISSUE_TEMPLATES" | "DISCONNECTED";
73
- interface WSMessage<T = unknown> {
74
- type: WSMessageType;
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, wsUrl?: 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, wsUrl?: string): {
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: WSMessage<unknown>) => void;
109
- type Handlers = Partial<Record<WSMessageType, Handler>>;
110
- declare function useSayrWS(wsUrl?: string, handlers?: Handlers): react.RefObject<{
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, useSayrWS, useTask, useTasks };
114
+ export { useComments, useOrg, useSayrSSE, useTask, useTasks };
@@ -23,7 +23,7 @@ interface Organization {
23
23
  createdAt: string;
24
24
  updatedAt: string;
25
25
  members: OrganizationMember[];
26
- wsUrl: string;
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 WSMessageType = "CONNECTION_STATUS" | "SUBSCRIBED" | "ERROR" | "PING" | "PONG" | "UPDATE_ORG" | "CREATE_TASK" | "UPDATE_TASK" | "UPDATE_TASK_COMMENTS" | "UPDATE_TASK_VOTE" | "UPDATE_LABELS" | "UPDATE_VIEWS" | "UPDATE_CATEGORIES" | "UPDATE_ISSUE_TEMPLATES" | "DISCONNECTED";
73
- interface WSMessage<T = unknown> {
74
- type: WSMessageType;
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, wsUrl?: 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, wsUrl?: string): {
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: WSMessage<unknown>) => void;
109
- type Handlers = Partial<Record<WSMessageType, Handler>>;
110
- declare function useSayrWS(wsUrl?: string, handlers?: Handlers): react.RefObject<{
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, useSayrWS, useTask, useTasks };
114
+ export { useComments, useOrg, useSayrSSE, useTask, useTasks };
@@ -335,61 +335,52 @@ var v1 = {
335
335
  };
336
336
  var v1_default = v1;
337
337
 
338
- // src/ws/types.ts
339
- var WS_EVENTS = {
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.ws] WebSocket URL is required. Did you forget to pass org.wsUrl?"
342
+ "[Sayr.sse] SSE URL is required. Did you forget to pass org.eventsUrl?"
362
343
  );
363
344
  }
364
- let socket;
365
- let retry = 0;
345
+ let source = null;
366
346
  let closed = false;
367
347
  function connect() {
368
348
  if (closed) return;
369
- socket = new WebSocket(url);
370
- socket.onmessage = (e) => {
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
- socket.onclose = () => {
354
+ source.onerror = () => {
379
355
  if (closed) return;
380
- setTimeout(connect, Math.min(1e3 * 2 ** retry++, 3e4));
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
- socket?.close();
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
- ws,
410
- WS_EVENTS
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/useSayrWS.ts
441
+ // src/react/useSayrSSE.ts
451
442
  import { useEffect as useEffect2, useRef as useRef2 } from "react";
452
- function useSayrWS(wsUrl, handlers) {
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 (!wsUrl) return;
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 = ws(wsUrl, proxyHandlers);
464
+ connRef.current = sse(eventsUrl, proxyHandlers);
474
465
  return () => {
475
466
  connRef.current?.close();
476
467
  connRef.current = null;
477
468
  };
478
- }, [wsUrl]);
469
+ }, [eventsUrl]);
479
470
  return connRef;
480
471
  }
481
472
 
482
473
  // src/react/useTasks.ts
483
- function useTasks(slug, wsUrl) {
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
- useSayrWS(wsUrl, {
511
- [index_default.WS_EVENTS.CREATE_TASK]: fetchTasks,
512
- [index_default.WS_EVENTS.UPDATE_TASK]: fetchTasks
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, wsUrl) {
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
- useSayrWS(wsUrl, {
589
- [index_default.WS_EVENTS.UPDATE_TASK_COMMENTS]: fetchComments
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
- useSayrWS,
589
+ useSayrSSE,
602
590
  useTask,
603
591
  useTasks
604
592
  };