@fluxbase/sdk 0.0.1-rc.48 → 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
@@ -2266,6 +2438,282 @@ var StorageBucket = class {
2266
2438
  return { data: null, error };
2267
2439
  }
2268
2440
  }
2441
+ /**
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
+ * });
2470
+ */
2471
+ async uploadResumable(path, file, options) {
2472
+ try {
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;
2527
+ } else {
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 || [];
2545
+ }
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;
2551
+ }
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
+ }
2633
+ }
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
+ );
2647
+ }
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
+ };
2657
+ } catch (error) {
2658
+ return { data: null, error };
2659
+ }
2660
+ }
2661
+ /**
2662
+ * Abort an in-progress resumable upload
2663
+ * @param sessionId - The upload session ID to abort
2664
+ */
2665
+ async abortResumableUpload(sessionId) {
2666
+ try {
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
+ }
2269
2717
  /**
2270
2718
  * List files in the bucket
2271
2719
  * Supports both Supabase-style list(path, options) and Fluxbase-style list(options)
@@ -2837,40 +3285,244 @@ var FluxbaseJobs = class {
2837
3285
  return { data: null, error };
2838
3286
  }
2839
3287
  }
2840
- };
2841
-
2842
- // src/settings.ts
2843
- var SystemSettingsManager = class {
2844
- constructor(fetch2) {
2845
- this.fetch = fetch2;
2846
- }
2847
3288
  /**
2848
- * List all system settings
3289
+ * Get execution logs for a job
2849
3290
  *
2850
- * @returns Promise resolving to ListSystemSettingsResponse
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
2851
3297
  *
2852
3298
  * @example
2853
3299
  * ```typescript
2854
- * const response = await client.admin.settings.system.list()
2855
- * console.log(response.settings)
2856
- * ```
2857
- */
2858
- async list() {
2859
- const settings = await this.fetch.get(
2860
- "/api/v1/admin/system/settings"
2861
- );
2862
- return { settings: Array.isArray(settings) ? settings : [] };
2863
- }
2864
- /**
2865
- * Get a specific system setting by key
3300
+ * // Get all logs for a job
3301
+ * const { data: logs, error } = await client.jobs.getLogs('550e8400-e29b-41d4-a716-446655440000')
2866
3302
  *
2867
- * @param key - Setting key (e.g., 'app.auth.enable_signup')
2868
- * @returns Promise resolving to SystemSetting
3303
+ * if (logs) {
3304
+ * for (const log of logs) {
3305
+ * console.log(`[${log.level}] ${log.message}`)
3306
+ * }
3307
+ * }
2869
3308
  *
2870
- * @example
2871
- * ```typescript
2872
- * const setting = await client.admin.settings.system.get('app.auth.enable_signup')
2873
- * console.log(setting.value)
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
+ }
3492
+ };
3493
+
3494
+ // src/settings.ts
3495
+ var SystemSettingsManager = class {
3496
+ constructor(fetch2) {
3497
+ this.fetch = fetch2;
3498
+ }
3499
+ /**
3500
+ * List all system settings
3501
+ *
3502
+ * @returns Promise resolving to ListSystemSettingsResponse
3503
+ *
3504
+ * @example
3505
+ * ```typescript
3506
+ * const response = await client.admin.settings.system.list()
3507
+ * console.log(response.settings)
3508
+ * ```
3509
+ */
3510
+ async list() {
3511
+ const settings = await this.fetch.get(
3512
+ "/api/v1/admin/system/settings"
3513
+ );
3514
+ return { settings: Array.isArray(settings) ? settings : [] };
3515
+ }
3516
+ /**
3517
+ * Get a specific system setting by key
3518
+ *
3519
+ * @param key - Setting key (e.g., 'app.auth.enable_signup')
3520
+ * @returns Promise resolving to SystemSetting
3521
+ *
3522
+ * @example
3523
+ * ```typescript
3524
+ * const setting = await client.admin.settings.system.get('app.auth.enable_signup')
3525
+ * console.log(setting.value)
2874
3526
  * ```
2875
3527
  */
2876
3528
  async get(key) {
@@ -5193,54 +5845,16 @@ var FluxbaseAdminMigrations = class {
5193
5845
  }
5194
5846
  }
5195
5847
  /**
5196
- * Trigger schema refresh which will restart the server
5197
- * 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
5198
5850
  *
5199
5851
  * @private
5200
5852
  */
5201
- async triggerSchemaRefreshWithRestart() {
5202
- console.log("Triggering schema refresh (server will restart)...");
5203
- try {
5204
- const response = await this.fetch.post(
5205
- "/api/v1/admin/schema/refresh",
5206
- {}
5207
- );
5208
- console.log("Server restart initiated:", response.message || "Schema refresh in progress");
5209
- } catch (error) {
5210
- const isConnectionError = error.message?.includes("fetch failed") || error.message?.includes("ECONNREFUSED") || error.message?.includes("ECONNRESET") || error.code === "ECONNREFUSED" || error.code === "ECONNRESET";
5211
- if (!isConnectionError) {
5212
- throw error;
5213
- }
5214
- console.log("Connection dropped (expected during restart)...");
5215
- }
5216
- console.log("Waiting 6 seconds for server to restart...");
5217
- await this.sleep(6e3);
5218
- const maxAttempts = 5;
5219
- const baseDelay = 1e3;
5220
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
5221
- try {
5222
- await this.fetch.get("/health");
5223
- console.log("Server is back online and ready");
5224
- return;
5225
- } catch (error) {
5226
- const isLastAttempt = attempt === maxAttempts;
5227
- if (isLastAttempt) {
5228
- throw new Error(
5229
- `Server did not come back online after ${maxAttempts} attempts. Please check server logs and try again.`
5230
- );
5231
- }
5232
- const delay = baseDelay * Math.pow(2, attempt - 1);
5233
- console.log(`Server not ready yet, retrying in ${delay}ms... (attempt ${attempt}/${maxAttempts})`);
5234
- await this.sleep(delay);
5235
- }
5236
- }
5237
- }
5238
- /**
5239
- * Helper function to sleep for a given duration
5240
- * @private
5241
- */
5242
- sleep(ms) {
5243
- 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
+ );
5244
5858
  }
5245
5859
  /**
5246
5860
  * Smart sync all registered migrations
@@ -5342,9 +5956,9 @@ var FluxbaseAdminMigrations = class {
5342
5956
  const migrationsAppliedSuccessfully = combined.summary.applied > 0 && combined.summary.errors === 0;
5343
5957
  if (!combined.dry_run && migrationsAppliedSuccessfully) {
5344
5958
  try {
5345
- await this.triggerSchemaRefreshWithRestart();
5959
+ await this.triggerSchemaRefresh();
5346
5960
  } catch (refreshError) {
5347
- console.warn("Schema refresh completed with warnings:", refreshError);
5961
+ console.warn("Schema refresh warning:", refreshError);
5348
5962
  }
5349
5963
  }
5350
5964
  if (errors.length > 0 || combined.summary.errors > 0) {
@@ -5377,7 +5991,10 @@ var FluxbaseAdminMigrations = class {
5377
5991
  */
5378
5992
  async create(request) {
5379
5993
  try {
5380
- 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
+ );
5381
5998
  return { data, error: null };
5382
5999
  } catch (error) {
5383
6000
  return { data: null, error };
@@ -5478,7 +6095,9 @@ var FluxbaseAdminMigrations = class {
5478
6095
  async delete(name, namespace = "default") {
5479
6096
  try {
5480
6097
  const params = new URLSearchParams({ namespace });
5481
- 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
+ );
5482
6101
  return { data: null, error: null };
5483
6102
  } catch (error) {
5484
6103
  return { data: null, error };
@@ -5579,7 +6198,10 @@ var FluxbaseAdminMigrations = class {
5579
6198
  */
5580
6199
  async getExecutions(name, namespace = "default", limit = 50) {
5581
6200
  try {
5582
- const params = new URLSearchParams({ namespace, limit: limit.toString() });
6201
+ const params = new URLSearchParams({
6202
+ namespace,
6203
+ limit: limit.toString()
6204
+ });
5583
6205
  const data = await this.fetch.get(
5584
6206
  `/api/v1/admin/migrations/${name}/executions?${params.toString()}`
5585
6207
  );
@@ -6379,39 +7001,444 @@ var FluxbaseAdminAI = class {
6379
7001
  return { data: null, error };
6380
7002
  }
6381
7003
  }
6382
- };
6383
-
6384
- // src/admin-rpc.ts
6385
- var FluxbaseAdminRPC = class {
6386
- constructor(fetch2) {
6387
- this.fetch = fetch2;
6388
- }
6389
7004
  // ============================================================================
6390
- // PROCEDURE MANAGEMENT
7005
+ // KNOWLEDGE BASE MANAGEMENT (RAG)
6391
7006
  // ============================================================================
6392
7007
  /**
6393
- * Sync RPC procedures from filesystem or API payload
6394
- *
6395
- * Can sync from:
6396
- * 1. Filesystem (if no procedures provided) - loads from configured procedures directory
6397
- * 2. API payload (if procedures array provided) - syncs provided procedure specifications
6398
- *
6399
- * Requires service_role or admin authentication.
7008
+ * List all knowledge bases
6400
7009
  *
6401
- * @param options - Sync options including namespace and optional procedures array
6402
- * @returns Promise resolving to { data, error } tuple with sync results
7010
+ * @returns Promise resolving to { data, error } tuple with array of knowledge base summaries
6403
7011
  *
6404
7012
  * @example
6405
7013
  * ```typescript
6406
- * // Sync from filesystem
6407
- * const { data, error } = await client.admin.rpc.sync()
6408
- *
6409
- * // Sync with provided procedure code
6410
- * const { data, error } = await client.admin.rpc.sync({
6411
- * namespace: 'default',
6412
- * procedures: [{
6413
- * name: 'get-user-orders',
6414
- * code: myProcedureSQL,
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,
6415
7442
  * }],
6416
7443
  * options: {
6417
7444
  * delete_missing: false, // Don't remove procedures not in this sync
@@ -6697,6 +7724,230 @@ var FluxbaseAdminRPC = class {
6697
7724
  }
6698
7725
  };
6699
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
+
6700
7951
  // src/admin.ts
6701
7952
  var FluxbaseAdmin = class {
6702
7953
  constructor(fetch2) {
@@ -6713,6 +7964,7 @@ var FluxbaseAdmin = class {
6713
7964
  this.migrations = new FluxbaseAdminMigrations(fetch2);
6714
7965
  this.ai = new FluxbaseAdminAI(fetch2);
6715
7966
  this.rpc = new FluxbaseAdminRPC(fetch2);
7967
+ this.storage = new FluxbaseAdminStorage(fetch2);
6716
7968
  }
6717
7969
  /**
6718
7970
  * Set admin authentication token
@@ -6735,6 +7987,54 @@ var FluxbaseAdmin = class {
6735
7987
  this.fetch.setAuthToken(null);
6736
7988
  }
6737
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
+ // ============================================================================
6738
8038
  // Admin Authentication
6739
8039
  // ============================================================================
6740
8040
  /**
@@ -7412,7 +8712,111 @@ var FluxbaseAI = class {
7412
8712
  }
7413
8713
  };
7414
8714
 
8715
+ // src/vector.ts
8716
+ var FluxbaseVector = class {
8717
+ constructor(fetch2) {
8718
+ this.fetch = fetch2;
8719
+ }
8720
+ /**
8721
+ * Generate embeddings for text
8722
+ *
8723
+ * @example
8724
+ * ```typescript
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
+ * })
8736
+ * ```
8737
+ */
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
+ }
8751
+ }
8752
+ /**
8753
+ * Search for similar vectors with automatic text embedding
8754
+ *
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
8759
+ *
8760
+ * @example
8761
+ * ```typescript
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
+ * })
8790
+ * ```
8791
+ */
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
+ }
8815
+ }
8816
+ };
8817
+
7415
8818
  // src/query-builder.ts
8819
+ var URL_LENGTH_THRESHOLD = 4096;
7416
8820
  var QueryBuilder = class {
7417
8821
  constructor(fetch2, table, schema) {
7418
8822
  this.selectQuery = "*";
@@ -7425,6 +8829,7 @@ var QueryBuilder = class {
7425
8829
  this.maybeSingleRow = false;
7426
8830
  this.operationType = "select";
7427
8831
  this.headOnly = false;
8832
+ this.isCountAggregation = false;
7428
8833
  this.fetch = fetch2;
7429
8834
  this.table = table;
7430
8835
  this.schema = schema;
@@ -7796,6 +9201,80 @@ var QueryBuilder = class {
7796
9201
  });
7797
9202
  return this;
7798
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
+ }
7799
9278
  /**
7800
9279
  * Limit number of rows returned
7801
9280
  */
@@ -7900,6 +9379,7 @@ var QueryBuilder = class {
7900
9379
  */
7901
9380
  count(column = "*") {
7902
9381
  this.selectQuery = `count(${column})`;
9382
+ this.isCountAggregation = true;
7903
9383
  return this;
7904
9384
  }
7905
9385
  /**
@@ -8120,6 +9600,9 @@ var QueryBuilder = class {
8120
9600
  statusText: "No Content"
8121
9601
  };
8122
9602
  }
9603
+ if (this.shouldUsePostQuery()) {
9604
+ return this.executePostQuery();
9605
+ }
8123
9606
  const queryString = this.buildQueryString();
8124
9607
  const path = `${this.buildTablePath()}${queryString}`;
8125
9608
  if (this.countType) {
@@ -8181,7 +9664,30 @@ var QueryBuilder = class {
8181
9664
  statusText: "OK"
8182
9665
  };
8183
9666
  }
8184
- const data = await this.fetch.get(path);
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
+ }
8185
9691
  if (this.singleRow) {
8186
9692
  if (Array.isArray(data) && data.length === 0) {
8187
9693
  return {
@@ -8302,32 +9808,43 @@ var QueryBuilder = class {
8302
9808
  `${filter.operator}.${this.formatValue(filter.value)}`
8303
9809
  );
8304
9810
  }
9811
+ const orExpressions = [];
8305
9812
  for (const orFilter of this.orFilters) {
8306
- params.append("or", `(${orFilter})`);
8307
- }
8308
- for (const andFilter of this.andFilters) {
8309
- params.append("and", `(${andFilter})`);
9813
+ orExpressions.push(`or(${orFilter})`);
8310
9814
  }
8311
9815
  for (const bf of this.betweenFilters) {
8312
9816
  const minFormatted = this.formatValue(bf.min);
8313
9817
  const maxFormatted = this.formatValue(bf.max);
8314
9818
  if (bf.negated) {
8315
- params.append(
8316
- "or",
8317
- `(${bf.column}.lt.${minFormatted},${bf.column}.gt.${maxFormatted})`
9819
+ orExpressions.push(
9820
+ `or(${bf.column}.lt.${minFormatted},${bf.column}.gt.${maxFormatted})`
8318
9821
  );
8319
9822
  } else {
8320
9823
  params.append(bf.column, `gte.${minFormatted}`);
8321
9824
  params.append(bf.column, `lte.${maxFormatted}`);
8322
9825
  }
8323
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(",")})`);
9833
+ }
9834
+ for (const andFilter of this.andFilters) {
9835
+ params.append("and", `(${andFilter})`);
9836
+ }
8324
9837
  if (this.groupByColumns && this.groupByColumns.length > 0) {
8325
9838
  params.append("group_by", this.groupByColumns.join(","));
8326
9839
  }
8327
9840
  if (this.orderBys.length > 0) {
8328
- const orderStr = this.orderBys.map(
8329
- (o) => `${o.column}.${o.direction}${o.nulls ? `.nulls${o.nulls}` : ""}`
8330
- ).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(",");
8331
9848
  params.append("order", orderStr);
8332
9849
  }
8333
9850
  if (this.limitValue !== void 0) {
@@ -8403,6 +9920,160 @@ var QueryBuilder = class {
8403
9920
  }
8404
9921
  return null;
8405
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
+ }
8406
10077
  };
8407
10078
 
8408
10079
  // src/schema-query-builder.ts
@@ -8475,6 +10146,23 @@ var FluxbaseClient = class {
8475
10146
  const wsProtocol = fluxbaseUrl.startsWith("https") ? "wss" : "ws";
8476
10147
  const wsBaseUrl = fluxbaseUrl.replace(/^https?:/, wsProtocol + ":");
8477
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;
8478
10166
  this.setupAuthSync();
8479
10167
  }
8480
10168
  /**
@@ -8514,12 +10202,12 @@ var FluxbaseClient = class {
8514
10202
  *
8515
10203
  * @example
8516
10204
  * ```typescript
8517
- * // Query the jobs.execution_logs table
10205
+ * // Query the logging.entries table
8518
10206
  * const { data } = await client
8519
- * .schema('jobs')
8520
- * .from('execution_logs')
10207
+ * .schema('logging')
10208
+ * .from('entries')
8521
10209
  * .select('*')
8522
- * .eq('job_id', jobId)
10210
+ * .eq('execution_id', executionId)
8523
10211
  * .execute()
8524
10212
  *
8525
10213
  * // Insert into a custom schema table
@@ -8548,7 +10236,10 @@ var FluxbaseClient = class {
8548
10236
  this.realtime.setTokenRefreshCallback(async () => {
8549
10237
  const result = await this.auth.refreshSession();
8550
10238
  if (result.error || !result.data?.session) {
8551
- 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
+ );
8552
10243
  return null;
8553
10244
  }
8554
10245
  return result.data.session.access_token;
@@ -8662,168 +10353,12 @@ function createClient(fluxbaseUrl, fluxbaseKey, options) {
8662
10353
  return new FluxbaseClient(url, key, options);
8663
10354
  }
8664
10355
 
8665
- // src/rpc.ts
8666
- var FluxbaseRPC = class {
8667
- constructor(fetch2) {
8668
- this.fetch = fetch2;
8669
- }
8670
- /**
8671
- * List available RPC procedures (public, enabled)
8672
- *
8673
- * @param namespace - Optional namespace filter
8674
- * @returns Promise resolving to { data, error } tuple with array of procedure summaries
8675
- */
8676
- async list(namespace) {
8677
- try {
8678
- const params = namespace ? `?namespace=${encodeURIComponent(namespace)}` : "";
8679
- const response = await this.fetch.get(
8680
- `/api/v1/rpc/procedures${params}`
8681
- );
8682
- return { data: response.procedures || [], error: null };
8683
- } catch (error) {
8684
- return { data: null, error };
8685
- }
8686
- }
8687
- /**
8688
- * Invoke an RPC procedure
8689
- *
8690
- * @param name - Procedure name
8691
- * @param params - Optional parameters to pass to the procedure
8692
- * @param options - Optional invocation options
8693
- * @returns Promise resolving to { data, error } tuple with invocation response
8694
- *
8695
- * @example
8696
- * ```typescript
8697
- * // Synchronous invocation
8698
- * const { data, error } = await fluxbase.rpc.invoke('get-user-orders', {
8699
- * user_id: '123',
8700
- * limit: 10
8701
- * });
8702
- * console.log(data.result); // Query results
8703
- *
8704
- * // Asynchronous invocation
8705
- * const { data: asyncData } = await fluxbase.rpc.invoke('generate-report', {
8706
- * year: 2024
8707
- * }, { async: true });
8708
- * console.log(asyncData.execution_id); // Use to poll status
8709
- * ```
8710
- */
8711
- async invoke(name, params, options) {
8712
- try {
8713
- const namespace = options?.namespace || "default";
8714
- const response = await this.fetch.post(
8715
- `/api/v1/rpc/${encodeURIComponent(namespace)}/${encodeURIComponent(name)}`,
8716
- {
8717
- params,
8718
- async: options?.async
8719
- }
8720
- );
8721
- return { data: response, error: null };
8722
- } catch (error) {
8723
- return { data: null, error };
8724
- }
8725
- }
8726
- /**
8727
- * Get execution status (for async invocations or checking history)
8728
- *
8729
- * @param executionId - The execution ID returned from async invoke
8730
- * @returns Promise resolving to { data, error } tuple with execution details
8731
- *
8732
- * @example
8733
- * ```typescript
8734
- * const { data, error } = await fluxbase.rpc.getStatus('execution-uuid');
8735
- * if (data.status === 'completed') {
8736
- * console.log('Result:', data.result);
8737
- * } else if (data.status === 'running') {
8738
- * console.log('Still running...');
8739
- * }
8740
- * ```
8741
- */
8742
- async getStatus(executionId) {
8743
- try {
8744
- const data = await this.fetch.get(
8745
- `/api/v1/rpc/executions/${encodeURIComponent(executionId)}`
8746
- );
8747
- return { data, error: null };
8748
- } catch (error) {
8749
- return { data: null, error };
8750
- }
8751
- }
8752
- /**
8753
- * Get execution logs (for debugging and monitoring)
8754
- *
8755
- * @param executionId - The execution ID
8756
- * @param afterLine - Optional line number to get logs after (for polling)
8757
- * @returns Promise resolving to { data, error } tuple with execution logs
8758
- *
8759
- * @example
8760
- * ```typescript
8761
- * const { data: logs } = await fluxbase.rpc.getLogs('execution-uuid');
8762
- * for (const log of logs) {
8763
- * console.log(`[${log.level}] ${log.message}`);
8764
- * }
8765
- * ```
8766
- */
8767
- async getLogs(executionId, afterLine) {
8768
- try {
8769
- const params = afterLine !== void 0 ? `?after=${afterLine}` : "";
8770
- const response = await this.fetch.get(
8771
- `/api/v1/rpc/executions/${encodeURIComponent(executionId)}/logs${params}`
8772
- );
8773
- return { data: response.logs || [], error: null };
8774
- } catch (error) {
8775
- return { data: null, error };
8776
- }
8777
- }
8778
- /**
8779
- * Poll for execution completion with exponential backoff
8780
- *
8781
- * @param executionId - The execution ID to poll
8782
- * @param options - Polling options
8783
- * @returns Promise resolving to final execution state
8784
- *
8785
- * @example
8786
- * ```typescript
8787
- * const { data: result } = await fluxbase.rpc.invoke('long-task', {}, { async: true });
8788
- * const { data: final } = await fluxbase.rpc.waitForCompletion(result.execution_id, {
8789
- * maxWaitMs: 60000, // Wait up to 1 minute
8790
- * onProgress: (exec) => console.log(`Status: ${exec.status}`)
8791
- * });
8792
- * console.log('Final result:', final.result);
8793
- * ```
8794
- */
8795
- async waitForCompletion(executionId, options) {
8796
- const maxWait = options?.maxWaitMs || 3e4;
8797
- const initialInterval = options?.initialIntervalMs || 500;
8798
- const maxInterval = options?.maxIntervalMs || 5e3;
8799
- const startTime = Date.now();
8800
- let interval = initialInterval;
8801
- while (Date.now() - startTime < maxWait) {
8802
- const { data: execution, error } = await this.getStatus(executionId);
8803
- if (error) {
8804
- return { data: null, error };
8805
- }
8806
- if (!execution) {
8807
- return { data: null, error: new Error("Execution not found") };
8808
- }
8809
- if (options?.onProgress) {
8810
- options.onProgress(execution);
8811
- }
8812
- if (execution.status === "completed" || execution.status === "failed" || execution.status === "cancelled" || execution.status === "timeout") {
8813
- return { data: execution, error: null };
8814
- }
8815
- await new Promise((resolve) => setTimeout(resolve, interval));
8816
- interval = Math.min(interval * 1.5, maxInterval);
8817
- }
8818
- return { data: null, error: new Error("Timeout waiting for execution to complete") };
8819
- }
8820
- };
8821
-
8822
10356
  exports.APIKeysManager = APIKeysManager;
8823
10357
  exports.AppSettingsManager = AppSettingsManager;
8824
10358
  exports.AuthSettingsManager = AuthSettingsManager;
8825
10359
  exports.DDLManager = DDLManager;
8826
10360
  exports.EmailTemplateManager = EmailTemplateManager;
10361
+ exports.ExecutionLogsChannel = ExecutionLogsChannel;
8827
10362
  exports.FluxbaseAI = FluxbaseAI;
8828
10363
  exports.FluxbaseAIChat = FluxbaseAIChat;
8829
10364
  exports.FluxbaseAdmin = FluxbaseAdmin;
@@ -8832,6 +10367,7 @@ exports.FluxbaseAdminFunctions = FluxbaseAdminFunctions;
8832
10367
  exports.FluxbaseAdminJobs = FluxbaseAdminJobs;
8833
10368
  exports.FluxbaseAdminMigrations = FluxbaseAdminMigrations;
8834
10369
  exports.FluxbaseAdminRPC = FluxbaseAdminRPC;
10370
+ exports.FluxbaseAdminStorage = FluxbaseAdminStorage;
8835
10371
  exports.FluxbaseAuth = FluxbaseAuth;
8836
10372
  exports.FluxbaseClient = FluxbaseClient;
8837
10373
  exports.FluxbaseFetch = FluxbaseFetch;
@@ -8843,6 +10379,7 @@ exports.FluxbaseRPC = FluxbaseRPC;
8843
10379
  exports.FluxbaseRealtime = FluxbaseRealtime;
8844
10380
  exports.FluxbaseSettings = FluxbaseSettings;
8845
10381
  exports.FluxbaseStorage = FluxbaseStorage;
10382
+ exports.FluxbaseVector = FluxbaseVector;
8846
10383
  exports.ImpersonationManager = ImpersonationManager;
8847
10384
  exports.InvitationsManager = InvitationsManager;
8848
10385
  exports.OAuthProviderManager = OAuthProviderManager;