@sayrio/public 0.1.2 → 0.1.4

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 CHANGED
@@ -13,39 +13,41 @@ real‑time updates via WebSockets.
13
13
 
14
14
  ---
15
15
 
16
- ## Install
16
+ ## Installation
17
+
18
+ Install the public Sayr SDK using your preferred package manager:
17
19
 
18
20
  ```bash
19
21
  npm install @sayrio/public
20
22
  ```
21
-
22
23
  or
23
-
24
24
  ```bash
25
25
  pnpm add @sayrio/public
26
26
  ```
27
27
 
28
- ---
29
-
30
28
  ## Usage
31
29
 
32
- ### Basic (REST)
30
+ ### Basic Usage (REST)
31
+
32
+ Fetch public organization data using the REST API:
33
33
 
34
34
  ```ts
35
35
  import Sayr from "@sayrio/public";
36
36
 
37
- const org = await Sayr.org.get("test");
37
+ const org = await Sayr.org.get("acme");
38
38
 
39
39
  console.log(org.name);
40
40
  ```
41
41
 
42
42
  ---
43
43
 
44
- ### Tasks
44
+ ### Listing Tasks
45
+
46
+ Retrieve tasks for an organization with pagination and ordering support:
45
47
 
46
48
  ```ts
47
49
  const { data: tasks, pagination } =
48
- await Sayr.org.tasks("test", {
50
+ await Sayr.org.tasks("acme", {
49
51
  order: "desc",
50
52
  limit: 10
51
53
  });
@@ -57,21 +59,25 @@ console.log(tasks);
57
59
 
58
60
  ### Task Comments
59
61
 
62
+ Fetch comments for a specific task:
63
+
60
64
  ```ts
61
65
  const { data: comments } =
62
- await Sayr.org.comments("test", 12);
66
+ await Sayr.org.comments("acme", 12);
63
67
 
64
68
  console.log(comments);
65
69
  ```
66
70
 
67
71
  ---
68
72
 
69
- ## WebSocket (RealTime Updates)
73
+ ### Real-Time Updates (WebSocket)
74
+
75
+ Subscribe to public real-time events using WebSockets:
70
76
 
71
77
  ```ts
72
78
  Sayr.ws(org.wsUrl, {
73
79
  [Sayr.wsTypes.UPDATE_ORG]: (data) => {
74
- console.log("Org updated", data);
80
+ console.log("Organization updated", data);
75
81
  },
76
82
 
77
83
  [Sayr.wsTypes.UPDATE_TASK]: (task) => {
@@ -80,45 +86,51 @@ Sayr.ws(org.wsUrl, {
80
86
  });
81
87
  ```
82
88
 
83
- ### Features
84
- - Automatic reconnect
85
- - Heartbeat (PING / PONG)
86
- - Typed event names
87
- - Publicsafe payloads
89
+ ### WebSocket Features
90
+ - Automatic reconnection
91
+ - Heartbeat support (PING / PONG)
92
+ - Typed event constants
93
+ - Public-safe payloads only
88
94
 
89
95
  ---
90
96
 
91
97
  ## React Hooks
92
98
 
93
- React hooks are available via a subpath export:
99
+ React hooks are available via a dedicated sub-path export:
94
100
 
95
101
  ```ts
96
- import { useOrg, useTasks } from "@sayrio/public/react";
102
+ import { useOrg, useTasks, useComments } from "@sayrio/public/react";
97
103
  ```
98
104
 
99
105
  ---
100
106
 
101
107
  ### `useOrg`
102
108
 
109
+ Fetch and subscribe to an organization:
110
+
103
111
  ```tsx
104
- const { data: org, loading } = useOrg("test");
112
+ const { data: org, loading } = useOrg("acme");
105
113
  ```
106
114
 
107
115
  ---
108
116
 
109
117
  ### `useTasks`
110
118
 
119
+ Fetch and subscribe to tasks for an organization:
120
+
111
121
  ```tsx
112
- const { tasks } = useTasks("test", org?.wsUrl);
122
+ const { tasks } = useTasks("acme", org?.wsUrl);
113
123
  ```
114
124
 
115
125
  ---
116
126
 
117
127
  ### `useComments`
118
128
 
129
+ Fetch and subscribe to comments for a task:
130
+
119
131
  ```tsx
120
132
  const { comments } = useComments(
121
- "test",
133
+ "acme",
122
134
  task.shortId,
123
135
  org?.wsUrl
124
136
  );
@@ -130,13 +142,13 @@ Hooks automatically refresh when relevant WebSocket events occur.
130
142
 
131
143
  ## Browser Usage (No Bundler)
132
144
 
133
- You can use the SDK directly in the browser via ESM:
145
+ The SDK can be used directly in the browser via ESM:
134
146
 
135
147
  ```html
136
148
  <script type="module">
137
149
  import Sayr from "https://esm.sh/@sayrio/public";
138
150
 
139
- const org = await Sayr.org.get("test");
151
+ const org = await Sayr.org.get("acme");
140
152
  console.log(org);
141
153
  </script>
142
154
  ```
@@ -147,27 +159,28 @@ You can use the SDK directly in the browser via ESM:
147
159
 
148
160
  ### `Sayr.org`
149
161
 
150
- | Method | Description |
151
- |------|-------------|
152
- | `get(slug)` | Get public organization |
153
- | `labels(slug)` | List labels |
154
- | `categories(slug, order?)` | List categories |
155
- | `tasks(slug, opts?)` | List tasks (paginated) |
156
- | `task(slug, shortId)` | Get single task |
157
- | `comments(slug, shortId, opts?)` | List comments |
162
+ | Method | Description |
163
+ | -------------------------------- | --------------------------- |
164
+ | `get(slug)` | Fetch a public organization |
165
+ | `labels(slug)` | List organization labels |
166
+ | `categories(slug, order?)` | List categories |
167
+ | `tasks(slug, opts?)` | List tasks (paginated) |
168
+ | `task(slug, shortId)` | Fetch a single task |
169
+ | `comments(slug, shortId, opts?)` | List task comments |
170
+
158
171
 
159
172
  ---
160
173
 
161
174
  ### `Sayr.ws(url, handlers)`
162
175
 
163
- Create a WebSocket connection for public events.
176
+ Create a WebSocket connection for public events:
164
177
 
165
178
  ```ts
166
179
  const conn = Sayr.ws(wsUrl, {
167
180
  UPDATE_TASK: () => {}
168
181
  });
169
182
 
170
- // later
183
+ // Close the connection when no longer needed
171
184
  conn.close();
172
185
  ```
173
186
 
@@ -186,86 +199,8 @@ Sayr.wsTypes.ERROR
186
199
 
187
200
  ## TypeScript
188
201
 
189
- This package ships with full TypeScript definitions.
202
+ This package ships with full TypeScript definitions:
190
203
 
191
204
  ```ts
192
205
  import type { Organization, Task } from "@sayrio/public";
193
206
  ```
194
-
195
- ---
196
-
197
- ## Basic HTML Example
198
-
199
- This example shows how to use `@sayrio/public` in a **plain HTML file** with no
200
- build tools.
201
-
202
- > **Important:**
203
- > This SDK is published as an **ES module**, so you must use
204
- > `type="module"` and `import`.
205
- > It does **not** create a global `Sayr` variable.
206
-
207
- ```html
208
- <!DOCTYPE html>
209
- <html lang="en">
210
- <head>
211
- <meta charset="utf-8" />
212
- <title>Sayr Public SDK – HTML Example</title>
213
- <style>
214
- body {
215
- background: #0b0b0b;
216
- color: #e5e7eb;
217
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
218
- padding: 24px;
219
- }
220
- </style>
221
- </head>
222
-
223
- <body>
224
- <h1>Sayr Public SDK</h1>
225
- <pre id="out">Loading…</pre>
226
-
227
- <script type="module">
228
- import Sayr from "https://esm.sh/@sayrio/public";
229
-
230
- const out = document.getElementById("out");
231
-
232
- function log(label, value) {
233
- out.textContent +=
234
- label + ": " + JSON.stringify(value, null, 2) + "\n\n";
235
- }
236
-
237
- const slug = "org";
238
-
239
- // Fetch public organization
240
- const org = await Sayr.org.get(slug);
241
- log("Organization", org);
242
-
243
- // Connect to public WebSocket
244
- Sayr.ws(org.wsUrl, {
245
- [Sayr.wsTypes.UPDATE_ORG]: (data) => {
246
- log("Org updated", data);
247
- },
248
-
249
- [Sayr.wsTypes.UPDATE_TASK]: (task) => {
250
- log("Task updated", task);
251
- },
252
-
253
- [Sayr.wsTypes.ERROR]: (err) => {
254
- log("WebSocket error", err);
255
- }
256
- });
257
- </script>
258
- </body>
259
- </html>
260
- ```
261
-
262
- ---
263
-
264
- ## Notes on Browser Usage
265
-
266
- - This package **does not expose a global `Sayr`**
267
- - Always use `type="module"` and `import`
268
- - For legacy `<script>` usage, a UMD build is not currently provided
269
- - `esm.sh` is recommended for CDN usage
270
-
271
- ---
package/dist/index.cjs CHANGED
@@ -113,6 +113,11 @@ var wsTypes = {
113
113
  DISCONNECTED: "DISCONNECTED"
114
114
  };
115
115
  function ws(url, handlers = {}) {
116
+ if (!url) {
117
+ throw new Error(
118
+ "[Sayr.ws] WebSocket URL is required. Did you forget to pass org.wsUrl?"
119
+ );
120
+ }
116
121
  let socket;
117
122
  let retry = 0;
118
123
  let closed = false;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/org.ts","../src/ws.ts"],"sourcesContent":["import { org } from \"./org\";\r\nimport { ws, wsTypes } from \"./ws\";\r\n\r\nconst Sayr = { org, ws, wsTypes };\r\n\r\nexport default Sayr;\r\nexport { org, ws, wsTypes };\r\nexport * from \"./types\";","import {\r\n Organization,\r\n Label,\r\n Category,\r\n Task,\r\n Comment,\r\n Pagination,\r\n ApiSuccess\r\n} from \"./types\";\r\n\r\nconst API = \"https://sayr.io/api/public\";\r\n\r\nasync function get<T>(url: string): Promise<T> {\r\n const res = await fetch(url);\r\n const json = await res.json();\r\n if (!json.success) throw json;\r\n return json;\r\n}\r\n\r\nexport const org = {\r\n async get(slug: string): Promise<Organization> {\r\n const r = await get<ApiSuccess<Organization>>(\r\n `${API}/organization/${slug}`\r\n );\r\n return r.data;\r\n },\r\n\r\n async labels(slug: string): Promise<Label[]> {\r\n const r = await get<ApiSuccess<Label[]>>(\r\n `${API}/organization/${slug}/labels`\r\n );\r\n return r.data;\r\n },\r\n\r\n async categories(\r\n slug: string,\r\n order: \"asc\" | \"desc\" = \"desc\"\r\n ): Promise<Category[]> {\r\n const r = await get<ApiSuccess<Category[]>>(\r\n `${API}/organization/${slug}/categories?order=${order}`\r\n );\r\n return r.data;\r\n },\r\n\r\n async tasks(\r\n slug: string,\r\n opts?: { order?: \"asc\" | \"desc\"; limit?: number; page?: number }\r\n ): Promise<{ data: Task[]; pagination: Pagination }> {\r\n const q = new URLSearchParams({\r\n order: opts?.order ?? \"desc\",\r\n limit: String(opts?.limit ?? 5),\r\n page: String(opts?.page ?? 1)\r\n });\r\n\r\n const res = await fetch(\r\n `${API}/organization/${slug}/tasks?${q}`\r\n );\r\n const json = await res.json();\r\n if (!json.success) throw json;\r\n\r\n return {\r\n data: json.data,\r\n pagination: json.pagination\r\n };\r\n },\r\n\r\n async task(slug: string, shortId: number): Promise<Task> {\r\n const r = await get<ApiSuccess<Task>>(\r\n `${API}/organization/${slug}/tasks/${shortId}`\r\n );\r\n return r.data;\r\n },\r\n\r\n async comments(\r\n slug: string,\r\n shortId: number,\r\n opts?: { order?: \"asc\" | \"desc\"; limit?: number; page?: number }\r\n ): Promise<{ data: Comment[]; pagination: Pagination }> {\r\n const q = new URLSearchParams({\r\n order: opts?.order ?? \"desc\",\r\n limit: String(opts?.limit ?? 5),\r\n page: String(opts?.page ?? 1)\r\n });\r\n\r\n const res = await fetch(\r\n `${API}/organization/${slug}/tasks/${shortId}/comments?${q}`\r\n );\r\n const json = await res.json();\r\n if (!json.success) throw json;\r\n\r\n return {\r\n data: json.data,\r\n pagination: json.pagination\r\n };\r\n }\r\n};","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\nexport const wsTypes: 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}\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 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 === wsTypes.PING) {\r\n socket.send(JSON.stringify({ type: wsTypes.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}"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/org.ts","../src/ws.ts"],"sourcesContent":["import { org } from \"./org\";\r\nimport { ws, wsTypes } from \"./ws\";\r\n\r\nconst Sayr = { org, ws, wsTypes };\r\n\r\nexport default Sayr;\r\nexport { org, ws, wsTypes };\r\nexport * from \"./types\";","import {\r\n Organization,\r\n Label,\r\n Category,\r\n Task,\r\n Comment,\r\n Pagination,\r\n ApiSuccess\r\n} from \"./types\";\r\n\r\nconst API = \"https://sayr.io/api/public\";\r\n\r\nasync function get<T>(url: string): Promise<T> {\r\n const res = await fetch(url);\r\n const json = await res.json();\r\n if (!json.success) throw json;\r\n return json;\r\n}\r\n\r\nexport const org = {\r\n async get(slug: string): Promise<Organization> {\r\n const r = await get<ApiSuccess<Organization>>(\r\n `${API}/organization/${slug}`\r\n );\r\n return r.data;\r\n },\r\n\r\n async labels(slug: string): Promise<Label[]> {\r\n const r = await get<ApiSuccess<Label[]>>(\r\n `${API}/organization/${slug}/labels`\r\n );\r\n return r.data;\r\n },\r\n\r\n async categories(\r\n slug: string,\r\n order: \"asc\" | \"desc\" = \"desc\"\r\n ): Promise<Category[]> {\r\n const r = await get<ApiSuccess<Category[]>>(\r\n `${API}/organization/${slug}/categories?order=${order}`\r\n );\r\n return r.data;\r\n },\r\n\r\n async tasks(\r\n slug: string,\r\n opts?: { order?: \"asc\" | \"desc\"; limit?: number; page?: number }\r\n ): Promise<{ data: Task[]; pagination: Pagination }> {\r\n const q = new URLSearchParams({\r\n order: opts?.order ?? \"desc\",\r\n limit: String(opts?.limit ?? 5),\r\n page: String(opts?.page ?? 1)\r\n });\r\n\r\n const res = await fetch(\r\n `${API}/organization/${slug}/tasks?${q}`\r\n );\r\n const json = await res.json();\r\n if (!json.success) throw json;\r\n\r\n return {\r\n data: json.data,\r\n pagination: json.pagination\r\n };\r\n },\r\n\r\n async task(slug: string, shortId: number): Promise<Task> {\r\n const r = await get<ApiSuccess<Task>>(\r\n `${API}/organization/${slug}/tasks/${shortId}`\r\n );\r\n return r.data;\r\n },\r\n\r\n async comments(\r\n slug: string,\r\n shortId: number,\r\n opts?: { order?: \"asc\" | \"desc\"; limit?: number; page?: number }\r\n ): Promise<{ data: Comment[]; pagination: Pagination }> {\r\n const q = new URLSearchParams({\r\n order: opts?.order ?? \"desc\",\r\n limit: String(opts?.limit ?? 5),\r\n page: String(opts?.page ?? 1)\r\n });\r\n\r\n const res = await fetch(\r\n `${API}/organization/${slug}/tasks/${shortId}/comments?${q}`\r\n );\r\n const json = await res.json();\r\n if (!json.success) throw json;\r\n\r\n return {\r\n data: json.data,\r\n pagination: json.pagination\r\n };\r\n }\r\n};","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\nexport const wsTypes: 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}\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 === wsTypes.PING) {\r\n socket.send(JSON.stringify({ type: wsTypes.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}"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -84,6 +84,11 @@ var wsTypes = {
84
84
  DISCONNECTED: "DISCONNECTED"
85
85
  };
86
86
  function ws(url, handlers = {}) {
87
+ if (!url) {
88
+ throw new Error(
89
+ "[Sayr.ws] WebSocket URL is required. Did you forget to pass org.wsUrl?"
90
+ );
91
+ }
87
92
  let socket;
88
93
  let retry = 0;
89
94
  let closed = false;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/org.ts","../src/ws.ts","../src/index.ts"],"sourcesContent":["import {\r\n Organization,\r\n Label,\r\n Category,\r\n Task,\r\n Comment,\r\n Pagination,\r\n ApiSuccess\r\n} from \"./types\";\r\n\r\nconst API = \"https://sayr.io/api/public\";\r\n\r\nasync function get<T>(url: string): Promise<T> {\r\n const res = await fetch(url);\r\n const json = await res.json();\r\n if (!json.success) throw json;\r\n return json;\r\n}\r\n\r\nexport const org = {\r\n async get(slug: string): Promise<Organization> {\r\n const r = await get<ApiSuccess<Organization>>(\r\n `${API}/organization/${slug}`\r\n );\r\n return r.data;\r\n },\r\n\r\n async labels(slug: string): Promise<Label[]> {\r\n const r = await get<ApiSuccess<Label[]>>(\r\n `${API}/organization/${slug}/labels`\r\n );\r\n return r.data;\r\n },\r\n\r\n async categories(\r\n slug: string,\r\n order: \"asc\" | \"desc\" = \"desc\"\r\n ): Promise<Category[]> {\r\n const r = await get<ApiSuccess<Category[]>>(\r\n `${API}/organization/${slug}/categories?order=${order}`\r\n );\r\n return r.data;\r\n },\r\n\r\n async tasks(\r\n slug: string,\r\n opts?: { order?: \"asc\" | \"desc\"; limit?: number; page?: number }\r\n ): Promise<{ data: Task[]; pagination: Pagination }> {\r\n const q = new URLSearchParams({\r\n order: opts?.order ?? \"desc\",\r\n limit: String(opts?.limit ?? 5),\r\n page: String(opts?.page ?? 1)\r\n });\r\n\r\n const res = await fetch(\r\n `${API}/organization/${slug}/tasks?${q}`\r\n );\r\n const json = await res.json();\r\n if (!json.success) throw json;\r\n\r\n return {\r\n data: json.data,\r\n pagination: json.pagination\r\n };\r\n },\r\n\r\n async task(slug: string, shortId: number): Promise<Task> {\r\n const r = await get<ApiSuccess<Task>>(\r\n `${API}/organization/${slug}/tasks/${shortId}`\r\n );\r\n return r.data;\r\n },\r\n\r\n async comments(\r\n slug: string,\r\n shortId: number,\r\n opts?: { order?: \"asc\" | \"desc\"; limit?: number; page?: number }\r\n ): Promise<{ data: Comment[]; pagination: Pagination }> {\r\n const q = new URLSearchParams({\r\n order: opts?.order ?? \"desc\",\r\n limit: String(opts?.limit ?? 5),\r\n page: String(opts?.page ?? 1)\r\n });\r\n\r\n const res = await fetch(\r\n `${API}/organization/${slug}/tasks/${shortId}/comments?${q}`\r\n );\r\n const json = await res.json();\r\n if (!json.success) throw json;\r\n\r\n return {\r\n data: json.data,\r\n pagination: json.pagination\r\n };\r\n }\r\n};","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\nexport const wsTypes: 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}\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 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 === wsTypes.PING) {\r\n socket.send(JSON.stringify({ type: wsTypes.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}","import { org } from \"./org\";\r\nimport { ws, wsTypes } from \"./ws\";\r\n\r\nconst Sayr = { org, ws, wsTypes };\r\n\r\nexport default Sayr;\r\nexport { org, ws, wsTypes };\r\nexport * from \"./types\";"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/org.ts","../src/ws.ts","../src/index.ts"],"sourcesContent":["import {\r\n Organization,\r\n Label,\r\n Category,\r\n Task,\r\n Comment,\r\n Pagination,\r\n ApiSuccess\r\n} from \"./types\";\r\n\r\nconst API = \"https://sayr.io/api/public\";\r\n\r\nasync function get<T>(url: string): Promise<T> {\r\n const res = await fetch(url);\r\n const json = await res.json();\r\n if (!json.success) throw json;\r\n return json;\r\n}\r\n\r\nexport const org = {\r\n async get(slug: string): Promise<Organization> {\r\n const r = await get<ApiSuccess<Organization>>(\r\n `${API}/organization/${slug}`\r\n );\r\n return r.data;\r\n },\r\n\r\n async labels(slug: string): Promise<Label[]> {\r\n const r = await get<ApiSuccess<Label[]>>(\r\n `${API}/organization/${slug}/labels`\r\n );\r\n return r.data;\r\n },\r\n\r\n async categories(\r\n slug: string,\r\n order: \"asc\" | \"desc\" = \"desc\"\r\n ): Promise<Category[]> {\r\n const r = await get<ApiSuccess<Category[]>>(\r\n `${API}/organization/${slug}/categories?order=${order}`\r\n );\r\n return r.data;\r\n },\r\n\r\n async tasks(\r\n slug: string,\r\n opts?: { order?: \"asc\" | \"desc\"; limit?: number; page?: number }\r\n ): Promise<{ data: Task[]; pagination: Pagination }> {\r\n const q = new URLSearchParams({\r\n order: opts?.order ?? \"desc\",\r\n limit: String(opts?.limit ?? 5),\r\n page: String(opts?.page ?? 1)\r\n });\r\n\r\n const res = await fetch(\r\n `${API}/organization/${slug}/tasks?${q}`\r\n );\r\n const json = await res.json();\r\n if (!json.success) throw json;\r\n\r\n return {\r\n data: json.data,\r\n pagination: json.pagination\r\n };\r\n },\r\n\r\n async task(slug: string, shortId: number): Promise<Task> {\r\n const r = await get<ApiSuccess<Task>>(\r\n `${API}/organization/${slug}/tasks/${shortId}`\r\n );\r\n return r.data;\r\n },\r\n\r\n async comments(\r\n slug: string,\r\n shortId: number,\r\n opts?: { order?: \"asc\" | \"desc\"; limit?: number; page?: number }\r\n ): Promise<{ data: Comment[]; pagination: Pagination }> {\r\n const q = new URLSearchParams({\r\n order: opts?.order ?? \"desc\",\r\n limit: String(opts?.limit ?? 5),\r\n page: String(opts?.page ?? 1)\r\n });\r\n\r\n const res = await fetch(\r\n `${API}/organization/${slug}/tasks/${shortId}/comments?${q}`\r\n );\r\n const json = await res.json();\r\n if (!json.success) throw json;\r\n\r\n return {\r\n data: json.data,\r\n pagination: json.pagination\r\n };\r\n }\r\n};","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\nexport const wsTypes: 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}\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 === wsTypes.PING) {\r\n socket.send(JSON.stringify({ type: wsTypes.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}","import { org } from \"./org\";\r\nimport { ws, wsTypes } from \"./ws\";\r\n\r\nconst Sayr = { org, ws, wsTypes };\r\n\r\nexport default Sayr;\r\nexport { org, ws, wsTypes };\r\nexport * from \"./types\";"],"mappings":"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","names":[]}
@@ -117,6 +117,11 @@ var wsTypes = {
117
117
  DISCONNECTED: "DISCONNECTED"
118
118
  };
119
119
  function ws(url, handlers = {}) {
120
+ if (!url) {
121
+ throw new Error(
122
+ "[Sayr.ws] WebSocket URL is required. Did you forget to pass org.wsUrl?"
123
+ );
124
+ }
120
125
  let socket;
121
126
  let retry = 0;
122
127
  let closed = false;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/react/index.ts","../../src/react/useOrg.ts","../../src/org.ts","../../src/ws.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, useState } from \"react\";\r\nimport { org, 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<unknown>(null);\r\n\r\n useEffect(() => {\r\n if (!slug) return;\r\n\r\n setLoading(true);\r\n org\r\n .get(slug)\r\n .then(setData)\r\n .catch(setError)\r\n .finally(() => setLoading(false));\r\n }, [slug]);\r\n\r\n return { data, loading, error };\r\n}","import {\r\n Organization,\r\n Label,\r\n Category,\r\n Task,\r\n Comment,\r\n Pagination,\r\n ApiSuccess\r\n} from \"./types\";\r\n\r\nconst API = \"https://sayr.io/api/public\";\r\n\r\nasync function get<T>(url: string): Promise<T> {\r\n const res = await fetch(url);\r\n const json = await res.json();\r\n if (!json.success) throw json;\r\n return json;\r\n}\r\n\r\nexport const org = {\r\n async get(slug: string): Promise<Organization> {\r\n const r = await get<ApiSuccess<Organization>>(\r\n `${API}/organization/${slug}`\r\n );\r\n return r.data;\r\n },\r\n\r\n async labels(slug: string): Promise<Label[]> {\r\n const r = await get<ApiSuccess<Label[]>>(\r\n `${API}/organization/${slug}/labels`\r\n );\r\n return r.data;\r\n },\r\n\r\n async categories(\r\n slug: string,\r\n order: \"asc\" | \"desc\" = \"desc\"\r\n ): Promise<Category[]> {\r\n const r = await get<ApiSuccess<Category[]>>(\r\n `${API}/organization/${slug}/categories?order=${order}`\r\n );\r\n return r.data;\r\n },\r\n\r\n async tasks(\r\n slug: string,\r\n opts?: { order?: \"asc\" | \"desc\"; limit?: number; page?: number }\r\n ): Promise<{ data: Task[]; pagination: Pagination }> {\r\n const q = new URLSearchParams({\r\n order: opts?.order ?? \"desc\",\r\n limit: String(opts?.limit ?? 5),\r\n page: String(opts?.page ?? 1)\r\n });\r\n\r\n const res = await fetch(\r\n `${API}/organization/${slug}/tasks?${q}`\r\n );\r\n const json = await res.json();\r\n if (!json.success) throw json;\r\n\r\n return {\r\n data: json.data,\r\n pagination: json.pagination\r\n };\r\n },\r\n\r\n async task(slug: string, shortId: number): Promise<Task> {\r\n const r = await get<ApiSuccess<Task>>(\r\n `${API}/organization/${slug}/tasks/${shortId}`\r\n );\r\n return r.data;\r\n },\r\n\r\n async comments(\r\n slug: string,\r\n shortId: number,\r\n opts?: { order?: \"asc\" | \"desc\"; limit?: number; page?: number }\r\n ): Promise<{ data: Comment[]; pagination: Pagination }> {\r\n const q = new URLSearchParams({\r\n order: opts?.order ?? \"desc\",\r\n limit: String(opts?.limit ?? 5),\r\n page: String(opts?.page ?? 1)\r\n });\r\n\r\n const res = await fetch(\r\n `${API}/organization/${slug}/tasks/${shortId}/comments?${q}`\r\n );\r\n const json = await res.json();\r\n if (!json.success) throw json;\r\n\r\n return {\r\n data: json.data,\r\n pagination: json.pagination\r\n };\r\n }\r\n};","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\nexport const wsTypes: 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}\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 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 === wsTypes.PING) {\r\n socket.send(JSON.stringify({ type: wsTypes.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}","import { useEffect, useState } from \"react\";\r\nimport { org, wsTypes } from \"..\";\r\nimport type { Task } from \"../types\";\r\nimport { useSayrWS } from \"./useSayrWS\";\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\r\n function fetchTasks() {\r\n if (!slug) return;\r\n setLoading(true);\r\n org\r\n .tasks(slug)\r\n .then((r) => setTasks(r.data))\r\n .finally(() => setLoading(false));\r\n }\r\n\r\n useEffect(fetchTasks, [slug]);\r\n\r\n useSayrWS(wsUrl, {\r\n [wsTypes.CREATE_TASK]: fetchTasks,\r\n [wsTypes.UPDATE_TASK]: fetchTasks\r\n });\r\n\r\n return { tasks, loading, refetch: fetchTasks };\r\n}","import { useEffect, useRef } from \"react\";\r\nimport { ws } from \"..\";\r\nimport { WSMessageType } from \"../ws\";\r\n\r\ntype Handlers = Partial<\r\n Record<WSMessageType, (data: any, msg: any) => void>\r\n>;\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\r\n useEffect(() => {\r\n if (!wsUrl) return;\r\n\r\n connRef.current = ws(wsUrl, handlers);\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, useState } from \"react\";\r\nimport { org } from \"..\";\r\nimport type { Task } from \"../types\";\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\r\n useEffect(() => {\r\n if (!slug || shortId == null) return;\r\n\r\n setLoading(true);\r\n org\r\n .task(slug, shortId)\r\n .then(setTask)\r\n .finally(() => setLoading(false));\r\n }, [slug, shortId]);\r\n\r\n return { task, loading };\r\n}","import { useEffect, useState } from \"react\";\r\nimport { org, wsTypes } from \"..\";\r\nimport type { Comment } from \"../types\";\r\nimport { useSayrWS } from \"./useSayrWS\";\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\r\n function fetchComments() {\r\n if (!slug || shortId == null) return;\r\n\r\n setLoading(true);\r\n org\r\n .comments(slug, shortId)\r\n .then((r) => setComments(r.data))\r\n .finally(() => setLoading(false));\r\n }\r\n\r\n useEffect(fetchComments, [slug, shortId]);\r\n\r\n useSayrWS(wsUrl, {\r\n [wsTypes.UPDATE_TASK_COMMENTS]: fetchComments\r\n });\r\n\r\n return { comments, loading, refetch: fetchComments };\r\n}"],"mappings":"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","names":["import_react","import_react","import_react","import_react"]}
1
+ {"version":3,"sources":["../../src/react/index.ts","../../src/react/useOrg.ts","../../src/org.ts","../../src/ws.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, useState } from \"react\";\r\nimport { org, 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<unknown>(null);\r\n\r\n useEffect(() => {\r\n if (!slug) return;\r\n\r\n setLoading(true);\r\n org\r\n .get(slug)\r\n .then(setData)\r\n .catch(setError)\r\n .finally(() => setLoading(false));\r\n }, [slug]);\r\n\r\n return { data, loading, error };\r\n}","import {\r\n Organization,\r\n Label,\r\n Category,\r\n Task,\r\n Comment,\r\n Pagination,\r\n ApiSuccess\r\n} from \"./types\";\r\n\r\nconst API = \"https://sayr.io/api/public\";\r\n\r\nasync function get<T>(url: string): Promise<T> {\r\n const res = await fetch(url);\r\n const json = await res.json();\r\n if (!json.success) throw json;\r\n return json;\r\n}\r\n\r\nexport const org = {\r\n async get(slug: string): Promise<Organization> {\r\n const r = await get<ApiSuccess<Organization>>(\r\n `${API}/organization/${slug}`\r\n );\r\n return r.data;\r\n },\r\n\r\n async labels(slug: string): Promise<Label[]> {\r\n const r = await get<ApiSuccess<Label[]>>(\r\n `${API}/organization/${slug}/labels`\r\n );\r\n return r.data;\r\n },\r\n\r\n async categories(\r\n slug: string,\r\n order: \"asc\" | \"desc\" = \"desc\"\r\n ): Promise<Category[]> {\r\n const r = await get<ApiSuccess<Category[]>>(\r\n `${API}/organization/${slug}/categories?order=${order}`\r\n );\r\n return r.data;\r\n },\r\n\r\n async tasks(\r\n slug: string,\r\n opts?: { order?: \"asc\" | \"desc\"; limit?: number; page?: number }\r\n ): Promise<{ data: Task[]; pagination: Pagination }> {\r\n const q = new URLSearchParams({\r\n order: opts?.order ?? \"desc\",\r\n limit: String(opts?.limit ?? 5),\r\n page: String(opts?.page ?? 1)\r\n });\r\n\r\n const res = await fetch(\r\n `${API}/organization/${slug}/tasks?${q}`\r\n );\r\n const json = await res.json();\r\n if (!json.success) throw json;\r\n\r\n return {\r\n data: json.data,\r\n pagination: json.pagination\r\n };\r\n },\r\n\r\n async task(slug: string, shortId: number): Promise<Task> {\r\n const r = await get<ApiSuccess<Task>>(\r\n `${API}/organization/${slug}/tasks/${shortId}`\r\n );\r\n return r.data;\r\n },\r\n\r\n async comments(\r\n slug: string,\r\n shortId: number,\r\n opts?: { order?: \"asc\" | \"desc\"; limit?: number; page?: number }\r\n ): Promise<{ data: Comment[]; pagination: Pagination }> {\r\n const q = new URLSearchParams({\r\n order: opts?.order ?? \"desc\",\r\n limit: String(opts?.limit ?? 5),\r\n page: String(opts?.page ?? 1)\r\n });\r\n\r\n const res = await fetch(\r\n `${API}/organization/${slug}/tasks/${shortId}/comments?${q}`\r\n );\r\n const json = await res.json();\r\n if (!json.success) throw json;\r\n\r\n return {\r\n data: json.data,\r\n pagination: json.pagination\r\n };\r\n }\r\n};","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\nexport const wsTypes: 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}\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 === wsTypes.PING) {\r\n socket.send(JSON.stringify({ type: wsTypes.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}","import { useEffect, useState } from \"react\";\r\nimport { org, wsTypes } from \"..\";\r\nimport type { Task } from \"../types\";\r\nimport { useSayrWS } from \"./useSayrWS\";\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\r\n function fetchTasks() {\r\n if (!slug) return;\r\n setLoading(true);\r\n org\r\n .tasks(slug)\r\n .then((r) => setTasks(r.data))\r\n .finally(() => setLoading(false));\r\n }\r\n\r\n useEffect(fetchTasks, [slug]);\r\n\r\n useSayrWS(wsUrl, {\r\n [wsTypes.CREATE_TASK]: fetchTasks,\r\n [wsTypes.UPDATE_TASK]: fetchTasks\r\n });\r\n\r\n return { tasks, loading, refetch: fetchTasks };\r\n}","import { useEffect, useRef } from \"react\";\r\nimport { ws } from \"..\";\r\nimport { WSMessageType } from \"../ws\";\r\n\r\ntype Handlers = Partial<\r\n Record<WSMessageType, (data: any, msg: any) => void>\r\n>;\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\r\n useEffect(() => {\r\n if (!wsUrl) return;\r\n\r\n connRef.current = ws(wsUrl, handlers);\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, useState } from \"react\";\r\nimport { org } from \"..\";\r\nimport type { Task } from \"../types\";\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\r\n useEffect(() => {\r\n if (!slug || shortId == null) return;\r\n\r\n setLoading(true);\r\n org\r\n .task(slug, shortId)\r\n .then(setTask)\r\n .finally(() => setLoading(false));\r\n }, [slug, shortId]);\r\n\r\n return { task, loading };\r\n}","import { useEffect, useState } from \"react\";\r\nimport { org, wsTypes } from \"..\";\r\nimport type { Comment } from \"../types\";\r\nimport { useSayrWS } from \"./useSayrWS\";\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\r\n function fetchComments() {\r\n if (!slug || shortId == null) return;\r\n\r\n setLoading(true);\r\n org\r\n .comments(slug, shortId)\r\n .then((r) => setComments(r.data))\r\n .finally(() => setLoading(false));\r\n }\r\n\r\n useEffect(fetchComments, [slug, shortId]);\r\n\r\n useSayrWS(wsUrl, {\r\n [wsTypes.UPDATE_TASK_COMMENTS]: fetchComments\r\n });\r\n\r\n return { comments, loading, refetch: fetchComments };\r\n}"],"mappings":"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","names":["import_react","import_react","import_react","import_react"]}
@@ -87,6 +87,11 @@ var wsTypes = {
87
87
  DISCONNECTED: "DISCONNECTED"
88
88
  };
89
89
  function ws(url, handlers = {}) {
90
+ if (!url) {
91
+ throw new Error(
92
+ "[Sayr.ws] WebSocket URL is required. Did you forget to pass org.wsUrl?"
93
+ );
94
+ }
90
95
  let socket;
91
96
  let retry = 0;
92
97
  let closed = false;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/react/useOrg.ts","../../src/org.ts","../../src/ws.ts","../../src/react/useTasks.ts","../../src/react/useSayrWS.ts","../../src/react/useTask.ts","../../src/react/useComments.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\r\nimport { org, 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<unknown>(null);\r\n\r\n useEffect(() => {\r\n if (!slug) return;\r\n\r\n setLoading(true);\r\n org\r\n .get(slug)\r\n .then(setData)\r\n .catch(setError)\r\n .finally(() => setLoading(false));\r\n }, [slug]);\r\n\r\n return { data, loading, error };\r\n}","import {\r\n Organization,\r\n Label,\r\n Category,\r\n Task,\r\n Comment,\r\n Pagination,\r\n ApiSuccess\r\n} from \"./types\";\r\n\r\nconst API = \"https://sayr.io/api/public\";\r\n\r\nasync function get<T>(url: string): Promise<T> {\r\n const res = await fetch(url);\r\n const json = await res.json();\r\n if (!json.success) throw json;\r\n return json;\r\n}\r\n\r\nexport const org = {\r\n async get(slug: string): Promise<Organization> {\r\n const r = await get<ApiSuccess<Organization>>(\r\n `${API}/organization/${slug}`\r\n );\r\n return r.data;\r\n },\r\n\r\n async labels(slug: string): Promise<Label[]> {\r\n const r = await get<ApiSuccess<Label[]>>(\r\n `${API}/organization/${slug}/labels`\r\n );\r\n return r.data;\r\n },\r\n\r\n async categories(\r\n slug: string,\r\n order: \"asc\" | \"desc\" = \"desc\"\r\n ): Promise<Category[]> {\r\n const r = await get<ApiSuccess<Category[]>>(\r\n `${API}/organization/${slug}/categories?order=${order}`\r\n );\r\n return r.data;\r\n },\r\n\r\n async tasks(\r\n slug: string,\r\n opts?: { order?: \"asc\" | \"desc\"; limit?: number; page?: number }\r\n ): Promise<{ data: Task[]; pagination: Pagination }> {\r\n const q = new URLSearchParams({\r\n order: opts?.order ?? \"desc\",\r\n limit: String(opts?.limit ?? 5),\r\n page: String(opts?.page ?? 1)\r\n });\r\n\r\n const res = await fetch(\r\n `${API}/organization/${slug}/tasks?${q}`\r\n );\r\n const json = await res.json();\r\n if (!json.success) throw json;\r\n\r\n return {\r\n data: json.data,\r\n pagination: json.pagination\r\n };\r\n },\r\n\r\n async task(slug: string, shortId: number): Promise<Task> {\r\n const r = await get<ApiSuccess<Task>>(\r\n `${API}/organization/${slug}/tasks/${shortId}`\r\n );\r\n return r.data;\r\n },\r\n\r\n async comments(\r\n slug: string,\r\n shortId: number,\r\n opts?: { order?: \"asc\" | \"desc\"; limit?: number; page?: number }\r\n ): Promise<{ data: Comment[]; pagination: Pagination }> {\r\n const q = new URLSearchParams({\r\n order: opts?.order ?? \"desc\",\r\n limit: String(opts?.limit ?? 5),\r\n page: String(opts?.page ?? 1)\r\n });\r\n\r\n const res = await fetch(\r\n `${API}/organization/${slug}/tasks/${shortId}/comments?${q}`\r\n );\r\n const json = await res.json();\r\n if (!json.success) throw json;\r\n\r\n return {\r\n data: json.data,\r\n pagination: json.pagination\r\n };\r\n }\r\n};","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\nexport const wsTypes: 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}\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 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 === wsTypes.PING) {\r\n socket.send(JSON.stringify({ type: wsTypes.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}","import { useEffect, useState } from \"react\";\r\nimport { org, wsTypes } from \"..\";\r\nimport type { Task } from \"../types\";\r\nimport { useSayrWS } from \"./useSayrWS\";\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\r\n function fetchTasks() {\r\n if (!slug) return;\r\n setLoading(true);\r\n org\r\n .tasks(slug)\r\n .then((r) => setTasks(r.data))\r\n .finally(() => setLoading(false));\r\n }\r\n\r\n useEffect(fetchTasks, [slug]);\r\n\r\n useSayrWS(wsUrl, {\r\n [wsTypes.CREATE_TASK]: fetchTasks,\r\n [wsTypes.UPDATE_TASK]: fetchTasks\r\n });\r\n\r\n return { tasks, loading, refetch: fetchTasks };\r\n}","import { useEffect, useRef } from \"react\";\r\nimport { ws } from \"..\";\r\nimport { WSMessageType } from \"../ws\";\r\n\r\ntype Handlers = Partial<\r\n Record<WSMessageType, (data: any, msg: any) => void>\r\n>;\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\r\n useEffect(() => {\r\n if (!wsUrl) return;\r\n\r\n connRef.current = ws(wsUrl, handlers);\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, useState } from \"react\";\r\nimport { org } from \"..\";\r\nimport type { Task } from \"../types\";\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\r\n useEffect(() => {\r\n if (!slug || shortId == null) return;\r\n\r\n setLoading(true);\r\n org\r\n .task(slug, shortId)\r\n .then(setTask)\r\n .finally(() => setLoading(false));\r\n }, [slug, shortId]);\r\n\r\n return { task, loading };\r\n}","import { useEffect, useState } from \"react\";\r\nimport { org, wsTypes } from \"..\";\r\nimport type { Comment } from \"../types\";\r\nimport { useSayrWS } from \"./useSayrWS\";\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\r\n function fetchComments() {\r\n if (!slug || shortId == null) return;\r\n\r\n setLoading(true);\r\n org\r\n .comments(slug, shortId)\r\n .then((r) => setComments(r.data))\r\n .finally(() => setLoading(false));\r\n }\r\n\r\n useEffect(fetchComments, [slug, shortId]);\r\n\r\n useSayrWS(wsUrl, {\r\n [wsTypes.UPDATE_TASK_COMMENTS]: fetchComments\r\n });\r\n\r\n return { comments, loading, refetch: fetchComments };\r\n}"],"mappings":"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","names":["useEffect","useState","useEffect","useEffect","useState","useEffect","useEffect","useState","useState","useEffect","useEffect","useState","useState","useEffect"]}
1
+ {"version":3,"sources":["../../src/react/useOrg.ts","../../src/org.ts","../../src/ws.ts","../../src/react/useTasks.ts","../../src/react/useSayrWS.ts","../../src/react/useTask.ts","../../src/react/useComments.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\r\nimport { org, 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<unknown>(null);\r\n\r\n useEffect(() => {\r\n if (!slug) return;\r\n\r\n setLoading(true);\r\n org\r\n .get(slug)\r\n .then(setData)\r\n .catch(setError)\r\n .finally(() => setLoading(false));\r\n }, [slug]);\r\n\r\n return { data, loading, error };\r\n}","import {\r\n Organization,\r\n Label,\r\n Category,\r\n Task,\r\n Comment,\r\n Pagination,\r\n ApiSuccess\r\n} from \"./types\";\r\n\r\nconst API = \"https://sayr.io/api/public\";\r\n\r\nasync function get<T>(url: string): Promise<T> {\r\n const res = await fetch(url);\r\n const json = await res.json();\r\n if (!json.success) throw json;\r\n return json;\r\n}\r\n\r\nexport const org = {\r\n async get(slug: string): Promise<Organization> {\r\n const r = await get<ApiSuccess<Organization>>(\r\n `${API}/organization/${slug}`\r\n );\r\n return r.data;\r\n },\r\n\r\n async labels(slug: string): Promise<Label[]> {\r\n const r = await get<ApiSuccess<Label[]>>(\r\n `${API}/organization/${slug}/labels`\r\n );\r\n return r.data;\r\n },\r\n\r\n async categories(\r\n slug: string,\r\n order: \"asc\" | \"desc\" = \"desc\"\r\n ): Promise<Category[]> {\r\n const r = await get<ApiSuccess<Category[]>>(\r\n `${API}/organization/${slug}/categories?order=${order}`\r\n );\r\n return r.data;\r\n },\r\n\r\n async tasks(\r\n slug: string,\r\n opts?: { order?: \"asc\" | \"desc\"; limit?: number; page?: number }\r\n ): Promise<{ data: Task[]; pagination: Pagination }> {\r\n const q = new URLSearchParams({\r\n order: opts?.order ?? \"desc\",\r\n limit: String(opts?.limit ?? 5),\r\n page: String(opts?.page ?? 1)\r\n });\r\n\r\n const res = await fetch(\r\n `${API}/organization/${slug}/tasks?${q}`\r\n );\r\n const json = await res.json();\r\n if (!json.success) throw json;\r\n\r\n return {\r\n data: json.data,\r\n pagination: json.pagination\r\n };\r\n },\r\n\r\n async task(slug: string, shortId: number): Promise<Task> {\r\n const r = await get<ApiSuccess<Task>>(\r\n `${API}/organization/${slug}/tasks/${shortId}`\r\n );\r\n return r.data;\r\n },\r\n\r\n async comments(\r\n slug: string,\r\n shortId: number,\r\n opts?: { order?: \"asc\" | \"desc\"; limit?: number; page?: number }\r\n ): Promise<{ data: Comment[]; pagination: Pagination }> {\r\n const q = new URLSearchParams({\r\n order: opts?.order ?? \"desc\",\r\n limit: String(opts?.limit ?? 5),\r\n page: String(opts?.page ?? 1)\r\n });\r\n\r\n const res = await fetch(\r\n `${API}/organization/${slug}/tasks/${shortId}/comments?${q}`\r\n );\r\n const json = await res.json();\r\n if (!json.success) throw json;\r\n\r\n return {\r\n data: json.data,\r\n pagination: json.pagination\r\n };\r\n }\r\n};","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\nexport const wsTypes: 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}\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 === wsTypes.PING) {\r\n socket.send(JSON.stringify({ type: wsTypes.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}","import { useEffect, useState } from \"react\";\r\nimport { org, wsTypes } from \"..\";\r\nimport type { Task } from \"../types\";\r\nimport { useSayrWS } from \"./useSayrWS\";\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\r\n function fetchTasks() {\r\n if (!slug) return;\r\n setLoading(true);\r\n org\r\n .tasks(slug)\r\n .then((r) => setTasks(r.data))\r\n .finally(() => setLoading(false));\r\n }\r\n\r\n useEffect(fetchTasks, [slug]);\r\n\r\n useSayrWS(wsUrl, {\r\n [wsTypes.CREATE_TASK]: fetchTasks,\r\n [wsTypes.UPDATE_TASK]: fetchTasks\r\n });\r\n\r\n return { tasks, loading, refetch: fetchTasks };\r\n}","import { useEffect, useRef } from \"react\";\r\nimport { ws } from \"..\";\r\nimport { WSMessageType } from \"../ws\";\r\n\r\ntype Handlers = Partial<\r\n Record<WSMessageType, (data: any, msg: any) => void>\r\n>;\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\r\n useEffect(() => {\r\n if (!wsUrl) return;\r\n\r\n connRef.current = ws(wsUrl, handlers);\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, useState } from \"react\";\r\nimport { org } from \"..\";\r\nimport type { Task } from \"../types\";\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\r\n useEffect(() => {\r\n if (!slug || shortId == null) return;\r\n\r\n setLoading(true);\r\n org\r\n .task(slug, shortId)\r\n .then(setTask)\r\n .finally(() => setLoading(false));\r\n }, [slug, shortId]);\r\n\r\n return { task, loading };\r\n}","import { useEffect, useState } from \"react\";\r\nimport { org, wsTypes } from \"..\";\r\nimport type { Comment } from \"../types\";\r\nimport { useSayrWS } from \"./useSayrWS\";\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\r\n function fetchComments() {\r\n if (!slug || shortId == null) return;\r\n\r\n setLoading(true);\r\n org\r\n .comments(slug, shortId)\r\n .then((r) => setComments(r.data))\r\n .finally(() => setLoading(false));\r\n }\r\n\r\n useEffect(fetchComments, [slug, shortId]);\r\n\r\n useSayrWS(wsUrl, {\r\n [wsTypes.UPDATE_TASK_COMMENTS]: fetchComments\r\n });\r\n\r\n return { comments, loading, refetch: fetchComments };\r\n}"],"mappings":"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","names":["useEffect","useState","useEffect","useEffect","useState","useEffect","useEffect","useState","useState","useEffect","useEffect","useState","useState","useEffect"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sayrio/public",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Sayr.io public REST + WebSocket SDK",
5
5
  "license": "MIT",
6
6
  "type": "module",