@fluxbase/sdk 0.0.1-rc.46 → 0.0.1-rc.50

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/dist/index.js CHANGED
@@ -153,6 +153,12 @@ var FluxbaseFetch = class {
153
153
  async getWithHeaders(path, options = {}) {
154
154
  return this.requestWithHeaders(path, { ...options, method: "GET" });
155
155
  }
156
+ /**
157
+ * POST request that returns response with headers (for POST-based queries with count)
158
+ */
159
+ async postWithHeaders(path, body, options = {}) {
160
+ return this.requestWithHeaders(path, { ...options, method: "POST", body });
161
+ }
156
162
  /**
157
163
  * Make an HTTP request and return response with headers
158
164
  */
@@ -260,6 +266,44 @@ var FluxbaseFetch = class {
260
266
  });
261
267
  return response.headers;
262
268
  }
269
+ /**
270
+ * GET request that returns response as Blob (for file downloads)
271
+ */
272
+ async getBlob(path, options = {}) {
273
+ const url = `${this.baseUrl}${path}`;
274
+ const headers = { ...this.defaultHeaders, ...options.headers };
275
+ delete headers["Content-Type"];
276
+ const controller = new AbortController();
277
+ const timeoutId = setTimeout(() => controller.abort(), options.timeout ?? this.timeout);
278
+ if (this.debug) {
279
+ console.log(`[Fluxbase SDK] GET (blob) ${url}`);
280
+ }
281
+ try {
282
+ const response = await fetch(url, {
283
+ method: "GET",
284
+ headers,
285
+ signal: controller.signal
286
+ });
287
+ clearTimeout(timeoutId);
288
+ if (!response.ok) {
289
+ const error = new Error(response.statusText);
290
+ error.status = response.status;
291
+ throw error;
292
+ }
293
+ return await response.blob();
294
+ } catch (err) {
295
+ clearTimeout(timeoutId);
296
+ if (err instanceof Error) {
297
+ if (err.name === "AbortError") {
298
+ const timeoutError = new Error("Request timeout");
299
+ timeoutError.status = 408;
300
+ throw timeoutError;
301
+ }
302
+ throw err;
303
+ }
304
+ throw new Error("Unknown error occurred");
305
+ }
306
+ }
263
307
  };
264
308
 
265
309
  // src/utils/error-handling.ts
@@ -1110,8 +1154,10 @@ var RealtimeChannel = class {
1110
1154
  this.callbacks = /* @__PURE__ */ new Map();
1111
1155
  this.presenceCallbacks = /* @__PURE__ */ new Map();
1112
1156
  this.broadcastCallbacks = /* @__PURE__ */ new Map();
1157
+ this.executionLogCallbacks = /* @__PURE__ */ new Set();
1113
1158
  this.subscriptionConfig = null;
1114
1159
  this.subscriptionId = null;
1160
+ this.executionLogConfig = null;
1115
1161
  this._presenceState = {};
1116
1162
  this.myPresenceKey = null;
1117
1163
  this.reconnectAttempts = 0;
@@ -1160,6 +1206,11 @@ var RealtimeChannel = class {
1160
1206
  this.presenceCallbacks.set(config.event, /* @__PURE__ */ new Set());
1161
1207
  }
1162
1208
  this.presenceCallbacks.get(config.event).add(actualCallback);
1209
+ } else if (event === "execution_log" && typeof configOrCallback !== "function") {
1210
+ const config = configOrCallback;
1211
+ this.executionLogConfig = config;
1212
+ const actualCallback = callback;
1213
+ this.executionLogCallbacks.add(actualCallback);
1163
1214
  } else {
1164
1215
  const actualEvent = event;
1165
1216
  const actualCallback = configOrCallback;
@@ -1440,14 +1491,26 @@ var RealtimeChannel = class {
1440
1491
  this.ws.onopen = () => {
1441
1492
  console.log("[Fluxbase Realtime] Connected");
1442
1493
  this.reconnectAttempts = 0;
1443
- const subscribeMessage = {
1444
- type: "subscribe",
1445
- channel: this.channelName
1446
- };
1447
- if (this.subscriptionConfig) {
1448
- subscribeMessage.config = this.subscriptionConfig;
1494
+ if (this.executionLogConfig) {
1495
+ const logSubscribeMessage = {
1496
+ type: "subscribe_logs",
1497
+ channel: this.channelName,
1498
+ config: {
1499
+ execution_id: this.executionLogConfig.execution_id,
1500
+ type: this.executionLogConfig.type || "function"
1501
+ }
1502
+ };
1503
+ this.sendMessage(logSubscribeMessage);
1504
+ } else {
1505
+ const subscribeMessage = {
1506
+ type: "subscribe",
1507
+ channel: this.channelName
1508
+ };
1509
+ if (this.subscriptionConfig) {
1510
+ subscribeMessage.config = this.subscriptionConfig;
1511
+ }
1512
+ this.sendMessage(subscribeMessage);
1449
1513
  }
1450
- this.sendMessage(subscribeMessage);
1451
1514
  this.startHeartbeat();
1452
1515
  };
1453
1516
  this.ws.onmessage = (event) => {
@@ -1557,8 +1620,25 @@ var RealtimeChannel = class {
1557
1620
  this.handlePostgresChanges(message.payload);
1558
1621
  }
1559
1622
  break;
1623
+ case "execution_log":
1624
+ if (message.payload) {
1625
+ this.handleExecutionLog(message.payload);
1626
+ }
1627
+ break;
1560
1628
  }
1561
1629
  }
1630
+ /**
1631
+ * Internal: Handle execution log message
1632
+ */
1633
+ handleExecutionLog(log) {
1634
+ this.executionLogCallbacks.forEach((callback) => {
1635
+ try {
1636
+ callback(log);
1637
+ } catch (err) {
1638
+ console.error("[Fluxbase Realtime] Error in execution log callback:", err);
1639
+ }
1640
+ });
1641
+ }
1562
1642
  /**
1563
1643
  * Internal: Handle broadcast message
1564
1644
  */
@@ -1802,6 +1882,98 @@ var FluxbaseRealtime = class {
1802
1882
  channel.updateToken(token);
1803
1883
  });
1804
1884
  }
1885
+ /**
1886
+ * Create an execution log subscription channel
1887
+ *
1888
+ * This provides a cleaner API for subscribing to execution logs
1889
+ * (functions, jobs, or RPC procedures).
1890
+ *
1891
+ * @param executionId - The execution ID to subscribe to
1892
+ * @param type - The type of execution ('function', 'job', 'rpc')
1893
+ * @returns ExecutionLogsChannel instance with fluent API
1894
+ *
1895
+ * @example
1896
+ * ```typescript
1897
+ * const channel = client.realtime.executionLogs('exec-123', 'function')
1898
+ * .onLog((log) => {
1899
+ * console.log(`[${log.level}] ${log.message}`)
1900
+ * })
1901
+ * .subscribe()
1902
+ * ```
1903
+ */
1904
+ executionLogs(executionId, type = "function") {
1905
+ return new ExecutionLogsChannel(this.url, executionId, type, this.token, this.tokenRefreshCallback);
1906
+ }
1907
+ };
1908
+ var ExecutionLogsChannel = class {
1909
+ constructor(url, executionId, type, token, tokenRefreshCallback) {
1910
+ this.logCallbacks = [];
1911
+ this.executionId = executionId;
1912
+ this.executionType = type;
1913
+ const channelName = `execution:${executionId}`;
1914
+ this.channel = new RealtimeChannel(url, channelName, token);
1915
+ if (tokenRefreshCallback) {
1916
+ this.channel.setTokenRefreshCallback(tokenRefreshCallback);
1917
+ }
1918
+ }
1919
+ /**
1920
+ * Register a callback for log events
1921
+ *
1922
+ * @param callback - Function to call when log entries are received
1923
+ * @returns This channel for chaining
1924
+ *
1925
+ * @example
1926
+ * ```typescript
1927
+ * channel.onLog((log) => {
1928
+ * console.log(`[${log.level}] Line ${log.line_number}: ${log.message}`)
1929
+ * })
1930
+ * ```
1931
+ */
1932
+ onLog(callback) {
1933
+ this.logCallbacks.push(callback);
1934
+ return this;
1935
+ }
1936
+ /**
1937
+ * Subscribe to execution logs
1938
+ *
1939
+ * @param callback - Optional status callback
1940
+ * @returns Promise that resolves when subscribed
1941
+ *
1942
+ * @example
1943
+ * ```typescript
1944
+ * await channel.subscribe()
1945
+ * ```
1946
+ */
1947
+ subscribe(callback) {
1948
+ this.channel.on(
1949
+ "execution_log",
1950
+ { execution_id: this.executionId, type: this.executionType },
1951
+ (log) => {
1952
+ this.logCallbacks.forEach((cb) => {
1953
+ try {
1954
+ cb(log);
1955
+ } catch (err) {
1956
+ console.error("[Fluxbase ExecutionLogs] Error in log callback:", err);
1957
+ }
1958
+ });
1959
+ }
1960
+ );
1961
+ this.channel.subscribe(callback);
1962
+ return this;
1963
+ }
1964
+ /**
1965
+ * Unsubscribe from execution logs
1966
+ *
1967
+ * @returns Promise resolving to status
1968
+ *
1969
+ * @example
1970
+ * ```typescript
1971
+ * await channel.unsubscribe()
1972
+ * ```
1973
+ */
1974
+ async unsubscribe() {
1975
+ return this.channel.unsubscribe();
1976
+ }
1805
1977
  };
1806
1978
 
1807
1979
  // src/storage.ts
@@ -1917,6 +2089,127 @@ var StorageBucket = class {
1917
2089
  xhr.send(formData);
1918
2090
  });
1919
2091
  }
2092
+ /**
2093
+ * Upload a file using streaming for reduced memory usage.
2094
+ * This method bypasses FormData buffering and streams data directly to the server.
2095
+ * Ideal for large files where memory efficiency is important.
2096
+ *
2097
+ * @param path - The path/key for the file
2098
+ * @param stream - ReadableStream of the file data
2099
+ * @param size - The size of the file in bytes (required for Content-Length header)
2100
+ * @param options - Upload options
2101
+ *
2102
+ * @example
2103
+ * ```typescript
2104
+ * // Upload from a File's stream
2105
+ * const file = new File([...], 'large-video.mp4');
2106
+ * const { data, error } = await storage
2107
+ * .from('videos')
2108
+ * .uploadStream('video.mp4', file.stream(), file.size, {
2109
+ * contentType: 'video/mp4',
2110
+ * });
2111
+ *
2112
+ * // Upload from a fetch response stream
2113
+ * const response = await fetch('https://example.com/data.zip');
2114
+ * const size = parseInt(response.headers.get('content-length') || '0');
2115
+ * const { data, error } = await storage
2116
+ * .from('files')
2117
+ * .uploadStream('data.zip', response.body!, size, {
2118
+ * contentType: 'application/zip',
2119
+ * });
2120
+ * ```
2121
+ */
2122
+ async uploadStream(path, stream, size, options) {
2123
+ try {
2124
+ if (size <= 0) {
2125
+ return { data: null, error: new Error("size must be a positive number") };
2126
+ }
2127
+ const headers = {
2128
+ ...this.fetch["defaultHeaders"],
2129
+ "Content-Length": String(size)
2130
+ };
2131
+ if (options?.contentType) {
2132
+ headers["X-Storage-Content-Type"] = options.contentType;
2133
+ }
2134
+ if (options?.cacheControl) {
2135
+ headers["X-Storage-Cache-Control"] = options.cacheControl;
2136
+ }
2137
+ if (options?.metadata && Object.keys(options.metadata).length > 0) {
2138
+ headers["X-Storage-Metadata"] = JSON.stringify(options.metadata);
2139
+ }
2140
+ let bodyStream = stream;
2141
+ if (options?.onUploadProgress) {
2142
+ let uploadedBytes = 0;
2143
+ const progressCallback = options.onUploadProgress;
2144
+ const totalSize = size;
2145
+ const transformStream = new TransformStream({
2146
+ transform(chunk, controller) {
2147
+ uploadedBytes += chunk.byteLength;
2148
+ const percentage = Math.round(uploadedBytes / totalSize * 100);
2149
+ progressCallback({
2150
+ loaded: uploadedBytes,
2151
+ total: totalSize,
2152
+ percentage
2153
+ });
2154
+ controller.enqueue(chunk);
2155
+ }
2156
+ });
2157
+ bodyStream = stream.pipeThrough(transformStream);
2158
+ }
2159
+ const response = await fetch(
2160
+ `${this.fetch["baseUrl"]}/api/v1/storage/${this.bucketName}/stream/${path}`,
2161
+ {
2162
+ method: "POST",
2163
+ headers,
2164
+ body: bodyStream,
2165
+ signal: options?.signal,
2166
+ // @ts-expect-error - duplex is not yet in TypeScript's RequestInit type
2167
+ duplex: "half"
2168
+ }
2169
+ );
2170
+ if (!response.ok) {
2171
+ const errorData = await response.json().catch(() => ({ error: response.statusText }));
2172
+ throw new Error(errorData.error || `Upload failed: ${response.statusText}`);
2173
+ }
2174
+ const result = await response.json();
2175
+ return {
2176
+ data: {
2177
+ id: result.key || path,
2178
+ path,
2179
+ fullPath: `${this.bucketName}/${path}`
2180
+ },
2181
+ error: null
2182
+ };
2183
+ } catch (error) {
2184
+ return { data: null, error };
2185
+ }
2186
+ }
2187
+ /**
2188
+ * Upload a large file using streaming for reduced memory usage.
2189
+ * This is a convenience method that converts a File or Blob to a stream.
2190
+ *
2191
+ * @param path - The path/key for the file
2192
+ * @param file - The File or Blob to upload
2193
+ * @param options - Upload options
2194
+ *
2195
+ * @example
2196
+ * ```typescript
2197
+ * const file = new File([...], 'large-video.mp4');
2198
+ * const { data, error } = await storage
2199
+ * .from('videos')
2200
+ * .uploadLargeFile('video.mp4', file, {
2201
+ * contentType: 'video/mp4',
2202
+ * onUploadProgress: (p) => console.log(`${p.percentage}% complete`),
2203
+ * });
2204
+ * ```
2205
+ */
2206
+ async uploadLargeFile(path, file, options) {
2207
+ const opts = {
2208
+ ...options,
2209
+ contentType: options?.contentType || file.type || "application/octet-stream"
2210
+ };
2211
+ return this.uploadStream(path, file.stream(), file.size, opts);
2212
+ }
1920
2213
  async download(path, options) {
1921
2214
  try {
1922
2215
  const controller = new AbortController();
@@ -2144,59 +2437,335 @@ var StorageBucket = class {
2144
2437
  }
2145
2438
  }
2146
2439
  /**
2147
- * List files in the bucket
2148
- * Supports both Supabase-style list(path, options) and Fluxbase-style list(options)
2149
- * @param pathOrOptions - The folder path or list options
2150
- * @param maybeOptions - List options when first param is a path
2440
+ * Upload a large file with resumable chunked uploads.
2441
+ *
2442
+ * Features:
2443
+ * - Uploads file in chunks for reliability
2444
+ * - Automatically retries failed chunks with exponential backoff
2445
+ * - Reports progress via callback with chunk-level granularity
2446
+ * - Can resume interrupted uploads using session ID
2447
+ *
2448
+ * @param path - The file path within the bucket
2449
+ * @param file - The File or Blob to upload
2450
+ * @param options - Upload options including chunk size, retries, and progress callback
2451
+ * @returns Upload result with file info
2452
+ *
2453
+ * @example
2454
+ * const { data, error } = await storage.from('uploads').uploadResumable('large.zip', file, {
2455
+ * chunkSize: 5 * 1024 * 1024, // 5MB chunks
2456
+ * maxRetries: 3,
2457
+ * onProgress: (p) => {
2458
+ * console.log(`${p.percentage}% (chunk ${p.currentChunk}/${p.totalChunks})`);
2459
+ * console.log(`Speed: ${(p.bytesPerSecond / 1024 / 1024).toFixed(2)} MB/s`);
2460
+ * console.log(`Session ID (for resume): ${p.sessionId}`);
2461
+ * }
2462
+ * });
2463
+ *
2464
+ * // To resume an interrupted upload:
2465
+ * const { data, error } = await storage.from('uploads').uploadResumable('large.zip', file, {
2466
+ * resumeSessionId: 'previous-session-id',
2467
+ * });
2151
2468
  */
2152
- async list(pathOrOptions, maybeOptions) {
2469
+ async uploadResumable(path, file, options) {
2153
2470
  try {
2154
- const params = new URLSearchParams();
2155
- let prefix;
2156
- let options;
2157
- if (typeof pathOrOptions === "string") {
2158
- prefix = pathOrOptions;
2159
- options = maybeOptions;
2471
+ const chunkSize = options?.chunkSize ?? 5 * 1024 * 1024;
2472
+ const maxRetries = options?.maxRetries ?? 3;
2473
+ const retryDelayMs = options?.retryDelayMs ?? 1e3;
2474
+ const chunkTimeout = options?.chunkTimeout ?? 6e4;
2475
+ const totalSize = file.size;
2476
+ const totalChunks = Math.ceil(totalSize / chunkSize);
2477
+ if (options?.signal?.aborted) {
2478
+ return { data: null, error: new Error("Upload aborted") };
2479
+ }
2480
+ const baseUrl = this.fetch["baseUrl"];
2481
+ const headers = this.fetch["defaultHeaders"];
2482
+ let sessionId = options?.resumeSessionId;
2483
+ let session;
2484
+ let completedChunks = [];
2485
+ if (!sessionId) {
2486
+ const initResponse = await fetch(
2487
+ `${baseUrl}/api/v1/storage/${this.bucketName}/chunked/init`,
2488
+ {
2489
+ method: "POST",
2490
+ headers: {
2491
+ ...headers,
2492
+ "Content-Type": "application/json"
2493
+ },
2494
+ body: JSON.stringify({
2495
+ path,
2496
+ total_size: totalSize,
2497
+ chunk_size: chunkSize,
2498
+ content_type: options?.contentType || file.type || "application/octet-stream",
2499
+ metadata: options?.metadata,
2500
+ cache_control: options?.cacheControl
2501
+ }),
2502
+ signal: options?.signal
2503
+ }
2504
+ );
2505
+ if (!initResponse.ok) {
2506
+ const errorData = await initResponse.json().catch(() => ({}));
2507
+ throw new Error(
2508
+ errorData.error || `Failed to initialize upload: ${initResponse.statusText}`
2509
+ );
2510
+ }
2511
+ const initData = await initResponse.json();
2512
+ session = {
2513
+ sessionId: initData.session_id,
2514
+ bucket: initData.bucket,
2515
+ path: initData.path,
2516
+ totalSize: initData.total_size,
2517
+ chunkSize: initData.chunk_size,
2518
+ totalChunks: initData.total_chunks,
2519
+ completedChunks: initData.completed_chunks || [],
2520
+ status: initData.status,
2521
+ expiresAt: initData.expires_at,
2522
+ createdAt: initData.created_at
2523
+ };
2524
+ sessionId = session.sessionId;
2160
2525
  } else {
2161
- options = pathOrOptions;
2162
- prefix = options?.prefix;
2526
+ const statusResponse = await fetch(
2527
+ `${baseUrl}/api/v1/storage/${this.bucketName}/chunked/${sessionId}/status`,
2528
+ {
2529
+ method: "GET",
2530
+ headers,
2531
+ signal: options?.signal
2532
+ }
2533
+ );
2534
+ if (!statusResponse.ok) {
2535
+ const errorData = await statusResponse.json().catch(() => ({}));
2536
+ throw new Error(
2537
+ errorData.error || `Failed to get session status: ${statusResponse.statusText}`
2538
+ );
2539
+ }
2540
+ const statusData = await statusResponse.json();
2541
+ session = statusData.session;
2542
+ completedChunks = session.completedChunks || [];
2163
2543
  }
2164
- if (prefix) {
2165
- params.set("prefix", prefix);
2544
+ let uploadedBytes = 0;
2545
+ for (const chunkIdx of completedChunks) {
2546
+ const chunkStart = chunkIdx * chunkSize;
2547
+ const chunkEnd = Math.min(chunkStart + chunkSize, totalSize);
2548
+ uploadedBytes += chunkEnd - chunkStart;
2166
2549
  }
2167
- if (options?.limit) {
2168
- params.set("limit", String(options.limit));
2550
+ let lastProgressTime = Date.now();
2551
+ let lastProgressBytes = uploadedBytes;
2552
+ for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
2553
+ if (options?.signal?.aborted) {
2554
+ return { data: null, error: new Error("Upload aborted") };
2555
+ }
2556
+ if (completedChunks.includes(chunkIndex)) {
2557
+ continue;
2558
+ }
2559
+ const start = chunkIndex * chunkSize;
2560
+ const end = Math.min(start + chunkSize, totalSize);
2561
+ const chunk = file.slice(start, end);
2562
+ const chunkArrayBuffer = await chunk.arrayBuffer();
2563
+ let retryCount = 0;
2564
+ let chunkUploaded = false;
2565
+ while (retryCount <= maxRetries && !chunkUploaded) {
2566
+ try {
2567
+ if (options?.signal?.aborted) {
2568
+ return { data: null, error: new Error("Upload aborted") };
2569
+ }
2570
+ const chunkController = new AbortController();
2571
+ const timeoutId = setTimeout(() => chunkController.abort(), chunkTimeout);
2572
+ if (options?.signal) {
2573
+ options.signal.addEventListener(
2574
+ "abort",
2575
+ () => chunkController.abort(),
2576
+ { once: true }
2577
+ );
2578
+ }
2579
+ const chunkResponse = await fetch(
2580
+ `${baseUrl}/api/v1/storage/${this.bucketName}/chunked/${sessionId}/${chunkIndex}`,
2581
+ {
2582
+ method: "PUT",
2583
+ headers: {
2584
+ ...headers,
2585
+ "Content-Type": "application/octet-stream",
2586
+ "Content-Length": String(chunkArrayBuffer.byteLength)
2587
+ },
2588
+ body: chunkArrayBuffer,
2589
+ signal: chunkController.signal
2590
+ }
2591
+ );
2592
+ clearTimeout(timeoutId);
2593
+ if (!chunkResponse.ok) {
2594
+ const errorData = await chunkResponse.json().catch(() => ({}));
2595
+ throw new Error(
2596
+ errorData.error || `Chunk upload failed: ${chunkResponse.statusText}`
2597
+ );
2598
+ }
2599
+ chunkUploaded = true;
2600
+ } catch (err) {
2601
+ if (options?.signal?.aborted) {
2602
+ return { data: null, error: new Error("Upload aborted") };
2603
+ }
2604
+ retryCount++;
2605
+ if (retryCount > maxRetries) {
2606
+ throw new Error(
2607
+ `Failed to upload chunk ${chunkIndex} after ${maxRetries} retries: ${err.message}`
2608
+ );
2609
+ }
2610
+ const delay = retryDelayMs * Math.pow(2, retryCount - 1);
2611
+ await new Promise((resolve) => setTimeout(resolve, delay));
2612
+ }
2613
+ }
2614
+ uploadedBytes += end - start;
2615
+ if (options?.onProgress) {
2616
+ const now = Date.now();
2617
+ const elapsed = (now - lastProgressTime) / 1e3;
2618
+ const bytesPerSecond = elapsed > 0 ? (uploadedBytes - lastProgressBytes) / elapsed : 0;
2619
+ lastProgressTime = now;
2620
+ lastProgressBytes = uploadedBytes;
2621
+ options.onProgress({
2622
+ loaded: uploadedBytes,
2623
+ total: totalSize,
2624
+ percentage: Math.round(uploadedBytes / totalSize * 100),
2625
+ currentChunk: chunkIndex + 1,
2626
+ totalChunks,
2627
+ bytesPerSecond,
2628
+ sessionId
2629
+ });
2630
+ }
2169
2631
  }
2170
- if (options?.offset) {
2171
- params.set("offset", String(options.offset));
2632
+ const completeResponse = await fetch(
2633
+ `${baseUrl}/api/v1/storage/${this.bucketName}/chunked/${sessionId}/complete`,
2634
+ {
2635
+ method: "POST",
2636
+ headers,
2637
+ signal: options?.signal
2638
+ }
2639
+ );
2640
+ if (!completeResponse.ok) {
2641
+ const errorData = await completeResponse.json().catch(() => ({}));
2642
+ throw new Error(
2643
+ errorData.error || `Failed to complete upload: ${completeResponse.statusText}`
2644
+ );
2172
2645
  }
2173
- const queryString = params.toString();
2174
- const path = `/api/v1/storage/${this.bucketName}${queryString ? `?${queryString}` : ""}`;
2175
- const response = await this.fetch.get(path);
2176
- const files = (response.files || []).map((file) => ({
2177
- name: file.key || file.name,
2178
- id: file.id,
2179
- bucket_id: file.bucket || this.bucketName,
2180
- created_at: file.last_modified || file.created_at,
2181
- updated_at: file.updated_at,
2182
- last_accessed_at: file.last_accessed_at,
2183
- metadata: file.metadata
2184
- }));
2185
- return { data: files, error: null };
2646
+ const result = await completeResponse.json();
2647
+ return {
2648
+ data: {
2649
+ id: result.id,
2650
+ path: result.path,
2651
+ fullPath: result.full_path
2652
+ },
2653
+ error: null
2654
+ };
2186
2655
  } catch (error) {
2187
2656
  return { data: null, error };
2188
2657
  }
2189
2658
  }
2190
2659
  /**
2191
- * Remove files from the bucket
2192
- * @param paths - Array of file paths to remove
2660
+ * Abort an in-progress resumable upload
2661
+ * @param sessionId - The upload session ID to abort
2193
2662
  */
2194
- async remove(paths) {
2663
+ async abortResumableUpload(sessionId) {
2195
2664
  try {
2196
- const removedFiles = [];
2197
- for (const path of paths) {
2198
- await this.fetch.delete(`/api/v1/storage/${this.bucketName}/${path}`);
2199
- removedFiles.push({
2665
+ const baseUrl = this.fetch["baseUrl"];
2666
+ const headers = this.fetch["defaultHeaders"];
2667
+ const response = await fetch(
2668
+ `${baseUrl}/api/v1/storage/${this.bucketName}/chunked/${sessionId}`,
2669
+ {
2670
+ method: "DELETE",
2671
+ headers
2672
+ }
2673
+ );
2674
+ if (!response.ok && response.status !== 204) {
2675
+ const errorData = await response.json().catch(() => ({}));
2676
+ throw new Error(
2677
+ errorData.error || `Failed to abort upload: ${response.statusText}`
2678
+ );
2679
+ }
2680
+ return { error: null };
2681
+ } catch (error) {
2682
+ return { error };
2683
+ }
2684
+ }
2685
+ /**
2686
+ * Get the status of a resumable upload session
2687
+ * @param sessionId - The upload session ID to check
2688
+ */
2689
+ async getResumableUploadStatus(sessionId) {
2690
+ try {
2691
+ const baseUrl = this.fetch["baseUrl"];
2692
+ const headers = this.fetch["defaultHeaders"];
2693
+ const response = await fetch(
2694
+ `${baseUrl}/api/v1/storage/${this.bucketName}/chunked/${sessionId}/status`,
2695
+ {
2696
+ method: "GET",
2697
+ headers
2698
+ }
2699
+ );
2700
+ if (!response.ok) {
2701
+ const errorData = await response.json().catch(() => ({}));
2702
+ throw new Error(
2703
+ errorData.error || `Failed to get upload status: ${response.statusText}`
2704
+ );
2705
+ }
2706
+ const data = await response.json();
2707
+ return {
2708
+ data: data.session,
2709
+ error: null
2710
+ };
2711
+ } catch (error) {
2712
+ return { data: null, error };
2713
+ }
2714
+ }
2715
+ /**
2716
+ * List files in the bucket
2717
+ * Supports both Supabase-style list(path, options) and Fluxbase-style list(options)
2718
+ * @param pathOrOptions - The folder path or list options
2719
+ * @param maybeOptions - List options when first param is a path
2720
+ */
2721
+ async list(pathOrOptions, maybeOptions) {
2722
+ try {
2723
+ const params = new URLSearchParams();
2724
+ let prefix;
2725
+ let options;
2726
+ if (typeof pathOrOptions === "string") {
2727
+ prefix = pathOrOptions;
2728
+ options = maybeOptions;
2729
+ } else {
2730
+ options = pathOrOptions;
2731
+ prefix = options?.prefix;
2732
+ }
2733
+ if (prefix) {
2734
+ params.set("prefix", prefix);
2735
+ }
2736
+ if (options?.limit) {
2737
+ params.set("limit", String(options.limit));
2738
+ }
2739
+ if (options?.offset) {
2740
+ params.set("offset", String(options.offset));
2741
+ }
2742
+ const queryString = params.toString();
2743
+ const path = `/api/v1/storage/${this.bucketName}${queryString ? `?${queryString}` : ""}`;
2744
+ const response = await this.fetch.get(path);
2745
+ const files = (response.files || []).map((file) => ({
2746
+ name: file.key || file.name,
2747
+ id: file.id,
2748
+ bucket_id: file.bucket || this.bucketName,
2749
+ created_at: file.last_modified || file.created_at,
2750
+ updated_at: file.updated_at,
2751
+ last_accessed_at: file.last_accessed_at,
2752
+ metadata: file.metadata
2753
+ }));
2754
+ return { data: files, error: null };
2755
+ } catch (error) {
2756
+ return { data: null, error };
2757
+ }
2758
+ }
2759
+ /**
2760
+ * Remove files from the bucket
2761
+ * @param paths - Array of file paths to remove
2762
+ */
2763
+ async remove(paths) {
2764
+ try {
2765
+ const removedFiles = [];
2766
+ for (const path of paths) {
2767
+ await this.fetch.delete(`/api/v1/storage/${this.bucketName}/${path}`);
2768
+ removedFiles.push({
2200
2769
  name: path,
2201
2770
  bucket_id: this.bucketName
2202
2771
  });
@@ -2548,7 +3117,7 @@ var FluxbaseJobs = class {
2548
3117
  *
2549
3118
  * @param jobName - Name of the job function to execute
2550
3119
  * @param payload - Job input data
2551
- * @param options - Additional options (priority, namespace, scheduled time)
3120
+ * @param options - Additional options (priority, namespace, scheduled time, onBehalfOf)
2552
3121
  * @returns Promise resolving to { data, error } tuple with submitted job details
2553
3122
  *
2554
3123
  * @example
@@ -2574,6 +3143,14 @@ var FluxbaseJobs = class {
2574
3143
  * const { data } = await client.jobs.submit('scheduled-task', payload, {
2575
3144
  * scheduled: '2025-01-01T00:00:00Z'
2576
3145
  * })
3146
+ *
3147
+ * // Submit on behalf of a user (service_role only)
3148
+ * const { data } = await serviceClient.jobs.submit('user-task', payload, {
3149
+ * onBehalfOf: {
3150
+ * user_id: 'user-uuid',
3151
+ * user_email: 'user@example.com'
3152
+ * }
3153
+ * })
2577
3154
  * ```
2578
3155
  */
2579
3156
  async submit(jobName, payload, options) {
@@ -2581,7 +3158,10 @@ var FluxbaseJobs = class {
2581
3158
  const request = {
2582
3159
  job_name: jobName,
2583
3160
  payload,
2584
- ...options
3161
+ priority: options?.priority,
3162
+ namespace: options?.namespace,
3163
+ scheduled: options?.scheduled,
3164
+ on_behalf_of: options?.onBehalfOf
2585
3165
  };
2586
3166
  const data = await this.fetch.post("/api/v1/jobs/submit", request);
2587
3167
  return { data, error: null };
@@ -2703,6 +3283,210 @@ var FluxbaseJobs = class {
2703
3283
  return { data: null, error };
2704
3284
  }
2705
3285
  }
3286
+ /**
3287
+ * Get execution logs for a job
3288
+ *
3289
+ * Returns logs for the specified job. Only returns logs for jobs
3290
+ * owned by the authenticated user (unless using service_role).
3291
+ *
3292
+ * @param jobId - Job ID
3293
+ * @param afterLine - Optional line number to get logs after (for polling/streaming)
3294
+ * @returns Promise resolving to { data, error } tuple with execution logs
3295
+ *
3296
+ * @example
3297
+ * ```typescript
3298
+ * // Get all logs for a job
3299
+ * const { data: logs, error } = await client.jobs.getLogs('550e8400-e29b-41d4-a716-446655440000')
3300
+ *
3301
+ * if (logs) {
3302
+ * for (const log of logs) {
3303
+ * console.log(`[${log.level}] ${log.message}`)
3304
+ * }
3305
+ * }
3306
+ *
3307
+ * // Backfill + stream pattern
3308
+ * const { data: logs } = await client.jobs.getLogs(jobId)
3309
+ * let lastLine = Math.max(...(logs?.map(l => l.line_number) ?? []), 0)
3310
+ *
3311
+ * const channel = client.realtime
3312
+ * .executionLogs(jobId, 'job')
3313
+ * .onLog((log) => {
3314
+ * if (log.line_number > lastLine) {
3315
+ * displayLog(log)
3316
+ * lastLine = log.line_number
3317
+ * }
3318
+ * })
3319
+ * .subscribe()
3320
+ * ```
3321
+ */
3322
+ async getLogs(jobId, afterLine) {
3323
+ try {
3324
+ const params = afterLine !== void 0 ? `?after_line=${afterLine}` : "";
3325
+ const response = await this.fetch.get(
3326
+ `/api/v1/jobs/${jobId}/logs${params}`
3327
+ );
3328
+ return { data: response.logs || [], error: null };
3329
+ } catch (error) {
3330
+ return { data: null, error };
3331
+ }
3332
+ }
3333
+ };
3334
+
3335
+ // src/rpc.ts
3336
+ var FluxbaseRPC = class {
3337
+ constructor(fetch2) {
3338
+ this.fetch = fetch2;
3339
+ }
3340
+ /**
3341
+ * List available RPC procedures (public, enabled)
3342
+ *
3343
+ * @param namespace - Optional namespace filter
3344
+ * @returns Promise resolving to { data, error } tuple with array of procedure summaries
3345
+ */
3346
+ async list(namespace) {
3347
+ try {
3348
+ const params = namespace ? `?namespace=${encodeURIComponent(namespace)}` : "";
3349
+ const response = await this.fetch.get(
3350
+ `/api/v1/rpc/procedures${params}`
3351
+ );
3352
+ return { data: response.procedures || [], error: null };
3353
+ } catch (error) {
3354
+ return { data: null, error };
3355
+ }
3356
+ }
3357
+ /**
3358
+ * Invoke an RPC procedure
3359
+ *
3360
+ * @param name - Procedure name
3361
+ * @param params - Optional parameters to pass to the procedure
3362
+ * @param options - Optional invocation options
3363
+ * @returns Promise resolving to { data, error } tuple with invocation response
3364
+ *
3365
+ * @example
3366
+ * ```typescript
3367
+ * // Synchronous invocation
3368
+ * const { data, error } = await fluxbase.rpc.invoke('get-user-orders', {
3369
+ * user_id: '123',
3370
+ * limit: 10
3371
+ * });
3372
+ * console.log(data.result); // Query results
3373
+ *
3374
+ * // Asynchronous invocation
3375
+ * const { data: asyncData } = await fluxbase.rpc.invoke('generate-report', {
3376
+ * year: 2024
3377
+ * }, { async: true });
3378
+ * console.log(asyncData.execution_id); // Use to poll status
3379
+ * ```
3380
+ */
3381
+ async invoke(name, params, options) {
3382
+ try {
3383
+ const namespace = options?.namespace || "default";
3384
+ const response = await this.fetch.post(
3385
+ `/api/v1/rpc/${encodeURIComponent(namespace)}/${encodeURIComponent(name)}`,
3386
+ {
3387
+ params,
3388
+ async: options?.async
3389
+ }
3390
+ );
3391
+ return { data: response, error: null };
3392
+ } catch (error) {
3393
+ return { data: null, error };
3394
+ }
3395
+ }
3396
+ /**
3397
+ * Get execution status (for async invocations or checking history)
3398
+ *
3399
+ * @param executionId - The execution ID returned from async invoke
3400
+ * @returns Promise resolving to { data, error } tuple with execution details
3401
+ *
3402
+ * @example
3403
+ * ```typescript
3404
+ * const { data, error } = await fluxbase.rpc.getStatus('execution-uuid');
3405
+ * if (data.status === 'completed') {
3406
+ * console.log('Result:', data.result);
3407
+ * } else if (data.status === 'running') {
3408
+ * console.log('Still running...');
3409
+ * }
3410
+ * ```
3411
+ */
3412
+ async getStatus(executionId) {
3413
+ try {
3414
+ const data = await this.fetch.get(
3415
+ `/api/v1/rpc/executions/${encodeURIComponent(executionId)}`
3416
+ );
3417
+ return { data, error: null };
3418
+ } catch (error) {
3419
+ return { data: null, error };
3420
+ }
3421
+ }
3422
+ /**
3423
+ * Get execution logs (for debugging and monitoring)
3424
+ *
3425
+ * @param executionId - The execution ID
3426
+ * @param afterLine - Optional line number to get logs after (for polling)
3427
+ * @returns Promise resolving to { data, error } tuple with execution logs
3428
+ *
3429
+ * @example
3430
+ * ```typescript
3431
+ * const { data: logs } = await fluxbase.rpc.getLogs('execution-uuid');
3432
+ * for (const log of logs) {
3433
+ * console.log(`[${log.level}] ${log.message}`);
3434
+ * }
3435
+ * ```
3436
+ */
3437
+ async getLogs(executionId, afterLine) {
3438
+ try {
3439
+ const params = afterLine !== void 0 ? `?after=${afterLine}` : "";
3440
+ const response = await this.fetch.get(
3441
+ `/api/v1/rpc/executions/${encodeURIComponent(executionId)}/logs${params}`
3442
+ );
3443
+ return { data: response.logs || [], error: null };
3444
+ } catch (error) {
3445
+ return { data: null, error };
3446
+ }
3447
+ }
3448
+ /**
3449
+ * Poll for execution completion with exponential backoff
3450
+ *
3451
+ * @param executionId - The execution ID to poll
3452
+ * @param options - Polling options
3453
+ * @returns Promise resolving to final execution state
3454
+ *
3455
+ * @example
3456
+ * ```typescript
3457
+ * const { data: result } = await fluxbase.rpc.invoke('long-task', {}, { async: true });
3458
+ * const { data: final } = await fluxbase.rpc.waitForCompletion(result.execution_id, {
3459
+ * maxWaitMs: 60000, // Wait up to 1 minute
3460
+ * onProgress: (exec) => console.log(`Status: ${exec.status}`)
3461
+ * });
3462
+ * console.log('Final result:', final.result);
3463
+ * ```
3464
+ */
3465
+ async waitForCompletion(executionId, options) {
3466
+ const maxWait = options?.maxWaitMs || 3e4;
3467
+ const initialInterval = options?.initialIntervalMs || 500;
3468
+ const maxInterval = options?.maxIntervalMs || 5e3;
3469
+ const startTime = Date.now();
3470
+ let interval = initialInterval;
3471
+ while (Date.now() - startTime < maxWait) {
3472
+ const { data: execution, error } = await this.getStatus(executionId);
3473
+ if (error) {
3474
+ return { data: null, error };
3475
+ }
3476
+ if (!execution) {
3477
+ return { data: null, error: new Error("Execution not found") };
3478
+ }
3479
+ if (options?.onProgress) {
3480
+ options.onProgress(execution);
3481
+ }
3482
+ if (execution.status === "completed" || execution.status === "failed" || execution.status === "cancelled" || execution.status === "timeout") {
3483
+ return { data: execution, error: null };
3484
+ }
3485
+ await new Promise((resolve) => setTimeout(resolve, interval));
3486
+ interval = Math.min(interval * 1.5, maxInterval);
3487
+ }
3488
+ return { data: null, error: new Error("Timeout waiting for execution to complete") };
3489
+ }
2706
3490
  };
2707
3491
 
2708
3492
  // src/settings.ts
@@ -3268,11 +4052,13 @@ var AppSettingsManager = class {
3268
4052
  * ```
3269
4053
  */
3270
4054
  async setSetting(key, value, options) {
4055
+ const wrappedValue = value !== null && typeof value === "object" && !Array.isArray(value) ? value : { value };
3271
4056
  try {
3272
4057
  return await this.fetch.put(
3273
4058
  `/api/v1/admin/settings/custom/${key}`,
3274
4059
  {
3275
- value,
4060
+ value: wrappedValue,
4061
+ value_type: options?.value_type || "json",
3276
4062
  description: options?.description,
3277
4063
  is_public: options?.is_public,
3278
4064
  is_secret: options?.is_secret
@@ -3284,7 +4070,7 @@ var AppSettingsManager = class {
3284
4070
  "/api/v1/admin/settings/custom",
3285
4071
  {
3286
4072
  key,
3287
- value,
4073
+ value: wrappedValue,
3288
4074
  value_type: options?.value_type || "json",
3289
4075
  description: options?.description,
3290
4076
  is_public: options?.is_public ?? false,
@@ -4522,8 +5308,146 @@ var FluxbaseManagement = class {
4522
5308
  }
4523
5309
  };
4524
5310
 
5311
+ // src/bundling.ts
5312
+ var esbuild = null;
5313
+ var fs = null;
5314
+ async function loadEsbuild() {
5315
+ if (esbuild) return true;
5316
+ try {
5317
+ esbuild = await import('esbuild');
5318
+ return true;
5319
+ } catch {
5320
+ return false;
5321
+ }
5322
+ }
5323
+ async function loadFs() {
5324
+ if (fs) return true;
5325
+ try {
5326
+ fs = await import('fs');
5327
+ return true;
5328
+ } catch {
5329
+ return false;
5330
+ }
5331
+ }
5332
+ var denoExternalPlugin = {
5333
+ name: "deno-external",
5334
+ setup(build) {
5335
+ build.onResolve({ filter: /^npm:/ }, (args) => ({
5336
+ path: args.path,
5337
+ external: true
5338
+ }));
5339
+ build.onResolve({ filter: /^https?:\/\// }, (args) => ({
5340
+ path: args.path,
5341
+ external: true
5342
+ }));
5343
+ build.onResolve({ filter: /^jsr:/ }, (args) => ({
5344
+ path: args.path,
5345
+ external: true
5346
+ }));
5347
+ }
5348
+ };
5349
+ async function loadImportMap(denoJsonPath) {
5350
+ const hasFs = await loadFs();
5351
+ if (!hasFs || !fs) {
5352
+ console.warn("fs module not available, cannot load import map");
5353
+ return null;
5354
+ }
5355
+ try {
5356
+ const content = fs.readFileSync(denoJsonPath, "utf-8");
5357
+ const config = JSON.parse(content);
5358
+ return config.imports || null;
5359
+ } catch (error) {
5360
+ console.warn(`Failed to load import map from ${denoJsonPath}:`, error);
5361
+ return null;
5362
+ }
5363
+ }
5364
+ async function bundleCode(options) {
5365
+ const hasEsbuild = await loadEsbuild();
5366
+ if (!hasEsbuild || !esbuild) {
5367
+ throw new Error(
5368
+ "esbuild is required for bundling. Install it with: npm install esbuild"
5369
+ );
5370
+ }
5371
+ const externals = [...options.external ?? []];
5372
+ const alias = {};
5373
+ if (options.importMap) {
5374
+ for (const [key, value] of Object.entries(options.importMap)) {
5375
+ if (value.startsWith("npm:")) {
5376
+ externals.push(key);
5377
+ } else if (value.startsWith("https://") || value.startsWith("http://")) {
5378
+ externals.push(key);
5379
+ } else if (value.startsWith("/") || value.startsWith("./") || value.startsWith("../")) {
5380
+ alias[key] = value;
5381
+ } else {
5382
+ externals.push(key);
5383
+ }
5384
+ }
5385
+ }
5386
+ const denoPlugin = {
5387
+ name: "deno-external",
5388
+ setup(build) {
5389
+ build.onResolve({ filter: /^npm:/ }, (args) => ({
5390
+ path: args.path,
5391
+ external: true
5392
+ }));
5393
+ build.onResolve({ filter: /^https?:\/\// }, (args) => ({
5394
+ path: args.path,
5395
+ external: true
5396
+ }));
5397
+ build.onResolve({ filter: /^jsr:/ }, (args) => ({
5398
+ path: args.path,
5399
+ external: true
5400
+ }));
5401
+ }
5402
+ };
5403
+ const resolveDir = options.baseDir || process.cwd?.() || "/";
5404
+ const buildOptions = {
5405
+ stdin: {
5406
+ contents: options.code,
5407
+ loader: "ts",
5408
+ resolveDir
5409
+ },
5410
+ // Set absWorkingDir for consistent path resolution
5411
+ absWorkingDir: resolveDir,
5412
+ bundle: true,
5413
+ write: false,
5414
+ format: "esm",
5415
+ // Use 'node' platform for better node_modules resolution (Deno supports Node APIs)
5416
+ platform: "node",
5417
+ target: "esnext",
5418
+ minify: options.minify ?? false,
5419
+ sourcemap: options.sourcemap ? "inline" : false,
5420
+ external: externals,
5421
+ plugins: [denoPlugin],
5422
+ // Preserve handler export
5423
+ treeShaking: true,
5424
+ // Resolve .ts, .js, .mjs extensions
5425
+ resolveExtensions: [".ts", ".tsx", ".js", ".mjs", ".json"],
5426
+ // ESM conditions for better module resolution
5427
+ conditions: ["import", "module"]
5428
+ };
5429
+ if (Object.keys(alias).length > 0) {
5430
+ buildOptions.alias = alias;
5431
+ }
5432
+ if (options.nodePaths && options.nodePaths.length > 0) {
5433
+ buildOptions.nodePaths = options.nodePaths;
5434
+ }
5435
+ if (options.define) {
5436
+ buildOptions.define = options.define;
5437
+ }
5438
+ const result = await esbuild.build(buildOptions);
5439
+ const output = result.outputFiles?.[0];
5440
+ if (!output) {
5441
+ throw new Error("Bundling failed: no output generated");
5442
+ }
5443
+ return {
5444
+ code: output.text,
5445
+ sourceMap: options.sourcemap ? output.text : void 0
5446
+ };
5447
+ }
5448
+
4525
5449
  // src/admin-functions.ts
4526
- var FluxbaseAdminFunctions = class {
5450
+ var FluxbaseAdminFunctions = class _FluxbaseAdminFunctions {
4527
5451
  constructor(fetch2) {
4528
5452
  this.fetch = fetch2;
4529
5453
  }
@@ -4760,34 +5684,139 @@ var FluxbaseAdminFunctions = class {
4760
5684
  return { data: null, error };
4761
5685
  }
4762
5686
  }
4763
- };
4764
-
4765
- // src/admin-migrations.ts
4766
- var FluxbaseAdminMigrations = class {
4767
- constructor(fetch2) {
4768
- this.localMigrations = /* @__PURE__ */ new Map();
4769
- this.fetch = fetch2;
4770
- }
4771
5687
  /**
4772
- * Register a migration locally for smart sync
5688
+ * Sync edge functions with automatic client-side bundling
4773
5689
  *
4774
- * Call this method to register migrations in your application code.
4775
- * When you call sync(), only new or changed migrations will be sent to the server.
5690
+ * This is a convenience method that bundles all function code using esbuild
5691
+ * before sending to the server. Requires esbuild as a peer dependency.
4776
5692
  *
4777
- * @param migration - Migration definition
4778
- * @returns { error } tuple (always succeeds unless validation fails)
5693
+ * @param options - Sync options including namespace and functions array
5694
+ * @param bundleOptions - Optional bundling configuration
5695
+ * @returns Promise resolving to { data, error } tuple with sync results
4779
5696
  *
4780
5697
  * @example
4781
5698
  * ```typescript
4782
- * // In your app initialization
4783
- * const { error: err1 } = client.admin.migrations.register({
4784
- * name: '001_create_users_table',
4785
- * namespace: 'myapp',
4786
- * up_sql: 'CREATE TABLE app.users (...)',
4787
- * down_sql: 'DROP TABLE app.users',
4788
- * description: 'Initial users table'
5699
+ * const { data, error } = await client.admin.functions.syncWithBundling({
5700
+ * namespace: 'default',
5701
+ * functions: [
5702
+ * { name: 'hello', code: helloCode },
5703
+ * { name: 'goodbye', code: goodbyeCode },
5704
+ * ],
5705
+ * options: { delete_missing: true }
4789
5706
  * })
4790
- *
5707
+ * ```
5708
+ */
5709
+ async syncWithBundling(options, bundleOptions) {
5710
+ if (!options.functions || options.functions.length === 0) {
5711
+ return this.sync(options);
5712
+ }
5713
+ const hasEsbuild = await loadEsbuild();
5714
+ if (!hasEsbuild) {
5715
+ return {
5716
+ data: null,
5717
+ error: new Error(
5718
+ "esbuild is required for client-side bundling. Install it with: npm install esbuild"
5719
+ )
5720
+ };
5721
+ }
5722
+ try {
5723
+ const bundledFunctions = await Promise.all(
5724
+ options.functions.map(async (fn) => {
5725
+ if (fn.is_pre_bundled) {
5726
+ return fn;
5727
+ }
5728
+ const bundled = await _FluxbaseAdminFunctions.bundleCode({
5729
+ // Apply global bundle options first
5730
+ ...bundleOptions,
5731
+ // Then override with per-function values (these take priority)
5732
+ code: fn.code,
5733
+ // Use function's sourceDir for resolving relative imports
5734
+ baseDir: fn.sourceDir || bundleOptions?.baseDir,
5735
+ // Use function's nodePaths for additional module resolution
5736
+ nodePaths: fn.nodePaths || bundleOptions?.nodePaths
5737
+ });
5738
+ return {
5739
+ ...fn,
5740
+ code: bundled.code,
5741
+ original_code: fn.code,
5742
+ is_pre_bundled: true
5743
+ };
5744
+ })
5745
+ );
5746
+ return this.sync({
5747
+ ...options,
5748
+ functions: bundledFunctions
5749
+ });
5750
+ } catch (error) {
5751
+ return { data: null, error };
5752
+ }
5753
+ }
5754
+ /**
5755
+ * Bundle function code using esbuild (client-side)
5756
+ *
5757
+ * Transforms and bundles TypeScript/JavaScript code into a single file
5758
+ * that can be executed by the Fluxbase edge functions runtime.
5759
+ *
5760
+ * Requires esbuild as a peer dependency.
5761
+ *
5762
+ * @param options - Bundle options including source code
5763
+ * @returns Promise resolving to bundled code
5764
+ * @throws Error if esbuild is not available
5765
+ *
5766
+ * @example
5767
+ * ```typescript
5768
+ * const bundled = await FluxbaseAdminFunctions.bundleCode({
5769
+ * code: `
5770
+ * import { helper } from './utils'
5771
+ * export default async function handler(req) {
5772
+ * return helper(req.body)
5773
+ * }
5774
+ * `,
5775
+ * minify: true,
5776
+ * })
5777
+ *
5778
+ * // Use bundled code in sync
5779
+ * await client.admin.functions.sync({
5780
+ * namespace: 'default',
5781
+ * functions: [{
5782
+ * name: 'my-function',
5783
+ * code: bundled.code,
5784
+ * is_pre_bundled: true,
5785
+ * }]
5786
+ * })
5787
+ * ```
5788
+ */
5789
+ static async bundleCode(options) {
5790
+ return bundleCode(options);
5791
+ }
5792
+ };
5793
+
5794
+ // src/admin-migrations.ts
5795
+ var FluxbaseAdminMigrations = class {
5796
+ constructor(fetch2) {
5797
+ this.localMigrations = /* @__PURE__ */ new Map();
5798
+ this.fetch = fetch2;
5799
+ }
5800
+ /**
5801
+ * Register a migration locally for smart sync
5802
+ *
5803
+ * Call this method to register migrations in your application code.
5804
+ * When you call sync(), only new or changed migrations will be sent to the server.
5805
+ *
5806
+ * @param migration - Migration definition
5807
+ * @returns { error } tuple (always succeeds unless validation fails)
5808
+ *
5809
+ * @example
5810
+ * ```typescript
5811
+ * // In your app initialization
5812
+ * const { error: err1 } = client.admin.migrations.register({
5813
+ * name: '001_create_users_table',
5814
+ * namespace: 'myapp',
5815
+ * up_sql: 'CREATE TABLE app.users (...)',
5816
+ * down_sql: 'DROP TABLE app.users',
5817
+ * description: 'Initial users table'
5818
+ * })
5819
+ *
4791
5820
  * const { error: err2 } = client.admin.migrations.register({
4792
5821
  * name: '002_add_posts_table',
4793
5822
  * namespace: 'myapp',
@@ -4814,54 +5843,16 @@ var FluxbaseAdminMigrations = class {
4814
5843
  }
4815
5844
  }
4816
5845
  /**
4817
- * Trigger schema refresh which will restart the server
4818
- * Handles the restart gracefully by waiting for the server to come back online
5846
+ * Trigger schema refresh to update the REST API cache
5847
+ * Note: Server no longer restarts - cache is invalidated instantly
4819
5848
  *
4820
5849
  * @private
4821
5850
  */
4822
- async triggerSchemaRefreshWithRestart() {
4823
- console.log("Triggering schema refresh (server will restart)...");
4824
- try {
4825
- const response = await this.fetch.post(
4826
- "/api/v1/admin/schema/refresh",
4827
- {}
4828
- );
4829
- console.log("Server restart initiated:", response.message || "Schema refresh in progress");
4830
- } catch (error) {
4831
- const isConnectionError = error.message?.includes("fetch failed") || error.message?.includes("ECONNREFUSED") || error.message?.includes("ECONNRESET") || error.code === "ECONNREFUSED" || error.code === "ECONNRESET";
4832
- if (!isConnectionError) {
4833
- throw error;
4834
- }
4835
- console.log("Connection dropped (expected during restart)...");
4836
- }
4837
- console.log("Waiting 6 seconds for server to restart...");
4838
- await this.sleep(6e3);
4839
- const maxAttempts = 5;
4840
- const baseDelay = 1e3;
4841
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
4842
- try {
4843
- await this.fetch.get("/health");
4844
- console.log("Server is back online and ready");
4845
- return;
4846
- } catch (error) {
4847
- const isLastAttempt = attempt === maxAttempts;
4848
- if (isLastAttempt) {
4849
- throw new Error(
4850
- `Server did not come back online after ${maxAttempts} attempts. Please check server logs and try again.`
4851
- );
4852
- }
4853
- const delay = baseDelay * Math.pow(2, attempt - 1);
4854
- console.log(`Server not ready yet, retrying in ${delay}ms... (attempt ${attempt}/${maxAttempts})`);
4855
- await this.sleep(delay);
4856
- }
4857
- }
4858
- }
4859
- /**
4860
- * Helper function to sleep for a given duration
4861
- * @private
4862
- */
4863
- sleep(ms) {
4864
- return new Promise((resolve) => setTimeout(resolve, ms));
5851
+ async triggerSchemaRefresh() {
5852
+ const response = await this.fetch.post("/api/v1/admin/schema/refresh", {});
5853
+ console.log(
5854
+ `Schema cache refreshed: ${response.tables} tables, ${response.views} views`
5855
+ );
4865
5856
  }
4866
5857
  /**
4867
5858
  * Smart sync all registered migrations
@@ -4960,12 +5951,12 @@ var FluxbaseAdminMigrations = class {
4960
5951
  dry_run: options.dry_run ?? false,
4961
5952
  warnings: results.flatMap((r) => r.warnings || [])
4962
5953
  };
4963
- const hasChanges = combined.summary.created > 0 || combined.summary.updated > 0;
4964
- if (!combined.dry_run && hasChanges) {
5954
+ const migrationsAppliedSuccessfully = combined.summary.applied > 0 && combined.summary.errors === 0;
5955
+ if (!combined.dry_run && migrationsAppliedSuccessfully) {
4965
5956
  try {
4966
- await this.triggerSchemaRefreshWithRestart();
5957
+ await this.triggerSchemaRefresh();
4967
5958
  } catch (refreshError) {
4968
- console.warn("Schema refresh completed with warnings:", refreshError);
5959
+ console.warn("Schema refresh warning:", refreshError);
4969
5960
  }
4970
5961
  }
4971
5962
  if (errors.length > 0 || combined.summary.errors > 0) {
@@ -4998,7 +5989,10 @@ var FluxbaseAdminMigrations = class {
4998
5989
  */
4999
5990
  async create(request) {
5000
5991
  try {
5001
- const data = await this.fetch.post("/api/v1/admin/migrations", request);
5992
+ const data = await this.fetch.post(
5993
+ "/api/v1/admin/migrations",
5994
+ request
5995
+ );
5002
5996
  return { data, error: null };
5003
5997
  } catch (error) {
5004
5998
  return { data: null, error };
@@ -5099,7 +6093,9 @@ var FluxbaseAdminMigrations = class {
5099
6093
  async delete(name, namespace = "default") {
5100
6094
  try {
5101
6095
  const params = new URLSearchParams({ namespace });
5102
- await this.fetch.delete(`/api/v1/admin/migrations/${name}?${params.toString()}`);
6096
+ await this.fetch.delete(
6097
+ `/api/v1/admin/migrations/${name}?${params.toString()}`
6098
+ );
5103
6099
  return { data: null, error: null };
5104
6100
  } catch (error) {
5105
6101
  return { data: null, error };
@@ -5200,7 +6196,10 @@ var FluxbaseAdminMigrations = class {
5200
6196
  */
5201
6197
  async getExecutions(name, namespace = "default", limit = 50) {
5202
6198
  try {
5203
- const params = new URLSearchParams({ namespace, limit: limit.toString() });
6199
+ const params = new URLSearchParams({
6200
+ namespace,
6201
+ limit: limit.toString()
6202
+ });
5204
6203
  const data = await this.fetch.get(
5205
6204
  `/api/v1/admin/migrations/${name}/executions?${params.toString()}`
5206
6205
  );
@@ -5212,16 +6211,6 @@ var FluxbaseAdminMigrations = class {
5212
6211
  };
5213
6212
 
5214
6213
  // src/admin-jobs.ts
5215
- var esbuild = null;
5216
- async function loadEsbuild() {
5217
- if (esbuild) return true;
5218
- try {
5219
- esbuild = await import('esbuild');
5220
- return true;
5221
- } catch {
5222
- return false;
5223
- }
5224
- }
5225
6214
  var FluxbaseAdminJobs = class _FluxbaseAdminJobs {
5226
6215
  constructor(fetch2) {
5227
6216
  this.fetch = fetch2;
@@ -5706,201 +6695,1418 @@ var FluxbaseAdminJobs = class _FluxbaseAdminJobs {
5706
6695
  * ```
5707
6696
  */
5708
6697
  static async bundleCode(options) {
5709
- const hasEsbuild = await loadEsbuild();
5710
- if (!hasEsbuild || !esbuild) {
5711
- throw new Error(
5712
- "esbuild is required for bundling. Install it with: npm install esbuild"
5713
- );
5714
- }
5715
- const externals = [...options.external ?? []];
5716
- const alias = {};
5717
- if (options.importMap) {
5718
- for (const [key, value] of Object.entries(options.importMap)) {
5719
- if (value.startsWith("npm:")) {
5720
- externals.push(key);
5721
- } else if (value.startsWith("https://") || value.startsWith("http://")) {
5722
- externals.push(key);
5723
- } else if (value.startsWith("/") || value.startsWith("./") || value.startsWith("../")) {
5724
- alias[key] = value;
5725
- } else {
5726
- externals.push(key);
5727
- }
5728
- }
5729
- }
5730
- const denoExternalPlugin = {
5731
- name: "deno-external",
5732
- setup(build) {
5733
- build.onResolve({ filter: /^npm:/ }, (args) => ({
5734
- path: args.path,
5735
- external: true
5736
- }));
5737
- build.onResolve({ filter: /^https?:\/\// }, (args) => ({
5738
- path: args.path,
5739
- external: true
5740
- }));
5741
- build.onResolve({ filter: /^jsr:/ }, (args) => ({
5742
- path: args.path,
5743
- external: true
5744
- }));
5745
- }
5746
- };
5747
- const resolveDir = options.baseDir || process.cwd?.() || "/";
5748
- const buildOptions = {
5749
- stdin: {
5750
- contents: options.code,
5751
- loader: "ts",
5752
- resolveDir
5753
- },
5754
- // Set absWorkingDir for consistent path resolution
5755
- absWorkingDir: resolveDir,
5756
- bundle: true,
5757
- write: false,
5758
- format: "esm",
5759
- // Use 'node' platform for better node_modules resolution (Deno supports Node APIs)
5760
- platform: "node",
5761
- target: "esnext",
5762
- minify: options.minify ?? false,
5763
- sourcemap: options.sourcemap ? "inline" : false,
5764
- external: externals,
5765
- plugins: [denoExternalPlugin],
5766
- // Preserve handler export
5767
- treeShaking: true,
5768
- // Resolve .ts, .js, .mjs extensions
5769
- resolveExtensions: [".ts", ".tsx", ".js", ".mjs", ".json"],
5770
- // ESM conditions for better module resolution
5771
- conditions: ["import", "module"]
5772
- };
5773
- if (Object.keys(alias).length > 0) {
5774
- buildOptions.alias = alias;
5775
- }
5776
- if (options.nodePaths && options.nodePaths.length > 0) {
5777
- buildOptions.nodePaths = options.nodePaths;
5778
- }
5779
- if (options.define) {
5780
- buildOptions.define = options.define;
5781
- }
5782
- const result = await esbuild.build(buildOptions);
5783
- const output = result.outputFiles?.[0];
5784
- if (!output) {
5785
- throw new Error("Bundling failed: no output generated");
5786
- }
5787
- return {
5788
- code: output.text,
5789
- sourceMap: options.sourcemap ? output.text : void 0
5790
- };
6698
+ return bundleCode(options);
5791
6699
  }
5792
6700
  };
5793
6701
 
5794
- // src/admin.ts
5795
- var FluxbaseAdmin = class {
6702
+ // src/admin-ai.ts
6703
+ var FluxbaseAdminAI = class {
5796
6704
  constructor(fetch2) {
5797
- this.adminToken = null;
5798
6705
  this.fetch = fetch2;
5799
- this.settings = new FluxbaseSettings(fetch2);
5800
- this.ddl = new DDLManager(fetch2);
5801
- this.oauth = new FluxbaseOAuth(fetch2);
5802
- this.impersonation = new ImpersonationManager(fetch2);
5803
- this.management = new FluxbaseManagement(fetch2);
5804
- this.emailTemplates = new EmailTemplateManager(fetch2);
5805
- this.functions = new FluxbaseAdminFunctions(fetch2);
5806
- this.jobs = new FluxbaseAdminJobs(fetch2);
5807
- this.migrations = new FluxbaseAdminMigrations(fetch2);
5808
- }
5809
- /**
5810
- * Set admin authentication token
5811
- */
5812
- setToken(token) {
5813
- this.adminToken = token;
5814
- this.fetch.setAuthToken(token);
5815
- }
5816
- /**
5817
- * Get current admin token
5818
- */
5819
- getToken() {
5820
- return this.adminToken;
5821
- }
5822
- /**
5823
- * Clear admin token
5824
- */
5825
- clearToken() {
5826
- this.adminToken = null;
5827
- this.fetch.setAuthToken(null);
5828
6706
  }
5829
6707
  // ============================================================================
5830
- // Admin Authentication
6708
+ // CHATBOT MANAGEMENT
5831
6709
  // ============================================================================
5832
6710
  /**
5833
- * Check if initial admin setup is needed
6711
+ * List all chatbots (admin view)
5834
6712
  *
5835
- * @returns Setup status indicating if initial setup is required
6713
+ * @param namespace - Optional namespace filter
6714
+ * @returns Promise resolving to { data, error } tuple with array of chatbot summaries
5836
6715
  *
5837
6716
  * @example
5838
6717
  * ```typescript
5839
- * const status = await admin.getSetupStatus();
5840
- * if (status.needs_setup) {
5841
- * console.log('Initial setup required');
6718
+ * const { data, error } = await client.admin.ai.listChatbots()
6719
+ * if (data) {
6720
+ * console.log('Chatbots:', data.map(c => c.name))
5842
6721
  * }
5843
6722
  * ```
5844
6723
  */
5845
- async getSetupStatus() {
5846
- return wrapAsync(async () => {
5847
- return await this.fetch.get(
5848
- "/api/v1/admin/setup/status"
5849
- );
5850
- });
6724
+ async listChatbots(namespace) {
6725
+ try {
6726
+ const params = namespace ? `?namespace=${namespace}` : "";
6727
+ const response = await this.fetch.get(`/api/v1/admin/ai/chatbots${params}`);
6728
+ return { data: response.chatbots || [], error: null };
6729
+ } catch (error) {
6730
+ return { data: null, error };
6731
+ }
5851
6732
  }
5852
6733
  /**
5853
- * Perform initial admin setup
5854
- *
5855
- * Creates the first admin user and completes initial setup.
5856
- * This endpoint can only be called once.
6734
+ * Get details of a specific chatbot
5857
6735
  *
5858
- * @param request - Setup request containing email, password, and name
5859
- * @returns Authentication response with tokens
6736
+ * @param id - Chatbot ID
6737
+ * @returns Promise resolving to { data, error } tuple with chatbot details
5860
6738
  *
5861
6739
  * @example
5862
6740
  * ```typescript
5863
- * const response = await admin.setup({
5864
- * email: 'admin@example.com',
5865
- * password: 'SecurePassword123!',
5866
- * name: 'Admin User'
5867
- * });
5868
- *
5869
- * // Store tokens
5870
- * localStorage.setItem('admin_token', response.access_token);
6741
+ * const { data, error } = await client.admin.ai.getChatbot('uuid')
6742
+ * if (data) {
6743
+ * console.log('Chatbot:', data.name)
6744
+ * }
5871
6745
  * ```
5872
6746
  */
5873
- async setup(request) {
5874
- return wrapAsync(async () => {
5875
- const response = await this.fetch.post(
5876
- "/api/v1/admin/setup",
5877
- request
6747
+ async getChatbot(id) {
6748
+ try {
6749
+ const data = await this.fetch.get(
6750
+ `/api/v1/admin/ai/chatbots/${id}`
5878
6751
  );
5879
- this.setToken(response.access_token);
5880
- return response;
5881
- });
6752
+ return { data, error: null };
6753
+ } catch (error) {
6754
+ return { data: null, error };
6755
+ }
5882
6756
  }
5883
6757
  /**
5884
- * Admin login
5885
- *
5886
- * Authenticate as an admin user
6758
+ * Enable or disable a chatbot
5887
6759
  *
5888
- * @param request - Login request containing email and password
5889
- * @returns Authentication response with tokens
6760
+ * @param id - Chatbot ID
6761
+ * @param enabled - Whether to enable or disable
6762
+ * @returns Promise resolving to { data, error } tuple with updated chatbot
5890
6763
  *
5891
6764
  * @example
5892
6765
  * ```typescript
5893
- * const response = await admin.login({
5894
- * email: 'admin@example.com',
5895
- * password: 'password123'
5896
- * });
5897
- *
5898
- * // Token is automatically set in the client
5899
- * console.log('Logged in as:', response.user.email);
6766
+ * const { data, error } = await client.admin.ai.toggleChatbot('uuid', true)
5900
6767
  * ```
5901
6768
  */
5902
- async login(request) {
5903
- return wrapAsync(async () => {
6769
+ async toggleChatbot(id, enabled) {
6770
+ try {
6771
+ const data = await this.fetch.put(
6772
+ `/api/v1/admin/ai/chatbots/${id}/toggle`,
6773
+ { enabled }
6774
+ );
6775
+ return { data, error: null };
6776
+ } catch (error) {
6777
+ return { data: null, error };
6778
+ }
6779
+ }
6780
+ /**
6781
+ * Delete a chatbot
6782
+ *
6783
+ * @param id - Chatbot ID
6784
+ * @returns Promise resolving to { data, error } tuple
6785
+ *
6786
+ * @example
6787
+ * ```typescript
6788
+ * const { data, error } = await client.admin.ai.deleteChatbot('uuid')
6789
+ * ```
6790
+ */
6791
+ async deleteChatbot(id) {
6792
+ try {
6793
+ await this.fetch.delete(`/api/v1/admin/ai/chatbots/${id}`);
6794
+ return { data: null, error: null };
6795
+ } catch (error) {
6796
+ return { data: null, error };
6797
+ }
6798
+ }
6799
+ /**
6800
+ * Sync chatbots from filesystem or API payload
6801
+ *
6802
+ * Can sync from:
6803
+ * 1. Filesystem (if no chatbots provided) - loads from configured chatbots directory
6804
+ * 2. API payload (if chatbots array provided) - syncs provided chatbot specifications
6805
+ *
6806
+ * Requires service_role or admin authentication.
6807
+ *
6808
+ * @param options - Sync options including namespace and optional chatbots array
6809
+ * @returns Promise resolving to { data, error } tuple with sync results
6810
+ *
6811
+ * @example
6812
+ * ```typescript
6813
+ * // Sync from filesystem
6814
+ * const { data, error } = await client.admin.ai.sync()
6815
+ *
6816
+ * // Sync with provided chatbot code
6817
+ * const { data, error } = await client.admin.ai.sync({
6818
+ * namespace: 'default',
6819
+ * chatbots: [{
6820
+ * name: 'sql-assistant',
6821
+ * code: myChatbotCode,
6822
+ * }],
6823
+ * options: {
6824
+ * delete_missing: false, // Don't remove chatbots not in this sync
6825
+ * dry_run: false, // Preview changes without applying
6826
+ * }
6827
+ * })
6828
+ *
6829
+ * if (data) {
6830
+ * console.log(`Synced: ${data.summary.created} created, ${data.summary.updated} updated`)
6831
+ * }
6832
+ * ```
6833
+ */
6834
+ async sync(options) {
6835
+ try {
6836
+ const data = await this.fetch.post(
6837
+ "/api/v1/admin/ai/chatbots/sync",
6838
+ {
6839
+ namespace: options?.namespace || "default",
6840
+ chatbots: options?.chatbots,
6841
+ options: {
6842
+ delete_missing: options?.options?.delete_missing ?? false,
6843
+ dry_run: options?.options?.dry_run ?? false
6844
+ }
6845
+ }
6846
+ );
6847
+ return { data, error: null };
6848
+ } catch (error) {
6849
+ return { data: null, error };
6850
+ }
6851
+ }
6852
+ // ============================================================================
6853
+ // PROVIDER MANAGEMENT
6854
+ // ============================================================================
6855
+ /**
6856
+ * List all AI providers
6857
+ *
6858
+ * @returns Promise resolving to { data, error } tuple with array of providers
6859
+ *
6860
+ * @example
6861
+ * ```typescript
6862
+ * const { data, error } = await client.admin.ai.listProviders()
6863
+ * if (data) {
6864
+ * console.log('Providers:', data.map(p => p.name))
6865
+ * }
6866
+ * ```
6867
+ */
6868
+ async listProviders() {
6869
+ try {
6870
+ const response = await this.fetch.get("/api/v1/admin/ai/providers");
6871
+ return { data: response.providers || [], error: null };
6872
+ } catch (error) {
6873
+ return { data: null, error };
6874
+ }
6875
+ }
6876
+ /**
6877
+ * Get details of a specific provider
6878
+ *
6879
+ * @param id - Provider ID
6880
+ * @returns Promise resolving to { data, error } tuple with provider details
6881
+ *
6882
+ * @example
6883
+ * ```typescript
6884
+ * const { data, error } = await client.admin.ai.getProvider('uuid')
6885
+ * if (data) {
6886
+ * console.log('Provider:', data.display_name)
6887
+ * }
6888
+ * ```
6889
+ */
6890
+ async getProvider(id) {
6891
+ try {
6892
+ const data = await this.fetch.get(
6893
+ `/api/v1/admin/ai/providers/${id}`
6894
+ );
6895
+ return { data, error: null };
6896
+ } catch (error) {
6897
+ return { data: null, error };
6898
+ }
6899
+ }
6900
+ /**
6901
+ * Create a new AI provider
6902
+ *
6903
+ * @param request - Provider configuration
6904
+ * @returns Promise resolving to { data, error } tuple with created provider
6905
+ *
6906
+ * @example
6907
+ * ```typescript
6908
+ * const { data, error } = await client.admin.ai.createProvider({
6909
+ * name: 'openai-main',
6910
+ * display_name: 'OpenAI (Main)',
6911
+ * provider_type: 'openai',
6912
+ * is_default: true,
6913
+ * config: {
6914
+ * api_key: 'sk-...',
6915
+ * model: 'gpt-4-turbo',
6916
+ * }
6917
+ * })
6918
+ * ```
6919
+ */
6920
+ async createProvider(request) {
6921
+ try {
6922
+ const data = await this.fetch.post(
6923
+ "/api/v1/admin/ai/providers",
6924
+ request
6925
+ );
6926
+ return { data, error: null };
6927
+ } catch (error) {
6928
+ return { data: null, error };
6929
+ }
6930
+ }
6931
+ /**
6932
+ * Update an existing AI provider
6933
+ *
6934
+ * @param id - Provider ID
6935
+ * @param updates - Fields to update
6936
+ * @returns Promise resolving to { data, error } tuple with updated provider
6937
+ *
6938
+ * @example
6939
+ * ```typescript
6940
+ * const { data, error } = await client.admin.ai.updateProvider('uuid', {
6941
+ * display_name: 'Updated Name',
6942
+ * config: {
6943
+ * api_key: 'new-key',
6944
+ * model: 'gpt-4-turbo',
6945
+ * },
6946
+ * enabled: true,
6947
+ * })
6948
+ * ```
6949
+ */
6950
+ async updateProvider(id, updates) {
6951
+ try {
6952
+ const data = await this.fetch.put(
6953
+ `/api/v1/admin/ai/providers/${id}`,
6954
+ updates
6955
+ );
6956
+ return { data, error: null };
6957
+ } catch (error) {
6958
+ return { data: null, error };
6959
+ }
6960
+ }
6961
+ /**
6962
+ * Set a provider as the default
6963
+ *
6964
+ * @param id - Provider ID
6965
+ * @returns Promise resolving to { data, error } tuple with updated provider
6966
+ *
6967
+ * @example
6968
+ * ```typescript
6969
+ * const { data, error } = await client.admin.ai.setDefaultProvider('uuid')
6970
+ * ```
6971
+ */
6972
+ async setDefaultProvider(id) {
6973
+ try {
6974
+ const data = await this.fetch.put(
6975
+ `/api/v1/admin/ai/providers/${id}/default`,
6976
+ {}
6977
+ );
6978
+ return { data, error: null };
6979
+ } catch (error) {
6980
+ return { data: null, error };
6981
+ }
6982
+ }
6983
+ /**
6984
+ * Delete a provider
6985
+ *
6986
+ * @param id - Provider ID
6987
+ * @returns Promise resolving to { data, error } tuple
6988
+ *
6989
+ * @example
6990
+ * ```typescript
6991
+ * const { data, error } = await client.admin.ai.deleteProvider('uuid')
6992
+ * ```
6993
+ */
6994
+ async deleteProvider(id) {
6995
+ try {
6996
+ await this.fetch.delete(`/api/v1/admin/ai/providers/${id}`);
6997
+ return { data: null, error: null };
6998
+ } catch (error) {
6999
+ return { data: null, error };
7000
+ }
7001
+ }
7002
+ // ============================================================================
7003
+ // KNOWLEDGE BASE MANAGEMENT (RAG)
7004
+ // ============================================================================
7005
+ /**
7006
+ * List all knowledge bases
7007
+ *
7008
+ * @returns Promise resolving to { data, error } tuple with array of knowledge base summaries
7009
+ *
7010
+ * @example
7011
+ * ```typescript
7012
+ * const { data, error } = await client.admin.ai.listKnowledgeBases()
7013
+ * if (data) {
7014
+ * console.log('Knowledge bases:', data.map(kb => kb.name))
7015
+ * }
7016
+ * ```
7017
+ */
7018
+ async listKnowledgeBases() {
7019
+ try {
7020
+ const response = await this.fetch.get("/api/v1/admin/ai/knowledge-bases");
7021
+ return { data: response.knowledge_bases || [], error: null };
7022
+ } catch (error) {
7023
+ return { data: null, error };
7024
+ }
7025
+ }
7026
+ /**
7027
+ * Get a specific knowledge base
7028
+ *
7029
+ * @param id - Knowledge base ID
7030
+ * @returns Promise resolving to { data, error } tuple with knowledge base details
7031
+ *
7032
+ * @example
7033
+ * ```typescript
7034
+ * const { data, error } = await client.admin.ai.getKnowledgeBase('uuid')
7035
+ * if (data) {
7036
+ * console.log('Knowledge base:', data.name)
7037
+ * }
7038
+ * ```
7039
+ */
7040
+ async getKnowledgeBase(id) {
7041
+ try {
7042
+ const data = await this.fetch.get(
7043
+ `/api/v1/admin/ai/knowledge-bases/${id}`
7044
+ );
7045
+ return { data, error: null };
7046
+ } catch (error) {
7047
+ return { data: null, error };
7048
+ }
7049
+ }
7050
+ /**
7051
+ * Create a new knowledge base
7052
+ *
7053
+ * @param request - Knowledge base configuration
7054
+ * @returns Promise resolving to { data, error } tuple with created knowledge base
7055
+ *
7056
+ * @example
7057
+ * ```typescript
7058
+ * const { data, error } = await client.admin.ai.createKnowledgeBase({
7059
+ * name: 'product-docs',
7060
+ * description: 'Product documentation',
7061
+ * chunk_size: 512,
7062
+ * chunk_overlap: 50,
7063
+ * })
7064
+ * ```
7065
+ */
7066
+ async createKnowledgeBase(request) {
7067
+ try {
7068
+ const data = await this.fetch.post(
7069
+ "/api/v1/admin/ai/knowledge-bases",
7070
+ request
7071
+ );
7072
+ return { data, error: null };
7073
+ } catch (error) {
7074
+ return { data: null, error };
7075
+ }
7076
+ }
7077
+ /**
7078
+ * Update an existing knowledge base
7079
+ *
7080
+ * @param id - Knowledge base ID
7081
+ * @param updates - Fields to update
7082
+ * @returns Promise resolving to { data, error } tuple with updated knowledge base
7083
+ *
7084
+ * @example
7085
+ * ```typescript
7086
+ * const { data, error } = await client.admin.ai.updateKnowledgeBase('uuid', {
7087
+ * description: 'Updated description',
7088
+ * enabled: true,
7089
+ * })
7090
+ * ```
7091
+ */
7092
+ async updateKnowledgeBase(id, updates) {
7093
+ try {
7094
+ const data = await this.fetch.put(
7095
+ `/api/v1/admin/ai/knowledge-bases/${id}`,
7096
+ updates
7097
+ );
7098
+ return { data, error: null };
7099
+ } catch (error) {
7100
+ return { data: null, error };
7101
+ }
7102
+ }
7103
+ /**
7104
+ * Delete a knowledge base
7105
+ *
7106
+ * @param id - Knowledge base ID
7107
+ * @returns Promise resolving to { data, error } tuple
7108
+ *
7109
+ * @example
7110
+ * ```typescript
7111
+ * const { data, error } = await client.admin.ai.deleteKnowledgeBase('uuid')
7112
+ * ```
7113
+ */
7114
+ async deleteKnowledgeBase(id) {
7115
+ try {
7116
+ await this.fetch.delete(`/api/v1/admin/ai/knowledge-bases/${id}`);
7117
+ return { data: null, error: null };
7118
+ } catch (error) {
7119
+ return { data: null, error };
7120
+ }
7121
+ }
7122
+ // ============================================================================
7123
+ // DOCUMENT MANAGEMENT
7124
+ // ============================================================================
7125
+ /**
7126
+ * List documents in a knowledge base
7127
+ *
7128
+ * @param knowledgeBaseId - Knowledge base ID
7129
+ * @returns Promise resolving to { data, error } tuple with array of documents
7130
+ *
7131
+ * @example
7132
+ * ```typescript
7133
+ * const { data, error } = await client.admin.ai.listDocuments('kb-uuid')
7134
+ * if (data) {
7135
+ * console.log('Documents:', data.map(d => d.title))
7136
+ * }
7137
+ * ```
7138
+ */
7139
+ async listDocuments(knowledgeBaseId) {
7140
+ try {
7141
+ const response = await this.fetch.get(`/api/v1/admin/ai/knowledge-bases/${knowledgeBaseId}/documents`);
7142
+ return { data: response.documents || [], error: null };
7143
+ } catch (error) {
7144
+ return { data: null, error };
7145
+ }
7146
+ }
7147
+ /**
7148
+ * Get a specific document
7149
+ *
7150
+ * @param knowledgeBaseId - Knowledge base ID
7151
+ * @param documentId - Document ID
7152
+ * @returns Promise resolving to { data, error } tuple with document details
7153
+ *
7154
+ * @example
7155
+ * ```typescript
7156
+ * const { data, error } = await client.admin.ai.getDocument('kb-uuid', 'doc-uuid')
7157
+ * ```
7158
+ */
7159
+ async getDocument(knowledgeBaseId, documentId) {
7160
+ try {
7161
+ const data = await this.fetch.get(
7162
+ `/api/v1/admin/ai/knowledge-bases/${knowledgeBaseId}/documents/${documentId}`
7163
+ );
7164
+ return { data, error: null };
7165
+ } catch (error) {
7166
+ return { data: null, error };
7167
+ }
7168
+ }
7169
+ /**
7170
+ * Add a document to a knowledge base
7171
+ *
7172
+ * Document will be chunked and embedded asynchronously.
7173
+ *
7174
+ * @param knowledgeBaseId - Knowledge base ID
7175
+ * @param request - Document content and metadata
7176
+ * @returns Promise resolving to { data, error } tuple with document ID
7177
+ *
7178
+ * @example
7179
+ * ```typescript
7180
+ * const { data, error } = await client.admin.ai.addDocument('kb-uuid', {
7181
+ * title: 'Getting Started Guide',
7182
+ * content: 'This is the content of the document...',
7183
+ * metadata: { category: 'guides' },
7184
+ * })
7185
+ * if (data) {
7186
+ * console.log('Document ID:', data.document_id)
7187
+ * }
7188
+ * ```
7189
+ */
7190
+ async addDocument(knowledgeBaseId, request) {
7191
+ try {
7192
+ const data = await this.fetch.post(
7193
+ `/api/v1/admin/ai/knowledge-bases/${knowledgeBaseId}/documents`,
7194
+ request
7195
+ );
7196
+ return { data, error: null };
7197
+ } catch (error) {
7198
+ return { data: null, error };
7199
+ }
7200
+ }
7201
+ /**
7202
+ * Upload a document file to a knowledge base
7203
+ *
7204
+ * Supported file types: PDF, TXT, MD, HTML, CSV, DOCX, XLSX, RTF, EPUB, JSON
7205
+ * Maximum file size: 50MB
7206
+ *
7207
+ * @param knowledgeBaseId - Knowledge base ID
7208
+ * @param file - File to upload (File or Blob)
7209
+ * @param title - Optional document title (defaults to filename without extension)
7210
+ * @returns Promise resolving to { data, error } tuple with upload result
7211
+ *
7212
+ * @example
7213
+ * ```typescript
7214
+ * // Browser
7215
+ * const fileInput = document.getElementById('file') as HTMLInputElement
7216
+ * const file = fileInput.files?.[0]
7217
+ * if (file) {
7218
+ * const { data, error } = await client.admin.ai.uploadDocument('kb-uuid', file)
7219
+ * if (data) {
7220
+ * console.log('Document ID:', data.document_id)
7221
+ * console.log('Extracted length:', data.extracted_length)
7222
+ * }
7223
+ * }
7224
+ *
7225
+ * // Node.js (with node-fetch or similar)
7226
+ * import { Blob } from 'buffer'
7227
+ * const content = await fs.readFile('document.pdf')
7228
+ * const blob = new Blob([content], { type: 'application/pdf' })
7229
+ * const { data, error } = await client.admin.ai.uploadDocument('kb-uuid', blob, 'My Document')
7230
+ * ```
7231
+ */
7232
+ async uploadDocument(knowledgeBaseId, file, title) {
7233
+ try {
7234
+ const formData = new FormData();
7235
+ formData.append("file", file);
7236
+ if (title) {
7237
+ formData.append("title", title);
7238
+ }
7239
+ const data = await this.fetch.post(
7240
+ `/api/v1/admin/ai/knowledge-bases/${knowledgeBaseId}/documents/upload`,
7241
+ formData
7242
+ );
7243
+ return { data, error: null };
7244
+ } catch (error) {
7245
+ return { data: null, error };
7246
+ }
7247
+ }
7248
+ /**
7249
+ * Delete a document from a knowledge base
7250
+ *
7251
+ * @param knowledgeBaseId - Knowledge base ID
7252
+ * @param documentId - Document ID
7253
+ * @returns Promise resolving to { data, error } tuple
7254
+ *
7255
+ * @example
7256
+ * ```typescript
7257
+ * const { data, error } = await client.admin.ai.deleteDocument('kb-uuid', 'doc-uuid')
7258
+ * ```
7259
+ */
7260
+ async deleteDocument(knowledgeBaseId, documentId) {
7261
+ try {
7262
+ await this.fetch.delete(
7263
+ `/api/v1/admin/ai/knowledge-bases/${knowledgeBaseId}/documents/${documentId}`
7264
+ );
7265
+ return { data: null, error: null };
7266
+ } catch (error) {
7267
+ return { data: null, error };
7268
+ }
7269
+ }
7270
+ /**
7271
+ * Search a knowledge base
7272
+ *
7273
+ * @param knowledgeBaseId - Knowledge base ID
7274
+ * @param query - Search query
7275
+ * @param options - Search options
7276
+ * @returns Promise resolving to { data, error } tuple with search results
7277
+ *
7278
+ * @example
7279
+ * ```typescript
7280
+ * const { data, error } = await client.admin.ai.searchKnowledgeBase('kb-uuid', 'how to reset password', {
7281
+ * max_chunks: 5,
7282
+ * threshold: 0.7,
7283
+ * })
7284
+ * if (data) {
7285
+ * console.log('Results:', data.results.map(r => r.content))
7286
+ * }
7287
+ * ```
7288
+ */
7289
+ async searchKnowledgeBase(knowledgeBaseId, query, options) {
7290
+ try {
7291
+ const data = await this.fetch.post(
7292
+ `/api/v1/admin/ai/knowledge-bases/${knowledgeBaseId}/search`,
7293
+ {
7294
+ query,
7295
+ max_chunks: options?.max_chunks,
7296
+ threshold: options?.threshold
7297
+ }
7298
+ );
7299
+ return { data, error: null };
7300
+ } catch (error) {
7301
+ return { data: null, error };
7302
+ }
7303
+ }
7304
+ // ============================================================================
7305
+ // CHATBOT KNOWLEDGE BASE LINKING
7306
+ // ============================================================================
7307
+ /**
7308
+ * List knowledge bases linked to a chatbot
7309
+ *
7310
+ * @param chatbotId - Chatbot ID
7311
+ * @returns Promise resolving to { data, error } tuple with linked knowledge bases
7312
+ *
7313
+ * @example
7314
+ * ```typescript
7315
+ * const { data, error } = await client.admin.ai.listChatbotKnowledgeBases('chatbot-uuid')
7316
+ * if (data) {
7317
+ * console.log('Linked KBs:', data.map(l => l.knowledge_base_id))
7318
+ * }
7319
+ * ```
7320
+ */
7321
+ async listChatbotKnowledgeBases(chatbotId) {
7322
+ try {
7323
+ const response = await this.fetch.get(`/api/v1/admin/ai/chatbots/${chatbotId}/knowledge-bases`);
7324
+ return { data: response.knowledge_bases || [], error: null };
7325
+ } catch (error) {
7326
+ return { data: null, error };
7327
+ }
7328
+ }
7329
+ /**
7330
+ * Link a knowledge base to a chatbot
7331
+ *
7332
+ * @param chatbotId - Chatbot ID
7333
+ * @param request - Link configuration
7334
+ * @returns Promise resolving to { data, error } tuple with link details
7335
+ *
7336
+ * @example
7337
+ * ```typescript
7338
+ * const { data, error } = await client.admin.ai.linkKnowledgeBase('chatbot-uuid', {
7339
+ * knowledge_base_id: 'kb-uuid',
7340
+ * priority: 1,
7341
+ * max_chunks: 5,
7342
+ * similarity_threshold: 0.7,
7343
+ * })
7344
+ * ```
7345
+ */
7346
+ async linkKnowledgeBase(chatbotId, request) {
7347
+ try {
7348
+ const data = await this.fetch.post(
7349
+ `/api/v1/admin/ai/chatbots/${chatbotId}/knowledge-bases`,
7350
+ request
7351
+ );
7352
+ return { data, error: null };
7353
+ } catch (error) {
7354
+ return { data: null, error };
7355
+ }
7356
+ }
7357
+ /**
7358
+ * Update a chatbot-knowledge base link
7359
+ *
7360
+ * @param chatbotId - Chatbot ID
7361
+ * @param knowledgeBaseId - Knowledge base ID
7362
+ * @param updates - Fields to update
7363
+ * @returns Promise resolving to { data, error } tuple with updated link
7364
+ *
7365
+ * @example
7366
+ * ```typescript
7367
+ * const { data, error } = await client.admin.ai.updateChatbotKnowledgeBase(
7368
+ * 'chatbot-uuid',
7369
+ * 'kb-uuid',
7370
+ * { max_chunks: 10, enabled: true }
7371
+ * )
7372
+ * ```
7373
+ */
7374
+ async updateChatbotKnowledgeBase(chatbotId, knowledgeBaseId, updates) {
7375
+ try {
7376
+ const data = await this.fetch.put(
7377
+ `/api/v1/admin/ai/chatbots/${chatbotId}/knowledge-bases/${knowledgeBaseId}`,
7378
+ updates
7379
+ );
7380
+ return { data, error: null };
7381
+ } catch (error) {
7382
+ return { data: null, error };
7383
+ }
7384
+ }
7385
+ /**
7386
+ * Unlink a knowledge base from a chatbot
7387
+ *
7388
+ * @param chatbotId - Chatbot ID
7389
+ * @param knowledgeBaseId - Knowledge base ID
7390
+ * @returns Promise resolving to { data, error } tuple
7391
+ *
7392
+ * @example
7393
+ * ```typescript
7394
+ * const { data, error } = await client.admin.ai.unlinkKnowledgeBase('chatbot-uuid', 'kb-uuid')
7395
+ * ```
7396
+ */
7397
+ async unlinkKnowledgeBase(chatbotId, knowledgeBaseId) {
7398
+ try {
7399
+ await this.fetch.delete(
7400
+ `/api/v1/admin/ai/chatbots/${chatbotId}/knowledge-bases/${knowledgeBaseId}`
7401
+ );
7402
+ return { data: null, error: null };
7403
+ } catch (error) {
7404
+ return { data: null, error };
7405
+ }
7406
+ }
7407
+ };
7408
+
7409
+ // src/admin-rpc.ts
7410
+ var FluxbaseAdminRPC = class {
7411
+ constructor(fetch2) {
7412
+ this.fetch = fetch2;
7413
+ }
7414
+ // ============================================================================
7415
+ // PROCEDURE MANAGEMENT
7416
+ // ============================================================================
7417
+ /**
7418
+ * Sync RPC procedures from filesystem or API payload
7419
+ *
7420
+ * Can sync from:
7421
+ * 1. Filesystem (if no procedures provided) - loads from configured procedures directory
7422
+ * 2. API payload (if procedures array provided) - syncs provided procedure specifications
7423
+ *
7424
+ * Requires service_role or admin authentication.
7425
+ *
7426
+ * @param options - Sync options including namespace and optional procedures array
7427
+ * @returns Promise resolving to { data, error } tuple with sync results
7428
+ *
7429
+ * @example
7430
+ * ```typescript
7431
+ * // Sync from filesystem
7432
+ * const { data, error } = await client.admin.rpc.sync()
7433
+ *
7434
+ * // Sync with provided procedure code
7435
+ * const { data, error } = await client.admin.rpc.sync({
7436
+ * namespace: 'default',
7437
+ * procedures: [{
7438
+ * name: 'get-user-orders',
7439
+ * code: myProcedureSQL,
7440
+ * }],
7441
+ * options: {
7442
+ * delete_missing: false, // Don't remove procedures not in this sync
7443
+ * dry_run: false, // Preview changes without applying
7444
+ * }
7445
+ * })
7446
+ *
7447
+ * if (data) {
7448
+ * console.log(`Synced: ${data.summary.created} created, ${data.summary.updated} updated`)
7449
+ * }
7450
+ * ```
7451
+ */
7452
+ async sync(options) {
7453
+ try {
7454
+ const data = await this.fetch.post(
7455
+ "/api/v1/admin/rpc/sync",
7456
+ {
7457
+ namespace: options?.namespace || "default",
7458
+ procedures: options?.procedures,
7459
+ options: {
7460
+ delete_missing: options?.options?.delete_missing ?? false,
7461
+ dry_run: options?.options?.dry_run ?? false
7462
+ }
7463
+ }
7464
+ );
7465
+ return { data, error: null };
7466
+ } catch (error) {
7467
+ return { data: null, error };
7468
+ }
7469
+ }
7470
+ /**
7471
+ * List all RPC procedures (admin view)
7472
+ *
7473
+ * @param namespace - Optional namespace filter
7474
+ * @returns Promise resolving to { data, error } tuple with array of procedure summaries
7475
+ *
7476
+ * @example
7477
+ * ```typescript
7478
+ * const { data, error } = await client.admin.rpc.list()
7479
+ * if (data) {
7480
+ * console.log('Procedures:', data.map(p => p.name))
7481
+ * }
7482
+ * ```
7483
+ */
7484
+ async list(namespace) {
7485
+ try {
7486
+ const params = namespace ? `?namespace=${encodeURIComponent(namespace)}` : "";
7487
+ const response = await this.fetch.get(
7488
+ `/api/v1/admin/rpc/procedures${params}`
7489
+ );
7490
+ return { data: response.procedures || [], error: null };
7491
+ } catch (error) {
7492
+ return { data: null, error };
7493
+ }
7494
+ }
7495
+ /**
7496
+ * List all namespaces
7497
+ *
7498
+ * @returns Promise resolving to { data, error } tuple with array of namespace names
7499
+ *
7500
+ * @example
7501
+ * ```typescript
7502
+ * const { data, error } = await client.admin.rpc.listNamespaces()
7503
+ * if (data) {
7504
+ * console.log('Namespaces:', data)
7505
+ * }
7506
+ * ```
7507
+ */
7508
+ async listNamespaces() {
7509
+ try {
7510
+ const response = await this.fetch.get(
7511
+ "/api/v1/admin/rpc/namespaces"
7512
+ );
7513
+ return { data: response.namespaces || [], error: null };
7514
+ } catch (error) {
7515
+ return { data: null, error };
7516
+ }
7517
+ }
7518
+ /**
7519
+ * Get details of a specific RPC procedure
7520
+ *
7521
+ * @param namespace - Procedure namespace
7522
+ * @param name - Procedure name
7523
+ * @returns Promise resolving to { data, error } tuple with procedure details
7524
+ *
7525
+ * @example
7526
+ * ```typescript
7527
+ * const { data, error } = await client.admin.rpc.get('default', 'get-user-orders')
7528
+ * if (data) {
7529
+ * console.log('Procedure:', data.name)
7530
+ * console.log('SQL:', data.sql_query)
7531
+ * }
7532
+ * ```
7533
+ */
7534
+ async get(namespace, name) {
7535
+ try {
7536
+ const data = await this.fetch.get(
7537
+ `/api/v1/admin/rpc/procedures/${encodeURIComponent(namespace)}/${encodeURIComponent(name)}`
7538
+ );
7539
+ return { data, error: null };
7540
+ } catch (error) {
7541
+ return { data: null, error };
7542
+ }
7543
+ }
7544
+ /**
7545
+ * Update an RPC procedure
7546
+ *
7547
+ * @param namespace - Procedure namespace
7548
+ * @param name - Procedure name
7549
+ * @param updates - Fields to update
7550
+ * @returns Promise resolving to { data, error } tuple with updated procedure
7551
+ *
7552
+ * @example
7553
+ * ```typescript
7554
+ * const { data, error } = await client.admin.rpc.update('default', 'get-user-orders', {
7555
+ * enabled: false,
7556
+ * max_execution_time_seconds: 60,
7557
+ * })
7558
+ * ```
7559
+ */
7560
+ async update(namespace, name, updates) {
7561
+ try {
7562
+ const data = await this.fetch.put(
7563
+ `/api/v1/admin/rpc/procedures/${encodeURIComponent(namespace)}/${encodeURIComponent(name)}`,
7564
+ updates
7565
+ );
7566
+ return { data, error: null };
7567
+ } catch (error) {
7568
+ return { data: null, error };
7569
+ }
7570
+ }
7571
+ /**
7572
+ * Enable or disable an RPC procedure
7573
+ *
7574
+ * @param namespace - Procedure namespace
7575
+ * @param name - Procedure name
7576
+ * @param enabled - Whether to enable or disable
7577
+ * @returns Promise resolving to { data, error } tuple with updated procedure
7578
+ *
7579
+ * @example
7580
+ * ```typescript
7581
+ * const { data, error } = await client.admin.rpc.toggle('default', 'get-user-orders', true)
7582
+ * ```
7583
+ */
7584
+ async toggle(namespace, name, enabled) {
7585
+ return this.update(namespace, name, { enabled });
7586
+ }
7587
+ /**
7588
+ * Delete an RPC procedure
7589
+ *
7590
+ * @param namespace - Procedure namespace
7591
+ * @param name - Procedure name
7592
+ * @returns Promise resolving to { data, error } tuple
7593
+ *
7594
+ * @example
7595
+ * ```typescript
7596
+ * const { data, error } = await client.admin.rpc.delete('default', 'get-user-orders')
7597
+ * ```
7598
+ */
7599
+ async delete(namespace, name) {
7600
+ try {
7601
+ await this.fetch.delete(
7602
+ `/api/v1/admin/rpc/procedures/${encodeURIComponent(namespace)}/${encodeURIComponent(name)}`
7603
+ );
7604
+ return { data: null, error: null };
7605
+ } catch (error) {
7606
+ return { data: null, error };
7607
+ }
7608
+ }
7609
+ // ============================================================================
7610
+ // EXECUTION MONITORING
7611
+ // ============================================================================
7612
+ /**
7613
+ * List RPC executions with optional filters
7614
+ *
7615
+ * @param filters - Optional filters for namespace, procedure, status, user
7616
+ * @returns Promise resolving to { data, error } tuple with array of executions
7617
+ *
7618
+ * @example
7619
+ * ```typescript
7620
+ * // List all executions
7621
+ * const { data, error } = await client.admin.rpc.listExecutions()
7622
+ *
7623
+ * // List failed executions for a specific procedure
7624
+ * const { data, error } = await client.admin.rpc.listExecutions({
7625
+ * namespace: 'default',
7626
+ * procedure: 'get-user-orders',
7627
+ * status: 'failed',
7628
+ * })
7629
+ * ```
7630
+ */
7631
+ async listExecutions(filters) {
7632
+ try {
7633
+ const params = new URLSearchParams();
7634
+ if (filters?.namespace) params.set("namespace", filters.namespace);
7635
+ if (filters?.procedure) params.set("procedure_name", filters.procedure);
7636
+ if (filters?.status) params.set("status", filters.status);
7637
+ if (filters?.user_id) params.set("user_id", filters.user_id);
7638
+ if (filters?.limit) params.set("limit", filters.limit.toString());
7639
+ if (filters?.offset) params.set("offset", filters.offset.toString());
7640
+ const queryString = params.toString();
7641
+ const path = queryString ? `/api/v1/admin/rpc/executions?${queryString}` : "/api/v1/admin/rpc/executions";
7642
+ const response = await this.fetch.get(path);
7643
+ return { data: response.executions || [], error: null };
7644
+ } catch (error) {
7645
+ return { data: null, error };
7646
+ }
7647
+ }
7648
+ /**
7649
+ * Get details of a specific execution
7650
+ *
7651
+ * @param executionId - Execution ID
7652
+ * @returns Promise resolving to { data, error } tuple with execution details
7653
+ *
7654
+ * @example
7655
+ * ```typescript
7656
+ * const { data, error } = await client.admin.rpc.getExecution('execution-uuid')
7657
+ * if (data) {
7658
+ * console.log('Status:', data.status)
7659
+ * console.log('Duration:', data.duration_ms, 'ms')
7660
+ * }
7661
+ * ```
7662
+ */
7663
+ async getExecution(executionId) {
7664
+ try {
7665
+ const data = await this.fetch.get(
7666
+ `/api/v1/admin/rpc/executions/${encodeURIComponent(executionId)}`
7667
+ );
7668
+ return { data, error: null };
7669
+ } catch (error) {
7670
+ return { data: null, error };
7671
+ }
7672
+ }
7673
+ /**
7674
+ * Get execution logs for a specific execution
7675
+ *
7676
+ * @param executionId - Execution ID
7677
+ * @param afterLine - Optional line number to get logs after (for polling)
7678
+ * @returns Promise resolving to { data, error } tuple with execution logs
7679
+ *
7680
+ * @example
7681
+ * ```typescript
7682
+ * const { data, error } = await client.admin.rpc.getExecutionLogs('execution-uuid')
7683
+ * if (data) {
7684
+ * for (const log of data) {
7685
+ * console.log(`[${log.level}] ${log.message}`)
7686
+ * }
7687
+ * }
7688
+ * ```
7689
+ */
7690
+ async getExecutionLogs(executionId, afterLine) {
7691
+ try {
7692
+ const params = afterLine !== void 0 ? `?after=${afterLine}` : "";
7693
+ const response = await this.fetch.get(
7694
+ `/api/v1/admin/rpc/executions/${encodeURIComponent(executionId)}/logs${params}`
7695
+ );
7696
+ return { data: response.logs || [], error: null };
7697
+ } catch (error) {
7698
+ return { data: null, error };
7699
+ }
7700
+ }
7701
+ /**
7702
+ * Cancel a running execution
7703
+ *
7704
+ * @param executionId - Execution ID
7705
+ * @returns Promise resolving to { data, error } tuple with updated execution
7706
+ *
7707
+ * @example
7708
+ * ```typescript
7709
+ * const { data, error } = await client.admin.rpc.cancelExecution('execution-uuid')
7710
+ * ```
7711
+ */
7712
+ async cancelExecution(executionId) {
7713
+ try {
7714
+ const data = await this.fetch.post(
7715
+ `/api/v1/admin/rpc/executions/${encodeURIComponent(executionId)}/cancel`,
7716
+ {}
7717
+ );
7718
+ return { data, error: null };
7719
+ } catch (error) {
7720
+ return { data: null, error };
7721
+ }
7722
+ }
7723
+ };
7724
+
7725
+ // src/admin-storage.ts
7726
+ var FluxbaseAdminStorage = class {
7727
+ constructor(fetch2) {
7728
+ this.fetch = fetch2;
7729
+ }
7730
+ // ============================================================================
7731
+ // Bucket Operations
7732
+ // ============================================================================
7733
+ /**
7734
+ * List all storage buckets
7735
+ *
7736
+ * @returns List of buckets
7737
+ *
7738
+ * @example
7739
+ * ```typescript
7740
+ * const { data, error } = await admin.storage.listBuckets();
7741
+ * if (data) {
7742
+ * console.log(`Found ${data.buckets.length} buckets`);
7743
+ * }
7744
+ * ```
7745
+ */
7746
+ async listBuckets() {
7747
+ return wrapAsync(async () => {
7748
+ return await this.fetch.get(
7749
+ "/api/v1/storage/buckets"
7750
+ );
7751
+ });
7752
+ }
7753
+ /**
7754
+ * Create a new storage bucket
7755
+ *
7756
+ * @param name - Bucket name
7757
+ * @returns Success message
7758
+ *
7759
+ * @example
7760
+ * ```typescript
7761
+ * const { error } = await admin.storage.createBucket('my-bucket');
7762
+ * if (!error) {
7763
+ * console.log('Bucket created');
7764
+ * }
7765
+ * ```
7766
+ */
7767
+ async createBucket(name) {
7768
+ return wrapAsync(async () => {
7769
+ return await this.fetch.post(
7770
+ `/api/v1/storage/buckets/${encodeURIComponent(name)}`
7771
+ );
7772
+ });
7773
+ }
7774
+ /**
7775
+ * Delete a storage bucket
7776
+ *
7777
+ * @param name - Bucket name
7778
+ * @returns Success message
7779
+ *
7780
+ * @example
7781
+ * ```typescript
7782
+ * const { error } = await admin.storage.deleteBucket('my-bucket');
7783
+ * if (!error) {
7784
+ * console.log('Bucket deleted');
7785
+ * }
7786
+ * ```
7787
+ */
7788
+ async deleteBucket(name) {
7789
+ return wrapAsync(async () => {
7790
+ return await this.fetch.delete(
7791
+ `/api/v1/storage/buckets/${encodeURIComponent(name)}`
7792
+ );
7793
+ });
7794
+ }
7795
+ // ============================================================================
7796
+ // Object Operations
7797
+ // ============================================================================
7798
+ /**
7799
+ * List objects in a bucket
7800
+ *
7801
+ * @param bucket - Bucket name
7802
+ * @param prefix - Optional path prefix to filter results
7803
+ * @param delimiter - Optional delimiter for hierarchical listing (usually '/')
7804
+ * @returns List of objects and prefixes (folders)
7805
+ *
7806
+ * @example
7807
+ * ```typescript
7808
+ * // List all objects in bucket
7809
+ * const { data } = await admin.storage.listObjects('my-bucket');
7810
+ *
7811
+ * // List objects in a folder
7812
+ * const { data } = await admin.storage.listObjects('my-bucket', 'folder/', '/');
7813
+ * ```
7814
+ */
7815
+ async listObjects(bucket, prefix, delimiter) {
7816
+ return wrapAsync(async () => {
7817
+ const params = new URLSearchParams();
7818
+ if (prefix) params.append("prefix", prefix);
7819
+ if (delimiter) params.append("delimiter", delimiter);
7820
+ const queryString = params.toString();
7821
+ const url = `/api/v1/storage/${encodeURIComponent(bucket)}${queryString ? `?${queryString}` : ""}`;
7822
+ return await this.fetch.get(url);
7823
+ });
7824
+ }
7825
+ /**
7826
+ * Get object metadata
7827
+ *
7828
+ * @param bucket - Bucket name
7829
+ * @param key - Object key (path)
7830
+ * @returns Object metadata
7831
+ *
7832
+ * @example
7833
+ * ```typescript
7834
+ * const { data } = await admin.storage.getObjectMetadata('my-bucket', 'path/to/file.txt');
7835
+ * if (data) {
7836
+ * console.log(`File size: ${data.size} bytes`);
7837
+ * }
7838
+ * ```
7839
+ */
7840
+ async getObjectMetadata(bucket, key) {
7841
+ return wrapAsync(async () => {
7842
+ const encodedKey = key.split("/").map((s) => encodeURIComponent(s)).join("/");
7843
+ return await this.fetch.get(
7844
+ `/api/v1/storage/${encodeURIComponent(bucket)}/${encodedKey}`,
7845
+ {
7846
+ headers: { "X-Metadata-Only": "true" }
7847
+ }
7848
+ );
7849
+ });
7850
+ }
7851
+ /**
7852
+ * Download an object as a Blob
7853
+ *
7854
+ * @param bucket - Bucket name
7855
+ * @param key - Object key (path)
7856
+ * @returns Object data as Blob
7857
+ *
7858
+ * @example
7859
+ * ```typescript
7860
+ * const { data: blob } = await admin.storage.downloadObject('my-bucket', 'file.pdf');
7861
+ * if (blob) {
7862
+ * // Use the blob
7863
+ * const url = URL.createObjectURL(blob);
7864
+ * }
7865
+ * ```
7866
+ */
7867
+ async downloadObject(bucket, key) {
7868
+ return wrapAsync(async () => {
7869
+ const encodedKey = key.split("/").map((s) => encodeURIComponent(s)).join("/");
7870
+ const response = await this.fetch.getBlob(
7871
+ `/api/v1/storage/${encodeURIComponent(bucket)}/${encodedKey}`
7872
+ );
7873
+ return response;
7874
+ });
7875
+ }
7876
+ /**
7877
+ * Delete an object
7878
+ *
7879
+ * @param bucket - Bucket name
7880
+ * @param key - Object key (path)
7881
+ *
7882
+ * @example
7883
+ * ```typescript
7884
+ * const { error } = await admin.storage.deleteObject('my-bucket', 'path/to/file.txt');
7885
+ * if (!error) {
7886
+ * console.log('Object deleted');
7887
+ * }
7888
+ * ```
7889
+ */
7890
+ async deleteObject(bucket, key) {
7891
+ return wrapAsyncVoid(async () => {
7892
+ const encodedKey = key.split("/").map((s) => encodeURIComponent(s)).join("/");
7893
+ await this.fetch.delete(
7894
+ `/api/v1/storage/${encodeURIComponent(bucket)}/${encodedKey}`
7895
+ );
7896
+ });
7897
+ }
7898
+ /**
7899
+ * Create a folder (empty object with directory content type)
7900
+ *
7901
+ * @param bucket - Bucket name
7902
+ * @param folderPath - Folder path (should end with /)
7903
+ *
7904
+ * @example
7905
+ * ```typescript
7906
+ * const { error } = await admin.storage.createFolder('my-bucket', 'new-folder/');
7907
+ * ```
7908
+ */
7909
+ async createFolder(bucket, folderPath) {
7910
+ return wrapAsyncVoid(async () => {
7911
+ const encodedPath = folderPath.split("/").map((s) => encodeURIComponent(s)).join("/");
7912
+ await this.fetch.post(
7913
+ `/api/v1/storage/${encodeURIComponent(bucket)}/${encodedPath}`,
7914
+ null,
7915
+ {
7916
+ headers: { "Content-Type": "application/x-directory" }
7917
+ }
7918
+ );
7919
+ });
7920
+ }
7921
+ /**
7922
+ * Generate a signed URL for temporary access
7923
+ *
7924
+ * @param bucket - Bucket name
7925
+ * @param key - Object key (path)
7926
+ * @param expiresIn - Expiration time in seconds
7927
+ * @returns Signed URL and expiration info
7928
+ *
7929
+ * @example
7930
+ * ```typescript
7931
+ * const { data } = await admin.storage.generateSignedUrl('my-bucket', 'file.pdf', 3600);
7932
+ * if (data) {
7933
+ * console.log(`Download at: ${data.url}`);
7934
+ * console.log(`Expires in: ${data.expires_in} seconds`);
7935
+ * }
7936
+ * ```
7937
+ */
7938
+ async generateSignedUrl(bucket, key, expiresIn) {
7939
+ return wrapAsync(async () => {
7940
+ const encodedKey = key.split("/").map((s) => encodeURIComponent(s)).join("/");
7941
+ return await this.fetch.post(
7942
+ `/api/v1/storage/${encodeURIComponent(bucket)}/${encodedKey}/signed-url`,
7943
+ { expires_in: expiresIn }
7944
+ );
7945
+ });
7946
+ }
7947
+ };
7948
+
7949
+ // src/admin.ts
7950
+ var FluxbaseAdmin = class {
7951
+ constructor(fetch2) {
7952
+ this.adminToken = null;
7953
+ this.fetch = fetch2;
7954
+ this.settings = new FluxbaseSettings(fetch2);
7955
+ this.ddl = new DDLManager(fetch2);
7956
+ this.oauth = new FluxbaseOAuth(fetch2);
7957
+ this.impersonation = new ImpersonationManager(fetch2);
7958
+ this.management = new FluxbaseManagement(fetch2);
7959
+ this.emailTemplates = new EmailTemplateManager(fetch2);
7960
+ this.functions = new FluxbaseAdminFunctions(fetch2);
7961
+ this.jobs = new FluxbaseAdminJobs(fetch2);
7962
+ this.migrations = new FluxbaseAdminMigrations(fetch2);
7963
+ this.ai = new FluxbaseAdminAI(fetch2);
7964
+ this.rpc = new FluxbaseAdminRPC(fetch2);
7965
+ this.storage = new FluxbaseAdminStorage(fetch2);
7966
+ }
7967
+ /**
7968
+ * Set admin authentication token
7969
+ */
7970
+ setToken(token) {
7971
+ this.adminToken = token;
7972
+ this.fetch.setAuthToken(token);
7973
+ }
7974
+ /**
7975
+ * Get current admin token
7976
+ */
7977
+ getToken() {
7978
+ return this.adminToken;
7979
+ }
7980
+ /**
7981
+ * Clear admin token
7982
+ */
7983
+ clearToken() {
7984
+ this.adminToken = null;
7985
+ this.fetch.setAuthToken(null);
7986
+ }
7987
+ // ============================================================================
7988
+ // Health Check
7989
+ // ============================================================================
7990
+ /**
7991
+ * Get system health status
7992
+ *
7993
+ * @returns Health status including database and realtime service status
7994
+ *
7995
+ * @example
7996
+ * ```typescript
7997
+ * const { data, error } = await admin.getHealth();
7998
+ * if (data) {
7999
+ * console.log('Status:', data.status);
8000
+ * console.log('Database:', data.services.database);
8001
+ * console.log('Realtime:', data.services.realtime);
8002
+ * }
8003
+ * ```
8004
+ */
8005
+ async getHealth() {
8006
+ return wrapAsync(async () => {
8007
+ return await this.fetch.get("/health");
8008
+ });
8009
+ }
8010
+ // ============================================================================
8011
+ // Email
8012
+ // ============================================================================
8013
+ /**
8014
+ * Send an email
8015
+ *
8016
+ * @param request - Email details (to, subject, html/text)
8017
+ *
8018
+ * @example
8019
+ * ```typescript
8020
+ * const { error } = await admin.sendEmail({
8021
+ * to: 'user@example.com',
8022
+ * subject: 'Hello',
8023
+ * html: '<p>Your message here</p>'
8024
+ * });
8025
+ * if (!error) {
8026
+ * console.log('Email sent');
8027
+ * }
8028
+ * ```
8029
+ */
8030
+ async sendEmail(request) {
8031
+ return wrapAsyncVoid(async () => {
8032
+ await this.fetch.post("/api/v1/admin/email/send", request);
8033
+ });
8034
+ }
8035
+ // ============================================================================
8036
+ // Admin Authentication
8037
+ // ============================================================================
8038
+ /**
8039
+ * Check if initial admin setup is needed
8040
+ *
8041
+ * @returns Setup status indicating if initial setup is required
8042
+ *
8043
+ * @example
8044
+ * ```typescript
8045
+ * const status = await admin.getSetupStatus();
8046
+ * if (status.needs_setup) {
8047
+ * console.log('Initial setup required');
8048
+ * }
8049
+ * ```
8050
+ */
8051
+ async getSetupStatus() {
8052
+ return wrapAsync(async () => {
8053
+ return await this.fetch.get(
8054
+ "/api/v1/admin/setup/status"
8055
+ );
8056
+ });
8057
+ }
8058
+ /**
8059
+ * Perform initial admin setup
8060
+ *
8061
+ * Creates the first admin user and completes initial setup.
8062
+ * This endpoint can only be called once.
8063
+ *
8064
+ * @param request - Setup request containing email, password, and name
8065
+ * @returns Authentication response with tokens
8066
+ *
8067
+ * @example
8068
+ * ```typescript
8069
+ * const response = await admin.setup({
8070
+ * email: 'admin@example.com',
8071
+ * password: 'SecurePassword123!',
8072
+ * name: 'Admin User'
8073
+ * });
8074
+ *
8075
+ * // Store tokens
8076
+ * localStorage.setItem('admin_token', response.access_token);
8077
+ * ```
8078
+ */
8079
+ async setup(request) {
8080
+ return wrapAsync(async () => {
8081
+ const response = await this.fetch.post(
8082
+ "/api/v1/admin/setup",
8083
+ request
8084
+ );
8085
+ this.setToken(response.access_token);
8086
+ return response;
8087
+ });
8088
+ }
8089
+ /**
8090
+ * Admin login
8091
+ *
8092
+ * Authenticate as an admin user
8093
+ *
8094
+ * @param request - Login request containing email and password
8095
+ * @returns Authentication response with tokens
8096
+ *
8097
+ * @example
8098
+ * ```typescript
8099
+ * const response = await admin.login({
8100
+ * email: 'admin@example.com',
8101
+ * password: 'password123'
8102
+ * });
8103
+ *
8104
+ * // Token is automatically set in the client
8105
+ * console.log('Logged in as:', response.user.email);
8106
+ * ```
8107
+ */
8108
+ async login(request) {
8109
+ return wrapAsync(async () => {
5904
8110
  const response = await this.fetch.post(
5905
8111
  "/api/v1/admin/login",
5906
8112
  request
@@ -6070,79 +8276,558 @@ var FluxbaseAdmin = class {
6070
8276
  *
6071
8277
  * Permanently deletes a user and all associated data
6072
8278
  *
6073
- * @param userId - User ID to delete
6074
- * @param type - User type ('app' or 'dashboard')
6075
- * @returns Deletion confirmation
8279
+ * @param userId - User ID to delete
8280
+ * @param type - User type ('app' or 'dashboard')
8281
+ * @returns Deletion confirmation
8282
+ *
8283
+ * @example
8284
+ * ```typescript
8285
+ * await admin.deleteUser('user-uuid');
8286
+ * console.log('User deleted');
8287
+ * ```
8288
+ */
8289
+ async deleteUser(userId, type = "app") {
8290
+ return wrapAsync(async () => {
8291
+ const url = `/api/v1/admin/users/${userId}?type=${type}`;
8292
+ return await this.fetch.delete(url);
8293
+ });
8294
+ }
8295
+ /**
8296
+ * Update user role
8297
+ *
8298
+ * Changes a user's role
8299
+ *
8300
+ * @param userId - User ID
8301
+ * @param role - New role
8302
+ * @param type - User type ('app' or 'dashboard')
8303
+ * @returns Updated user
8304
+ *
8305
+ * @example
8306
+ * ```typescript
8307
+ * const user = await admin.updateUserRole('user-uuid', 'admin');
8308
+ * console.log('User role updated:', user.role);
8309
+ * ```
8310
+ */
8311
+ async updateUserRole(userId, role, type = "app") {
8312
+ return wrapAsync(async () => {
8313
+ const url = `/api/v1/admin/users/${userId}/role?type=${type}`;
8314
+ return await this.fetch.patch(url, { role });
8315
+ });
8316
+ }
8317
+ /**
8318
+ * Reset user password
8319
+ *
8320
+ * Generates a new password for the user and optionally sends it via email
8321
+ *
8322
+ * @param userId - User ID
8323
+ * @param type - User type ('app' or 'dashboard')
8324
+ * @returns Reset confirmation message
8325
+ *
8326
+ * @example
8327
+ * ```typescript
8328
+ * const response = await admin.resetUserPassword('user-uuid');
8329
+ * console.log(response.message);
8330
+ * ```
8331
+ */
8332
+ async resetUserPassword(userId, type = "app") {
8333
+ return wrapAsync(async () => {
8334
+ const url = `/api/v1/admin/users/${userId}/reset-password?type=${type}`;
8335
+ return await this.fetch.post(url, {});
8336
+ });
8337
+ }
8338
+ };
8339
+
8340
+ // src/ai.ts
8341
+ var FluxbaseAIChat = class {
8342
+ constructor(options) {
8343
+ this.ws = null;
8344
+ this.reconnectCount = 0;
8345
+ this.pendingStartResolve = null;
8346
+ this.pendingStartReject = null;
8347
+ this.accumulatedContent = /* @__PURE__ */ new Map();
8348
+ this.options = {
8349
+ reconnectAttempts: 3,
8350
+ reconnectDelay: 1e3,
8351
+ ...options
8352
+ };
8353
+ }
8354
+ /**
8355
+ * Connect to the AI chat WebSocket
8356
+ *
8357
+ * @returns Promise that resolves when connected
8358
+ */
8359
+ async connect() {
8360
+ return new Promise((resolve, reject) => {
8361
+ const url = this.buildWsUrl();
8362
+ try {
8363
+ this.ws = new WebSocket(url);
8364
+ this.ws.onopen = () => {
8365
+ this.reconnectCount = 0;
8366
+ this.emitEvent({ type: "connected" });
8367
+ resolve();
8368
+ };
8369
+ this.ws.onmessage = (event) => {
8370
+ this.handleMessage(event.data);
8371
+ };
8372
+ this.ws.onclose = (event) => {
8373
+ this.emitEvent({ type: "disconnected" });
8374
+ this.handleClose(event);
8375
+ };
8376
+ this.ws.onerror = () => {
8377
+ reject(new Error("WebSocket connection failed"));
8378
+ };
8379
+ } catch (error) {
8380
+ reject(error);
8381
+ }
8382
+ });
8383
+ }
8384
+ /**
8385
+ * Disconnect from the AI chat WebSocket
8386
+ */
8387
+ disconnect() {
8388
+ if (this.ws) {
8389
+ this.ws.close();
8390
+ this.ws = null;
8391
+ }
8392
+ }
8393
+ /**
8394
+ * Check if connected
8395
+ */
8396
+ isConnected() {
8397
+ return this.ws?.readyState === WebSocket.OPEN;
8398
+ }
8399
+ /**
8400
+ * Start a new chat session with a chatbot
8401
+ *
8402
+ * @param chatbot - Chatbot name
8403
+ * @param namespace - Optional namespace (defaults to 'default')
8404
+ * @param conversationId - Optional conversation ID to resume
8405
+ * @param impersonateUserId - Optional user ID to impersonate (admin only)
8406
+ * @returns Promise resolving to conversation ID
8407
+ */
8408
+ async startChat(chatbot, namespace, conversationId, impersonateUserId) {
8409
+ return new Promise((resolve, reject) => {
8410
+ if (!this.isConnected()) {
8411
+ reject(new Error("Not connected to AI chat"));
8412
+ return;
8413
+ }
8414
+ this.pendingStartResolve = resolve;
8415
+ this.pendingStartReject = reject;
8416
+ const message = {
8417
+ type: "start_chat",
8418
+ chatbot,
8419
+ namespace: namespace || "default",
8420
+ conversation_id: conversationId,
8421
+ impersonate_user_id: impersonateUserId
8422
+ };
8423
+ this.ws.send(JSON.stringify(message));
8424
+ });
8425
+ }
8426
+ /**
8427
+ * Send a message in a conversation
8428
+ *
8429
+ * @param conversationId - Conversation ID
8430
+ * @param content - Message content
8431
+ */
8432
+ sendMessage(conversationId, content) {
8433
+ if (!this.isConnected()) {
8434
+ throw new Error("Not connected to AI chat");
8435
+ }
8436
+ this.accumulatedContent.set(conversationId, "");
8437
+ const message = {
8438
+ type: "message",
8439
+ conversation_id: conversationId,
8440
+ content
8441
+ };
8442
+ this.ws.send(JSON.stringify(message));
8443
+ }
8444
+ /**
8445
+ * Cancel an ongoing message generation
8446
+ *
8447
+ * @param conversationId - Conversation ID
8448
+ */
8449
+ cancel(conversationId) {
8450
+ if (!this.isConnected()) {
8451
+ throw new Error("Not connected to AI chat");
8452
+ }
8453
+ const message = {
8454
+ type: "cancel",
8455
+ conversation_id: conversationId
8456
+ };
8457
+ this.ws.send(JSON.stringify(message));
8458
+ }
8459
+ /**
8460
+ * Get the full accumulated response content for a conversation
8461
+ *
8462
+ * @param conversationId - Conversation ID
8463
+ * @returns Accumulated content string
8464
+ */
8465
+ getAccumulatedContent(conversationId) {
8466
+ return this.accumulatedContent.get(conversationId) || "";
8467
+ }
8468
+ buildWsUrl() {
8469
+ let url = this.options.wsUrl || "/ai/ws";
8470
+ if (this.options.token) {
8471
+ const separator = url.includes("?") ? "&" : "?";
8472
+ url = `${url}${separator}token=${encodeURIComponent(this.options.token)}`;
8473
+ }
8474
+ return url;
8475
+ }
8476
+ handleMessage(data) {
8477
+ try {
8478
+ const message = JSON.parse(data);
8479
+ const event = this.serverMessageToEvent(message);
8480
+ switch (message.type) {
8481
+ case "chat_started":
8482
+ if (this.pendingStartResolve && message.conversation_id) {
8483
+ this.pendingStartResolve(message.conversation_id);
8484
+ this.pendingStartResolve = null;
8485
+ this.pendingStartReject = null;
8486
+ }
8487
+ break;
8488
+ case "content":
8489
+ if (message.conversation_id && message.delta) {
8490
+ const current = this.accumulatedContent.get(message.conversation_id) || "";
8491
+ this.accumulatedContent.set(message.conversation_id, current + message.delta);
8492
+ this.options.onContent?.(message.delta, message.conversation_id);
8493
+ }
8494
+ break;
8495
+ case "progress":
8496
+ if (message.step && message.message && message.conversation_id) {
8497
+ this.options.onProgress?.(message.step, message.message, message.conversation_id);
8498
+ }
8499
+ break;
8500
+ case "query_result":
8501
+ if (message.conversation_id) {
8502
+ this.options.onQueryResult?.(
8503
+ message.query || "",
8504
+ message.summary || "",
8505
+ message.row_count || 0,
8506
+ message.data || [],
8507
+ message.conversation_id
8508
+ );
8509
+ }
8510
+ break;
8511
+ case "done":
8512
+ if (message.conversation_id) {
8513
+ this.options.onDone?.(message.usage, message.conversation_id);
8514
+ }
8515
+ break;
8516
+ case "error":
8517
+ if (this.pendingStartReject) {
8518
+ this.pendingStartReject(new Error(message.error || "Unknown error"));
8519
+ this.pendingStartResolve = null;
8520
+ this.pendingStartReject = null;
8521
+ }
8522
+ this.options.onError?.(message.error || "Unknown error", message.code, message.conversation_id);
8523
+ break;
8524
+ }
8525
+ this.emitEvent(event);
8526
+ } catch (error) {
8527
+ console.error("Failed to parse AI chat message:", error);
8528
+ }
8529
+ }
8530
+ serverMessageToEvent(message) {
8531
+ return {
8532
+ type: message.type,
8533
+ conversationId: message.conversation_id,
8534
+ chatbot: message.chatbot,
8535
+ step: message.step,
8536
+ message: message.message,
8537
+ delta: message.delta,
8538
+ query: message.query,
8539
+ summary: message.summary,
8540
+ rowCount: message.row_count,
8541
+ data: message.data,
8542
+ usage: message.usage,
8543
+ error: message.error,
8544
+ code: message.code
8545
+ };
8546
+ }
8547
+ emitEvent(event) {
8548
+ this.options.onEvent?.(event);
8549
+ }
8550
+ handleClose(_event) {
8551
+ if (this.options.reconnectAttempts && this.reconnectCount < this.options.reconnectAttempts) {
8552
+ this.reconnectCount++;
8553
+ setTimeout(() => {
8554
+ this.connect().catch(() => {
8555
+ });
8556
+ }, this.options.reconnectDelay);
8557
+ }
8558
+ }
8559
+ };
8560
+ var FluxbaseAI = class {
8561
+ constructor(fetch2, wsBaseUrl) {
8562
+ this.fetch = fetch2;
8563
+ this.wsBaseUrl = wsBaseUrl;
8564
+ }
8565
+ /**
8566
+ * List available chatbots (public, enabled)
8567
+ *
8568
+ * @returns Promise resolving to { data, error } tuple with array of chatbot summaries
8569
+ */
8570
+ async listChatbots() {
8571
+ try {
8572
+ const response = await this.fetch.get(
8573
+ "/api/v1/ai/chatbots"
8574
+ );
8575
+ return { data: response.chatbots || [], error: null };
8576
+ } catch (error) {
8577
+ return { data: null, error };
8578
+ }
8579
+ }
8580
+ /**
8581
+ * Get details of a specific chatbot
8582
+ *
8583
+ * @param id - Chatbot ID
8584
+ * @returns Promise resolving to { data, error } tuple with chatbot details
8585
+ */
8586
+ async getChatbot(id) {
8587
+ try {
8588
+ const data = await this.fetch.get(`/api/v1/ai/chatbots/${id}`);
8589
+ return { data, error: null };
8590
+ } catch (error) {
8591
+ return { data: null, error };
8592
+ }
8593
+ }
8594
+ /**
8595
+ * Create a new AI chat connection
8596
+ *
8597
+ * @param options - Chat connection options
8598
+ * @returns FluxbaseAIChat instance
8599
+ */
8600
+ createChat(options) {
8601
+ return new FluxbaseAIChat({
8602
+ ...options,
8603
+ wsUrl: `${this.wsBaseUrl}/ai/ws`
8604
+ });
8605
+ }
8606
+ /**
8607
+ * List the authenticated user's conversations
8608
+ *
8609
+ * @param options - Optional filters and pagination
8610
+ * @returns Promise resolving to { data, error } tuple with conversations
8611
+ *
8612
+ * @example
8613
+ * ```typescript
8614
+ * // List all conversations
8615
+ * const { data, error } = await ai.listConversations()
8616
+ *
8617
+ * // Filter by chatbot
8618
+ * const { data, error } = await ai.listConversations({ chatbot: 'sql-assistant' })
8619
+ *
8620
+ * // With pagination
8621
+ * const { data, error } = await ai.listConversations({ limit: 20, offset: 0 })
8622
+ * ```
8623
+ */
8624
+ async listConversations(options) {
8625
+ try {
8626
+ const params = new URLSearchParams();
8627
+ if (options?.chatbot) params.set("chatbot", options.chatbot);
8628
+ if (options?.namespace) params.set("namespace", options.namespace);
8629
+ if (options?.limit !== void 0) params.set("limit", String(options.limit));
8630
+ if (options?.offset !== void 0) params.set("offset", String(options.offset));
8631
+ const queryString = params.toString();
8632
+ const path = `/api/v1/ai/conversations${queryString ? `?${queryString}` : ""}`;
8633
+ const response = await this.fetch.get(path);
8634
+ return { data: response, error: null };
8635
+ } catch (error) {
8636
+ return { data: null, error };
8637
+ }
8638
+ }
8639
+ /**
8640
+ * Get a single conversation with all messages
8641
+ *
8642
+ * @param id - Conversation ID
8643
+ * @returns Promise resolving to { data, error } tuple with conversation detail
8644
+ *
8645
+ * @example
8646
+ * ```typescript
8647
+ * const { data, error } = await ai.getConversation('conv-uuid-123')
8648
+ * if (data) {
8649
+ * console.log(`Title: ${data.title}`)
8650
+ * console.log(`Messages: ${data.messages.length}`)
8651
+ * }
8652
+ * ```
8653
+ */
8654
+ async getConversation(id) {
8655
+ try {
8656
+ const response = await this.fetch.get(
8657
+ `/api/v1/ai/conversations/${id}`
8658
+ );
8659
+ return { data: response, error: null };
8660
+ } catch (error) {
8661
+ return { data: null, error };
8662
+ }
8663
+ }
8664
+ /**
8665
+ * Delete a conversation
8666
+ *
8667
+ * @param id - Conversation ID
8668
+ * @returns Promise resolving to { error } (null on success)
8669
+ *
8670
+ * @example
8671
+ * ```typescript
8672
+ * const { error } = await ai.deleteConversation('conv-uuid-123')
8673
+ * if (!error) {
8674
+ * console.log('Conversation deleted')
8675
+ * }
8676
+ * ```
8677
+ */
8678
+ async deleteConversation(id) {
8679
+ try {
8680
+ await this.fetch.delete(`/api/v1/ai/conversations/${id}`);
8681
+ return { error: null };
8682
+ } catch (error) {
8683
+ return { error };
8684
+ }
8685
+ }
8686
+ /**
8687
+ * Update a conversation (currently supports title update only)
8688
+ *
8689
+ * @param id - Conversation ID
8690
+ * @param updates - Fields to update
8691
+ * @returns Promise resolving to { data, error } tuple with updated conversation
6076
8692
  *
6077
8693
  * @example
6078
8694
  * ```typescript
6079
- * await admin.deleteUser('user-uuid');
6080
- * console.log('User deleted');
8695
+ * const { data, error } = await ai.updateConversation('conv-uuid-123', {
8696
+ * title: 'My custom conversation title'
8697
+ * })
6081
8698
  * ```
6082
8699
  */
6083
- async deleteUser(userId, type = "app") {
6084
- return wrapAsync(async () => {
6085
- const url = `/api/v1/admin/users/${userId}?type=${type}`;
6086
- return await this.fetch.delete(url);
6087
- });
8700
+ async updateConversation(id, updates) {
8701
+ try {
8702
+ const response = await this.fetch.patch(
8703
+ `/api/v1/ai/conversations/${id}`,
8704
+ updates
8705
+ );
8706
+ return { data: response, error: null };
8707
+ } catch (error) {
8708
+ return { data: null, error };
8709
+ }
8710
+ }
8711
+ };
8712
+
8713
+ // src/vector.ts
8714
+ var FluxbaseVector = class {
8715
+ constructor(fetch2) {
8716
+ this.fetch = fetch2;
6088
8717
  }
6089
8718
  /**
6090
- * Update user role
6091
- *
6092
- * Changes a user's role
6093
- *
6094
- * @param userId - User ID
6095
- * @param role - New role
6096
- * @param type - User type ('app' or 'dashboard')
6097
- * @returns Updated user
8719
+ * Generate embeddings for text
6098
8720
  *
6099
8721
  * @example
6100
8722
  * ```typescript
6101
- * const user = await admin.updateUserRole('user-uuid', 'admin');
6102
- * console.log('User role updated:', user.role);
8723
+ * // Single text
8724
+ * const { data } = await client.vector.embed({
8725
+ * text: 'Hello world'
8726
+ * })
8727
+ * console.log(data.embeddings[0]) // [0.1, 0.2, ...]
8728
+ *
8729
+ * // Multiple texts
8730
+ * const { data } = await client.vector.embed({
8731
+ * texts: ['Hello', 'World'],
8732
+ * model: 'text-embedding-3-small'
8733
+ * })
6103
8734
  * ```
6104
8735
  */
6105
- async updateUserRole(userId, role, type = "app") {
6106
- return wrapAsync(async () => {
6107
- const url = `/api/v1/admin/users/${userId}/role?type=${type}`;
6108
- return await this.fetch.patch(url, { role });
6109
- });
8736
+ async embed(request) {
8737
+ try {
8738
+ const response = await this.fetch.request(
8739
+ "/api/v1/vector/embed",
8740
+ {
8741
+ method: "POST",
8742
+ body: request
8743
+ }
8744
+ );
8745
+ return { data: response, error: null };
8746
+ } catch (error) {
8747
+ return { data: null, error };
8748
+ }
6110
8749
  }
6111
8750
  /**
6112
- * Reset user password
8751
+ * Search for similar vectors with automatic text embedding
6113
8752
  *
6114
- * Generates a new password for the user and optionally sends it via email
6115
- *
6116
- * @param userId - User ID
6117
- * @param type - User type ('app' or 'dashboard')
6118
- * @returns Reset confirmation message
8753
+ * This is a convenience method that:
8754
+ * 1. Embeds the query text automatically (if `query` is provided)
8755
+ * 2. Performs vector similarity search
8756
+ * 3. Returns results with distance scores
6119
8757
  *
6120
8758
  * @example
6121
8759
  * ```typescript
6122
- * const response = await admin.resetUserPassword('user-uuid');
6123
- * console.log(response.message);
8760
+ * // Search with text query (auto-embedded)
8761
+ * const { data } = await client.vector.search({
8762
+ * table: 'documents',
8763
+ * column: 'embedding',
8764
+ * query: 'How to use TypeScript?',
8765
+ * match_count: 10,
8766
+ * match_threshold: 0.8
8767
+ * })
8768
+ *
8769
+ * // Search with pre-computed vector
8770
+ * const { data } = await client.vector.search({
8771
+ * table: 'documents',
8772
+ * column: 'embedding',
8773
+ * vector: [0.1, 0.2, ...],
8774
+ * metric: 'cosine',
8775
+ * match_count: 10
8776
+ * })
8777
+ *
8778
+ * // With additional filters
8779
+ * const { data } = await client.vector.search({
8780
+ * table: 'documents',
8781
+ * column: 'embedding',
8782
+ * query: 'TypeScript tutorial',
8783
+ * filters: [
8784
+ * { column: 'status', operator: 'eq', value: 'published' }
8785
+ * ],
8786
+ * match_count: 10
8787
+ * })
6124
8788
  * ```
6125
8789
  */
6126
- async resetUserPassword(userId, type = "app") {
6127
- return wrapAsync(async () => {
6128
- const url = `/api/v1/admin/users/${userId}/reset-password?type=${type}`;
6129
- return await this.fetch.post(url, {});
6130
- });
8790
+ async search(options) {
8791
+ try {
8792
+ const response = await this.fetch.request(
8793
+ "/api/v1/vector/search",
8794
+ {
8795
+ method: "POST",
8796
+ body: {
8797
+ table: options.table,
8798
+ column: options.column,
8799
+ query: options.query,
8800
+ vector: options.vector,
8801
+ metric: options.metric || "cosine",
8802
+ match_threshold: options.match_threshold,
8803
+ match_count: options.match_count,
8804
+ select: options.select,
8805
+ filters: options.filters
8806
+ }
8807
+ }
8808
+ );
8809
+ return { data: response, error: null };
8810
+ } catch (error) {
8811
+ return { data: null, error };
8812
+ }
6131
8813
  }
6132
8814
  };
6133
8815
 
6134
8816
  // src/query-builder.ts
8817
+ var URL_LENGTH_THRESHOLD = 4096;
6135
8818
  var QueryBuilder = class {
6136
8819
  constructor(fetch2, table, schema) {
6137
8820
  this.selectQuery = "*";
6138
8821
  this.filters = [];
6139
8822
  this.orFilters = [];
6140
8823
  this.andFilters = [];
8824
+ this.betweenFilters = [];
6141
8825
  this.orderBys = [];
6142
8826
  this.singleRow = false;
6143
8827
  this.maybeSingleRow = false;
6144
8828
  this.operationType = "select";
6145
8829
  this.headOnly = false;
8830
+ this.isCountAggregation = false;
6146
8831
  this.fetch = fetch2;
6147
8832
  this.table = table;
6148
8833
  this.schema = schema;
@@ -6357,8 +9042,20 @@ var QueryBuilder = class {
6357
9042
  * Generic filter method using PostgREST syntax (Supabase-compatible)
6358
9043
  * @example filter('name', 'in', '("Han","Yoda")')
6359
9044
  * @example filter('age', 'gte', '18')
9045
+ * @example filter('recorded_at', 'between', ['2024-01-01', '2024-01-10'])
9046
+ * @example filter('recorded_at', 'not.between', ['2024-01-01', '2024-01-10'])
6360
9047
  */
6361
9048
  filter(column, operator, value) {
9049
+ if (operator === "between" || operator === "not.between") {
9050
+ const [min, max] = this.validateBetweenValue(value, operator);
9051
+ this.betweenFilters.push({
9052
+ column,
9053
+ min,
9054
+ max,
9055
+ negated: operator === "not.between"
9056
+ });
9057
+ return this;
9058
+ }
6362
9059
  this.filters.push({ column, operator, value });
6363
9060
  return this;
6364
9061
  }
@@ -6379,6 +9076,33 @@ var QueryBuilder = class {
6379
9076
  this.filters.push({ column, operator: "ov", value });
6380
9077
  return this;
6381
9078
  }
9079
+ /**
9080
+ * Filter column value within an inclusive range (BETWEEN)
9081
+ * Generates: AND (column >= min AND column <= max)
9082
+ *
9083
+ * @param column - Column name to filter
9084
+ * @param min - Minimum value (inclusive)
9085
+ * @param max - Maximum value (inclusive)
9086
+ * @example between('recorded_at', '2024-01-01', '2024-01-10')
9087
+ * @example between('price', 10, 100)
9088
+ */
9089
+ between(column, min, max) {
9090
+ return this.filter(column, "between", [min, max]);
9091
+ }
9092
+ /**
9093
+ * Filter column value outside an inclusive range (NOT BETWEEN)
9094
+ * Generates: OR (column < min OR column > max)
9095
+ * Multiple notBetween calls on the same column AND together
9096
+ *
9097
+ * @param column - Column name to filter
9098
+ * @param min - Minimum value of excluded range
9099
+ * @param max - Maximum value of excluded range
9100
+ * @example notBetween('recorded_at', '2024-01-01', '2024-01-10')
9101
+ * @example notBetween('price', 0, 10) // Excludes items priced 0-10
9102
+ */
9103
+ notBetween(column, min, max) {
9104
+ return this.filter(column, "not.between", [min, max]);
9105
+ }
6382
9106
  // PostGIS Spatial Query Methods
6383
9107
  /**
6384
9108
  * Check if geometries intersect (PostGIS ST_Intersects)
@@ -6475,6 +9199,80 @@ var QueryBuilder = class {
6475
9199
  });
6476
9200
  return this;
6477
9201
  }
9202
+ /**
9203
+ * Order results by vector similarity (pgvector)
9204
+ * Results are ordered by distance (ascending = closest first)
9205
+ *
9206
+ * @example
9207
+ * ```typescript
9208
+ * // Order by cosine similarity (closest matches first)
9209
+ * const { data } = await client
9210
+ * .from('documents')
9211
+ * .select('id, title, content')
9212
+ * .orderByVector('embedding', queryVector, 'cosine')
9213
+ * .limit(10)
9214
+ * ```
9215
+ *
9216
+ * @param column - The vector column to order by
9217
+ * @param vector - The query vector to compare against
9218
+ * @param metric - Distance metric: 'l2' (euclidean), 'cosine', or 'inner_product'
9219
+ * @param options - Optional: { ascending?: boolean } - defaults to true (closest first)
9220
+ */
9221
+ orderByVector(column, vector, metric = "cosine", options) {
9222
+ const opMap = {
9223
+ l2: "vec_l2",
9224
+ cosine: "vec_cos",
9225
+ inner_product: "vec_ip"
9226
+ };
9227
+ this.orderBys.push({
9228
+ column,
9229
+ direction: options?.ascending === false ? "desc" : "asc",
9230
+ vectorOp: opMap[metric],
9231
+ vectorValue: vector
9232
+ });
9233
+ return this;
9234
+ }
9235
+ /**
9236
+ * Filter by vector similarity (pgvector)
9237
+ * This is a convenience method that adds a vector filter using the specified distance metric.
9238
+ * Typically used with orderByVector() for similarity search.
9239
+ *
9240
+ * @example
9241
+ * ```typescript
9242
+ * // Find the 10 most similar documents
9243
+ * const { data } = await client
9244
+ * .from('documents')
9245
+ * .select('id, title, content')
9246
+ * .vectorSearch('embedding', queryVector, 'cosine')
9247
+ * .limit(10)
9248
+ *
9249
+ * // Combine with other filters
9250
+ * const { data } = await client
9251
+ * .from('documents')
9252
+ * .select('id, title, content')
9253
+ * .eq('status', 'published')
9254
+ * .vectorSearch('embedding', queryVector)
9255
+ * .limit(10)
9256
+ * ```
9257
+ *
9258
+ * @param column - The vector column to search
9259
+ * @param vector - The query vector
9260
+ * @param metric - Distance metric: 'l2' (euclidean), 'cosine', or 'inner_product'
9261
+ */
9262
+ vectorSearch(column, vector, metric = "cosine") {
9263
+ const opMap = {
9264
+ l2: "vec_l2",
9265
+ cosine: "vec_cos",
9266
+ inner_product: "vec_ip"
9267
+ };
9268
+ this.filters.push({
9269
+ column,
9270
+ operator: opMap[metric],
9271
+ value: vector
9272
+ });
9273
+ this.orderByVector(column, vector, metric, { ascending: true });
9274
+ return this;
9275
+ }
6478
9276
  /**
6479
9277
  * Limit number of rows returned
6480
9278
  */
@@ -6579,6 +9377,7 @@ var QueryBuilder = class {
6579
9377
  */
6580
9378
  count(column = "*") {
6581
9379
  this.selectQuery = `count(${column})`;
9380
+ this.isCountAggregation = true;
6582
9381
  return this;
6583
9382
  }
6584
9383
  /**
@@ -6799,6 +9598,9 @@ var QueryBuilder = class {
6799
9598
  statusText: "No Content"
6800
9599
  };
6801
9600
  }
9601
+ if (this.shouldUsePostQuery()) {
9602
+ return this.executePostQuery();
9603
+ }
6802
9604
  const queryString = this.buildQueryString();
6803
9605
  const path = `${this.buildTablePath()}${queryString}`;
6804
9606
  if (this.countType) {
@@ -6861,6 +9663,29 @@ var QueryBuilder = class {
6861
9663
  };
6862
9664
  }
6863
9665
  const data = await this.fetch.get(path);
9666
+ if (this.isCountAggregation && !this.groupByColumns) {
9667
+ if (Array.isArray(data) && data.length === 1) {
9668
+ const countData = data[0];
9669
+ if (countData && typeof countData.count === "number") {
9670
+ return {
9671
+ data,
9672
+ error: null,
9673
+ count: countData.count,
9674
+ status: 200,
9675
+ statusText: "OK"
9676
+ };
9677
+ }
9678
+ }
9679
+ if (Array.isArray(data) && data.length === 0) {
9680
+ return {
9681
+ data,
9682
+ error: null,
9683
+ count: 0,
9684
+ status: 200,
9685
+ statusText: "OK"
9686
+ };
9687
+ }
9688
+ }
6864
9689
  if (this.singleRow) {
6865
9690
  if (Array.isArray(data) && data.length === 0) {
6866
9691
  return {
@@ -6981,8 +9806,28 @@ var QueryBuilder = class {
6981
9806
  `${filter.operator}.${this.formatValue(filter.value)}`
6982
9807
  );
6983
9808
  }
9809
+ const orExpressions = [];
6984
9810
  for (const orFilter of this.orFilters) {
6985
- params.append("or", `(${orFilter})`);
9811
+ orExpressions.push(`or(${orFilter})`);
9812
+ }
9813
+ for (const bf of this.betweenFilters) {
9814
+ const minFormatted = this.formatValue(bf.min);
9815
+ const maxFormatted = this.formatValue(bf.max);
9816
+ if (bf.negated) {
9817
+ orExpressions.push(
9818
+ `or(${bf.column}.lt.${minFormatted},${bf.column}.gt.${maxFormatted})`
9819
+ );
9820
+ } else {
9821
+ params.append(bf.column, `gte.${minFormatted}`);
9822
+ params.append(bf.column, `lte.${maxFormatted}`);
9823
+ }
9824
+ }
9825
+ if (orExpressions.length === 1) {
9826
+ const expr = orExpressions[0];
9827
+ const inner = expr.replace(/^or\(/, "(");
9828
+ params.append("or", inner);
9829
+ } else if (orExpressions.length > 1) {
9830
+ params.append("and", `(${orExpressions.join(",")})`);
6986
9831
  }
6987
9832
  for (const andFilter of this.andFilters) {
6988
9833
  params.append("and", `(${andFilter})`);
@@ -6991,9 +9836,13 @@ var QueryBuilder = class {
6991
9836
  params.append("group_by", this.groupByColumns.join(","));
6992
9837
  }
6993
9838
  if (this.orderBys.length > 0) {
6994
- const orderStr = this.orderBys.map(
6995
- (o) => `${o.column}.${o.direction}${o.nulls ? `.nulls${o.nulls}` : ""}`
6996
- ).join(",");
9839
+ const orderStr = this.orderBys.map((o) => {
9840
+ if (o.vectorOp && o.vectorValue) {
9841
+ const vectorStr = `[${o.vectorValue.join(",")}]`;
9842
+ return `${o.column}.${o.vectorOp}.${vectorStr}.${o.direction}`;
9843
+ }
9844
+ return `${o.column}.${o.direction}${o.nulls ? `.nulls${o.nulls}` : ""}`;
9845
+ }).join(",");
6997
9846
  params.append("order", orderStr);
6998
9847
  }
6999
9848
  if (this.limitValue !== void 0) {
@@ -7026,6 +9875,34 @@ var QueryBuilder = class {
7026
9875
  }
7027
9876
  return String(value);
7028
9877
  }
9878
+ /**
9879
+ * Validate between filter value - must be array of exactly 2 elements
9880
+ * @throws Error if value is invalid
9881
+ */
9882
+ validateBetweenValue(value, operator) {
9883
+ if (!Array.isArray(value)) {
9884
+ throw new Error(
9885
+ `Invalid value for '${operator}' operator: expected array of [min, max], got ${typeof value}`
9886
+ );
9887
+ }
9888
+ if (value.length !== 2) {
9889
+ throw new Error(
9890
+ `Invalid value for '${operator}' operator: expected array with exactly 2 elements [min, max], got ${value.length} elements`
9891
+ );
9892
+ }
9893
+ const [min, max] = value;
9894
+ if (min === null || min === void 0) {
9895
+ throw new Error(
9896
+ `Invalid value for '${operator}' operator: min value cannot be null or undefined`
9897
+ );
9898
+ }
9899
+ if (max === null || max === void 0) {
9900
+ throw new Error(
9901
+ `Invalid value for '${operator}' operator: max value cannot be null or undefined`
9902
+ );
9903
+ }
9904
+ return [min, max];
9905
+ }
7029
9906
  /**
7030
9907
  * Parse the Content-Range header to extract the total count
7031
9908
  * Header format: "0-999/50000" or "* /50000" (when no rows returned)
@@ -7041,6 +9918,160 @@ var QueryBuilder = class {
7041
9918
  }
7042
9919
  return null;
7043
9920
  }
9921
+ /**
9922
+ * Check if the query should use POST-based query endpoint
9923
+ * Returns true if the query string would exceed the URL length threshold
9924
+ */
9925
+ shouldUsePostQuery() {
9926
+ const queryString = this.buildQueryString();
9927
+ return queryString.length > URL_LENGTH_THRESHOLD;
9928
+ }
9929
+ /**
9930
+ * Execute a SELECT query using the POST /query endpoint
9931
+ * Used when query parameters would exceed URL length limits
9932
+ */
9933
+ async executePostQuery() {
9934
+ const body = this.buildQueryBody();
9935
+ const path = `${this.buildTablePath()}/query`;
9936
+ if (this.countType) {
9937
+ const response = await this.fetch.postWithHeaders(path, body);
9938
+ const serverCount = this.parseContentRangeCount(response.headers);
9939
+ const data2 = response.data;
9940
+ if (this.headOnly) {
9941
+ return {
9942
+ data: null,
9943
+ error: null,
9944
+ count: serverCount,
9945
+ status: response.status,
9946
+ statusText: "OK"
9947
+ };
9948
+ }
9949
+ if (this.singleRow) {
9950
+ if (Array.isArray(data2) && data2.length === 0) {
9951
+ return {
9952
+ data: null,
9953
+ error: { message: "No rows found", code: "PGRST116" },
9954
+ count: serverCount ?? 0,
9955
+ status: 404,
9956
+ statusText: "Not Found"
9957
+ };
9958
+ }
9959
+ const singleData = Array.isArray(data2) ? data2[0] : data2;
9960
+ return {
9961
+ data: singleData,
9962
+ error: null,
9963
+ count: serverCount ?? 1,
9964
+ status: 200,
9965
+ statusText: "OK"
9966
+ };
9967
+ }
9968
+ if (this.maybeSingleRow) {
9969
+ if (Array.isArray(data2) && data2.length === 0) {
9970
+ return {
9971
+ data: null,
9972
+ error: null,
9973
+ count: serverCount ?? 0,
9974
+ status: 200,
9975
+ statusText: "OK"
9976
+ };
9977
+ }
9978
+ const singleData = Array.isArray(data2) ? data2[0] : data2;
9979
+ return {
9980
+ data: singleData,
9981
+ error: null,
9982
+ count: serverCount ?? 1,
9983
+ status: 200,
9984
+ statusText: "OK"
9985
+ };
9986
+ }
9987
+ return {
9988
+ data: data2,
9989
+ error: null,
9990
+ count: serverCount ?? (Array.isArray(data2) ? data2.length : null),
9991
+ status: 200,
9992
+ statusText: "OK"
9993
+ };
9994
+ }
9995
+ const data = await this.fetch.post(path, body);
9996
+ if (this.singleRow) {
9997
+ if (Array.isArray(data) && data.length === 0) {
9998
+ return {
9999
+ data: null,
10000
+ error: { message: "No rows found", code: "PGRST116" },
10001
+ count: 0,
10002
+ status: 404,
10003
+ statusText: "Not Found"
10004
+ };
10005
+ }
10006
+ const singleData = Array.isArray(data) ? data[0] : data;
10007
+ return {
10008
+ data: singleData,
10009
+ error: null,
10010
+ count: 1,
10011
+ status: 200,
10012
+ statusText: "OK"
10013
+ };
10014
+ }
10015
+ if (this.maybeSingleRow) {
10016
+ if (Array.isArray(data) && data.length === 0) {
10017
+ return {
10018
+ data: null,
10019
+ error: null,
10020
+ count: 0,
10021
+ status: 200,
10022
+ statusText: "OK"
10023
+ };
10024
+ }
10025
+ const singleData = Array.isArray(data) ? data[0] : data;
10026
+ return {
10027
+ data: singleData,
10028
+ error: null,
10029
+ count: 1,
10030
+ status: 200,
10031
+ statusText: "OK"
10032
+ };
10033
+ }
10034
+ return {
10035
+ data,
10036
+ error: null,
10037
+ count: Array.isArray(data) ? data.length : null,
10038
+ status: 200,
10039
+ statusText: "OK"
10040
+ };
10041
+ }
10042
+ /**
10043
+ * Build the request body for POST-based queries
10044
+ * Used when query parameters would exceed URL length limits
10045
+ */
10046
+ buildQueryBody() {
10047
+ return {
10048
+ select: this.selectQuery !== "*" ? this.selectQuery : void 0,
10049
+ filters: this.filters.map((f) => ({
10050
+ column: f.column,
10051
+ operator: f.operator,
10052
+ value: f.value
10053
+ })),
10054
+ orFilters: this.orFilters,
10055
+ andFilters: this.andFilters,
10056
+ betweenFilters: this.betweenFilters.map((bf) => ({
10057
+ column: bf.column,
10058
+ min: bf.min,
10059
+ max: bf.max,
10060
+ negated: bf.negated
10061
+ })),
10062
+ order: this.orderBys.map((o) => ({
10063
+ column: o.column,
10064
+ direction: o.direction,
10065
+ nulls: o.nulls,
10066
+ vectorOp: o.vectorOp,
10067
+ vectorValue: o.vectorValue
10068
+ })),
10069
+ limit: this.limitValue,
10070
+ offset: this.offsetValue,
10071
+ count: this.countType,
10072
+ groupBy: this.groupByColumns
10073
+ };
10074
+ }
7044
10075
  };
7045
10076
 
7046
10077
  // src/schema-query-builder.ts
@@ -7110,6 +10141,26 @@ var FluxbaseClient = class {
7110
10141
  this.admin = new FluxbaseAdmin(this.fetch);
7111
10142
  this.management = new FluxbaseManagement(this.fetch);
7112
10143
  this.settings = new SettingsClient(this.fetch);
10144
+ const wsProtocol = fluxbaseUrl.startsWith("https") ? "wss" : "ws";
10145
+ const wsBaseUrl = fluxbaseUrl.replace(/^https?:/, wsProtocol + ":");
10146
+ this.ai = new FluxbaseAI(this.fetch, wsBaseUrl);
10147
+ this.vector = new FluxbaseVector(this.fetch);
10148
+ const rpcInstance = new FluxbaseRPC(this.fetch);
10149
+ const rpcCallable = async (fn, params) => {
10150
+ const result = await rpcInstance.invoke(fn, params);
10151
+ return {
10152
+ data: result.data?.result ?? null,
10153
+ error: result.error
10154
+ };
10155
+ };
10156
+ Object.assign(rpcCallable, {
10157
+ invoke: rpcInstance.invoke.bind(rpcInstance),
10158
+ list: rpcInstance.list.bind(rpcInstance),
10159
+ getStatus: rpcInstance.getStatus.bind(rpcInstance),
10160
+ getLogs: rpcInstance.getLogs.bind(rpcInstance),
10161
+ waitForCompletion: rpcInstance.waitForCompletion.bind(rpcInstance)
10162
+ });
10163
+ this.rpc = rpcCallable;
7113
10164
  this.setupAuthSync();
7114
10165
  }
7115
10166
  /**
@@ -7149,12 +10200,12 @@ var FluxbaseClient = class {
7149
10200
  *
7150
10201
  * @example
7151
10202
  * ```typescript
7152
- * // Query the jobs.execution_logs table
10203
+ * // Query the logging.entries table
7153
10204
  * const { data } = await client
7154
- * .schema('jobs')
7155
- * .from('execution_logs')
10205
+ * .schema('logging')
10206
+ * .from('entries')
7156
10207
  * .select('*')
7157
- * .eq('job_id', jobId)
10208
+ * .eq('execution_id', executionId)
7158
10209
  * .execute()
7159
10210
  *
7160
10211
  * // Insert into a custom schema table
@@ -7170,38 +10221,6 @@ var FluxbaseClient = class {
7170
10221
  schema(schemaName) {
7171
10222
  return new SchemaQueryBuilder(this.fetch, schemaName);
7172
10223
  }
7173
- /**
7174
- * Call a PostgreSQL function (Remote Procedure Call)
7175
- *
7176
- * @param functionName - The name of the PostgreSQL function to call
7177
- * @param params - Optional parameters to pass to the function
7178
- * @returns Promise containing the function result or error
7179
- *
7180
- * @example
7181
- * ```typescript
7182
- * // Call a function without parameters
7183
- * const { data, error } = await client.rpc('get_total_users')
7184
- *
7185
- * // Call a function with parameters
7186
- * const { data, error } = await client.rpc('calculate_discount', {
7187
- * product_id: 123,
7188
- * coupon_code: 'SAVE20'
7189
- * })
7190
- * ```
7191
- *
7192
- * @category Database
7193
- */
7194
- async rpc(functionName, params) {
7195
- try {
7196
- const data = await this.fetch.post(
7197
- `/api/v1/rpc/${functionName}`,
7198
- params || {}
7199
- );
7200
- return { data, error: null };
7201
- } catch (error) {
7202
- return { data: null, error };
7203
- }
7204
- }
7205
10224
  /**
7206
10225
  * Sync auth state with realtime connections
7207
10226
  * @internal
@@ -7215,7 +10234,10 @@ var FluxbaseClient = class {
7215
10234
  this.realtime.setTokenRefreshCallback(async () => {
7216
10235
  const result = await this.auth.refreshSession();
7217
10236
  if (result.error || !result.data?.session) {
7218
- console.error("[Fluxbase] Failed to refresh token for realtime:", result.error);
10237
+ console.error(
10238
+ "[Fluxbase] Failed to refresh token for realtime:",
10239
+ result.error
10240
+ );
7219
10241
  return null;
7220
10242
  }
7221
10243
  return result.data.session.access_token;
@@ -7329,6 +10351,6 @@ function createClient(fluxbaseUrl, fluxbaseKey, options) {
7329
10351
  return new FluxbaseClient(url, key, options);
7330
10352
  }
7331
10353
 
7332
- export { APIKeysManager, AppSettingsManager, AuthSettingsManager, DDLManager, EmailTemplateManager, FluxbaseAdmin, FluxbaseAdminFunctions, FluxbaseAdminJobs, FluxbaseAdminMigrations, FluxbaseAuth, FluxbaseClient, FluxbaseFetch, FluxbaseFunctions, FluxbaseJobs, FluxbaseManagement, FluxbaseOAuth, FluxbaseRealtime, FluxbaseSettings, FluxbaseStorage, ImpersonationManager, InvitationsManager, OAuthProviderManager, QueryBuilder, RealtimeChannel, SchemaQueryBuilder, SettingsClient, StorageBucket, SystemSettingsManager, WebhooksManager, createClient };
10354
+ export { APIKeysManager, AppSettingsManager, AuthSettingsManager, DDLManager, EmailTemplateManager, ExecutionLogsChannel, FluxbaseAI, FluxbaseAIChat, FluxbaseAdmin, FluxbaseAdminAI, FluxbaseAdminFunctions, FluxbaseAdminJobs, FluxbaseAdminMigrations, FluxbaseAdminRPC, FluxbaseAdminStorage, FluxbaseAuth, FluxbaseClient, FluxbaseFetch, FluxbaseFunctions, FluxbaseJobs, FluxbaseManagement, FluxbaseOAuth, FluxbaseRPC, FluxbaseRealtime, FluxbaseSettings, FluxbaseStorage, FluxbaseVector, ImpersonationManager, InvitationsManager, OAuthProviderManager, QueryBuilder, RealtimeChannel, SchemaQueryBuilder, SettingsClient, StorageBucket, SystemSettingsManager, WebhooksManager, bundleCode, createClient, denoExternalPlugin, loadImportMap };
7333
10355
  //# sourceMappingURL=index.js.map
7334
10356
  //# sourceMappingURL=index.js.map