@sayrio/public 0.1.4 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  # @sayrio/public
3
2
 
4
3
  Public JavaScript & TypeScript SDK for **Sayr.io**.
@@ -8,28 +7,34 @@ real‑time updates via WebSockets.
8
7
  - ✅ REST + WebSocket
9
8
  - ✅ Browser‑safe
10
9
  - ✅ TypeScript first
11
- - ✅ React hooks included (`/react`)
12
10
  - ✅ Zero runtime dependencies
11
+ - ✅ Versioned API (`v1`)
12
+
13
+ > React hooks are available via the **`@sayrio/public/react`** sub‑path export.
13
14
 
14
15
  ---
15
16
 
16
17
  ## Installation
17
18
 
18
- Install the public Sayr SDK using your preferred package manager:
19
-
20
19
  ```bash
21
20
  npm install @sayrio/public
22
21
  ```
22
+
23
23
  or
24
+
24
25
  ```bash
25
26
  pnpm add @sayrio/public
26
27
  ```
27
28
 
29
+ ---
30
+
28
31
  ## Usage
29
32
 
30
33
  ### Basic Usage (REST)
31
34
 
32
- Fetch public organization data using the REST API:
35
+ Fetch public organization data.
36
+
37
+ `Sayr.org` is an alias for the current API version (`v1`):
33
38
 
34
39
  ```ts
35
40
  import Sayr from "@sayrio/public";
@@ -39,111 +44,116 @@ const org = await Sayr.org.get("acme");
39
44
  console.log(org.name);
40
45
  ```
41
46
 
47
+ You can also use the versioned API explicitly:
48
+
49
+ ```ts
50
+ const org = await Sayr.v1.org.get("acme");
51
+ ```
52
+
42
53
  ---
43
54
 
44
55
  ### Listing Tasks
45
56
 
46
- Retrieve tasks for an organization with pagination and ordering support:
57
+ Retrieve tasks for an organization:
47
58
 
48
59
  ```ts
49
- const { data: tasks, pagination } =
50
- await Sayr.org.tasks("acme", {
51
- order: "desc",
52
- limit: 10
53
- });
60
+ const { data: tasks } = await Sayr.org.tasks.list("acme", {
61
+ order: "desc",
62
+ limit: 10
63
+ });
54
64
 
55
65
  console.log(tasks);
56
66
  ```
57
67
 
58
68
  ---
59
69
 
70
+ ### Fetching a Single Task
71
+
72
+ ```ts
73
+ const task = await Sayr.org.tasks.get("acme", 42);
74
+
75
+ console.log(task.title);
76
+ ```
77
+
78
+ ---
79
+
60
80
  ### Task Comments
61
81
 
62
82
  Fetch comments for a specific task:
63
83
 
64
84
  ```ts
65
85
  const { data: comments } =
66
- await Sayr.org.comments("acme", 12);
86
+ await Sayr.org.comments.list("acme", 42);
67
87
 
68
88
  console.log(comments);
69
89
  ```
70
90
 
71
91
  ---
72
92
 
73
- ### Real-Time Updates (WebSocket)
74
-
75
- Subscribe to public real-time events using WebSockets:
93
+ ### Labels & Categories
76
94
 
77
95
  ```ts
78
- Sayr.ws(org.wsUrl, {
79
- [Sayr.wsTypes.UPDATE_ORG]: (data) => {
80
- console.log("Organization updated", data);
81
- },
82
-
83
- [Sayr.wsTypes.UPDATE_TASK]: (task) => {
84
- console.log("Task updated", task);
85
- }
86
- });
96
+ const labels = await Sayr.org.labels.list("acme");
97
+ const categories = await Sayr.org.categories.list("acme");
87
98
  ```
88
99
 
89
- ### WebSocket Features
90
- - Automatic reconnection
91
- - Heartbeat support (PING / PONG)
92
- - Typed event constants
93
- - Public-safe payloads only
94
-
95
100
  ---
96
101
 
97
- ## React Hooks
102
+ ## Authenticated User (`/me`)
98
103
 
99
- React hooks are available via a dedicated sub-path export:
104
+ The `/me` namespace provides **read‑only access** to the currently
105
+ authenticated user.
100
106
 
101
- ```ts
102
- import { useOrg, useTasks, useComments } from "@sayrio/public/react";
103
- ```
107
+ > Authentication is required.
108
+ > Set a token using `Sayr.client.setToken(...)`.
104
109
 
105
110
  ---
106
111
 
107
- ### `useOrg`
112
+ ### Fetch Current User
113
+
114
+ ```ts
115
+ Sayr.client.setToken(token);
108
116
 
109
- Fetch and subscribe to an organization:
117
+ const me = await Sayr.me.get();
110
118
 
111
- ```tsx
112
- const { data: org, loading } = useOrg("acme");
119
+ console.log(me.email);
113
120
  ```
114
121
 
115
122
  ---
116
123
 
117
- ### `useTasks`
124
+ ### List Your Organizations
118
125
 
119
- Fetch and subscribe to tasks for an organization:
126
+ ```ts
127
+ const orgs = await Sayr.me.organizations();
120
128
 
121
- ```tsx
122
- const { tasks } = useTasks("acme", org?.wsUrl);
129
+ console.log(orgs);
123
130
  ```
124
131
 
125
132
  ---
126
133
 
127
- ### `useComments`
134
+ ## Real‑Time Updates (WebSocket)
128
135
 
129
- Fetch and subscribe to comments for a task:
136
+ Subscribe to public real‑time events using WebSockets:
130
137
 
131
- ```tsx
132
- const { comments } = useComments(
133
- "acme",
134
- task.shortId,
135
- org?.wsUrl
136
- );
138
+ ```ts
139
+ Sayr.ws(org.wsUrl, {
140
+ [Sayr.WS_EVENTS.UPDATE_TASK]: (task) => {
141
+ console.log("Task updated", task);
142
+ }
143
+ });
137
144
  ```
138
145
 
139
- Hooks automatically refresh when relevant WebSocket events occur.
146
+ ### WebSocket Features
147
+
148
+ - Automatic reconnection
149
+ - Heartbeat support (PING / PONG)
150
+ - Typed event constants
151
+ - Public‑safe payloads only
140
152
 
141
153
  ---
142
154
 
143
155
  ## Browser Usage (No Bundler)
144
156
 
145
- The SDK can be used directly in the browser via ESM:
146
-
147
157
  ```html
148
158
  <script type="module">
149
159
  import Sayr from "https://esm.sh/@sayrio/public";
@@ -157,17 +167,59 @@ The SDK can be used directly in the browser via ESM:
157
167
 
158
168
  ## API
159
169
 
160
- ### `Sayr.org`
170
+ ### `Sayr.org` (latest)
171
+
172
+ Alias for `Sayr.v1.org`.
161
173
 
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 |
174
+ #### Organization
170
175
 
176
+ | Method | Description |
177
+ | ----------- | --------------------------- |
178
+ | `get(slug)` | Fetch a public organization |
179
+
180
+ ---
181
+
182
+ #### Tasks
183
+
184
+ | Method | Description |
185
+ | --------------------------- | ---------------------- |
186
+ | `tasks.list(slug, opts?)` | List tasks (paginated) |
187
+ | `tasks.get(slug, shortId)` | Fetch a single task |
188
+
189
+ ---
190
+
191
+ #### Comments
192
+
193
+ | Method | Description |
194
+ | ------------------------------------- | ------------------ |
195
+ | `comments.list(slug, shortId, opts?)` | List task comments |
196
+
197
+ ---
198
+
199
+ #### Labels
200
+
201
+ | Method | Description |
202
+ | ------------------- | ------------------------ |
203
+ | `labels.list(slug)` | List organization labels |
204
+
205
+ ---
206
+
207
+ #### Categories
208
+
209
+ | Method | Description |
210
+ | ------------------------------ | --------------- |
211
+ | `categories.list(slug, order?)` | List categories |
212
+
213
+ ---
214
+
215
+ ### `Sayr.me`
216
+
217
+ Authenticated user endpoints.
218
+
219
+ | Method | Description |
220
+ | ------------------- | -------------------------------------- |
221
+ | `get()` | Fetch the current authenticated user |
222
+ | `organizations()` | List organizations the user belongs to |
171
223
 
172
224
  ---
173
225
 
@@ -180,19 +232,19 @@ const conn = Sayr.ws(wsUrl, {
180
232
  UPDATE_TASK: () => {}
181
233
  });
182
234
 
183
- // Close the connection when no longer needed
184
235
  conn.close();
185
236
  ```
186
237
 
187
238
  ---
188
239
 
189
- ### `Sayr.wsTypes`
240
+ ### `WS_EVENTS`
241
+
242
+ Typed WebSocket event constants:
190
243
 
191
244
  ```ts
192
- Sayr.wsTypes.UPDATE_TASK
193
- Sayr.wsTypes.UPDATE_ORG
194
- Sayr.wsTypes.ERROR
195
- // ...
245
+ Sayr.WS_EVENTS.UPDATE_TASK;
246
+ Sayr.WS_EVENTS.UPDATE_ORG;
247
+ Sayr.WS_EVENTS.ERROR;
196
248
  ```
197
249
 
198
250
  ---
@@ -202,5 +254,11 @@ Sayr.wsTypes.ERROR
202
254
  This package ships with full TypeScript definitions:
203
255
 
204
256
  ```ts
205
- import type { Organization, Task } from "@sayrio/public";
257
+ import type {
258
+ Organization,
259
+ Task,
260
+ Comment
261
+ } from "@sayrio/public";
206
262
  ```
263
+
264
+ ---
package/dist/index.cjs CHANGED
@@ -20,82 +20,273 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
- default: () => index_default,
24
- org: () => org,
25
- ws: () => ws,
26
- wsTypes: () => wsTypes
23
+ SayrClient: () => SayrClient,
24
+ SayrV1: () => SayrV1,
25
+ SayrWS: () => SayrWS,
26
+ SayrWSEvents: () => SayrWSEvents,
27
+ WS_EVENTS: () => WS_EVENTS,
28
+ buildPaginationParams: () => buildPaginationParams,
29
+ default: () => index_default
27
30
  });
28
31
  module.exports = __toCommonJS(index_exports);
29
32
 
30
- // src/org.ts
31
- var API = "https://sayr.io/api/public";
32
- async function get(url) {
33
- const res = await fetch(url);
34
- const json = await res.json();
35
- if (!json.success) throw json;
33
+ // src/client/index.ts
34
+ var DEFAULT_API = "https://api.sayr.io";
35
+ var config = {
36
+ fetch: globalThis.fetch,
37
+ baseUrl: DEFAULT_API
38
+ };
39
+ var hooks = {};
40
+ function setToken(token) {
41
+ config.token = token;
42
+ }
43
+ function setHeaders(headers) {
44
+ config.headers = {
45
+ ...config.headers,
46
+ ...headers
47
+ };
48
+ }
49
+ function setFetch(fn) {
50
+ config.fetch = fn;
51
+ }
52
+ function setBaseUrl(url) {
53
+ config.baseUrl = url.replace(/\/$/, "");
54
+ }
55
+ function setHooks(h) {
56
+ Object.assign(hooks, h);
57
+ }
58
+ function resetClient() {
59
+ config.token = void 0;
60
+ config.headers = void 0;
61
+ config.baseUrl = DEFAULT_API;
62
+ }
63
+ async function request(path, opts = {}) {
64
+ let url;
65
+ try {
66
+ url = path.startsWith("http") ? path : `${config.baseUrl}${path}`;
67
+ } catch (err) {
68
+ throw err;
69
+ }
70
+ try {
71
+ hooks.onRequest?.(url, opts);
72
+ } catch (err) {
73
+ throw err;
74
+ }
75
+ let res;
76
+ try {
77
+ const method = opts.method ?? "GET";
78
+ if (method === "GET" && opts.body !== void 0) {
79
+ throw new Error("GET request cannot have a body");
80
+ }
81
+ res = await fetch(url, {
82
+ method,
83
+ headers: {
84
+ ...config.token ? { Authorization: `Bearer ${config.token}` } : {},
85
+ ...opts.body && method !== "GET" ? { "Content-Type": "application/json" } : {},
86
+ ...config.headers,
87
+ ...opts.headers
88
+ },
89
+ body: opts.body && method !== "GET" ? JSON.stringify(opts.body) : void 0,
90
+ signal: opts.signal
91
+ });
92
+ } catch (err) {
93
+ const error = {
94
+ success: false,
95
+ error: "NETWORK_ERROR",
96
+ message: "Failed to reach Sayr API",
97
+ // 👇 keep raw error so we can see it
98
+ // @ts-ignore
99
+ raw: err
100
+ };
101
+ hooks.onError?.(error);
102
+ throw error;
103
+ }
104
+ try {
105
+ hooks.onResponse?.(res);
106
+ } catch (err) {
107
+ throw err;
108
+ }
109
+ let json;
110
+ try {
111
+ json = await res.json();
112
+ } catch (err) {
113
+ const error = {
114
+ success: false,
115
+ error: "INVALID_RESPONSE",
116
+ message: "Server returned invalid JSON",
117
+ status: res.status
118
+ };
119
+ hooks.onError?.(error);
120
+ throw error;
121
+ }
122
+ if (!res.ok || !json.success) {
123
+ const error = {
124
+ ...json,
125
+ success: false,
126
+ status: res.status
127
+ };
128
+ hooks.onError?.(error);
129
+ throw error;
130
+ }
36
131
  return json;
37
132
  }
38
- var org = {
39
- async get(slug) {
40
- const r = await get(
41
- `${API}/organization/${slug}`
133
+
134
+ // src/api/v1/org/org.ts
135
+ var org_default = {
136
+ /**
137
+ * Fetches a public organization by slug.
138
+ *
139
+ * @since v1.0.0
140
+ */
141
+ async get(slug, opts) {
142
+ const r = await request(
143
+ `/v1/organization/${slug}`,
144
+ opts
42
145
  );
43
146
  return r.data;
44
- },
45
- async labels(slug) {
46
- const r = await get(
47
- `${API}/organization/${slug}/labels`
147
+ }
148
+ };
149
+
150
+ // src/shared/index.ts
151
+ function buildPaginationParams(params) {
152
+ return new URLSearchParams({
153
+ order: params?.order ?? "desc",
154
+ limit: String(params?.limit ?? 5),
155
+ page: String(params?.page ?? 1)
156
+ });
157
+ }
158
+
159
+ // src/api/v1/org/tasks.ts
160
+ var tasks_default = {
161
+ /**
162
+ * Lists public tasks for an organization.
163
+ *
164
+ * @since v1.0.0
165
+ */
166
+ async list(slug, params, opts) {
167
+ const q = buildPaginationParams(params);
168
+ const r = await request(
169
+ `/v1/organization/${slug}/tasks?${q}`,
170
+ opts
48
171
  );
49
- return r.data;
172
+ return {
173
+ data: r.data,
174
+ pagination: r.pagination
175
+ };
50
176
  },
51
- async categories(slug, order = "desc") {
52
- const r = await get(
53
- `${API}/organization/${slug}/categories?order=${order}`
177
+ /**
178
+ * Fetches a single public task by short ID.
179
+ *
180
+ * @since v1.0.0
181
+ */
182
+ async get(slug, shortId, opts) {
183
+ const r = await request(
184
+ `/v1/organization/${slug}/tasks/${shortId}`,
185
+ opts
54
186
  );
55
187
  return r.data;
56
- },
57
- async tasks(slug, opts) {
58
- const q = new URLSearchParams({
59
- order: opts?.order ?? "desc",
60
- limit: String(opts?.limit ?? 5),
61
- page: String(opts?.page ?? 1)
62
- });
63
- const res = await fetch(
64
- `${API}/organization/${slug}/tasks?${q}`
188
+ }
189
+ };
190
+
191
+ // src/api/v1/org/comments.ts
192
+ var comments_default = {
193
+ /**
194
+ * Lists public comments for a task.
195
+ *
196
+ * @since v1.0.0
197
+ */
198
+ async list(slug, shortId, params, opts) {
199
+ const q = buildPaginationParams(params);
200
+ const r = await request(
201
+ `/v1/organization/${slug}/tasks/${shortId}/comments?${q}`,
202
+ opts
65
203
  );
66
- const json = await res.json();
67
- if (!json.success) throw json;
68
204
  return {
69
- data: json.data,
70
- pagination: json.pagination
205
+ data: r.data,
206
+ pagination: r.pagination
71
207
  };
72
- },
73
- async task(slug, shortId) {
74
- const r = await get(
75
- `${API}/organization/${slug}/tasks/${shortId}`
208
+ }
209
+ };
210
+
211
+ // src/api/v1/org/labels.ts
212
+ var labels_default = {
213
+ /**
214
+ * Lists public labels for an organization.
215
+ *
216
+ * @since v1.0.0
217
+ */
218
+ async list(slug, opts) {
219
+ const r = await request(
220
+ `/v1/organization/${slug}/labels`,
221
+ opts
222
+ );
223
+ return r.data;
224
+ }
225
+ };
226
+
227
+ // src/api/v1/org/categories.ts
228
+ var categories_default = {
229
+ /**
230
+ * Lists public categories for an organization.
231
+ *
232
+ * @since v1.0.0
233
+ */
234
+ async list(slug, order = "desc", opts) {
235
+ const r = await request(
236
+ `/v1/organization/${slug}/categories?order=${order}`,
237
+ opts
238
+ );
239
+ return r.data;
240
+ }
241
+ };
242
+
243
+ // src/api/v1/org/index.ts
244
+ var OrgAPI = {
245
+ ...org_default,
246
+ tasks: tasks_default,
247
+ comments: comments_default,
248
+ labels: labels_default,
249
+ categories: categories_default
250
+ };
251
+ var org_default2 = OrgAPI;
252
+
253
+ // src/api/v1/me/index.ts
254
+ var me_default = {
255
+ /**
256
+ * Fetches the currently authenticated user.
257
+ *
258
+ * @since v1.0.0
259
+ */
260
+ async get(opts) {
261
+ const r = await request(
262
+ "/me",
263
+ opts
76
264
  );
77
265
  return r.data;
78
266
  },
79
- async comments(slug, shortId, opts) {
80
- const q = new URLSearchParams({
81
- order: opts?.order ?? "desc",
82
- limit: String(opts?.limit ?? 5),
83
- page: String(opts?.page ?? 1)
84
- });
85
- const res = await fetch(
86
- `${API}/organization/${slug}/tasks/${shortId}/comments?${q}`
267
+ /**
268
+ * Lists organizations the current user belongs to.
269
+ *
270
+ * @since v1.0.0
271
+ */
272
+ async organizations(opts) {
273
+ const r = await request(
274
+ "/organizations",
275
+ opts
87
276
  );
88
- const json = await res.json();
89
- if (!json.success) throw json;
90
- return {
91
- data: json.data,
92
- pagination: json.pagination
93
- };
277
+ return r.data;
94
278
  }
95
279
  };
96
280
 
97
- // src/ws.ts
98
- var wsTypes = {
281
+ // src/api/v1/index.ts
282
+ var v1 = {
283
+ org: org_default2,
284
+ me: me_default
285
+ };
286
+ var v1_default = v1;
287
+
288
+ // src/ws/types.ts
289
+ var WS_EVENTS = {
99
290
  CONNECTION_STATUS: "CONNECTION_STATUS",
100
291
  SUBSCRIBED: "SUBSCRIBED",
101
292
  ERROR: "ERROR",
@@ -112,6 +303,8 @@ var wsTypes = {
112
303
  UPDATE_ISSUE_TEMPLATES: "UPDATE_ISSUE_TEMPLATES",
113
304
  DISCONNECTED: "DISCONNECTED"
114
305
  };
306
+
307
+ // src/ws/index.ts
115
308
  function ws(url, handlers = {}) {
116
309
  if (!url) {
117
310
  throw new Error(
@@ -126,8 +319,8 @@ function ws(url, handlers = {}) {
126
319
  socket = new WebSocket(url);
127
320
  socket.onmessage = (e) => {
128
321
  const msg = JSON.parse(e.data);
129
- if (msg.type === wsTypes.PING) {
130
- socket.send(JSON.stringify({ type: wsTypes.PONG }));
322
+ if (msg.type === WS_EVENTS.PING) {
323
+ socket.send(JSON.stringify({ type: WS_EVENTS.PONG }));
131
324
  return;
132
325
  }
133
326
  handlers[msg.type]?.(msg.data, msg);
@@ -148,12 +341,36 @@ function ws(url, handlers = {}) {
148
341
  }
149
342
 
150
343
  // src/index.ts
151
- var Sayr = { org, ws, wsTypes };
344
+ var SayrV1 = v1_default;
345
+ var SayrWS = ws;
346
+ var SayrWSEvents = WS_EVENTS;
347
+ var SayrClient = {
348
+ setToken,
349
+ setHeaders,
350
+ setBaseUrl,
351
+ resetClient,
352
+ setHooks,
353
+ setFetch
354
+ };
355
+ var Sayr = {
356
+ // client configuration
357
+ client: SayrClient,
358
+ // APIs
359
+ v1: v1_default,
360
+ org: v1_default.org,
361
+ me: v1_default.me,
362
+ // realtime
363
+ ws,
364
+ WS_EVENTS
365
+ };
152
366
  var index_default = Sayr;
153
367
  // Annotate the CommonJS export names for ESM import in node:
154
368
  0 && (module.exports = {
155
- org,
156
- ws,
157
- wsTypes
369
+ SayrClient,
370
+ SayrV1,
371
+ SayrWS,
372
+ SayrWSEvents,
373
+ WS_EVENTS,
374
+ buildPaginationParams
158
375
  });
159
376
  //# sourceMappingURL=index.cjs.map