@surf-ai/sdk 1.0.0-alpha.0 → 1.0.0-alpha.10
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 +1 -1
- package/dist/db/index.cjs +157 -4
- package/dist/db/index.d.cts +35 -1
- package/dist/db/index.d.ts +35 -1
- package/dist/db/index.js +151 -3
- package/dist/server/index.cjs +173 -186
- package/dist/server/index.js +254 -134
- package/package.json +2 -2
- package/dist/chunk-4NA3GKVD.js +0 -100
- package/dist/client-Z45B2GYT.js +0 -8
package/dist/server/index.js
CHANGED
|
@@ -1,16 +1,246 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
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
7
|
|
|
8
8
|
// src/server/runtime.ts
|
|
9
9
|
import express from "express";
|
|
10
10
|
import cors from "cors";
|
|
11
|
-
import
|
|
11
|
+
import fs2 from "fs";
|
|
12
12
|
import path from "path";
|
|
13
13
|
import { Cron } from "croner";
|
|
14
|
+
|
|
15
|
+
// src/core/config.ts
|
|
16
|
+
var DEFAULT_API_BASE_URL = "https://api.asksurf.ai/gateway/v1";
|
|
17
|
+
function trimTrailingSlashes(value) {
|
|
18
|
+
return String(value || "").replace(/\/+$/, "");
|
|
19
|
+
}
|
|
20
|
+
function readSurfApiConfig() {
|
|
21
|
+
return {
|
|
22
|
+
baseUrl: trimTrailingSlashes(process.env.SURF_API_BASE_URL || DEFAULT_API_BASE_URL),
|
|
23
|
+
apiKey: process.env.SURF_API_KEY
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function requireSurfApiConfig() {
|
|
27
|
+
const config = readSurfApiConfig();
|
|
28
|
+
if (!config.apiKey) {
|
|
29
|
+
throw new Error("SURF_API_KEY is required");
|
|
30
|
+
}
|
|
31
|
+
return { baseUrl: config.baseUrl, apiKey: config.apiKey };
|
|
32
|
+
}
|
|
33
|
+
function readAdminApiKey() {
|
|
34
|
+
return process.env.SURF_API_KEY;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/db/schema-sync.ts
|
|
38
|
+
import fs from "fs";
|
|
39
|
+
|
|
40
|
+
// src/core/transport.ts
|
|
41
|
+
function sleep(ms) {
|
|
42
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
43
|
+
}
|
|
44
|
+
function normalizePath(path2) {
|
|
45
|
+
return String(path2 || "").replace(/^\/+/, "");
|
|
46
|
+
}
|
|
47
|
+
function buildUrl(path2, params) {
|
|
48
|
+
const { baseUrl } = requireSurfApiConfig();
|
|
49
|
+
const url = new URL(`${baseUrl}/${normalizePath(path2)}`);
|
|
50
|
+
if (params) {
|
|
51
|
+
for (const [key, value] of Object.entries(params)) {
|
|
52
|
+
if (value != null) {
|
|
53
|
+
url.searchParams.set(key, String(value));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return url.toString();
|
|
58
|
+
}
|
|
59
|
+
function buildHeaders(extra) {
|
|
60
|
+
const { apiKey } = requireSurfApiConfig();
|
|
61
|
+
const headers = new Headers(extra);
|
|
62
|
+
headers.set("Authorization", `Bearer ${apiKey}`);
|
|
63
|
+
return headers;
|
|
64
|
+
}
|
|
65
|
+
async function fetchJson(url, init, retries = 1) {
|
|
66
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
67
|
+
const res = await fetch(url, init);
|
|
68
|
+
if (!res.ok) {
|
|
69
|
+
const text2 = await res.text();
|
|
70
|
+
throw new Error(`API error ${res.status}: ${text2.slice(0, 200)}`);
|
|
71
|
+
}
|
|
72
|
+
const text = await res.text();
|
|
73
|
+
if (text) {
|
|
74
|
+
return JSON.parse(text);
|
|
75
|
+
}
|
|
76
|
+
if (attempt < retries) {
|
|
77
|
+
await sleep(1e3);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
throw new Error(`Empty response from ${url}`);
|
|
81
|
+
}
|
|
82
|
+
async function getJson(path2, params) {
|
|
83
|
+
return fetchJson(buildUrl(path2, params), {
|
|
84
|
+
headers: buildHeaders()
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
async function postJson(path2, body) {
|
|
88
|
+
return fetchJson(buildUrl(path2), {
|
|
89
|
+
method: "POST",
|
|
90
|
+
headers: buildHeaders({
|
|
91
|
+
"Content-Type": "application/json"
|
|
92
|
+
}),
|
|
93
|
+
body: body ? JSON.stringify(body) : void 0
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/data/client.ts
|
|
98
|
+
async function get(path2, params) {
|
|
99
|
+
return getJson(path2, params);
|
|
100
|
+
}
|
|
101
|
+
async function post(path2, body) {
|
|
102
|
+
return postJson(path2, body);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/db/schema-sync.ts
|
|
106
|
+
var syncing = false;
|
|
107
|
+
async function syncSchema(options) {
|
|
108
|
+
const { schemaPath, retries = 3, retryDelay = 2e3 } = options;
|
|
109
|
+
for (let i = 0; i < retries; i++) {
|
|
110
|
+
try {
|
|
111
|
+
await doSyncSchema(schemaPath);
|
|
112
|
+
return;
|
|
113
|
+
} catch (err) {
|
|
114
|
+
console.error(`DB schema sync attempt ${i + 1}/${retries} failed: ${err.message}`);
|
|
115
|
+
if (i < retries - 1) await new Promise((r) => setTimeout(r, retryDelay * (i + 1)));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
console.error("DB schema sync failed after all retries");
|
|
119
|
+
}
|
|
120
|
+
async function doSyncSchema(schemaPath) {
|
|
121
|
+
if (syncing) return;
|
|
122
|
+
syncing = true;
|
|
123
|
+
try {
|
|
124
|
+
if (!fs.existsSync(schemaPath)) return;
|
|
125
|
+
let schema;
|
|
126
|
+
if (schemaPath.endsWith(".ts")) {
|
|
127
|
+
try {
|
|
128
|
+
schema = await import(`${schemaPath}?t=${Date.now()}`);
|
|
129
|
+
} catch (err) {
|
|
130
|
+
if (err instanceof SyntaxError) {
|
|
131
|
+
console.log("DB: schema file has syntax error, waiting for next change...");
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (err.message.includes("Cannot find module") || err.message.includes("is not a function")) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
throw err;
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
try {
|
|
141
|
+
delete __require.cache[__require.resolve(schemaPath)];
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
schema = __require(schemaPath);
|
|
146
|
+
} catch (err) {
|
|
147
|
+
if (err instanceof SyntaxError) {
|
|
148
|
+
console.log("DB: schema file has syntax error, waiting for next change...");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (err.message.includes("Cannot find module") || err.message.includes("is not a function")) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
throw err;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
let getTableConfig;
|
|
158
|
+
try {
|
|
159
|
+
getTableConfig = __require("drizzle-orm/pg-core").getTableConfig;
|
|
160
|
+
} catch {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const tables = Object.values(schema).filter(
|
|
164
|
+
(t) => t && typeof t === "object" && /* @__PURE__ */ Symbol.for("drizzle:Name") in t
|
|
165
|
+
);
|
|
166
|
+
if (tables.length === 0) return;
|
|
167
|
+
await post("db/provision", {});
|
|
168
|
+
const tablesResult = await get("db/tables");
|
|
169
|
+
const tablesList = Array.isArray(tablesResult) ? tablesResult : tablesResult.tables || [];
|
|
170
|
+
const existing = tablesList.map((t) => typeof t === "string" ? t : t.name);
|
|
171
|
+
const missing = tables.filter((t) => !existing.includes(getTableConfig(t).name));
|
|
172
|
+
if (missing.length > 0) {
|
|
173
|
+
const { generateDrizzleJson, generateMigration } = __require("drizzle-kit/api");
|
|
174
|
+
const missingSchema = {};
|
|
175
|
+
for (const t of missing) missingSchema[getTableConfig(t).name] = t;
|
|
176
|
+
const sqls = await generateMigration(generateDrizzleJson({}), generateDrizzleJson(missingSchema));
|
|
177
|
+
for (const sql of sqls) {
|
|
178
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
179
|
+
try {
|
|
180
|
+
await post("db/query", { sql, params: [] });
|
|
181
|
+
console.log(`DB: Executed: ${sql.slice(0, 80)}...`);
|
|
182
|
+
break;
|
|
183
|
+
} catch (err) {
|
|
184
|
+
if (attempt === 0) {
|
|
185
|
+
console.warn(`DB: Retrying after: ${err.message}`);
|
|
186
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
187
|
+
} else {
|
|
188
|
+
console.error(`DB: Failed: ${sql.slice(0, 80)}... \u2014 ${err.message}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const existingTables = tables.filter((t) => existing.includes(getTableConfig(t).name));
|
|
195
|
+
for (const t of existingTables) {
|
|
196
|
+
const cfg = getTableConfig(t);
|
|
197
|
+
try {
|
|
198
|
+
const live = await get("db/table-schema", { table: cfg.name });
|
|
199
|
+
const liveCols = new Set((live.columns || []).map((c) => c.name));
|
|
200
|
+
for (const col of cfg.columns) {
|
|
201
|
+
if (!liveCols.has(col.name)) {
|
|
202
|
+
const colType = col.getSQLType();
|
|
203
|
+
const ddl = `ALTER TABLE "${cfg.name}" ADD COLUMN IF NOT EXISTS "${col.name}" ${colType}`;
|
|
204
|
+
try {
|
|
205
|
+
await post("db/query", { sql: ddl, params: [] });
|
|
206
|
+
console.log(`DB: Added column ${col.name} to ${cfg.name}`);
|
|
207
|
+
} catch (err) {
|
|
208
|
+
console.warn(`DB: Failed to add column ${col.name} to ${cfg.name}: ${err.message}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
} catch (err) {
|
|
213
|
+
console.warn(`DB: Column check failed for ${cfg.name}: ${err.message}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
} finally {
|
|
217
|
+
syncing = false;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function watchSchema(schemaPath, options) {
|
|
221
|
+
const { debounceMs = 1e3, retries = 2, retryDelay = 1500 } = options || {};
|
|
222
|
+
if (!fs.existsSync(schemaPath)) return () => {
|
|
223
|
+
};
|
|
224
|
+
let debounce = null;
|
|
225
|
+
fs.watchFile(schemaPath, { interval: 2e3 }, () => {
|
|
226
|
+
if (debounce) clearTimeout(debounce);
|
|
227
|
+
debounce = setTimeout(async () => {
|
|
228
|
+
console.log("DB: schema file changed, re-syncing tables...");
|
|
229
|
+
try {
|
|
230
|
+
await syncSchema({ schemaPath, retries, retryDelay });
|
|
231
|
+
console.log("DB: schema re-sync complete");
|
|
232
|
+
} catch (err) {
|
|
233
|
+
console.error(`DB: schema re-sync failed: ${err.message}`);
|
|
234
|
+
}
|
|
235
|
+
}, debounceMs);
|
|
236
|
+
});
|
|
237
|
+
return () => {
|
|
238
|
+
fs.unwatchFile(schemaPath);
|
|
239
|
+
if (debounce) clearTimeout(debounce);
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// src/server/runtime.ts
|
|
14
244
|
function requireBearerAuth(req, res, next) {
|
|
15
245
|
const apiKey = readAdminApiKey();
|
|
16
246
|
if (!apiKey) {
|
|
@@ -22,9 +252,10 @@ function requireBearerAuth(req, res, next) {
|
|
|
22
252
|
next();
|
|
23
253
|
}
|
|
24
254
|
function createServer(options = {}) {
|
|
25
|
-
const
|
|
255
|
+
const rawPort = process.env.BACKEND_PORT;
|
|
256
|
+
const port = options.port ?? (rawPort ? Number.parseInt(rawPort, 10) : void 0);
|
|
26
257
|
if (!Number.isInteger(port)) {
|
|
27
|
-
throw new Error("createServer requires a port via options.port or
|
|
258
|
+
throw new Error("createServer requires a port via options.port or BACKEND_PORT env var");
|
|
28
259
|
}
|
|
29
260
|
const routesDir = options.routesDir || path.join(process.cwd(), "routes");
|
|
30
261
|
const cronDir = options.cronDir || process.cwd();
|
|
@@ -34,8 +265,8 @@ function createServer(options = {}) {
|
|
|
34
265
|
app.get("/api/health", (_req, res) => {
|
|
35
266
|
res.json({ status: "ok" });
|
|
36
267
|
});
|
|
37
|
-
if (
|
|
38
|
-
for (const file of
|
|
268
|
+
if (fs2.existsSync(routesDir)) {
|
|
269
|
+
for (const file of fs2.readdirSync(routesDir)) {
|
|
39
270
|
if (!file.endsWith(".js") && !file.endsWith(".ts")) continue;
|
|
40
271
|
const name = file.replace(/\.(js|ts)$/, "");
|
|
41
272
|
try {
|
|
@@ -73,108 +304,11 @@ var schemaSync = { run: async () => {
|
|
|
73
304
|
}, watch: () => {
|
|
74
305
|
} };
|
|
75
306
|
function setupSchemaSync(app, schemaDir) {
|
|
76
|
-
let syncing = false;
|
|
77
307
|
let schemaReady = false;
|
|
78
|
-
|
|
79
|
-
if (syncing) return;
|
|
80
|
-
syncing = true;
|
|
81
|
-
try {
|
|
82
|
-
const schemaPath = path.join(schemaDir, "schema.js");
|
|
83
|
-
if (!fs.existsSync(schemaPath)) return;
|
|
84
|
-
try {
|
|
85
|
-
delete __require.cache[__require.resolve(schemaPath)];
|
|
86
|
-
} catch {
|
|
87
|
-
}
|
|
88
|
-
let schema;
|
|
89
|
-
try {
|
|
90
|
-
schema = __require(schemaPath);
|
|
91
|
-
} catch (err) {
|
|
92
|
-
if (err instanceof SyntaxError) {
|
|
93
|
-
console.log("DB: schema.js has syntax error, waiting for next change...");
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
if (err.message.includes("Cannot find module") || err.message.includes("is not a function")) {
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
throw err;
|
|
100
|
-
}
|
|
101
|
-
let getTableConfig;
|
|
102
|
-
try {
|
|
103
|
-
getTableConfig = __require("drizzle-orm/pg-core").getTableConfig;
|
|
104
|
-
} catch {
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
const tables = Object.values(schema).filter(
|
|
108
|
-
(t) => t && typeof t === "object" && /* @__PURE__ */ Symbol.for("drizzle:Name") in t
|
|
109
|
-
);
|
|
110
|
-
if (tables.length === 0) return;
|
|
111
|
-
const { get: dbGet, post: dbPost } = await import("../client-Z45B2GYT.js");
|
|
112
|
-
await dbPost("db/provision");
|
|
113
|
-
const existing = (await dbGet("db/tables")).map((t) => t.name);
|
|
114
|
-
const missing = tables.filter((t) => !existing.includes(getTableConfig(t).name));
|
|
115
|
-
if (missing.length > 0) {
|
|
116
|
-
const { generateDrizzleJson, generateMigration } = __require("drizzle-kit/api");
|
|
117
|
-
const missingSchema = {};
|
|
118
|
-
for (const t of missing) missingSchema[getTableConfig(t).name] = t;
|
|
119
|
-
const sqls = await generateMigration(generateDrizzleJson({}), generateDrizzleJson(missingSchema));
|
|
120
|
-
for (const sql of sqls) {
|
|
121
|
-
for (let attempt = 0; attempt < 2; attempt++) {
|
|
122
|
-
try {
|
|
123
|
-
await dbPost("db/query", { sql, params: [] });
|
|
124
|
-
console.log(`DB: Executed: ${sql.slice(0, 80)}...`);
|
|
125
|
-
break;
|
|
126
|
-
} catch (err) {
|
|
127
|
-
if (attempt === 0) {
|
|
128
|
-
console.warn(`DB: Retrying after: ${err.message}`);
|
|
129
|
-
await new Promise((r) => setTimeout(r, 1500));
|
|
130
|
-
} else {
|
|
131
|
-
console.error(`DB: Failed: ${sql.slice(0, 80)}... \u2014 ${err.message}`);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
const existingTables = tables.filter((t) => existing.includes(getTableConfig(t).name));
|
|
138
|
-
for (const t of existingTables) {
|
|
139
|
-
const cfg = getTableConfig(t);
|
|
140
|
-
try {
|
|
141
|
-
const live = await dbGet("db/table-schema", { table: cfg.name });
|
|
142
|
-
const liveCols = new Set((live.columns || []).map((c) => c.name));
|
|
143
|
-
for (const col of cfg.columns) {
|
|
144
|
-
if (!liveCols.has(col.name)) {
|
|
145
|
-
const colType = col.getSQLType();
|
|
146
|
-
const ddl = `ALTER TABLE "${cfg.name}" ADD COLUMN IF NOT EXISTS "${col.name}" ${colType}`;
|
|
147
|
-
try {
|
|
148
|
-
await dbPost("db/query", { sql: ddl, params: [] });
|
|
149
|
-
console.log(`DB: Added column ${col.name} to ${cfg.name}`);
|
|
150
|
-
} catch (err) {
|
|
151
|
-
console.warn(`DB: Failed to add column ${col.name} to ${cfg.name}: ${err.message}`);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
} catch (err) {
|
|
156
|
-
console.warn(`DB: Column check failed for ${cfg.name}: ${err.message}`);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
} finally {
|
|
160
|
-
syncing = false;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
async function syncWithRetry(retries = 3, delay = 2e3) {
|
|
164
|
-
for (let i = 0; i < retries; i++) {
|
|
165
|
-
try {
|
|
166
|
-
await doSyncSchema();
|
|
167
|
-
return;
|
|
168
|
-
} catch (err) {
|
|
169
|
-
console.error(`DB schema sync attempt ${i + 1}/${retries} failed: ${err.message}`);
|
|
170
|
-
if (i < retries - 1) await new Promise((r) => setTimeout(r, delay * (i + 1)));
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
console.error("DB schema sync failed after all retries");
|
|
174
|
-
}
|
|
308
|
+
const schemaPath = path.join(schemaDir, "schema.js");
|
|
175
309
|
app.post("/api/__sync-schema", requireBearerAuth, async (_req, res) => {
|
|
176
310
|
try {
|
|
177
|
-
await
|
|
311
|
+
await syncSchema({ schemaPath, retries: 2, retryDelay: 1500 });
|
|
178
312
|
res.json({ ok: true });
|
|
179
313
|
} catch (err) {
|
|
180
314
|
res.status(500).json({ ok: false, error: err.message });
|
|
@@ -186,7 +320,7 @@ function setupSchemaSync(app, schemaDir) {
|
|
|
186
320
|
});
|
|
187
321
|
schemaSync.run = async () => {
|
|
188
322
|
try {
|
|
189
|
-
await
|
|
323
|
+
await syncSchema({ schemaPath });
|
|
190
324
|
schemaReady = true;
|
|
191
325
|
console.log("Schema sync complete, API ready");
|
|
192
326
|
} catch {
|
|
@@ -195,21 +329,7 @@ function setupSchemaSync(app, schemaDir) {
|
|
|
195
329
|
}
|
|
196
330
|
};
|
|
197
331
|
schemaSync.watch = () => {
|
|
198
|
-
|
|
199
|
-
if (!fs.existsSync(schemaPath)) return;
|
|
200
|
-
let debounce = null;
|
|
201
|
-
fs.watchFile(schemaPath, { interval: 2e3 }, () => {
|
|
202
|
-
if (debounce) clearTimeout(debounce);
|
|
203
|
-
debounce = setTimeout(async () => {
|
|
204
|
-
console.log("DB: schema.js changed, re-syncing tables...");
|
|
205
|
-
try {
|
|
206
|
-
await syncWithRetry(2, 1500);
|
|
207
|
-
console.log("DB: schema re-sync complete");
|
|
208
|
-
} catch (err) {
|
|
209
|
-
console.error(`DB: schema re-sync failed: ${err.message}`);
|
|
210
|
-
}
|
|
211
|
-
}, 1e3);
|
|
212
|
-
});
|
|
332
|
+
watchSchema(schemaPath);
|
|
213
333
|
};
|
|
214
334
|
}
|
|
215
335
|
function setupCron(app, cronDir) {
|
|
@@ -225,10 +345,10 @@ function setupCron(app, cronDir) {
|
|
|
225
345
|
}
|
|
226
346
|
cronJobs.clear();
|
|
227
347
|
const cronPath = path.join(cronDir, "cron.json");
|
|
228
|
-
if (!
|
|
348
|
+
if (!fs2.existsSync(cronPath)) return;
|
|
229
349
|
let tasks;
|
|
230
350
|
try {
|
|
231
|
-
tasks = JSON.parse(
|
|
351
|
+
tasks = JSON.parse(fs2.readFileSync(cronPath, "utf-8"));
|
|
232
352
|
} catch (e) {
|
|
233
353
|
console.error("Failed to parse cron.json:", e.message);
|
|
234
354
|
return;
|
|
@@ -309,7 +429,7 @@ function setupCron(app, cronDir) {
|
|
|
309
429
|
const cronPath = path.join(cronDir, "cron.json");
|
|
310
430
|
let tasks = [];
|
|
311
431
|
try {
|
|
312
|
-
if (
|
|
432
|
+
if (fs2.existsSync(cronPath)) tasks = JSON.parse(fs2.readFileSync(cronPath, "utf8"));
|
|
313
433
|
} catch {
|
|
314
434
|
tasks = [];
|
|
315
435
|
}
|
|
@@ -318,7 +438,7 @@ function setupCron(app, cronDir) {
|
|
|
318
438
|
}
|
|
319
439
|
const newTask = { id, name, schedule, handler, enabled, timeout };
|
|
320
440
|
tasks.push(newTask);
|
|
321
|
-
|
|
441
|
+
fs2.writeFileSync(cronPath, JSON.stringify(tasks, null, 2));
|
|
322
442
|
loadCronJobs();
|
|
323
443
|
res.status(201).json(newTask);
|
|
324
444
|
});
|
|
@@ -326,7 +446,7 @@ function setupCron(app, cronDir) {
|
|
|
326
446
|
const cronPath = path.join(cronDir, "cron.json");
|
|
327
447
|
let tasks = [];
|
|
328
448
|
try {
|
|
329
|
-
if (
|
|
449
|
+
if (fs2.existsSync(cronPath)) tasks = JSON.parse(fs2.readFileSync(cronPath, "utf8"));
|
|
330
450
|
} catch {
|
|
331
451
|
tasks = [];
|
|
332
452
|
}
|
|
@@ -347,7 +467,7 @@ function setupCron(app, cronDir) {
|
|
|
347
467
|
}
|
|
348
468
|
}
|
|
349
469
|
tasks[idx] = { ...tasks[idx], ...updates, id: req.params.id };
|
|
350
|
-
|
|
470
|
+
fs2.writeFileSync(cronPath, JSON.stringify(tasks, null, 2));
|
|
351
471
|
loadCronJobs();
|
|
352
472
|
res.json(tasks[idx]);
|
|
353
473
|
});
|
|
@@ -355,14 +475,14 @@ function setupCron(app, cronDir) {
|
|
|
355
475
|
const cronPath = path.join(cronDir, "cron.json");
|
|
356
476
|
let tasks = [];
|
|
357
477
|
try {
|
|
358
|
-
if (
|
|
478
|
+
if (fs2.existsSync(cronPath)) tasks = JSON.parse(fs2.readFileSync(cronPath, "utf8"));
|
|
359
479
|
} catch {
|
|
360
480
|
tasks = [];
|
|
361
481
|
}
|
|
362
482
|
const idx = tasks.findIndex((t) => t.id === req.params.id);
|
|
363
483
|
if (idx === -1) return res.status(404).json({ error: "Task not found" });
|
|
364
484
|
tasks.splice(idx, 1);
|
|
365
|
-
|
|
485
|
+
fs2.writeFileSync(cronPath, JSON.stringify(tasks, null, 2));
|
|
366
486
|
cronState.delete(req.params.id);
|
|
367
487
|
loadCronJobs();
|
|
368
488
|
res.json({ ok: true });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@surf-ai/sdk",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.10",
|
|
4
4
|
"description": "Surf platform SDK — data API client, server runtime, and database helpers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"build": "tsup",
|
|
23
23
|
"dev": "tsup --watch",
|
|
24
24
|
"typecheck": "tsc --noEmit",
|
|
25
|
-
"codegen": "python3 scripts/gen_sdk.py --spec https://api.
|
|
25
|
+
"codegen": "python3 scripts/gen_sdk.py --spec https://api.asksurf.ai/gateway/openapi.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"express": "^4.22.0",
|
package/dist/chunk-4NA3GKVD.js
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
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
|
-
};
|