@surf-ai/sdk 0.1.6-beta → 1.0.0-alpha.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.
@@ -5,9 +5,6 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __esm = (fn, res) => function __init() {
9
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
- };
11
8
  var __export = (target, all) => {
12
9
  for (var name in all)
13
10
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -30,32 +27,70 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
27
  ));
31
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
29
 
33
- // src/data/client.ts
34
- var client_exports = {};
35
- __export(client_exports, {
36
- get: () => get,
37
- post: () => post
30
+ // src/server/index.ts
31
+ var server_exports = {};
32
+ __export(server_exports, {
33
+ createServer: () => createServer,
34
+ dataApi: () => dataApi
38
35
  });
39
- function env(surfName, legacyName) {
40
- return process.env[surfName] || process.env[legacyName];
36
+ module.exports = __toCommonJS(server_exports);
37
+
38
+ // src/server/runtime.ts
39
+ var import_express = __toESM(require("express"), 1);
40
+ var import_cors = __toESM(require("cors"), 1);
41
+ var import_fs2 = __toESM(require("fs"), 1);
42
+ var import_path = __toESM(require("path"), 1);
43
+ var import_croner = require("croner");
44
+
45
+ // src/core/config.ts
46
+ var DEFAULT_API_BASE_URL = "https://api.ask.surf/gateway/v1";
47
+ function trimTrailingSlashes(value) {
48
+ return String(value || "").replace(/\/+$/, "");
41
49
  }
42
- function resolveConfig() {
43
- const proxyBase = env("SURF_SANDBOX_PROXY_BASE", "DATA_PROXY_BASE");
44
- if (proxyBase) {
45
- return { baseUrl: proxyBase, headers: {} };
46
- }
47
- const gatewayUrl = env("SURF_DEPLOYED_GATEWAY_URL", "GATEWAY_URL");
48
- const appToken = env("SURF_DEPLOYED_APP_TOKEN", "APP_TOKEN");
49
- if (gatewayUrl && appToken) {
50
- return {
51
- baseUrl: `${gatewayUrl.replace(/\/$/, "")}/gateway/v1`,
52
- headers: { Authorization: `Bearer ${appToken}` }
53
- };
50
+ function readSurfApiConfig() {
51
+ return {
52
+ baseUrl: trimTrailingSlashes(process.env.SURF_API_BASE_URL || DEFAULT_API_BASE_URL),
53
+ apiKey: process.env.SURF_API_KEY
54
+ };
55
+ }
56
+ function requireSurfApiConfig() {
57
+ const config = readSurfApiConfig();
58
+ if (!config.apiKey) {
59
+ throw new Error("SURF_API_KEY is required");
54
60
  }
55
- return { baseUrl: DEFAULT_PUBLIC_URL, headers: {} };
61
+ return { baseUrl: config.baseUrl, apiKey: config.apiKey };
62
+ }
63
+ function readAdminApiKey() {
64
+ return process.env.SURF_API_KEY;
65
+ }
66
+
67
+ // src/db/schema-sync.ts
68
+ var import_fs = __toESM(require("fs"), 1);
69
+
70
+ // src/core/transport.ts
71
+ function sleep(ms) {
72
+ return new Promise((resolve) => setTimeout(resolve, ms));
56
73
  }
57
74
  function normalizePath(path2) {
58
- return String(path2 || "").replace(/^\/+/, "").replace(/^(?:proxy\/)+/, "");
75
+ return String(path2 || "").replace(/^\/+/, "");
76
+ }
77
+ function buildUrl(path2, params) {
78
+ const { baseUrl } = requireSurfApiConfig();
79
+ const url = new URL(`${baseUrl}/${normalizePath(path2)}`);
80
+ if (params) {
81
+ for (const [key, value] of Object.entries(params)) {
82
+ if (value != null) {
83
+ url.searchParams.set(key, String(value));
84
+ }
85
+ }
86
+ }
87
+ return url.toString();
88
+ }
89
+ function buildHeaders(extra) {
90
+ const { apiKey } = requireSurfApiConfig();
91
+ const headers = new Headers(extra);
92
+ headers.set("Authorization", `Bearer ${apiKey}`);
93
+ return headers;
59
94
  }
60
95
  async function fetchJson(url, init, retries = 1) {
61
96
  for (let attempt = 0; attempt <= retries; attempt++) {
@@ -65,80 +100,211 @@ async function fetchJson(url, init, retries = 1) {
65
100
  throw new Error(`API error ${res.status}: ${text2.slice(0, 200)}`);
66
101
  }
67
102
  const text = await res.text();
68
- if (text) return JSON.parse(text);
69
- if (attempt < retries) await new Promise((r) => setTimeout(r, 1e3));
103
+ if (text) {
104
+ return JSON.parse(text);
105
+ }
106
+ if (attempt < retries) {
107
+ await sleep(1e3);
108
+ }
70
109
  }
71
110
  throw new Error(`Empty response from ${url}`);
72
111
  }
73
- async function get(path2, params) {
74
- const config = resolveConfig();
75
- const cleaned = {};
76
- if (params) {
77
- for (const [k, v] of Object.entries(params)) {
78
- if (v != null) cleaned[k] = String(v);
79
- }
80
- }
81
- const qs = Object.keys(cleaned).length ? "?" + new URLSearchParams(cleaned).toString() : "";
82
- return fetchJson(`${config.baseUrl}/${normalizePath(path2)}${qs}`, {
83
- headers: config.headers
112
+ async function getJson(path2, params) {
113
+ return fetchJson(buildUrl(path2, params), {
114
+ headers: buildHeaders()
84
115
  });
85
116
  }
86
- async function post(path2, body) {
87
- const config = resolveConfig();
88
- return fetchJson(`${config.baseUrl}/${normalizePath(path2)}`, {
117
+ async function postJson(path2, body) {
118
+ return fetchJson(buildUrl(path2), {
89
119
  method: "POST",
90
- headers: { ...config.headers, "Content-Type": "application/json" },
120
+ headers: buildHeaders({
121
+ "Content-Type": "application/json"
122
+ }),
91
123
  body: body ? JSON.stringify(body) : void 0
92
124
  });
93
125
  }
94
- var DEFAULT_PUBLIC_URL;
95
- var init_client = __esm({
96
- "src/data/client.ts"() {
97
- "use strict";
98
- DEFAULT_PUBLIC_URL = "https://api.ask.surf/gateway/v1";
99
- }
100
- });
101
126
 
102
- // src/server/index.ts
103
- var server_exports = {};
104
- __export(server_exports, {
105
- createServer: () => createServer,
106
- dataApi: () => dataApi
107
- });
108
- module.exports = __toCommonJS(server_exports);
127
+ // src/data/client.ts
128
+ async function get(path2, params) {
129
+ return getJson(path2, params);
130
+ }
131
+ async function post(path2, body) {
132
+ return postJson(path2, body);
133
+ }
134
+
135
+ // src/db/schema-sync.ts
136
+ var syncing = false;
137
+ async function syncSchema(options) {
138
+ const { schemaPath, retries = 3, retryDelay = 2e3 } = options;
139
+ for (let i = 0; i < retries; i++) {
140
+ try {
141
+ await doSyncSchema(schemaPath);
142
+ return;
143
+ } catch (err) {
144
+ console.error(`DB schema sync attempt ${i + 1}/${retries} failed: ${err.message}`);
145
+ if (i < retries - 1) await new Promise((r) => setTimeout(r, retryDelay * (i + 1)));
146
+ }
147
+ }
148
+ console.error("DB schema sync failed after all retries");
149
+ }
150
+ async function doSyncSchema(schemaPath) {
151
+ if (syncing) return;
152
+ syncing = true;
153
+ try {
154
+ if (!import_fs.default.existsSync(schemaPath)) return;
155
+ let schema;
156
+ if (schemaPath.endsWith(".ts")) {
157
+ try {
158
+ schema = await import(`${schemaPath}?t=${Date.now()}`);
159
+ } catch (err) {
160
+ if (err instanceof SyntaxError) {
161
+ console.log("DB: schema file has syntax error, waiting for next change...");
162
+ return;
163
+ }
164
+ if (err.message.includes("Cannot find module") || err.message.includes("is not a function")) {
165
+ return;
166
+ }
167
+ throw err;
168
+ }
169
+ } else {
170
+ try {
171
+ delete require.cache[require.resolve(schemaPath)];
172
+ } catch {
173
+ }
174
+ try {
175
+ schema = require(schemaPath);
176
+ } catch (err) {
177
+ if (err instanceof SyntaxError) {
178
+ console.log("DB: schema file has syntax error, waiting for next change...");
179
+ return;
180
+ }
181
+ if (err.message.includes("Cannot find module") || err.message.includes("is not a function")) {
182
+ return;
183
+ }
184
+ throw err;
185
+ }
186
+ }
187
+ let getTableConfig;
188
+ try {
189
+ getTableConfig = require("drizzle-orm/pg-core").getTableConfig;
190
+ } catch {
191
+ return;
192
+ }
193
+ const tables = Object.values(schema).filter(
194
+ (t) => t && typeof t === "object" && /* @__PURE__ */ Symbol.for("drizzle:Name") in t
195
+ );
196
+ if (tables.length === 0) return;
197
+ await post("db/provision", {});
198
+ const existing = (await get("db/tables")).map((t) => t.name);
199
+ const missing = tables.filter((t) => !existing.includes(getTableConfig(t).name));
200
+ if (missing.length > 0) {
201
+ const { generateDrizzleJson, generateMigration } = require("drizzle-kit/api");
202
+ const missingSchema = {};
203
+ for (const t of missing) missingSchema[getTableConfig(t).name] = t;
204
+ const sqls = await generateMigration(generateDrizzleJson({}), generateDrizzleJson(missingSchema));
205
+ for (const sql of sqls) {
206
+ for (let attempt = 0; attempt < 2; attempt++) {
207
+ try {
208
+ await post("db/query", { sql, params: [] });
209
+ console.log(`DB: Executed: ${sql.slice(0, 80)}...`);
210
+ break;
211
+ } catch (err) {
212
+ if (attempt === 0) {
213
+ console.warn(`DB: Retrying after: ${err.message}`);
214
+ await new Promise((r) => setTimeout(r, 1500));
215
+ } else {
216
+ console.error(`DB: Failed: ${sql.slice(0, 80)}... \u2014 ${err.message}`);
217
+ }
218
+ }
219
+ }
220
+ }
221
+ }
222
+ const existingTables = tables.filter((t) => existing.includes(getTableConfig(t).name));
223
+ for (const t of existingTables) {
224
+ const cfg = getTableConfig(t);
225
+ try {
226
+ const live = await get("db/table-schema", { table: cfg.name });
227
+ const liveCols = new Set((live.columns || []).map((c) => c.name));
228
+ for (const col of cfg.columns) {
229
+ if (!liveCols.has(col.name)) {
230
+ const colType = col.getSQLType();
231
+ const ddl = `ALTER TABLE "${cfg.name}" ADD COLUMN IF NOT EXISTS "${col.name}" ${colType}`;
232
+ try {
233
+ await post("db/query", { sql: ddl, params: [] });
234
+ console.log(`DB: Added column ${col.name} to ${cfg.name}`);
235
+ } catch (err) {
236
+ console.warn(`DB: Failed to add column ${col.name} to ${cfg.name}: ${err.message}`);
237
+ }
238
+ }
239
+ }
240
+ } catch (err) {
241
+ console.warn(`DB: Column check failed for ${cfg.name}: ${err.message}`);
242
+ }
243
+ }
244
+ } finally {
245
+ syncing = false;
246
+ }
247
+ }
248
+ function watchSchema(schemaPath, options) {
249
+ const { debounceMs = 1e3, retries = 2, retryDelay = 1500 } = options || {};
250
+ if (!import_fs.default.existsSync(schemaPath)) return () => {
251
+ };
252
+ let debounce = null;
253
+ import_fs.default.watchFile(schemaPath, { interval: 2e3 }, () => {
254
+ if (debounce) clearTimeout(debounce);
255
+ debounce = setTimeout(async () => {
256
+ console.log("DB: schema file changed, re-syncing tables...");
257
+ try {
258
+ await syncSchema({ schemaPath, retries, retryDelay });
259
+ console.log("DB: schema re-sync complete");
260
+ } catch (err) {
261
+ console.error(`DB: schema re-sync failed: ${err.message}`);
262
+ }
263
+ }, debounceMs);
264
+ });
265
+ return () => {
266
+ import_fs.default.unwatchFile(schemaPath);
267
+ if (debounce) clearTimeout(debounce);
268
+ };
269
+ }
109
270
 
110
271
  // src/server/runtime.ts
111
- var import_express = __toESM(require("express"), 1);
112
- var import_cors = __toESM(require("cors"), 1);
113
- var import_fs = __toESM(require("fs"), 1);
114
- var import_path = __toESM(require("path"), 1);
115
- var import_http_proxy_middleware = require("http-proxy-middleware");
116
- var import_croner = require("croner");
272
+ function requireBearerAuth(req, res, next) {
273
+ const apiKey = readAdminApiKey();
274
+ if (!apiKey) {
275
+ return res.status(503).json({ error: "SURF_API_KEY is not configured" });
276
+ }
277
+ if (req.headers.authorization !== `Bearer ${apiKey}`) {
278
+ return res.status(401).json({ error: "Unauthorized" });
279
+ }
280
+ next();
281
+ }
117
282
  function createServer(options = {}) {
118
- const port = options.port || parseInt(process.env.PORT || "3001", 10);
283
+ const rawPort = process.env.BACKEND_PORT;
284
+ const port = options.port ?? (rawPort ? Number.parseInt(rawPort, 10) : void 0);
285
+ if (!Number.isInteger(port)) {
286
+ throw new Error("createServer requires a port via options.port or BACKEND_PORT env var");
287
+ }
119
288
  const routesDir = options.routesDir || import_path.default.join(process.cwd(), "routes");
120
289
  const cronDir = options.cronDir || process.cwd();
121
- const enableProxy = options.proxy !== false;
122
290
  const app = (0, import_express.default)();
123
291
  app.use((0, import_cors.default)());
124
- if (enableProxy) {
125
- setupProxy(app, port);
126
- }
127
292
  app.use(import_express.default.json());
128
293
  app.get("/api/health", (_req, res) => {
129
294
  res.json({ status: "ok" });
130
295
  });
131
- if (import_fs.default.existsSync(routesDir)) {
132
- for (const file of import_fs.default.readdirSync(routesDir)) {
296
+ if (import_fs2.default.existsSync(routesDir)) {
297
+ for (const file of import_fs2.default.readdirSync(routesDir)) {
133
298
  if (!file.endsWith(".js") && !file.endsWith(".ts")) continue;
134
299
  const name = file.replace(/\.(js|ts)$/, "");
135
300
  try {
136
- const route = require(import_path.default.join(routesDir, file));
137
- const handler = route.default || route;
301
+ const handler = require(import_path.default.join(routesDir, file));
138
302
  if (typeof handler === "function") {
139
303
  app.use(`/api/${name}`, handler);
140
304
  console.log(`Route registered: /api/${name}`);
305
+ continue;
141
306
  }
307
+ throw new Error(`Route module must export a handler function via module.exports`);
142
308
  } catch (err) {
143
309
  console.error(`Failed to load route ${file}: ${err.message}`);
144
310
  }
@@ -162,157 +328,15 @@ function createServer(options = {}) {
162
328
  }
163
329
  };
164
330
  }
165
- function env2(surfName, legacyName) {
166
- return process.env[surfName] || process.env[legacyName];
167
- }
168
- function setupProxy(app, port) {
169
- const gatewayUrl = env2("SURF_DEPLOYED_GATEWAY_URL", "GATEWAY_URL");
170
- const appToken = env2("SURF_DEPLOYED_APP_TOKEN", "APP_TOKEN");
171
- const proxyBase = env2("SURF_SANDBOX_PROXY_BASE", "DATA_PROXY_BASE");
172
- const isDeployed = Boolean(gatewayUrl && appToken);
173
- const bufferResponse = (0, import_http_proxy_middleware.responseInterceptor)(async (buf) => buf);
174
- if (isDeployed) {
175
- app.use("/proxy", (0, import_http_proxy_middleware.createProxyMiddleware)({
176
- target: gatewayUrl,
177
- changeOrigin: true,
178
- selfHandleResponse: true,
179
- pathRewrite: (p) => "/gateway/v1" + p,
180
- headers: {
181
- Authorization: `Bearer ${appToken}`,
182
- "Accept-Encoding": "identity"
183
- },
184
- on: { proxyRes: bufferResponse }
185
- }));
186
- const loopback = `http://127.0.0.1:${port}/proxy`;
187
- process.env.SURF_SANDBOX_PROXY_BASE = loopback;
188
- process.env.DATA_PROXY_BASE = loopback;
189
- } else if (proxyBase) {
190
- const target = proxyBase.replace(/\/proxy$/, "");
191
- app.use((0, import_http_proxy_middleware.createProxyMiddleware)({
192
- target,
193
- changeOrigin: true,
194
- pathFilter: "/proxy",
195
- on: {
196
- proxyReq: (proxyReq, req) => {
197
- console.log(`[proxy] >> ${req.method} ${req.originalUrl}`);
198
- },
199
- proxyRes: (proxyRes, req) => {
200
- console.log(`[proxy] << ${proxyRes.statusCode} ${req.method} ${req.originalUrl}`);
201
- },
202
- error: (err, req, res) => {
203
- console.error(`[proxy] !! ${req.method} ${req.originalUrl} error: ${err.message}`);
204
- if (!res.headersSent) res.status(502).json({ error: err.message });
205
- }
206
- }
207
- }));
208
- }
209
- }
210
331
  var schemaSync = { run: async () => {
211
332
  }, watch: () => {
212
333
  } };
213
334
  function setupSchemaSync(app, schemaDir) {
214
- let syncing = false;
215
335
  let schemaReady = false;
216
- async function doSyncSchema() {
217
- if (syncing) return;
218
- syncing = true;
219
- try {
220
- const schemaPath = import_path.default.join(schemaDir, "schema.js");
221
- if (!import_fs.default.existsSync(schemaPath)) return;
222
- try {
223
- delete require.cache[require.resolve(schemaPath)];
224
- } catch {
225
- }
226
- let schema;
227
- try {
228
- schema = require(schemaPath);
229
- } catch (err) {
230
- if (err instanceof SyntaxError) {
231
- console.log("DB: schema.js has syntax error, waiting for next change...");
232
- return;
233
- }
234
- if (err.message.includes("Cannot find module") || err.message.includes("is not a function")) {
235
- return;
236
- }
237
- throw err;
238
- }
239
- let getTableConfig;
240
- try {
241
- getTableConfig = require("drizzle-orm/pg-core").getTableConfig;
242
- } catch {
243
- return;
244
- }
245
- const tables = Object.values(schema).filter(
246
- (t) => t && typeof t === "object" && /* @__PURE__ */ Symbol.for("drizzle:Name") in t
247
- );
248
- if (tables.length === 0) return;
249
- const { get: dbGet, post: dbPost } = await Promise.resolve().then(() => (init_client(), client_exports));
250
- await dbPost("db/provision");
251
- const existing = (await dbGet("db/tables")).map((t) => t.name);
252
- const missing = tables.filter((t) => !existing.includes(getTableConfig(t).name));
253
- if (missing.length > 0) {
254
- const { generateDrizzleJson, generateMigration } = require("drizzle-kit/api");
255
- const missingSchema = {};
256
- for (const t of missing) missingSchema[getTableConfig(t).name] = t;
257
- const sqls = await generateMigration(generateDrizzleJson({}), generateDrizzleJson(missingSchema));
258
- for (const sql of sqls) {
259
- for (let attempt = 0; attempt < 2; attempt++) {
260
- try {
261
- await dbPost("db/query", { sql, params: [] });
262
- console.log(`DB: Executed: ${sql.slice(0, 80)}...`);
263
- break;
264
- } catch (err) {
265
- if (attempt === 0) {
266
- console.warn(`DB: Retrying after: ${err.message}`);
267
- await new Promise((r) => setTimeout(r, 1500));
268
- } else {
269
- console.error(`DB: Failed: ${sql.slice(0, 80)}... \u2014 ${err.message}`);
270
- }
271
- }
272
- }
273
- }
274
- }
275
- const existingTables = tables.filter((t) => existing.includes(getTableConfig(t).name));
276
- for (const t of existingTables) {
277
- const cfg = getTableConfig(t);
278
- try {
279
- const live = await dbGet("db/table-schema", { table: cfg.name });
280
- const liveCols = new Set((live.columns || []).map((c) => c.name));
281
- for (const col of cfg.columns) {
282
- if (!liveCols.has(col.name)) {
283
- const colType = col.getSQLType();
284
- const ddl = `ALTER TABLE "${cfg.name}" ADD COLUMN IF NOT EXISTS "${col.name}" ${colType}`;
285
- try {
286
- await dbPost("db/query", { sql: ddl, params: [] });
287
- console.log(`DB: Added column ${col.name} to ${cfg.name}`);
288
- } catch (err) {
289
- console.warn(`DB: Failed to add column ${col.name} to ${cfg.name}: ${err.message}`);
290
- }
291
- }
292
- }
293
- } catch (err) {
294
- console.warn(`DB: Column check failed for ${cfg.name}: ${err.message}`);
295
- }
296
- }
297
- } finally {
298
- syncing = false;
299
- }
300
- }
301
- async function syncWithRetry(retries = 3, delay = 2e3) {
302
- for (let i = 0; i < retries; i++) {
303
- try {
304
- await doSyncSchema();
305
- return;
306
- } catch (err) {
307
- console.error(`DB schema sync attempt ${i + 1}/${retries} failed: ${err.message}`);
308
- if (i < retries - 1) await new Promise((r) => setTimeout(r, delay * (i + 1)));
309
- }
310
- }
311
- console.error("DB schema sync failed after all retries");
312
- }
313
- app.post("/api/__sync-schema", async (_req, res) => {
336
+ const schemaPath = import_path.default.join(schemaDir, "schema.js");
337
+ app.post("/api/__sync-schema", requireBearerAuth, async (_req, res) => {
314
338
  try {
315
- await syncWithRetry(2, 1500);
339
+ await syncSchema({ schemaPath, retries: 2, retryDelay: 1500 });
316
340
  res.json({ ok: true });
317
341
  } catch (err) {
318
342
  res.status(500).json({ ok: false, error: err.message });
@@ -324,7 +348,7 @@ function setupSchemaSync(app, schemaDir) {
324
348
  });
325
349
  schemaSync.run = async () => {
326
350
  try {
327
- await syncWithRetry();
351
+ await syncSchema({ schemaPath });
328
352
  schemaReady = true;
329
353
  console.log("Schema sync complete, API ready");
330
354
  } catch {
@@ -333,21 +357,7 @@ function setupSchemaSync(app, schemaDir) {
333
357
  }
334
358
  };
335
359
  schemaSync.watch = () => {
336
- const schemaPath = import_path.default.join(schemaDir, "schema.js");
337
- if (!import_fs.default.existsSync(schemaPath)) return;
338
- let debounce = null;
339
- import_fs.default.watchFile(schemaPath, { interval: 2e3 }, () => {
340
- if (debounce) clearTimeout(debounce);
341
- debounce = setTimeout(async () => {
342
- console.log("DB: schema.js changed, re-syncing tables...");
343
- try {
344
- await syncWithRetry(2, 1500);
345
- console.log("DB: schema re-sync complete");
346
- } catch (err) {
347
- console.error(`DB: schema re-sync failed: ${err.message}`);
348
- }
349
- }, 1e3);
350
- });
360
+ watchSchema(schemaPath);
351
361
  };
352
362
  }
353
363
  function setupCron(app, cronDir) {
@@ -363,10 +373,10 @@ function setupCron(app, cronDir) {
363
373
  }
364
374
  cronJobs.clear();
365
375
  const cronPath = import_path.default.join(cronDir, "cron.json");
366
- if (!import_fs.default.existsSync(cronPath)) return;
376
+ if (!import_fs2.default.existsSync(cronPath)) return;
367
377
  let tasks;
368
378
  try {
369
- tasks = JSON.parse(import_fs.default.readFileSync(cronPath, "utf-8"));
379
+ tasks = JSON.parse(import_fs2.default.readFileSync(cronPath, "utf-8"));
370
380
  } catch (e) {
371
381
  console.error("Failed to parse cron.json:", e.message);
372
382
  return;
@@ -414,16 +424,7 @@ function setupCron(app, cronDir) {
414
424
  console.log(`Cron registered: [${task.id}] ${task.name} (${task.schedule})`);
415
425
  }
416
426
  }
417
- const envFn = (s, l) => process.env[s] || process.env[l];
418
- app.use("/api/cron", (req, res, next) => {
419
- const appToken = envFn("SURF_DEPLOYED_APP_TOKEN", "APP_TOKEN");
420
- if (!appToken) return next();
421
- const auth = req.headers.authorization;
422
- if (!auth || auth !== `Bearer ${appToken}`) {
423
- return res.status(401).json({ error: "Unauthorized" });
424
- }
425
- next();
426
- });
427
+ app.use("/api/cron", requireBearerAuth);
427
428
  app.get("/api/cron", (_req, res) => {
428
429
  res.json(cronTasks.map((t) => {
429
430
  const state = cronState.get(t.id) || { lastRunAt: null, lastStatus: null, lastError: null };
@@ -456,7 +457,7 @@ function setupCron(app, cronDir) {
456
457
  const cronPath = import_path.default.join(cronDir, "cron.json");
457
458
  let tasks = [];
458
459
  try {
459
- if (import_fs.default.existsSync(cronPath)) tasks = JSON.parse(import_fs.default.readFileSync(cronPath, "utf8"));
460
+ if (import_fs2.default.existsSync(cronPath)) tasks = JSON.parse(import_fs2.default.readFileSync(cronPath, "utf8"));
460
461
  } catch {
461
462
  tasks = [];
462
463
  }
@@ -465,7 +466,7 @@ function setupCron(app, cronDir) {
465
466
  }
466
467
  const newTask = { id, name, schedule, handler, enabled, timeout };
467
468
  tasks.push(newTask);
468
- import_fs.default.writeFileSync(cronPath, JSON.stringify(tasks, null, 2));
469
+ import_fs2.default.writeFileSync(cronPath, JSON.stringify(tasks, null, 2));
469
470
  loadCronJobs();
470
471
  res.status(201).json(newTask);
471
472
  });
@@ -473,7 +474,7 @@ function setupCron(app, cronDir) {
473
474
  const cronPath = import_path.default.join(cronDir, "cron.json");
474
475
  let tasks = [];
475
476
  try {
476
- if (import_fs.default.existsSync(cronPath)) tasks = JSON.parse(import_fs.default.readFileSync(cronPath, "utf8"));
477
+ if (import_fs2.default.existsSync(cronPath)) tasks = JSON.parse(import_fs2.default.readFileSync(cronPath, "utf8"));
477
478
  } catch {
478
479
  tasks = [];
479
480
  }
@@ -494,7 +495,7 @@ function setupCron(app, cronDir) {
494
495
  }
495
496
  }
496
497
  tasks[idx] = { ...tasks[idx], ...updates, id: req.params.id };
497
- import_fs.default.writeFileSync(cronPath, JSON.stringify(tasks, null, 2));
498
+ import_fs2.default.writeFileSync(cronPath, JSON.stringify(tasks, null, 2));
498
499
  loadCronJobs();
499
500
  res.json(tasks[idx]);
500
501
  });
@@ -502,14 +503,14 @@ function setupCron(app, cronDir) {
502
503
  const cronPath = import_path.default.join(cronDir, "cron.json");
503
504
  let tasks = [];
504
505
  try {
505
- if (import_fs.default.existsSync(cronPath)) tasks = JSON.parse(import_fs.default.readFileSync(cronPath, "utf8"));
506
+ if (import_fs2.default.existsSync(cronPath)) tasks = JSON.parse(import_fs2.default.readFileSync(cronPath, "utf8"));
506
507
  } catch {
507
508
  tasks = [];
508
509
  }
509
510
  const idx = tasks.findIndex((t) => t.id === req.params.id);
510
511
  if (idx === -1) return res.status(404).json({ error: "Task not found" });
511
512
  tasks.splice(idx, 1);
512
- import_fs.default.writeFileSync(cronPath, JSON.stringify(tasks, null, 2));
513
+ import_fs2.default.writeFileSync(cronPath, JSON.stringify(tasks, null, 2));
513
514
  cronState.delete(req.params.id);
514
515
  loadCronJobs();
515
516
  res.json({ ok: true });
@@ -527,11 +528,7 @@ function setupCron(app, cronDir) {
527
528
  loadCronJobs();
528
529
  }
529
530
 
530
- // src/data/data-api.ts
531
- init_client();
532
-
533
531
  // src/data/categories/exchange.ts
534
- init_client();
535
532
  var exchange = {
536
533
  /** Returns order book bid/ask levels with computed stats. **Included fields:** spread, spread percentage, mid-price, and total bid/ask depth. Use `limit` to control the number of price levels (1–100, default 20). Set `type=swap` to query perpetual contract order books instead of spot. */
537
534
  depth: (params) => get("exchange/depth", params),
@@ -550,7 +547,6 @@ var exchange = {
550
547
  };
551
548
 
552
549
  // src/data/categories/fund.ts
553
- init_client();
554
550
  var fund = {
555
551
  /** Returns a fund's **profile metadata**. **Included fields:** X accounts, team members, recent research, invested project count. This does NOT return the list of investments — use `/fund/portfolio` for that. **Lookup:** by UUID (`id`) or name (`q`). Returns 404 if not found. */
556
552
  detail: (params) => get("fund/detail", params),
@@ -561,7 +557,6 @@ var fund = {
561
557
  };
562
558
 
563
559
  // src/data/categories/kalshi.ts
564
- init_client();
565
560
  var kalshi = {
566
561
  /** Returns Kalshi events with nested markets, optionally filtered by `event_ticker`. Each event includes market count and a list of markets. **Data refresh:** ~30 minutes */
567
562
  events: (params) => get("prediction-market/kalshi/events", params),
@@ -580,7 +575,6 @@ var kalshi = {
580
575
  };
581
576
 
582
577
  // src/data/categories/market.ts
583
- init_client();
584
578
  var market = {
585
579
  /** Returns daily ETF flow history for US spot ETFs. **Included fields:** net flow (USD), token price, per-ticker breakdown. Sorted by date descending. `symbol`: `BTC` or `ETH`. */
586
580
  etf: (params) => get("market/etf", params),
@@ -607,7 +601,6 @@ var market = {
607
601
  };
608
602
 
609
603
  // src/data/categories/matching.ts
610
- init_client();
611
604
  var matching = {
612
605
  /** Returns daily volume and open interest comparison for a specific matched market pair. Both `polymarket_condition_id` and `kalshi_market_ticker` are required. **Volume units:** Polymarket = USD, Kalshi = contracts. */
613
606
  market_daily: (params) => get("prediction-market/matching/daily", params),
@@ -618,7 +611,6 @@ var matching = {
618
611
  };
619
612
 
620
613
  // src/data/categories/news.ts
621
- init_client();
622
614
  var news = {
623
615
  /** Returns the full content of a single news article by its ID (returned as `id` in feed and search results). */
624
616
  detail: (params) => get("news/detail", params),
@@ -627,7 +619,6 @@ var news = {
627
619
  };
628
620
 
629
621
  // src/data/categories/onchain.ts
630
- init_client();
631
622
  var onchain = {
632
623
  /** Returns bridge protocols ranked by total USD volume over a specified time range. */
633
624
  bridge_ranking: (params) => get("onchain/bridge/ranking", params),
@@ -646,7 +637,6 @@ var onchain = {
646
637
  };
647
638
 
648
639
  // src/data/categories/polymarket.ts
649
- init_client();
650
640
  var polymarket = {
651
641
  /** Returns trade and redemption activity for a Polymarket wallet. **Data refresh:** ~30 minutes */
652
642
  activity: (params) => get("prediction-market/polymarket/activity", params),
@@ -669,14 +659,12 @@ var polymarket = {
669
659
  };
670
660
 
671
661
  // src/data/categories/prediction_market.ts
672
- init_client();
673
662
  var prediction_market = {
674
663
  /** Returns daily notional volume and open interest aggregated by category across Kalshi and Polymarket. **Filters:** `source` or `category`. **Data refresh:** daily */
675
664
  category_metrics: (params) => get("prediction-market/category-metrics", params)
676
665
  };
677
666
 
678
667
  // src/data/categories/project.ts
679
- init_client();
680
668
  var project = {
681
669
  /** Returns time-series DeFi metrics for a project. **Available metrics:** `volume`, `fee`, `fees`, `revenue`, `tvl`, `users`. **Lookup:** by UUID (`id`) or name (`q`). Filter by `chain` and date range (`from`/`to`). Returns 404 if the project is not found. **Note:** this endpoint only returns data for DeFi protocol projects (e.g. `aave`, `uniswap`, `lido`, `makerdao`). Use `q` with a DeFi protocol name. */
682
670
  defi_metrics: (params) => get("project/defi/metrics", params),
@@ -687,7 +675,6 @@ var project = {
687
675
  };
688
676
 
689
677
  // src/data/categories/search.ts
690
- init_client();
691
678
  var search = {
692
679
  /** Searches and filters airdrop opportunities. **Filters:** keyword, status, reward type, task type. Returns paginated results with optional task details. */
693
680
  airdrop: (params) => get("search/airdrop", params),
@@ -714,7 +701,6 @@ var search = {
714
701
  };
715
702
 
716
703
  // src/data/categories/social.ts
717
- init_client();
718
704
  var social = {
719
705
  /** Returns a **point-in-time snapshot** of social analytics for a project. **Available fields** (via `fields`): `sentiment`, `follower_geo`, `smart_followers`. **Lookup:** by X account ID (`x_id`) or project name (`q`, e.g. `uniswap`, `solana`). The `q` parameter must be a crypto project name, not a personal Twitter handle. Returns 404 if the project has no linked Twitter account. For sentiment **trends over time**, use `/social/mindshare` instead. */
720
706
  detail: (params) => get("social/detail", params),
@@ -741,7 +727,6 @@ var social = {
741
727
  };
742
728
 
743
729
  // src/data/categories/token.ts
744
- init_client();
745
730
  var token = {
746
731
  /** Returns recent DEX swap events for a token contract address. **Covered DEXes:** `uniswap`, `sushiswap`, `curve`, `balancer`. **Included fields:** trading pair, amounts, USD value, taker address. **Data refresh:** ~24 hours · **Chains:** Ethereum, Base */
747
732
  dex_trades: (params) => get("token/dex-trades", params),
@@ -754,7 +739,6 @@ var token = {
754
739
  };
755
740
 
756
741
  // src/data/categories/v2.ts
757
- init_client();
758
742
  var v2 = {
759
743
  /** Historical orderbook snapshots for a Kalshi market. Returns yes-side bid/ask levels. Timestamps in milliseconds, prices in cents. Data refresh: ~5 minutes */
760
744
  kalshi_orderbooks: (params) => get("gateway/v2/kalshi/orderbooks", params),
@@ -769,7 +753,6 @@ var v2 = {
769
753
  };
770
754
 
771
755
  // src/data/categories/wallet.ts
772
- init_client();
773
756
  var wallet = {
774
757
  /** Returns multiple wallet sub-resources in a single request. **Available fields** (via `fields`): `balance`, `tokens`, `labels`, `nft`. **Lookup:** by `address`. Partial failures return available fields with per-field error info. Returns 422 if `fields` is invalid. */
775
758
  detail: (params) => get("wallet/detail", params),
@@ -786,7 +769,6 @@ var wallet = {
786
769
  };
787
770
 
788
771
  // src/data/categories/web.ts
789
- init_client();
790
772
  var web = {
791
773
  /** Fetches a web page and converts it to clean, LLM-friendly markdown. **Options:** - `target_selector` — extract specific page sections - `remove_selector` — strip unwanted elements Returns 400 if the URL is invalid or unreachable. */
792
774
  fetch: (params) => get("web/fetch", params)