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