onlybase-sdk 0.0.1
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/PUBLISH_NPM.md +120 -0
- package/index.ts +155 -0
- package/package.json +10 -0
package/PUBLISH_NPM.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Publish the SDK to npm (under your account)
|
|
2
|
+
|
|
3
|
+
Publish **without** the private `@onlybase` scope so the package lives under your npm user.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Choose the package name
|
|
8
|
+
|
|
9
|
+
- **Unscoped** (recommended): `onlybase-sdk` — install with `npm install onlybase-sdk`. The name must be free on npm.
|
|
10
|
+
- **Scoped under your user**: `@YOUR_NPM_USERNAME/onlybase-sdk` — install with `npm install @YOUR_NPM_USERNAME/onlybase-sdk`. Replace `YOUR_NPM_USERNAME` with your npm login.
|
|
11
|
+
|
|
12
|
+
Edit `packages/sdk/package.json` and set `"name"` to your choice (see step 3).
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 2. Log in to npm
|
|
17
|
+
|
|
18
|
+
If you don’t have an account: [sign up at npmjs.com](https://www.npmjs.com/signup).
|
|
19
|
+
|
|
20
|
+
In a terminal:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm login
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Enter your npm **username**, **password**, and **email** when asked. You can use a profile or access token instead of password if you use 2FA.
|
|
27
|
+
|
|
28
|
+
Check you’re logged in:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm whoami
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 2b. If you get "403 - Two-factor authentication required"
|
|
37
|
+
|
|
38
|
+
npm requires **two-factor authentication (2FA)** to publish packages (or a token that can bypass it).
|
|
39
|
+
|
|
40
|
+
**Option A — Enable 2FA (recommended):**
|
|
41
|
+
|
|
42
|
+
1. Go to [npmjs.com](https://www.npmjs.com) → log in → click your avatar → **Account settings**.
|
|
43
|
+
2. Under **Security**, enable **Two-Factor Authentication** (auth-only or auth-and-writes).
|
|
44
|
+
3. Run `npm publish` again. npm will prompt for your one-time code (app or SMS).
|
|
45
|
+
|
|
46
|
+
**Option B — Use an automation token (e.g. for CI):**
|
|
47
|
+
|
|
48
|
+
1. On npm: **Account settings** → **Access Tokens** → **Generate New Token**.
|
|
49
|
+
2. Choose **Granular Access Token**; set package access and enable **Bypass 2FA for publish** if you need to publish without a code.
|
|
50
|
+
3. Use the token when logging in: `npm login` and use the token as the password, or set `NPM_TOKEN` in the environment.
|
|
51
|
+
|
|
52
|
+
After 2FA is enabled or the token is set up, run `npm publish` again from `packages/sdk`.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## 3. Set the package name and version
|
|
57
|
+
|
|
58
|
+
From the **repo root**:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
cd packages/sdk
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Edit `package.json`:
|
|
65
|
+
|
|
66
|
+
- Set **name** to your chosen name, e.g.:
|
|
67
|
+
- `"name": "onlybase-sdk"` (unscoped), or
|
|
68
|
+
- `"name": "@YOUR_NPM_USERNAME/onlybase-sdk"` (scoped).
|
|
69
|
+
- Bump **version** if you’ve already published before (e.g. `0.0.2`). For the first publish, `0.0.1` is fine.
|
|
70
|
+
|
|
71
|
+
Optional but recommended for the npm listing:
|
|
72
|
+
|
|
73
|
+
- **description**: short one-liner.
|
|
74
|
+
- **keywords**: e.g. `["onlybase", "backend", "sdk", "realtime", "api"]`.
|
|
75
|
+
- **repository**: your Git URL.
|
|
76
|
+
- **license**: e.g. `"MIT"`.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 4. Publish
|
|
81
|
+
|
|
82
|
+
From **packages/sdk**:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
npm publish
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
- **Unscoped** (`onlybase-sdk`): publishes as a public package (no extra flags).
|
|
89
|
+
- **Scoped** (`@youruser/onlybase-sdk`): npm will treat it as restricted by default. To make it public:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npm publish --access public
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
After a short delay the package will appear on [npmjs.com](https://www.npmjs.com) and users can install it with the name you chose.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## 5. Update your apps to use the published package
|
|
100
|
+
|
|
101
|
+
Once the package is on npm, you can stop using the workspace alias in apps that use it:
|
|
102
|
+
|
|
103
|
+
- In `apps/dashboard`, `apps/example`, etc., in `package.json` replace:
|
|
104
|
+
- `"@onlybase/sdk": "workspace:*"`
|
|
105
|
+
with
|
|
106
|
+
- `"onlybase-sdk": "^0.0.1"` (or `"@YOUR_NPM_USERNAME/onlybase-sdk": "^0.0.1"`).
|
|
107
|
+
|
|
108
|
+
Then run `bun install` (or `npm install`) and update imports from `@onlybase/sdk` to your published package name (e.g. `onlybase-sdk`).
|
|
109
|
+
If you keep the same export names in the SDK, only the import path changes.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Quick reference
|
|
114
|
+
|
|
115
|
+
| Step | Command / action |
|
|
116
|
+
|------|-------------------|
|
|
117
|
+
| Login | `npm login` then `npm whoami` |
|
|
118
|
+
| Name in package.json | `"onlybase-sdk"` or `"@YOUR_USER/onlybase-sdk"` |
|
|
119
|
+
| Publish (unscoped) | `cd packages/sdk && npm publish` |
|
|
120
|
+
| Publish (scoped, public) | `cd packages/sdk && npm publish --access public` |
|
package/index.ts
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
const defaultBaseUrl = 'https://api.onlybase.app';
|
|
2
|
+
const defaultBaseWsUrl = 'wss://api.onlybase.app/ws';
|
|
3
|
+
|
|
4
|
+
type Result<T> = {
|
|
5
|
+
data: T;
|
|
6
|
+
error?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/** Payload shape for subscription events: insert, update, delete */
|
|
10
|
+
export type SubscriptionEvent = 'insert' | 'update' | 'delete';
|
|
11
|
+
|
|
12
|
+
/** Message received on subscription; server sends { event, collection, payload } */
|
|
13
|
+
export type SubscriptionMessage = {
|
|
14
|
+
event: string;
|
|
15
|
+
collection: string;
|
|
16
|
+
payload: unknown;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function getBaseUrl(baseUrl?: string): string {
|
|
20
|
+
return baseUrl ?? defaultBaseUrl;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getWsUrl(baseUrl?: string): string {
|
|
24
|
+
const base = getBaseUrl(baseUrl);
|
|
25
|
+
try {
|
|
26
|
+
const u = new URL(base);
|
|
27
|
+
u.protocol = u.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
28
|
+
u.pathname = u.pathname.replace(/\/?$/, '') + '/ws';
|
|
29
|
+
return u.toString();
|
|
30
|
+
} catch {
|
|
31
|
+
return defaultBaseWsUrl;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function client(apiKey: string, baseUrl?: string) {
|
|
36
|
+
const base = getBaseUrl(baseUrl);
|
|
37
|
+
const headers: Record<string, string> = {
|
|
38
|
+
'Content-Type': 'application/json',
|
|
39
|
+
'x-api-key': apiKey,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
collection: <T>(name: string) => ({
|
|
44
|
+
async create(data: T | T[]) {
|
|
45
|
+
const response = await fetch(
|
|
46
|
+
`${base}/v1/collections/${encodeURIComponent(name)}/rows`,
|
|
47
|
+
{
|
|
48
|
+
method: 'POST',
|
|
49
|
+
headers,
|
|
50
|
+
body: JSON.stringify(data),
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
return response.json() as Promise<Result<T | T[]>>;
|
|
54
|
+
},
|
|
55
|
+
async list(params?: { limit?: number; offset?: number; orderBy?: string; order?: 'asc' | 'desc' }) {
|
|
56
|
+
const { limit = 100, offset = 0, orderBy, order } = params || {};
|
|
57
|
+
const url = new URL(
|
|
58
|
+
`${base}/v1/collections/${encodeURIComponent(name)}/rows`
|
|
59
|
+
);
|
|
60
|
+
url.searchParams.set('limit', String(limit));
|
|
61
|
+
url.searchParams.set('offset', String(offset));
|
|
62
|
+
if (orderBy) url.searchParams.set('order_by', orderBy);
|
|
63
|
+
if (order) url.searchParams.set('order', order);
|
|
64
|
+
const response = await fetch(url.toString(), { method: 'GET', headers });
|
|
65
|
+
return response.json() as Promise<Result<T[]>>;
|
|
66
|
+
},
|
|
67
|
+
async one(id: string): Promise<Result<T>> {
|
|
68
|
+
const response = await fetch(
|
|
69
|
+
`${base}/v1/collections/${encodeURIComponent(name)}/rows/${encodeURIComponent(id)}`,
|
|
70
|
+
{ method: 'GET', headers }
|
|
71
|
+
);
|
|
72
|
+
return response.json() as Promise<Result<T>>;
|
|
73
|
+
},
|
|
74
|
+
async update(id: string, data: Partial<T>) {
|
|
75
|
+
const response = await fetch(
|
|
76
|
+
`${base}/v1/collections/${encodeURIComponent(name)}/rows/${encodeURIComponent(id)}`,
|
|
77
|
+
{
|
|
78
|
+
method: 'PATCH',
|
|
79
|
+
headers,
|
|
80
|
+
body: JSON.stringify(data),
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
return response.json() as Promise<Result<T>>;
|
|
84
|
+
},
|
|
85
|
+
async delete(id: string) {
|
|
86
|
+
const response = await fetch(
|
|
87
|
+
`${base}/v1/collections/${encodeURIComponent(name)}/rows/${encodeURIComponent(id)}`,
|
|
88
|
+
{ method: 'DELETE', headers }
|
|
89
|
+
);
|
|
90
|
+
return response.json() as Promise<Result<{ success: boolean }>>;
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
subscribe(
|
|
94
|
+
events: SubscriptionEvent,
|
|
95
|
+
onMessage: (message: SubscriptionMessage) => void
|
|
96
|
+
) {
|
|
97
|
+
const wsBase = getWsUrl(baseUrl);
|
|
98
|
+
const url = new URL(wsBase);
|
|
99
|
+
url.searchParams.set('api_key', apiKey);
|
|
100
|
+
url.searchParams.set('collection', name);
|
|
101
|
+
url.searchParams.set('events', events);
|
|
102
|
+
|
|
103
|
+
let ws: WebSocket;
|
|
104
|
+
let closed = false;
|
|
105
|
+
const reconnectDelayMs = 2000;
|
|
106
|
+
|
|
107
|
+
function connect() {
|
|
108
|
+
if (closed) return;
|
|
109
|
+
ws = new WebSocket(url.toString());
|
|
110
|
+
|
|
111
|
+
ws.onmessage = (e) => {
|
|
112
|
+
try {
|
|
113
|
+
const msg = JSON.parse(e.data as string) as SubscriptionMessage;
|
|
114
|
+
if (msg.event && msg.collection !== undefined) {
|
|
115
|
+
onMessage(msg);
|
|
116
|
+
}
|
|
117
|
+
} catch {
|
|
118
|
+
// ignore parse errors
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
ws.onclose = () => {
|
|
123
|
+
if (closed) return;
|
|
124
|
+
setTimeout(connect, reconnectDelayMs);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
ws.onerror = () => {
|
|
128
|
+
// close will follow; reconnect there
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
connect();
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
unsubscribe() {
|
|
136
|
+
closed = true;
|
|
137
|
+
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
|
|
138
|
+
ws.close();
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
},
|
|
143
|
+
}),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Factory for creating a configured OnlyBase client
|
|
148
|
+
export const OnlyBase = client;
|
|
149
|
+
|
|
150
|
+
// Convenience alias so you can do: only(apiKey, baseUrl).collection('users').list()
|
|
151
|
+
export const only = client;
|
|
152
|
+
|
|
153
|
+
export default OnlyBase;
|
|
154
|
+
|
|
155
|
+
export type OnlyBaseClient = ReturnType<typeof client>;
|
package/package.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "onlybase-sdk",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "JavaScript SDK for OnlyBase: collections, rows, and real-time subscriptions",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.ts",
|
|
7
|
+
"types": "index.ts",
|
|
8
|
+
"keywords": ["onlybase", "backend", "sdk", "realtime", "api", "collections"],
|
|
9
|
+
"license": "MIT"
|
|
10
|
+
}
|