@insignia-education/api-sdk-js 0.9.24 → 0.9.26

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/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ 24
package/AGENTS.md ADDED
@@ -0,0 +1,73 @@
1
+ # Insignia Education — API SDK (JavaScript)
2
+
3
+ ## Requirements
4
+ - Node 24 LTS (`nvm use 24`)
5
+
6
+ ## Quick start
7
+ ```bash
8
+ nvm use 24
9
+ npm install
10
+ npm test # runs Jest test suite
11
+ npm run lint # ESLint
12
+ ```
13
+
14
+ ## What this is
15
+ A zero-dependency JavaScript SDK that wraps the Insignia Education API (`/api/v1`).
16
+ Consumed by `insignia-education/front` via `@insignia-education/api-sdk-js`.
17
+
18
+ ## API versioning
19
+ The SDK is versioned to match the API:
20
+
21
+ - `src/index.js` — base client
22
+ - `src/api/index.js` — appends `/api` to the base URL
23
+ - `src/api/v1/index.js` — appends `/v1` → all requests land at `<host>/api/v1/...`
24
+ - **v1 is being finalized. Once stable it is permanently frozen.**
25
+ - A future v2 API will live in `src/api/v2/index.js` (new class, new resource modules).
26
+ - Never modify the URL construction logic in `v1/` to point at a different version.
27
+ - Tests for each version live in `tests/integration/api/v1/` — mirror this structure for v2+.
28
+ - The `upload(path, formData)` method on `Client` sends multipart — use `api.files.upload(fd)` for any file upload, never raw `fetch()`.
29
+
30
+ ## Structure
31
+ ```
32
+ src/
33
+ ├── index.js ← main export
34
+ ├── api/v1/
35
+ │ ├── index.js ← InsigniaApiV1 class (root client)
36
+ │ ├── Auth.js ← /auth endpoints
37
+ │ ├── Courses.js ← /courses endpoints
38
+ │ ├── Users.js ← /users endpoints
39
+ │ └── ... ← one file per API resource
40
+ ```
41
+
42
+ ## Usage pattern
43
+ ```js
44
+ import InsigniaApiV1 from '@insignia-education/api-sdk-js/api/v1';
45
+ const api = new InsigniaApiV1('http://localhost:8000');
46
+
47
+ api.auth.login({ email, password });
48
+ api.courses.get(null, { page: 1 });
49
+ api.users.cashReceivers();
50
+ ```
51
+
52
+ ## Conventions
53
+ - One class per API resource
54
+ - Methods match HTTP verbs: `get`, `post`, `put`, `patch`, `delete`
55
+ - No external runtime dependencies — only Node built-ins
56
+ - ESM modules (`"type": "module"`)
57
+
58
+ ## Adding a new resource
59
+ 1. Create `src/api/v1/ResourceName.js` with a class that receives the client
60
+ 2. Register it in `src/api/v1/index.js`
61
+ 3. Write integration tests in `tests/integration/api/v1/resource-name.test.js`
62
+
63
+ ## Internationalisation (i18n)
64
+ The SDK is language-neutral — it must never contain human-readable strings.
65
+
66
+ - Do not include hardcoded error messages or labels in SDK source.
67
+ - Error objects thrown by the SDK must expose a machine-readable `status` (HTTP code) and `data` (raw API body). The consuming app handles translation.
68
+ - Do not add locale/language logic to the SDK — that belongs to the frontend.
69
+
70
+ ## Never do
71
+ - Don't add runtime dependencies
72
+ - Don't change the constructor signature of `InsigniaApiV1`
73
+ - Don't hardcode API base URLs — always receive from constructor
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@insignia-education/api-sdk-js",
3
- "version": "0.9.24",
3
+ "version": "0.9.26",
4
4
  "description": "JavaScript SDK for the Insignia Education API",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -20,7 +20,7 @@
20
20
  "author": "Insignia Education",
21
21
  "license": "MIT",
22
22
  "engines": {
23
- "node": ">=25.5.0"
23
+ "node": ">=24"
24
24
  },
25
25
  "bugs": {
26
26
  "url": "https://github.com/insignia-education/api-sdk-js/issues"
package/src/Client.js CHANGED
@@ -76,6 +76,30 @@ export default class InsigniaClient {
76
76
  if (body !== null) options.body = JSON.stringify(body);
77
77
  const response = await fetch(`${this.#baseUrl}${path}`, options);
78
78
  this.#storeCookies(response);
79
+ if (!response.ok) {
80
+ const err = new Error(`HTTP ${response.status}`);
81
+ err.status = response.status;
82
+ try { err.data = await this.#parseResponse(response); } catch (_) {}
83
+ throw err;
84
+ }
85
+ const data = await this.#parseResponse(response);
86
+ return data?.success ? data.response : data;
87
+ }
88
+
89
+ async upload(path, formData) {
90
+ const headers = { 'Accept': 'application/json' };
91
+ const cookie = this.#cookieHeader();
92
+ if (cookie) headers.Cookie = cookie;
93
+ const response = await fetch(`${this.#baseUrl}${path}`, {
94
+ method: 'PUT', headers, credentials: 'include', body: formData,
95
+ });
96
+ this.#storeCookies(response);
97
+ if (!response.ok) {
98
+ const err = new Error(`HTTP ${response.status}`);
99
+ err.status = response.status;
100
+ try { err.data = await this.#parseResponse(response); } catch (_) {}
101
+ throw err;
102
+ }
79
103
  const data = await this.#parseResponse(response);
80
104
  return data?.success ? data.response : data;
81
105
  }
@@ -5,6 +5,7 @@ export default class Files {
5
5
  this.#client = client;
6
6
  }
7
7
 
8
- get(id = null) { return id ? this.#client.get(`/files/${id}`) : this.#client.get('/files'); }
9
- delete(id) { return this.#client.del(`/files/${id}`); }
8
+ get(id = null) { return id ? this.#client.get(`/files/${id}`) : this.#client.get('/files'); }
9
+ upload(formData) { return this.#client.upload('/files', formData); }
10
+ delete(id) { return this.#client.del(`/files/${id}`); }
10
11
  }
@@ -1,3 +1,4 @@
1
+ import { Blob } from 'buffer';
1
2
  import InsigniaApiV1 from '../../../../src/api/v1/index.js';
2
3
 
3
4
  const api = new InsigniaApiV1(process.env.INSIGNIA_EDUCATION_API_BASE_URL);
@@ -16,10 +17,23 @@ describe('api/v1/files', () => {
16
17
  expect(Array.isArray(response)).toBe(true);
17
18
  response.forEach(file => {
18
19
  expect(file["id"]).toBeDefined();
19
- expect(file["path"]).toBeDefined();
20
20
  expect(file["created_at"]).toBeDefined();
21
21
  expect(file["updated_at"]).toBeDefined();
22
22
  });
23
23
  });
24
24
  });
25
+
26
+ test('upload | authenticated', async () => {
27
+ await login();
28
+ const blob = new Blob(['test'], { type: 'image/png' });
29
+ const fd = new FormData();
30
+ fd.append('file', blob, 'test.png');
31
+ fd.append('type', 'uploads');
32
+ await api.files.upload(fd)
33
+ .then(response => {
34
+ expect(response).toBeDefined();
35
+ expect(response['url']).toBeDefined();
36
+ expect(response['id']).toBeDefined();
37
+ });
38
+ });
25
39
  });