@surf-ai/sdk 0.1.5-beta → 1.0.0-alpha.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 +93 -159
- package/dist/chunk-4NA3GKVD.js +100 -0
- package/dist/{client-3YMIRPDV.js → client-Z45B2GYT.js} +1 -1
- package/dist/db/index.cjs +63 -37
- package/dist/db/index.d.cts +5 -2
- package/dist/db/index.d.ts +5 -2
- package/dist/db/index.js +63 -37
- package/dist/server/index.cjs +211 -190
- package/dist/server/index.d.cts +204 -16
- package/dist/server/index.d.ts +204 -16
- package/dist/server/index.js +131 -153
- package/package.json +6 -29
- package/dist/chunk-J4OMYO3F.js +0 -70
- package/dist/react/index.d.ts +0 -3450
- package/dist/react/index.js +0 -1092
- package/src/theme/index.css +0 -314
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @surf-ai/sdk
|
|
2
2
|
|
|
3
|
-
Surf
|
|
3
|
+
Surf SDK 1.0 for backend apps, typed Surf data access, and database helpers.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -8,202 +8,136 @@ Surf platform SDK — data API client, Express server runtime, React hooks, and
|
|
|
8
8
|
bun add @surf-ai/sdk
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Configuration
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
SDK 1.0 uses a single direct-auth model.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
| Env Var | Default | Purpose |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| `SURF_API_BASE_URL` | `https://api.ask.surf/gateway/v1` | Full Surf API base URL |
|
|
18
|
+
| `SURF_API_KEY` | none | Bearer token used for upstream requests and protected runtime endpoints |
|
|
19
|
+
| `PORT` | none | Express server port when `createServer({ port })` is not provided |
|
|
20
|
+
|
|
21
|
+
All upstream SDK requests use:
|
|
17
22
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return (
|
|
21
|
-
<div className={cn('p-4', isLoading && 'opacity-50')}>
|
|
22
|
-
BTC: ${data?.data?.[0]?.value}
|
|
23
|
-
</div>
|
|
24
|
-
)
|
|
25
|
-
}
|
|
23
|
+
```http
|
|
24
|
+
Authorization: Bearer <SURF_API_KEY>
|
|
26
25
|
```
|
|
27
26
|
|
|
28
|
-
|
|
27
|
+
## Subpath exports
|
|
28
|
+
|
|
29
|
+
| Import | What it provides |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| `@surf-ai/sdk/server` | `createServer()`, `dataApi` |
|
|
32
|
+
| `@surf-ai/sdk/db` | `dbProvision()`, `dbQuery()`, `dbTables()`, `dbTableSchema()`, `dbStatus()` |
|
|
33
|
+
|
|
34
|
+
## Data API usage
|
|
29
35
|
|
|
30
36
|
```js
|
|
31
37
|
const { dataApi } = require('@surf-ai/sdk/server')
|
|
32
38
|
|
|
33
|
-
// Typed methods grouped by category
|
|
34
39
|
const btc = await dataApi.market.price({ symbol: 'BTC', time_range: '1d' })
|
|
35
|
-
const holders = await dataApi.token.holders({
|
|
36
|
-
|
|
40
|
+
const holders = await dataApi.token.holders({
|
|
41
|
+
address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
|
|
42
|
+
chain: 'ethereum',
|
|
43
|
+
})
|
|
37
44
|
|
|
38
|
-
// Escape hatch for
|
|
39
|
-
const
|
|
45
|
+
// Escape hatch for endpoints that do not have a typed helper yet.
|
|
46
|
+
const custom = await dataApi.get('market/price', { symbol: 'ETH', time_range: '1d' })
|
|
40
47
|
```
|
|
41
48
|
|
|
42
|
-
|
|
49
|
+
Available categories include:
|
|
50
|
+
|
|
51
|
+
- `market`
|
|
52
|
+
- `token`
|
|
53
|
+
- `wallet`
|
|
54
|
+
- `onchain`
|
|
55
|
+
- `social`
|
|
56
|
+
- `project`
|
|
57
|
+
- `news`
|
|
58
|
+
- `exchange`
|
|
59
|
+
- `fund`
|
|
60
|
+
- `search`
|
|
61
|
+
- `web`
|
|
62
|
+
- `polymarket`
|
|
63
|
+
- `kalshi`
|
|
64
|
+
- `prediction_market`
|
|
65
|
+
|
|
66
|
+
## Server runtime
|
|
43
67
|
|
|
44
68
|
```js
|
|
45
69
|
const { createServer } = require('@surf-ai/sdk/server')
|
|
46
70
|
|
|
47
|
-
// Starts Express with /proxy/*, route auto-loading, DB sync, cron, health check
|
|
48
71
|
createServer({ port: 3001 }).start()
|
|
49
72
|
```
|
|
50
73
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
```js
|
|
54
|
-
// routes/btc.js → /api/btc
|
|
55
|
-
const { dataApi } = require('@surf-ai/sdk/server')
|
|
56
|
-
const router = require('express').Router()
|
|
57
|
-
|
|
58
|
-
router.get('/', async (req, res) => {
|
|
59
|
-
const data = await dataApi.market.price({ symbol: 'BTC' })
|
|
60
|
-
res.json(data)
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
module.exports = router
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
## Subpath Exports
|
|
67
|
-
|
|
68
|
-
| Import | What |
|
|
69
|
-
|--------|------|
|
|
70
|
-
| `@surf-ai/sdk/server` | `createServer()`, `dataApi` — Express runtime + typed data API |
|
|
71
|
-
| `@surf-ai/sdk/react` | `useMarketPrice()`, `cn()`, `useToast()` — React hooks + utilities |
|
|
72
|
-
| `@surf-ai/sdk/db` | `dbQuery()`, `dbProvision()`, `dbTables()` — Drizzle/Neon database |
|
|
74
|
+
`createServer()` provides:
|
|
73
75
|
|
|
74
|
-
|
|
76
|
+
- Auto-loading of `routes/*.js` and `routes/*.ts` as `/api/{name}`
|
|
77
|
+
- `GET /api/health`
|
|
78
|
+
- `POST /api/__sync-schema`
|
|
79
|
+
- `GET/POST/PATCH/DELETE /api/cron`
|
|
80
|
+
- `POST /api/cron/:id/run`
|
|
81
|
+
- Schema sync on startup and when `db/schema.js` changes
|
|
75
82
|
|
|
76
|
-
`
|
|
83
|
+
`GET /api/health` is public.
|
|
77
84
|
|
|
78
|
-
|
|
79
|
-
|----------|--------|---------|
|
|
80
|
-
| `/api/health` | GET | Health check — `{ status: 'ok' }` |
|
|
81
|
-
| `/api/__sync-schema` | POST | Sync `db/schema.js` tables to database |
|
|
82
|
-
| `/api/cron` | GET | List cron jobs and status |
|
|
83
|
-
| `/api/cron` | POST | Update cron.json and reload |
|
|
84
|
-
| `/proxy/*` | ANY | Data API passthrough to hermod |
|
|
85
|
+
These runtime endpoints require `Authorization: Bearer <SURF_API_KEY>`:
|
|
85
86
|
|
|
86
|
-
|
|
87
|
+
- `POST /api/__sync-schema`
|
|
88
|
+
- `GET /api/cron`
|
|
89
|
+
- `POST /api/cron`
|
|
90
|
+
- `PATCH /api/cron/:id`
|
|
91
|
+
- `DELETE /api/cron/:id`
|
|
92
|
+
- `POST /api/cron/:id/run`
|
|
87
93
|
|
|
88
|
-
|
|
94
|
+
Routes you define in `routes/*` stay public unless your app adds its own auth.
|
|
89
95
|
|
|
90
|
-
|
|
96
|
+
Example route:
|
|
91
97
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
98
|
+
```js
|
|
99
|
+
const router = require('express').Router()
|
|
100
|
+
const { dataApi } = require('@surf-ai/sdk/server')
|
|
95
101
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
| `SURF_DEPLOYED_APP_TOKEN` | `APP_TOKEN` | Deployed | Bifrost |
|
|
102
|
+
router.get('/', async (_req, res) => {
|
|
103
|
+
const data = await dataApi.market.price({ symbol: 'BTC', time_range: '1d' })
|
|
104
|
+
res.json(data)
|
|
105
|
+
})
|
|
101
106
|
|
|
102
|
-
|
|
103
|
-
```
|
|
104
|
-
if SURF_SANDBOX_PROXY_BASE → sandbox (OutboundProxy handles auth)
|
|
105
|
-
elif SURF_DEPLOYED_GATEWAY_URL + SURF_DEPLOYED_APP_TOKEN → deployed (hermod with Bearer)
|
|
106
|
-
else → public (api.ask.surf, no auth)
|
|
107
|
+
module.exports = router
|
|
107
108
|
```
|
|
108
109
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
| Env Var | Default | Purpose |
|
|
112
|
-
|---------|---------|---------|
|
|
113
|
-
| `PORT` | `3001` | Express listen port |
|
|
114
|
-
|
|
115
|
-
### Vite dev server (scaffold, not SDK)
|
|
110
|
+
## Database helpers
|
|
116
111
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
| `VITE_PORT` | `5173` | Frontend dev server port |
|
|
120
|
-
| `VITE_BACKEND_PORT` | `3001` | Backend port for Vite proxy target |
|
|
121
|
-
|
|
122
|
-
### How routing works
|
|
112
|
+
```js
|
|
113
|
+
const { dbProvision, dbQuery, dbTables, dbTableSchema, dbStatus } = require('@surf-ai/sdk/db')
|
|
123
114
|
|
|
115
|
+
await dbProvision()
|
|
116
|
+
const result = await dbQuery('SELECT * FROM users WHERE id = $1', [123], { arrayMode: true })
|
|
117
|
+
const tables = await dbTables()
|
|
118
|
+
const schema = await dbTableSchema('users')
|
|
119
|
+
const status = await dbStatus()
|
|
124
120
|
```
|
|
125
|
-
Sandbox (urania preview):
|
|
126
|
-
Frontend hook: useMarketPrice()
|
|
127
|
-
→ fetch /proxy/market/price (same-origin to Vite)
|
|
128
|
-
→ Vite proxy → Express /proxy/* → OutboundProxy (JWT) → hermod
|
|
129
|
-
|
|
130
|
-
Backend route: dataApi.market.price()
|
|
131
|
-
→ fetch SURF_SANDBOX_PROXY_BASE/market/price
|
|
132
|
-
→ OutboundProxy (JWT) → hermod
|
|
133
|
-
|
|
134
|
-
Deployed (surf.computer):
|
|
135
|
-
Frontend hook: useMarketPrice()
|
|
136
|
-
→ fetch /proxy/market/price (same-origin to Express)
|
|
137
|
-
→ Express /proxy/* → hermod (APP_TOKEN)
|
|
138
|
-
|
|
139
|
-
Backend route: dataApi.market.price()
|
|
140
|
-
→ fetch http://127.0.0.1:PORT/proxy/market/price (loopback)
|
|
141
|
-
→ Express /proxy/* → hermod (APP_TOKEN)
|
|
142
|
-
|
|
143
|
-
Public (local dev):
|
|
144
|
-
Frontend hook: useMarketPrice()
|
|
145
|
-
→ fetch /proxy/market/price (same-origin to Vite)
|
|
146
|
-
→ Vite proxy → Express /proxy/* → hermod (GATEWAY_URL + APP_TOKEN)
|
|
147
|
-
|
|
148
|
-
Backend route: dataApi.market.price()
|
|
149
|
-
→ fetch SURF_DEPLOYED_GATEWAY_URL/gateway/v1/market/price (Bearer APP_TOKEN)
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
## Available Categories
|
|
153
|
-
|
|
154
|
-
| Category | Example Methods |
|
|
155
|
-
|----------|----------------|
|
|
156
|
-
| `market` | `price`, `ranking`, `etf`, `futures`, `options`, `fear_greed` |
|
|
157
|
-
| `token` | `holders`, `transfers`, `dex_trades`, `tokenomics` |
|
|
158
|
-
| `wallet` | `detail`, `net_worth`, `labels_batch`, `transfers` |
|
|
159
|
-
| `onchain` | `sql`, `tx`, `gas_price`, `schema`, `structured_query` |
|
|
160
|
-
| `social` | `detail`, `mindshare`, `tweets`, `user`, `ranking` |
|
|
161
|
-
| `project` | `detail`, `defi_metrics`, `defi_ranking` |
|
|
162
|
-
| `news` | `detail`, `feed` |
|
|
163
|
-
| `exchange` | `price`, `depth`, `klines`, `funding_history`, `perp` |
|
|
164
|
-
| `fund` | `detail`, `portfolio`, `ranking` |
|
|
165
|
-
| `search` | `project`, `news`, `wallet`, `web` |
|
|
166
|
-
| `web` | `fetch` |
|
|
167
|
-
| `polymarket` | `events`, `markets`, `prices`, `volumes` |
|
|
168
|
-
| `kalshi` | `events`, `markets`, `prices`, `volumes` |
|
|
169
|
-
| `prediction_market` | `category_metrics` |
|
|
170
|
-
|
|
171
|
-
## Codegen
|
|
172
|
-
|
|
173
|
-
API methods and React hooks are auto-generated from hermod's OpenAPI spec via the surf CLI:
|
|
174
121
|
|
|
175
|
-
|
|
176
|
-
# Regenerate all endpoints
|
|
177
|
-
python scripts/gen_sdk.py
|
|
178
|
-
|
|
179
|
-
# Regenerate specific endpoints
|
|
180
|
-
python scripts/gen_sdk.py --ops market-price token-holders
|
|
181
|
-
|
|
182
|
-
# Build
|
|
183
|
-
bun run build
|
|
184
|
-
```
|
|
122
|
+
Define tables in `db/schema.js` and the runtime will provision the database and create missing tables and columns during startup.
|
|
185
123
|
|
|
186
|
-
|
|
124
|
+
Example schema:
|
|
187
125
|
|
|
188
|
-
|
|
126
|
+
```js
|
|
127
|
+
const { pgTable, serial, text, timestamp } = require('drizzle-orm/pg-core')
|
|
189
128
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
129
|
+
exports.users = pgTable('users', {
|
|
130
|
+
id: serial('id').primaryKey(),
|
|
131
|
+
name: text('name').notNull(),
|
|
132
|
+
email: text('email'),
|
|
133
|
+
created_at: timestamp('created_at', { withTimezone: true }).defaultNow(),
|
|
134
|
+
})
|
|
195
135
|
```
|
|
196
136
|
|
|
197
|
-
##
|
|
198
|
-
|
|
199
|
-
```bash
|
|
200
|
-
# Unit tests
|
|
201
|
-
bun test ./tests/data-client.test.ts
|
|
137
|
+
## 1.0 migration notes
|
|
202
138
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
SURF_DEPLOYED_GATEWAY_URL=https://api.ask.surf SURF_DEPLOYED_APP_TOKEN=<token> bun test ./tests/e2e-all-envs.test.ts
|
|
209
|
-
```
|
|
139
|
+
- `SURF_API_BASE_URL` and `SURF_API_KEY` are the only supported SDK auth variables.
|
|
140
|
+
- The runtime no longer mounts `/proxy/*`.
|
|
141
|
+
- The `@surf-ai/sdk/react` subpath has been removed.
|
|
142
|
+
- Route modules must export the handler directly with `module.exports = router`.
|
|
143
|
+
- `createServer()` requires a port from `options.port` or `process.env.PORT`.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/core/config.ts
|
|
9
|
+
var DEFAULT_API_BASE_URL = "https://api.ask.surf/gateway/v1";
|
|
10
|
+
function trimTrailingSlashes(value) {
|
|
11
|
+
return String(value || "").replace(/\/+$/, "");
|
|
12
|
+
}
|
|
13
|
+
function readSurfApiConfig() {
|
|
14
|
+
return {
|
|
15
|
+
baseUrl: trimTrailingSlashes(process.env.SURF_API_BASE_URL || DEFAULT_API_BASE_URL),
|
|
16
|
+
apiKey: process.env.SURF_API_KEY
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function requireSurfApiConfig() {
|
|
20
|
+
const config = readSurfApiConfig();
|
|
21
|
+
if (!config.apiKey) {
|
|
22
|
+
throw new Error("SURF_API_KEY is required");
|
|
23
|
+
}
|
|
24
|
+
return { baseUrl: config.baseUrl, apiKey: config.apiKey };
|
|
25
|
+
}
|
|
26
|
+
function readAdminApiKey() {
|
|
27
|
+
return process.env.SURF_API_KEY;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/core/transport.ts
|
|
31
|
+
function sleep(ms) {
|
|
32
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
33
|
+
}
|
|
34
|
+
function normalizePath(path) {
|
|
35
|
+
return String(path || "").replace(/^\/+/, "");
|
|
36
|
+
}
|
|
37
|
+
function buildUrl(path, params) {
|
|
38
|
+
const { baseUrl } = requireSurfApiConfig();
|
|
39
|
+
const url = new URL(`${baseUrl}/${normalizePath(path)}`);
|
|
40
|
+
if (params) {
|
|
41
|
+
for (const [key, value] of Object.entries(params)) {
|
|
42
|
+
if (value != null) {
|
|
43
|
+
url.searchParams.set(key, String(value));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return url.toString();
|
|
48
|
+
}
|
|
49
|
+
function buildHeaders(extra) {
|
|
50
|
+
const { apiKey } = requireSurfApiConfig();
|
|
51
|
+
const headers = new Headers(extra);
|
|
52
|
+
headers.set("Authorization", `Bearer ${apiKey}`);
|
|
53
|
+
return headers;
|
|
54
|
+
}
|
|
55
|
+
async function fetchJson(url, init, retries = 1) {
|
|
56
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
57
|
+
const res = await fetch(url, init);
|
|
58
|
+
if (!res.ok) {
|
|
59
|
+
const text2 = await res.text();
|
|
60
|
+
throw new Error(`API error ${res.status}: ${text2.slice(0, 200)}`);
|
|
61
|
+
}
|
|
62
|
+
const text = await res.text();
|
|
63
|
+
if (text) {
|
|
64
|
+
return JSON.parse(text);
|
|
65
|
+
}
|
|
66
|
+
if (attempt < retries) {
|
|
67
|
+
await sleep(1e3);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
throw new Error(`Empty response from ${url}`);
|
|
71
|
+
}
|
|
72
|
+
async function getJson(path, params) {
|
|
73
|
+
return fetchJson(buildUrl(path, params), {
|
|
74
|
+
headers: buildHeaders()
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
async function postJson(path, body) {
|
|
78
|
+
return fetchJson(buildUrl(path), {
|
|
79
|
+
method: "POST",
|
|
80
|
+
headers: buildHeaders({
|
|
81
|
+
"Content-Type": "application/json"
|
|
82
|
+
}),
|
|
83
|
+
body: body ? JSON.stringify(body) : void 0
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/data/client.ts
|
|
88
|
+
async function get(path, params) {
|
|
89
|
+
return getJson(path, params);
|
|
90
|
+
}
|
|
91
|
+
async function post(path, body) {
|
|
92
|
+
return postJson(path, body);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export {
|
|
96
|
+
__require,
|
|
97
|
+
readAdminApiKey,
|
|
98
|
+
get,
|
|
99
|
+
post
|
|
100
|
+
};
|
package/dist/db/index.cjs
CHANGED
|
@@ -28,28 +28,49 @@ __export(db_exports, {
|
|
|
28
28
|
});
|
|
29
29
|
module.exports = __toCommonJS(db_exports);
|
|
30
30
|
|
|
31
|
-
// src/
|
|
32
|
-
var
|
|
33
|
-
function
|
|
34
|
-
return
|
|
35
|
-
}
|
|
36
|
-
function
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
headers: { Authorization: `Bearer ${appToken}` }
|
|
47
|
-
};
|
|
31
|
+
// src/core/config.ts
|
|
32
|
+
var DEFAULT_API_BASE_URL = "https://api.ask.surf/gateway/v1";
|
|
33
|
+
function trimTrailingSlashes(value) {
|
|
34
|
+
return String(value || "").replace(/\/+$/, "");
|
|
35
|
+
}
|
|
36
|
+
function readSurfApiConfig() {
|
|
37
|
+
return {
|
|
38
|
+
baseUrl: trimTrailingSlashes(process.env.SURF_API_BASE_URL || DEFAULT_API_BASE_URL),
|
|
39
|
+
apiKey: process.env.SURF_API_KEY
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function requireSurfApiConfig() {
|
|
43
|
+
const config = readSurfApiConfig();
|
|
44
|
+
if (!config.apiKey) {
|
|
45
|
+
throw new Error("SURF_API_KEY is required");
|
|
48
46
|
}
|
|
49
|
-
return { baseUrl:
|
|
47
|
+
return { baseUrl: config.baseUrl, apiKey: config.apiKey };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/core/transport.ts
|
|
51
|
+
function sleep(ms) {
|
|
52
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
50
53
|
}
|
|
51
54
|
function normalizePath(path) {
|
|
52
|
-
return String(path || "").replace(/^\/+/, "")
|
|
55
|
+
return String(path || "").replace(/^\/+/, "");
|
|
56
|
+
}
|
|
57
|
+
function buildUrl(path, params) {
|
|
58
|
+
const { baseUrl } = requireSurfApiConfig();
|
|
59
|
+
const url = new URL(`${baseUrl}/${normalizePath(path)}`);
|
|
60
|
+
if (params) {
|
|
61
|
+
for (const [key, value] of Object.entries(params)) {
|
|
62
|
+
if (value != null) {
|
|
63
|
+
url.searchParams.set(key, String(value));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return url.toString();
|
|
68
|
+
}
|
|
69
|
+
function buildHeaders(extra) {
|
|
70
|
+
const { apiKey } = requireSurfApiConfig();
|
|
71
|
+
const headers = new Headers(extra);
|
|
72
|
+
headers.set("Authorization", `Bearer ${apiKey}`);
|
|
73
|
+
return headers;
|
|
53
74
|
}
|
|
54
75
|
async function fetchJson(url, init, retries = 1) {
|
|
55
76
|
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
@@ -59,39 +80,44 @@ async function fetchJson(url, init, retries = 1) {
|
|
|
59
80
|
throw new Error(`API error ${res.status}: ${text2.slice(0, 200)}`);
|
|
60
81
|
}
|
|
61
82
|
const text = await res.text();
|
|
62
|
-
if (text)
|
|
63
|
-
|
|
83
|
+
if (text) {
|
|
84
|
+
return JSON.parse(text);
|
|
85
|
+
}
|
|
86
|
+
if (attempt < retries) {
|
|
87
|
+
await sleep(1e3);
|
|
88
|
+
}
|
|
64
89
|
}
|
|
65
90
|
throw new Error(`Empty response from ${url}`);
|
|
66
91
|
}
|
|
67
|
-
async function
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (params) {
|
|
71
|
-
for (const [k, v] of Object.entries(params)) {
|
|
72
|
-
if (v != null) cleaned[k] = String(v);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
const qs = Object.keys(cleaned).length ? "?" + new URLSearchParams(cleaned).toString() : "";
|
|
76
|
-
return fetchJson(`${config.baseUrl}/${normalizePath(path)}${qs}`, {
|
|
77
|
-
headers: config.headers
|
|
92
|
+
async function getJson(path, params) {
|
|
93
|
+
return fetchJson(buildUrl(path, params), {
|
|
94
|
+
headers: buildHeaders()
|
|
78
95
|
});
|
|
79
96
|
}
|
|
80
|
-
async function
|
|
81
|
-
|
|
82
|
-
return fetchJson(`${config.baseUrl}/${normalizePath(path)}`, {
|
|
97
|
+
async function postJson(path, body) {
|
|
98
|
+
return fetchJson(buildUrl(path), {
|
|
83
99
|
method: "POST",
|
|
84
|
-
headers: {
|
|
100
|
+
headers: buildHeaders({
|
|
101
|
+
"Content-Type": "application/json"
|
|
102
|
+
}),
|
|
85
103
|
body: body ? JSON.stringify(body) : void 0
|
|
86
104
|
});
|
|
87
105
|
}
|
|
88
106
|
|
|
107
|
+
// src/data/client.ts
|
|
108
|
+
async function get(path, params) {
|
|
109
|
+
return getJson(path, params);
|
|
110
|
+
}
|
|
111
|
+
async function post(path, body) {
|
|
112
|
+
return postJson(path, body);
|
|
113
|
+
}
|
|
114
|
+
|
|
89
115
|
// src/db/index.ts
|
|
90
116
|
async function dbProvision() {
|
|
91
117
|
return post("db/provision");
|
|
92
118
|
}
|
|
93
119
|
async function dbQuery(sql, params, options) {
|
|
94
|
-
return post("db/query", { sql, params,
|
|
120
|
+
return post("db/query", { sql, params, arrayMode: options?.arrayMode ?? false });
|
|
95
121
|
}
|
|
96
122
|
async function dbTables() {
|
|
97
123
|
return get("db/tables");
|
package/dist/db/index.d.cts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* const result = await dbQuery('SELECT * FROM users WHERE id = $1', [userId])
|
|
14
14
|
*/
|
|
15
15
|
/**
|
|
16
|
-
* Provision a database for the current user via
|
|
16
|
+
* Provision a database for the current user via db/provision.
|
|
17
17
|
* Returns connection metadata. Neon auto-creates the DB if it doesn't exist.
|
|
18
18
|
*/
|
|
19
19
|
declare function dbProvision(): Promise<{
|
|
@@ -23,8 +23,11 @@ declare function dbProvision(): Promise<{
|
|
|
23
23
|
password: string;
|
|
24
24
|
}>;
|
|
25
25
|
/**
|
|
26
|
-
* Execute a SQL query via
|
|
26
|
+
* Execute a SQL query via db/query.
|
|
27
27
|
* Uses pg-proxy driver under the hood — Drizzle ORM calls this automatically.
|
|
28
|
+
*
|
|
29
|
+
* @param options.arrayMode - When true, rows are returned as positional arrays
|
|
30
|
+
* instead of objects. Required for Drizzle ORM pg-proxy compatibility.
|
|
28
31
|
*/
|
|
29
32
|
declare function dbQuery(sql: string, params?: any[], options?: {
|
|
30
33
|
arrayMode?: boolean;
|
package/dist/db/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* const result = await dbQuery('SELECT * FROM users WHERE id = $1', [userId])
|
|
14
14
|
*/
|
|
15
15
|
/**
|
|
16
|
-
* Provision a database for the current user via
|
|
16
|
+
* Provision a database for the current user via db/provision.
|
|
17
17
|
* Returns connection metadata. Neon auto-creates the DB if it doesn't exist.
|
|
18
18
|
*/
|
|
19
19
|
declare function dbProvision(): Promise<{
|
|
@@ -23,8 +23,11 @@ declare function dbProvision(): Promise<{
|
|
|
23
23
|
password: string;
|
|
24
24
|
}>;
|
|
25
25
|
/**
|
|
26
|
-
* Execute a SQL query via
|
|
26
|
+
* Execute a SQL query via db/query.
|
|
27
27
|
* Uses pg-proxy driver under the hood — Drizzle ORM calls this automatically.
|
|
28
|
+
*
|
|
29
|
+
* @param options.arrayMode - When true, rows are returned as positional arrays
|
|
30
|
+
* instead of objects. Required for Drizzle ORM pg-proxy compatibility.
|
|
28
31
|
*/
|
|
29
32
|
declare function dbQuery(sql: string, params?: any[], options?: {
|
|
30
33
|
arrayMode?: boolean;
|