@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.js CHANGED
@@ -153,6 +153,12 @@ var FluxbaseFetch = class {
153
153
  async getWithHeaders(path, options = {}) {
154
154
  return this.requestWithHeaders(path, { ...options, method: "GET" });
155
155
  }
156
+ /**
157
+ * POST request that returns response with headers (for POST-based queries with count)
158
+ */
159
+ async postWithHeaders(path, body, options = {}) {
160
+ return this.requestWithHeaders(path, { ...options, method: "POST", body });
161
+ }
156
162
  /**
157
163
  * Make an HTTP request and return response with headers
158
164
  */
@@ -260,6 +266,44 @@ var FluxbaseFetch = class {
260
266
  });
261
267
  return response.headers;
262
268
  }
269
+ /**
270
+ * GET request that returns response as Blob (for file downloads)
271
+ */
272
+ async getBlob(path, options = {}) {
273
+ const url = `${this.baseUrl}${path}`;
274
+ const headers = { ...this.defaultHeaders, ...options.headers };
275
+ delete headers["Content-Type"];
276
+ const controller = new AbortController();
277
+ const timeoutId = setTimeout(() => controller.abort(), options.timeout ?? this.timeout);
278
+ if (this.debug) {
279
+ console.log(`[Fluxbase SDK] GET (blob) ${url}`);
280
+ }
281
+ try {
282
+ const response = await fetch(url, {
283
+ method: "GET",
284
+ headers,
285
+ signal: controller.signal
286
+ });
287
+ clearTimeout(timeoutId);
288
+ if (!response.ok) {
289
+ const error = new Error(response.statusText);
290
+ error.status = response.status;
291
+ throw error;
292
+ }
293
+ return await response.blob();
294
+ } catch (err) {
295
+ clearTimeout(timeoutId);
296
+ if (err instanceof Error) {
297
+ if (err.name === "AbortError") {
298
+ const timeoutError = new Error("Request timeout");
299
+ timeoutError.status = 408;
300
+ throw timeoutError;
301
+ }
302
+ throw err;
303
+ }
304
+ throw new Error("Unknown error occurred");
305
+ }
306
+ }
263
307
  };
264
308
 
265
309
  // src/utils/error-handling.ts
@@ -1110,8 +1154,10 @@ var RealtimeChannel = class {
1110
1154
  this.callbacks = /* @__PURE__ */ new Map();
1111
1155
  this.presenceCallbacks = /* @__PURE__ */ new Map();
1112
1156
  this.broadcastCallbacks = /* @__PURE__ */ new Map();
1157
+ this.executionLogCallbacks = /* @__PURE__ */ new Set();
1113
1158
  this.subscriptionConfig = null;
1114
1159
  this.subscriptionId = null;
1160
+ this.executionLogConfig = null;
1115
1161
  this._presenceState = {};
1116
1162
  this.myPresenceKey = null;
1117
1163
  this.reconnectAttempts = 0;
@@ -1160,6 +1206,11 @@ var RealtimeChannel = class {
1160
1206
  this.presenceCallbacks.set(config.event, /* @__PURE__ */ new Set());
1161
1207
  }
1162
1208
  this.presenceCallbacks.get(config.event).add(actualCallback);
1209
+ } else if (event === "execution_log" && typeof configOrCallback !== "function") {
1210
+ const config = configOrCallback;
1211
+ this.executionLogConfig = config;
1212
+ const actualCallback = callback;
1213
+ this.executionLogCallbacks.add(actualCallback);
1163
1214
  } else {
1164
1215
  const actualEvent = event;
1165
1216
  const actualCallback = configOrCallback;
@@ -1440,14 +1491,26 @@ var RealtimeChannel = class {
1440
1491
  this.ws.onopen = () => {
1441
1492
  console.log("[Fluxbase Realtime] Connected");
1442
1493
  this.reconnectAttempts = 0;
1443
- const subscribeMessage = {
1444
- type: "subscribe",
1445
- channel: this.channelName
1446
- };
1447
- if (this.subscriptionConfig) {
1448
- subscribeMessage.config = this.subscriptionConfig;
1494
+ if (this.executionLogConfig) {
1495
+ const logSubscribeMessage = {
1496
+ type: "subscribe_logs",
1497
+ channel: this.channelName,
1498
+ config: {
1499
+ execution_id: this.executionLogConfig.execution_id,
1500
+ type: this.executionLogConfig.type || "function"
1501
+ }
1502
+ };
1503
+ this.sendMessage(logSubscribeMessage);
1504
+ } else {
1505
+ const subscribeMessage = {
1506
+ type: "subscribe",
1507
+ channel: this.channelName
1508
+ };
1509
+ if (this.subscriptionConfig) {
1510
+ subscribeMessage.config = this.subscriptionConfig;
1511
+ }
1512
+ this.sendMessage(subscribeMessage);
1449
1513
  }
1450
- this.sendMessage(subscribeMessage);
1451
1514
  this.startHeartbeat();
1452
1515
  };
1453
1516
  this.ws.onmessage = (event) => {
@@ -1557,8 +1620,25 @@ var RealtimeChannel = class {
1557
1620
  this.handlePostgresChanges(message.payload);
1558
1621
  }
1559
1622
  break;
1623
+ case "execution_log":
1624
+ if (message.payload) {
1625
+ this.handleExecutionLog(message.payload);
1626
+ }
1627
+ break;
1560
1628
  }
1561
1629
  }
1630
+ /**
1631
+ * Internal: Handle execution log message
1632
+ */
1633
+ handleExecutionLog(log) {
1634
+ this.executionLogCallbacks.forEach((callback) => {
1635
+ try {
1636
+ callback(log);
1637
+ } catch (err) {
1638
+ console.error("[Fluxbase Realtime] Error in execution log callback:", err);
1639
+ }
1640
+ });
1641
+ }
1562
1642
  /**
1563
1643
  * Internal: Handle broadcast message
1564
1644
  */
@@ -1802,6 +1882,98 @@ var FluxbaseRealtime = class {
1802
1882
  channel.updateToken(token);
1803
1883
  });
1804
1884
  }
1885
+ /**
1886
+ * Create an execution log subscription channel
1887
+ *
1888
+ * This provides a cleaner API for subscribing to execution logs
1889
+ * (functions, jobs, or RPC procedures).
1890
+ *
1891
+ * @param executionId - The execution ID to subscribe to
1892
+ * @param type - The type of execution ('function', 'job', 'rpc')
1893
+ * @returns ExecutionLogsChannel instance with fluent API
1894
+ *
1895
+ * @example
1896
+ * ```typescript
1897
+ * const channel = client.realtime.executionLogs('exec-123', 'function')
1898
+ * .onLog((log) => {
1899
+ * console.log(`[${log.level}] ${log.message}`)
1900
+ * })
1901
+ * .subscribe()
1902
+ * ```
1903
+ */
1904
+ executionLogs(executionId, type = "function") {
1905
+ return new ExecutionLogsChannel(this.url, executionId, type, this.token, this.tokenRefreshCallback);
1906
+ }
1907
+ };
1908
+ var ExecutionLogsChannel = class {
1909
+ constructor(url, executionId, type, token, tokenRefreshCallback) {
1910
+ this.logCallbacks = [];
1911
+ this.executionId = executionId;
1912
+ this.executionType = type;
1913
+ const channelName = `execution:${executionId}`;
1914
+ this.channel = new RealtimeChannel(url, channelName, token);
1915
+ if (tokenRefreshCallback) {
1916
+ this.channel.setTokenRefreshCallback(tokenRefreshCallback);
1917
+ }
1918
+ }
1919
+ /**
1920
+ * Register a callback for log events
1921
+ *
1922
+ * @param callback - Function to call when log entries are received
1923
+ * @returns This channel for chaining
1924
+ *
1925
+ * @example
1926
+ * ```typescript
1927
+ * channel.onLog((log) => {
1928
+ * console.log(`[${log.level}] Line ${log.line_number}: ${log.message}`)
1929
+ * })
1930
+ * ```
1931
+ */
1932
+ onLog(callback) {
1933
+ this.logCallbacks.push(callback);
1934
+ return this;
1935
+ }
1936
+ /**
1937
+ * Subscribe to execution logs
1938
+ *
1939
+ * @param callback - Optional status callback
1940
+ * @returns Promise that resolves when subscribed
1941
+ *
1942
+ * @example
1943
+ * ```typescript
1944
+ * await channel.subscribe()
1945
+ * ```
1946
+ */
1947
+ subscribe(callback) {
1948
+ this.channel.on(
1949
+ "execution_log",
1950
+ { execution_id: this.executionId, type: this.executionType },
1951
+ (log) => {
1952
+ this.logCallbacks.forEach((cb) => {
1953
+ try {
1954
+ cb(log);
1955
+ } catch (err) {
1956
+ console.error("[Fluxbase ExecutionLogs] Error in log callback:", err);
1957
+ }
1958
+ });
1959
+ }
1960
+ );
1961
+ this.channel.subscribe(callback);
1962
+ return this;
1963
+ }
1964
+ /**
1965
+ * Unsubscribe from execution logs
1966
+ *
1967
+ * @returns Promise resolving to status
1968
+ *
1969
+ * @example
1970
+ * ```typescript
1971
+ * await channel.unsubscribe()
1972
+ * ```
1973
+ */
1974
+ async unsubscribe() {
1975
+ return this.channel.unsubscribe();
1976
+ }
1805
1977
  };
1806
1978
 
1807
1979
  // src/storage.ts
@@ -2264,6 +2436,282 @@ var StorageBucket = class {
2264
2436
  return { data: null, error };
2265
2437
  }
2266
2438
  }
2439
+ /**
2440
+ * Upload a large file with resumable chunked uploads.
2441
+ *
2442
+ * Features:
2443
+ * - Uploads file in chunks for reliability
2444
+ * - Automatically retries failed chunks with exponential backoff
2445
+ * - Reports progress via callback with chunk-level granularity
2446
+ * - Can resume interrupted uploads using session ID
2447
+ *
2448
+ * @param path - The file path within the bucket
2449
+ * @param file - The File or Blob to upload
2450
+ * @param options - Upload options including chunk size, retries, and progress callback
2451
+ * @returns Upload result with file info
2452
+ *
2453
+ * @example
2454
+ * const { data, error } = await storage.from('uploads').uploadResumable('large.zip', file, {
2455
+ * chunkSize: 5 * 1024 * 1024, // 5MB chunks
2456
+ * maxRetries: 3,
2457
+ * onProgress: (p) => {
2458
+ * console.log(`${p.percentage}% (chunk ${p.currentChunk}/${p.totalChunks})`);
2459
+ * console.log(`Speed: ${(p.bytesPerSecond / 1024 / 1024).toFixed(2)} MB/s`);
2460
+ * console.log(`Session ID (for resume): ${p.sessionId}`);
2461
+ * }
2462
+ * });
2463
+ *
2464
+ * // To resume an interrupted upload:
2465
+ * const { data, error } = await storage.from('uploads').uploadResumable('large.zip', file, {
2466
+ * resumeSessionId: 'previous-session-id',
2467
+ * });
2468
+ */
2469
+ async uploadResumable(path, file, options) {
2470
+ try {
2471
+ const chunkSize = options?.chunkSize ?? 5 * 1024 * 1024;
2472
+ const maxRetries = options?.maxRetries ?? 3;
2473
+ const retryDelayMs = options?.retryDelayMs ?? 1e3;
2474
+ const chunkTimeout = options?.chunkTimeout ?? 6e4;
2475
+ const totalSize = file.size;
2476
+ const totalChunks = Math.ceil(totalSize / chunkSize);
2477
+ if (options?.signal?.aborted) {
2478
+ return { data: null, error: new Error("Upload aborted") };
2479
+ }
2480
+ const baseUrl = this.fetch["baseUrl"];
2481
+ const headers = this.fetch["defaultHeaders"];
2482
+ let sessionId = options?.resumeSessionId;
2483
+ let session;
2484
+ let completedChunks = [];
2485
+ if (!sessionId) {
2486
+ const initResponse = await fetch(
2487
+ `${baseUrl}/api/v1/storage/${this.bucketName}/chunked/init`,
2488
+ {
2489
+ method: "POST",
2490
+ headers: {
2491
+ ...headers,
2492
+ "Content-Type": "application/json"
2493
+ },
2494
+ body: JSON.stringify({
2495
+ path,
2496
+ total_size: totalSize,
2497
+ chunk_size: chunkSize,
2498
+ content_type: options?.contentType || file.type || "application/octet-stream",
2499
+ metadata: options?.metadata,
2500
+ cache_control: options?.cacheControl
2501
+ }),
2502
+ signal: options?.signal
2503
+ }
2504
+ );
2505
+ if (!initResponse.ok) {
2506
+ const errorData = await initResponse.json().catch(() => ({}));
2507
+ throw new Error(
2508
+ errorData.error || `Failed to initialize upload: ${initResponse.statusText}`
2509
+ );
2510
+ }
2511
+ const initData = await initResponse.json();
2512
+ session = {
2513
+ sessionId: initData.session_id,
2514
+ bucket: initData.bucket,
2515
+ path: initData.path,
2516
+ totalSize: initData.total_size,
2517
+ chunkSize: initData.chunk_size,
2518
+ totalChunks: initData.total_chunks,
2519
+ completedChunks: initData.completed_chunks || [],
2520
+ status: initData.status,
2521
+ expiresAt: initData.expires_at,
2522
+ createdAt: initData.created_at
2523
+ };
2524
+ sessionId = session.sessionId;
2525
+ } else {
2526
+ const statusResponse = await fetch(
2527
+ `${baseUrl}/api/v1/storage/${this.bucketName}/chunked/${sessionId}/status`,
2528
+ {
2529
+ method: "GET",
2530
+ headers,
2531
+ signal: options?.signal
2532
+ }
2533
+ );
2534
+ if (!statusResponse.ok) {
2535
+ const errorData = await statusResponse.json().catch(() => ({}));
2536
+ throw new Error(
2537
+ errorData.error || `Failed to get session status: ${statusResponse.statusText}`
2538
+ );
2539
+ }
2540
+ const statusData = await statusResponse.json();
2541
+ session = statusData.session;
2542
+ completedChunks = session.completedChunks || [];
2543
+ }
2544
+ let uploadedBytes = 0;
2545
+ for (const chunkIdx of completedChunks) {
2546
+ const chunkStart = chunkIdx * chunkSize;
2547
+ const chunkEnd = Math.min(chunkStart + chunkSize, totalSize);
2548
+ uploadedBytes += chunkEnd - chunkStart;
2549
+ }
2550
+ let lastProgressTime = Date.now();
2551
+ let lastProgressBytes = uploadedBytes;
2552
+ for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
2553
+ if (options?.signal?.aborted) {
2554
+ return { data: null, error: new Error("Upload aborted") };
2555
+ }
2556
+ if (completedChunks.includes(chunkIndex)) {
2557
+ continue;
2558
+ }
2559
+ const start = chunkIndex * chunkSize;
2560
+ const end = Math.min(start + chunkSize, totalSize);
2561
+ const chunk = file.slice(start, end);
2562
+ const chunkArrayBuffer = await chunk.arrayBuffer();
2563
+ let retryCount = 0;
2564
+ let chunkUploaded = false;
2565
+ while (retryCount <= maxRetries && !chunkUploaded) {
2566
+ try {
2567
+ if (options?.signal?.aborted) {
2568
+ return { data: null, error: new Error("Upload aborted") };
2569
+ }
2570
+ const chunkController = new AbortController();
2571
+ const timeoutId = setTimeout(() => chunkController.abort(), chunkTimeout);
2572
+ if (options?.signal) {
2573
+ options.signal.addEventListener(
2574
+ "abort",
2575
+ () => chunkController.abort(),
2576
+ { once: true }
2577
+ );
2578
+ }
2579
+ const chunkResponse = await fetch(
2580
+ `${baseUrl}/api/v1/storage/${this.bucketName}/chunked/${sessionId}/${chunkIndex}`,
2581
+ {
2582
+ method: "PUT",
2583
+ headers: {
2584
+ ...headers,
2585
+ "Content-Type": "application/octet-stream",
2586
+ "Content-Length": String(chunkArrayBuffer.byteLength)
2587
+ },
2588
+ body: chunkArrayBuffer,
2589
+ signal: chunkController.signal
2590
+ }
2591
+ );
2592
+ clearTimeout(timeoutId);
2593
+ if (!chunkResponse.ok) {
2594
+ const errorData = await chunkResponse.json().catch(() => ({}));
2595
+ throw new Error(
2596
+ errorData.error || `Chunk upload failed: ${chunkResponse.statusText}`
2597
+ );
2598
+ }
2599
+ chunkUploaded = true;
2600
+ } catch (err) {
2601
+ if (options?.signal?.aborted) {
2602
+ return { data: null, error: new Error("Upload aborted") };
2603
+ }
2604
+ retryCount++;
2605
+ if (retryCount > maxRetries) {
2606
+ throw new Error(
2607
+ `Failed to upload chunk ${chunkIndex} after ${maxRetries} retries: ${err.message}`
2608
+ );
2609
+ }
2610
+ const delay = retryDelayMs * Math.pow(2, retryCount - 1);
2611
+ await new Promise((resolve) => setTimeout(resolve, delay));
2612
+ }
2613
+ }
2614
+ uploadedBytes += end - start;
2615
+ if (options?.onProgress) {
2616
+ const now = Date.now();
2617
+ const elapsed = (now - lastProgressTime) / 1e3;
2618
+ const bytesPerSecond = elapsed > 0 ? (uploadedBytes - lastProgressBytes) / elapsed : 0;
2619
+ lastProgressTime = now;
2620
+ lastProgressBytes = uploadedBytes;
2621
+ options.onProgress({
2622
+ loaded: uploadedBytes,
2623
+ total: totalSize,
2624
+ percentage: Math.round(uploadedBytes / totalSize * 100),
2625
+ currentChunk: chunkIndex + 1,
2626
+ totalChunks,
2627
+ bytesPerSecond,
2628
+ sessionId
2629
+ });
2630
+ }
2631
+ }
2632
+ const completeResponse = await fetch(
2633
+ `${baseUrl}/api/v1/storage/${this.bucketName}/chunked/${sessionId}/complete`,
2634
+ {
2635
+ method: "POST",
2636
+ headers,
2637
+ signal: options?.signal
2638
+ }
2639
+ );
2640
+ if (!completeResponse.ok) {
2641
+ const errorData = await completeResponse.json().catch(() => ({}));
2642
+ throw new Error(
2643
+ errorData.error || `Failed to complete upload: ${completeResponse.statusText}`
2644
+ );
2645
+ }
2646
+ const result = await completeResponse.json();
2647
+ return {
2648
+ data: {
2649
+ id: result.id,
2650
+ path: result.path,
2651
+ fullPath: result.full_path
2652
+ },
2653
+ error: null
2654
+ };
2655
+ } catch (error) {
2656
+ return { data: null, error };
2657
+ }
2658
+ }
2659
+ /**
2660
+ * Abort an in-progress resumable upload
2661
+ * @param sessionId - The upload session ID to abort
2662
+ */
2663
+ async abortResumableUpload(sessionId) {
2664
+ try {
2665
+ const baseUrl = this.fetch["baseUrl"];
2666
+ const headers = this.fetch["defaultHeaders"];
2667
+ const response = await fetch(
2668
+ `${baseUrl}/api/v1/storage/${this.bucketName}/chunked/${sessionId}`,
2669
+ {
2670
+ method: "DELETE",
2671
+ headers
2672
+ }
2673
+ );
2674
+ if (!response.ok && response.status !== 204) {
2675
+ const errorData = await response.json().catch(() => ({}));
2676
+ throw new Error(
2677
+ errorData.error || `Failed to abort upload: ${response.statusText}`
2678
+ );
2679
+ }
2680
+ return { error: null };
2681
+ } catch (error) {
2682
+ return { error };
2683
+ }
2684
+ }
2685
+ /**
2686
+ * Get the status of a resumable upload session
2687
+ * @param sessionId - The upload session ID to check
2688
+ */
2689
+ async getResumableUploadStatus(sessionId) {
2690
+ try {
2691
+ const baseUrl = this.fetch["baseUrl"];
2692
+ const headers = this.fetch["defaultHeaders"];
2693
+ const response = await fetch(
2694
+ `${baseUrl}/api/v1/storage/${this.bucketName}/chunked/${sessionId}/status`,
2695
+ {
2696
+ method: "GET",
2697
+ headers
2698
+ }
2699
+ );
2700
+ if (!response.ok) {
2701
+ const errorData = await response.json().catch(() => ({}));
2702
+ throw new Error(
2703
+ errorData.error || `Failed to get upload status: ${response.statusText}`
2704
+ );
2705
+ }
2706
+ const data = await response.json();
2707
+ return {
2708
+ data: data.session,
2709
+ error: null
2710
+ };
2711
+ } catch (error) {
2712
+ return { data: null, error };
2713
+ }
2714
+ }
2267
2715
  /**
2268
2716
  * List files in the bucket
2269
2717
  * Supports both Supabase-style list(path, options) and Fluxbase-style list(options)
@@ -2835,40 +3283,244 @@ var FluxbaseJobs = class {
2835
3283
  return { data: null, error };
2836
3284
  }
2837
3285
  }
2838
- };
2839
-
2840
- // src/settings.ts
2841
- var SystemSettingsManager = class {
2842
- constructor(fetch2) {
2843
- this.fetch = fetch2;
2844
- }
2845
3286
  /**
2846
- * List all system settings
3287
+ * Get execution logs for a job
2847
3288
  *
2848
- * @returns Promise resolving to ListSystemSettingsResponse
3289
+ * Returns logs for the specified job. Only returns logs for jobs
3290
+ * owned by the authenticated user (unless using service_role).
3291
+ *
3292
+ * @param jobId - Job ID
3293
+ * @param afterLine - Optional line number to get logs after (for polling/streaming)
3294
+ * @returns Promise resolving to { data, error } tuple with execution logs
2849
3295
  *
2850
3296
  * @example
2851
3297
  * ```typescript
2852
- * const response = await client.admin.settings.system.list()
2853
- * console.log(response.settings)
2854
- * ```
2855
- */
2856
- async list() {
2857
- const settings = await this.fetch.get(
2858
- "/api/v1/admin/system/settings"
2859
- );
2860
- return { settings: Array.isArray(settings) ? settings : [] };
2861
- }
2862
- /**
2863
- * Get a specific system setting by key
3298
+ * // Get all logs for a job
3299
+ * const { data: logs, error } = await client.jobs.getLogs('550e8400-e29b-41d4-a716-446655440000')
2864
3300
  *
2865
- * @param key - Setting key (e.g., 'app.auth.enable_signup')
2866
- * @returns Promise resolving to SystemSetting
3301
+ * if (logs) {
3302
+ * for (const log of logs) {
3303
+ * console.log(`[${log.level}] ${log.message}`)
3304
+ * }
3305
+ * }
2867
3306
  *
2868
- * @example
2869
- * ```typescript
2870
- * const setting = await client.admin.settings.system.get('app.auth.enable_signup')
2871
- * console.log(setting.value)
3307
+ * // Backfill + stream pattern
3308
+ * const { data: logs } = await client.jobs.getLogs(jobId)
3309
+ * let lastLine = Math.max(...(logs?.map(l => l.line_number) ?? []), 0)
3310
+ *
3311
+ * const channel = client.realtime
3312
+ * .executionLogs(jobId, 'job')
3313
+ * .onLog((log) => {
3314
+ * if (log.line_number > lastLine) {
3315
+ * displayLog(log)
3316
+ * lastLine = log.line_number
3317
+ * }
3318
+ * })
3319
+ * .subscribe()
3320
+ * ```
3321
+ */
3322
+ async getLogs(jobId, afterLine) {
3323
+ try {
3324
+ const params = afterLine !== void 0 ? `?after_line=${afterLine}` : "";
3325
+ const response = await this.fetch.get(
3326
+ `/api/v1/jobs/${jobId}/logs${params}`
3327
+ );
3328
+ return { data: response.logs || [], error: null };
3329
+ } catch (error) {
3330
+ return { data: null, error };
3331
+ }
3332
+ }
3333
+ };
3334
+
3335
+ // src/rpc.ts
3336
+ var FluxbaseRPC = class {
3337
+ constructor(fetch2) {
3338
+ this.fetch = fetch2;
3339
+ }
3340
+ /**
3341
+ * List available RPC procedures (public, enabled)
3342
+ *
3343
+ * @param namespace - Optional namespace filter
3344
+ * @returns Promise resolving to { data, error } tuple with array of procedure summaries
3345
+ */
3346
+ async list(namespace) {
3347
+ try {
3348
+ const params = namespace ? `?namespace=${encodeURIComponent(namespace)}` : "";
3349
+ const response = await this.fetch.get(
3350
+ `/api/v1/rpc/procedures${params}`
3351
+ );
3352
+ return { data: response.procedures || [], error: null };
3353
+ } catch (error) {
3354
+ return { data: null, error };
3355
+ }
3356
+ }
3357
+ /**
3358
+ * Invoke an RPC procedure
3359
+ *
3360
+ * @param name - Procedure name
3361
+ * @param params - Optional parameters to pass to the procedure
3362
+ * @param options - Optional invocation options
3363
+ * @returns Promise resolving to { data, error } tuple with invocation response
3364
+ *
3365
+ * @example
3366
+ * ```typescript
3367
+ * // Synchronous invocation
3368
+ * const { data, error } = await fluxbase.rpc.invoke('get-user-orders', {
3369
+ * user_id: '123',
3370
+ * limit: 10
3371
+ * });
3372
+ * console.log(data.result); // Query results
3373
+ *
3374
+ * // Asynchronous invocation
3375
+ * const { data: asyncData } = await fluxbase.rpc.invoke('generate-report', {
3376
+ * year: 2024
3377
+ * }, { async: true });
3378
+ * console.log(asyncData.execution_id); // Use to poll status
3379
+ * ```
3380
+ */
3381
+ async invoke(name, params, options) {
3382
+ try {
3383
+ const namespace = options?.namespace || "default";
3384
+ const response = await this.fetch.post(
3385
+ `/api/v1/rpc/${encodeURIComponent(namespace)}/${encodeURIComponent(name)}`,
3386
+ {
3387
+ params,
3388
+ async: options?.async
3389
+ }
3390
+ );
3391
+ return { data: response, error: null };
3392
+ } catch (error) {
3393
+ return { data: null, error };
3394
+ }
3395
+ }
3396
+ /**
3397
+ * Get execution status (for async invocations or checking history)
3398
+ *
3399
+ * @param executionId - The execution ID returned from async invoke
3400
+ * @returns Promise resolving to { data, error } tuple with execution details
3401
+ *
3402
+ * @example
3403
+ * ```typescript
3404
+ * const { data, error } = await fluxbase.rpc.getStatus('execution-uuid');
3405
+ * if (data.status === 'completed') {
3406
+ * console.log('Result:', data.result);
3407
+ * } else if (data.status === 'running') {
3408
+ * console.log('Still running...');
3409
+ * }
3410
+ * ```
3411
+ */
3412
+ async getStatus(executionId) {
3413
+ try {
3414
+ const data = await this.fetch.get(
3415
+ `/api/v1/rpc/executions/${encodeURIComponent(executionId)}`
3416
+ );
3417
+ return { data, error: null };
3418
+ } catch (error) {
3419
+ return { data: null, error };
3420
+ }
3421
+ }
3422
+ /**
3423
+ * Get execution logs (for debugging and monitoring)
3424
+ *
3425
+ * @param executionId - The execution ID
3426
+ * @param afterLine - Optional line number to get logs after (for polling)
3427
+ * @returns Promise resolving to { data, error } tuple with execution logs
3428
+ *
3429
+ * @example
3430
+ * ```typescript
3431
+ * const { data: logs } = await fluxbase.rpc.getLogs('execution-uuid');
3432
+ * for (const log of logs) {
3433
+ * console.log(`[${log.level}] ${log.message}`);
3434
+ * }
3435
+ * ```
3436
+ */
3437
+ async getLogs(executionId, afterLine) {
3438
+ try {
3439
+ const params = afterLine !== void 0 ? `?after=${afterLine}` : "";
3440
+ const response = await this.fetch.get(
3441
+ `/api/v1/rpc/executions/${encodeURIComponent(executionId)}/logs${params}`
3442
+ );
3443
+ return { data: response.logs || [], error: null };
3444
+ } catch (error) {
3445
+ return { data: null, error };
3446
+ }
3447
+ }
3448
+ /**
3449
+ * Poll for execution completion with exponential backoff
3450
+ *
3451
+ * @param executionId - The execution ID to poll
3452
+ * @param options - Polling options
3453
+ * @returns Promise resolving to final execution state
3454
+ *
3455
+ * @example
3456
+ * ```typescript
3457
+ * const { data: result } = await fluxbase.rpc.invoke('long-task', {}, { async: true });
3458
+ * const { data: final } = await fluxbase.rpc.waitForCompletion(result.execution_id, {
3459
+ * maxWaitMs: 60000, // Wait up to 1 minute
3460
+ * onProgress: (exec) => console.log(`Status: ${exec.status}`)
3461
+ * });
3462
+ * console.log('Final result:', final.result);
3463
+ * ```
3464
+ */
3465
+ async waitForCompletion(executionId, options) {
3466
+ const maxWait = options?.maxWaitMs || 3e4;
3467
+ const initialInterval = options?.initialIntervalMs || 500;
3468
+ const maxInterval = options?.maxIntervalMs || 5e3;
3469
+ const startTime = Date.now();
3470
+ let interval = initialInterval;
3471
+ while (Date.now() - startTime < maxWait) {
3472
+ const { data: execution, error } = await this.getStatus(executionId);
3473
+ if (error) {
3474
+ return { data: null, error };
3475
+ }
3476
+ if (!execution) {
3477
+ return { data: null, error: new Error("Execution not found") };
3478
+ }
3479
+ if (options?.onProgress) {
3480
+ options.onProgress(execution);
3481
+ }
3482
+ if (execution.status === "completed" || execution.status === "failed" || execution.status === "cancelled" || execution.status === "timeout") {
3483
+ return { data: execution, error: null };
3484
+ }
3485
+ await new Promise((resolve) => setTimeout(resolve, interval));
3486
+ interval = Math.min(interval * 1.5, maxInterval);
3487
+ }
3488
+ return { data: null, error: new Error("Timeout waiting for execution to complete") };
3489
+ }
3490
+ };
3491
+
3492
+ // src/settings.ts
3493
+ var SystemSettingsManager = class {
3494
+ constructor(fetch2) {
3495
+ this.fetch = fetch2;
3496
+ }
3497
+ /**
3498
+ * List all system settings
3499
+ *
3500
+ * @returns Promise resolving to ListSystemSettingsResponse
3501
+ *
3502
+ * @example
3503
+ * ```typescript
3504
+ * const response = await client.admin.settings.system.list()
3505
+ * console.log(response.settings)
3506
+ * ```
3507
+ */
3508
+ async list() {
3509
+ const settings = await this.fetch.get(
3510
+ "/api/v1/admin/system/settings"
3511
+ );
3512
+ return { settings: Array.isArray(settings) ? settings : [] };
3513
+ }
3514
+ /**
3515
+ * Get a specific system setting by key
3516
+ *
3517
+ * @param key - Setting key (e.g., 'app.auth.enable_signup')
3518
+ * @returns Promise resolving to SystemSetting
3519
+ *
3520
+ * @example
3521
+ * ```typescript
3522
+ * const setting = await client.admin.settings.system.get('app.auth.enable_signup')
3523
+ * console.log(setting.value)
2872
3524
  * ```
2873
3525
  */
2874
3526
  async get(key) {
@@ -5191,54 +5843,16 @@ var FluxbaseAdminMigrations = class {
5191
5843
  }
5192
5844
  }
5193
5845
  /**
5194
- * Trigger schema refresh which will restart the server
5195
- * Handles the restart gracefully by waiting for the server to come back online
5846
+ * Trigger schema refresh to update the REST API cache
5847
+ * Note: Server no longer restarts - cache is invalidated instantly
5196
5848
  *
5197
5849
  * @private
5198
5850
  */
5199
- async triggerSchemaRefreshWithRestart() {
5200
- console.log("Triggering schema refresh (server will restart)...");
5201
- try {
5202
- const response = await this.fetch.post(
5203
- "/api/v1/admin/schema/refresh",
5204
- {}
5205
- );
5206
- console.log("Server restart initiated:", response.message || "Schema refresh in progress");
5207
- } catch (error) {
5208
- const isConnectionError = error.message?.includes("fetch failed") || error.message?.includes("ECONNREFUSED") || error.message?.includes("ECONNRESET") || error.code === "ECONNREFUSED" || error.code === "ECONNRESET";
5209
- if (!isConnectionError) {
5210
- throw error;
5211
- }
5212
- console.log("Connection dropped (expected during restart)...");
5213
- }
5214
- console.log("Waiting 6 seconds for server to restart...");
5215
- await this.sleep(6e3);
5216
- const maxAttempts = 5;
5217
- const baseDelay = 1e3;
5218
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
5219
- try {
5220
- await this.fetch.get("/health");
5221
- console.log("Server is back online and ready");
5222
- return;
5223
- } catch (error) {
5224
- const isLastAttempt = attempt === maxAttempts;
5225
- if (isLastAttempt) {
5226
- throw new Error(
5227
- `Server did not come back online after ${maxAttempts} attempts. Please check server logs and try again.`
5228
- );
5229
- }
5230
- const delay = baseDelay * Math.pow(2, attempt - 1);
5231
- console.log(`Server not ready yet, retrying in ${delay}ms... (attempt ${attempt}/${maxAttempts})`);
5232
- await this.sleep(delay);
5233
- }
5234
- }
5235
- }
5236
- /**
5237
- * Helper function to sleep for a given duration
5238
- * @private
5239
- */
5240
- sleep(ms) {
5241
- return new Promise((resolve) => setTimeout(resolve, ms));
5851
+ async triggerSchemaRefresh() {
5852
+ const response = await this.fetch.post("/api/v1/admin/schema/refresh", {});
5853
+ console.log(
5854
+ `Schema cache refreshed: ${response.tables} tables, ${response.views} views`
5855
+ );
5242
5856
  }
5243
5857
  /**
5244
5858
  * Smart sync all registered migrations
@@ -5340,9 +5954,9 @@ var FluxbaseAdminMigrations = class {
5340
5954
  const migrationsAppliedSuccessfully = combined.summary.applied > 0 && combined.summary.errors === 0;
5341
5955
  if (!combined.dry_run && migrationsAppliedSuccessfully) {
5342
5956
  try {
5343
- await this.triggerSchemaRefreshWithRestart();
5957
+ await this.triggerSchemaRefresh();
5344
5958
  } catch (refreshError) {
5345
- console.warn("Schema refresh completed with warnings:", refreshError);
5959
+ console.warn("Schema refresh warning:", refreshError);
5346
5960
  }
5347
5961
  }
5348
5962
  if (errors.length > 0 || combined.summary.errors > 0) {
@@ -5375,7 +5989,10 @@ var FluxbaseAdminMigrations = class {
5375
5989
  */
5376
5990
  async create(request) {
5377
5991
  try {
5378
- const data = await this.fetch.post("/api/v1/admin/migrations", request);
5992
+ const data = await this.fetch.post(
5993
+ "/api/v1/admin/migrations",
5994
+ request
5995
+ );
5379
5996
  return { data, error: null };
5380
5997
  } catch (error) {
5381
5998
  return { data: null, error };
@@ -5476,7 +6093,9 @@ var FluxbaseAdminMigrations = class {
5476
6093
  async delete(name, namespace = "default") {
5477
6094
  try {
5478
6095
  const params = new URLSearchParams({ namespace });
5479
- await this.fetch.delete(`/api/v1/admin/migrations/${name}?${params.toString()}`);
6096
+ await this.fetch.delete(
6097
+ `/api/v1/admin/migrations/${name}?${params.toString()}`
6098
+ );
5480
6099
  return { data: null, error: null };
5481
6100
  } catch (error) {
5482
6101
  return { data: null, error };
@@ -5577,7 +6196,10 @@ var FluxbaseAdminMigrations = class {
5577
6196
  */
5578
6197
  async getExecutions(name, namespace = "default", limit = 50) {
5579
6198
  try {
5580
- const params = new URLSearchParams({ namespace, limit: limit.toString() });
6199
+ const params = new URLSearchParams({
6200
+ namespace,
6201
+ limit: limit.toString()
6202
+ });
5581
6203
  const data = await this.fetch.get(
5582
6204
  `/api/v1/admin/migrations/${name}/executions?${params.toString()}`
5583
6205
  );
@@ -6377,39 +6999,444 @@ var FluxbaseAdminAI = class {
6377
6999
  return { data: null, error };
6378
7000
  }
6379
7001
  }
6380
- };
6381
-
6382
- // src/admin-rpc.ts
6383
- var FluxbaseAdminRPC = class {
6384
- constructor(fetch2) {
6385
- this.fetch = fetch2;
6386
- }
6387
7002
  // ============================================================================
6388
- // PROCEDURE MANAGEMENT
7003
+ // KNOWLEDGE BASE MANAGEMENT (RAG)
6389
7004
  // ============================================================================
6390
7005
  /**
6391
- * Sync RPC procedures from filesystem or API payload
6392
- *
6393
- * Can sync from:
6394
- * 1. Filesystem (if no procedures provided) - loads from configured procedures directory
6395
- * 2. API payload (if procedures array provided) - syncs provided procedure specifications
6396
- *
6397
- * Requires service_role or admin authentication.
7006
+ * List all knowledge bases
6398
7007
  *
6399
- * @param options - Sync options including namespace and optional procedures array
6400
- * @returns Promise resolving to { data, error } tuple with sync results
7008
+ * @returns Promise resolving to { data, error } tuple with array of knowledge base summaries
6401
7009
  *
6402
7010
  * @example
6403
7011
  * ```typescript
6404
- * // Sync from filesystem
6405
- * const { data, error } = await client.admin.rpc.sync()
6406
- *
6407
- * // Sync with provided procedure code
6408
- * const { data, error } = await client.admin.rpc.sync({
6409
- * namespace: 'default',
6410
- * procedures: [{
6411
- * name: 'get-user-orders',
6412
- * code: myProcedureSQL,
7012
+ * const { data, error } = await client.admin.ai.listKnowledgeBases()
7013
+ * if (data) {
7014
+ * console.log('Knowledge bases:', data.map(kb => kb.name))
7015
+ * }
7016
+ * ```
7017
+ */
7018
+ async listKnowledgeBases() {
7019
+ try {
7020
+ const response = await this.fetch.get("/api/v1/admin/ai/knowledge-bases");
7021
+ return { data: response.knowledge_bases || [], error: null };
7022
+ } catch (error) {
7023
+ return { data: null, error };
7024
+ }
7025
+ }
7026
+ /**
7027
+ * Get a specific knowledge base
7028
+ *
7029
+ * @param id - Knowledge base ID
7030
+ * @returns Promise resolving to { data, error } tuple with knowledge base details
7031
+ *
7032
+ * @example
7033
+ * ```typescript
7034
+ * const { data, error } = await client.admin.ai.getKnowledgeBase('uuid')
7035
+ * if (data) {
7036
+ * console.log('Knowledge base:', data.name)
7037
+ * }
7038
+ * ```
7039
+ */
7040
+ async getKnowledgeBase(id) {
7041
+ try {
7042
+ const data = await this.fetch.get(
7043
+ `/api/v1/admin/ai/knowledge-bases/${id}`
7044
+ );
7045
+ return { data, error: null };
7046
+ } catch (error) {
7047
+ return { data: null, error };
7048
+ }
7049
+ }
7050
+ /**
7051
+ * Create a new knowledge base
7052
+ *
7053
+ * @param request - Knowledge base configuration
7054
+ * @returns Promise resolving to { data, error } tuple with created knowledge base
7055
+ *
7056
+ * @example
7057
+ * ```typescript
7058
+ * const { data, error } = await client.admin.ai.createKnowledgeBase({
7059
+ * name: 'product-docs',
7060
+ * description: 'Product documentation',
7061
+ * chunk_size: 512,
7062
+ * chunk_overlap: 50,
7063
+ * })
7064
+ * ```
7065
+ */
7066
+ async createKnowledgeBase(request) {
7067
+ try {
7068
+ const data = await this.fetch.post(
7069
+ "/api/v1/admin/ai/knowledge-bases",
7070
+ request
7071
+ );
7072
+ return { data, error: null };
7073
+ } catch (error) {
7074
+ return { data: null, error };
7075
+ }
7076
+ }
7077
+ /**
7078
+ * Update an existing knowledge base
7079
+ *
7080
+ * @param id - Knowledge base ID
7081
+ * @param updates - Fields to update
7082
+ * @returns Promise resolving to { data, error } tuple with updated knowledge base
7083
+ *
7084
+ * @example
7085
+ * ```typescript
7086
+ * const { data, error } = await client.admin.ai.updateKnowledgeBase('uuid', {
7087
+ * description: 'Updated description',
7088
+ * enabled: true,
7089
+ * })
7090
+ * ```
7091
+ */
7092
+ async updateKnowledgeBase(id, updates) {
7093
+ try {
7094
+ const data = await this.fetch.put(
7095
+ `/api/v1/admin/ai/knowledge-bases/${id}`,
7096
+ updates
7097
+ );
7098
+ return { data, error: null };
7099
+ } catch (error) {
7100
+ return { data: null, error };
7101
+ }
7102
+ }
7103
+ /**
7104
+ * Delete a knowledge base
7105
+ *
7106
+ * @param id - Knowledge base ID
7107
+ * @returns Promise resolving to { data, error } tuple
7108
+ *
7109
+ * @example
7110
+ * ```typescript
7111
+ * const { data, error } = await client.admin.ai.deleteKnowledgeBase('uuid')
7112
+ * ```
7113
+ */
7114
+ async deleteKnowledgeBase(id) {
7115
+ try {
7116
+ await this.fetch.delete(`/api/v1/admin/ai/knowledge-bases/${id}`);
7117
+ return { data: null, error: null };
7118
+ } catch (error) {
7119
+ return { data: null, error };
7120
+ }
7121
+ }
7122
+ // ============================================================================
7123
+ // DOCUMENT MANAGEMENT
7124
+ // ============================================================================
7125
+ /**
7126
+ * List documents in a knowledge base
7127
+ *
7128
+ * @param knowledgeBaseId - Knowledge base ID
7129
+ * @returns Promise resolving to { data, error } tuple with array of documents
7130
+ *
7131
+ * @example
7132
+ * ```typescript
7133
+ * const { data, error } = await client.admin.ai.listDocuments('kb-uuid')
7134
+ * if (data) {
7135
+ * console.log('Documents:', data.map(d => d.title))
7136
+ * }
7137
+ * ```
7138
+ */
7139
+ async listDocuments(knowledgeBaseId) {
7140
+ try {
7141
+ const response = await this.fetch.get(`/api/v1/admin/ai/knowledge-bases/${knowledgeBaseId}/documents`);
7142
+ return { data: response.documents || [], error: null };
7143
+ } catch (error) {
7144
+ return { data: null, error };
7145
+ }
7146
+ }
7147
+ /**
7148
+ * Get a specific document
7149
+ *
7150
+ * @param knowledgeBaseId - Knowledge base ID
7151
+ * @param documentId - Document ID
7152
+ * @returns Promise resolving to { data, error } tuple with document details
7153
+ *
7154
+ * @example
7155
+ * ```typescript
7156
+ * const { data, error } = await client.admin.ai.getDocument('kb-uuid', 'doc-uuid')
7157
+ * ```
7158
+ */
7159
+ async getDocument(knowledgeBaseId, documentId) {
7160
+ try {
7161
+ const data = await this.fetch.get(
7162
+ `/api/v1/admin/ai/knowledge-bases/${knowledgeBaseId}/documents/${documentId}`
7163
+ );
7164
+ return { data, error: null };
7165
+ } catch (error) {
7166
+ return { data: null, error };
7167
+ }
7168
+ }
7169
+ /**
7170
+ * Add a document to a knowledge base
7171
+ *
7172
+ * Document will be chunked and embedded asynchronously.
7173
+ *
7174
+ * @param knowledgeBaseId - Knowledge base ID
7175
+ * @param request - Document content and metadata
7176
+ * @returns Promise resolving to { data, error } tuple with document ID
7177
+ *
7178
+ * @example
7179
+ * ```typescript
7180
+ * const { data, error } = await client.admin.ai.addDocument('kb-uuid', {
7181
+ * title: 'Getting Started Guide',
7182
+ * content: 'This is the content of the document...',
7183
+ * metadata: { category: 'guides' },
7184
+ * })
7185
+ * if (data) {
7186
+ * console.log('Document ID:', data.document_id)
7187
+ * }
7188
+ * ```
7189
+ */
7190
+ async addDocument(knowledgeBaseId, request) {
7191
+ try {
7192
+ const data = await this.fetch.post(
7193
+ `/api/v1/admin/ai/knowledge-bases/${knowledgeBaseId}/documents`,
7194
+ request
7195
+ );
7196
+ return { data, error: null };
7197
+ } catch (error) {
7198
+ return { data: null, error };
7199
+ }
7200
+ }
7201
+ /**
7202
+ * Upload a document file to a knowledge base
7203
+ *
7204
+ * Supported file types: PDF, TXT, MD, HTML, CSV, DOCX, XLSX, RTF, EPUB, JSON
7205
+ * Maximum file size: 50MB
7206
+ *
7207
+ * @param knowledgeBaseId - Knowledge base ID
7208
+ * @param file - File to upload (File or Blob)
7209
+ * @param title - Optional document title (defaults to filename without extension)
7210
+ * @returns Promise resolving to { data, error } tuple with upload result
7211
+ *
7212
+ * @example
7213
+ * ```typescript
7214
+ * // Browser
7215
+ * const fileInput = document.getElementById('file') as HTMLInputElement
7216
+ * const file = fileInput.files?.[0]
7217
+ * if (file) {
7218
+ * const { data, error } = await client.admin.ai.uploadDocument('kb-uuid', file)
7219
+ * if (data) {
7220
+ * console.log('Document ID:', data.document_id)
7221
+ * console.log('Extracted length:', data.extracted_length)
7222
+ * }
7223
+ * }
7224
+ *
7225
+ * // Node.js (with node-fetch or similar)
7226
+ * import { Blob } from 'buffer'
7227
+ * const content = await fs.readFile('document.pdf')
7228
+ * const blob = new Blob([content], { type: 'application/pdf' })
7229
+ * const { data, error } = await client.admin.ai.uploadDocument('kb-uuid', blob, 'My Document')
7230
+ * ```
7231
+ */
7232
+ async uploadDocument(knowledgeBaseId, file, title) {
7233
+ try {
7234
+ const formData = new FormData();
7235
+ formData.append("file", file);
7236
+ if (title) {
7237
+ formData.append("title", title);
7238
+ }
7239
+ const data = await this.fetch.post(
7240
+ `/api/v1/admin/ai/knowledge-bases/${knowledgeBaseId}/documents/upload`,
7241
+ formData
7242
+ );
7243
+ return { data, error: null };
7244
+ } catch (error) {
7245
+ return { data: null, error };
7246
+ }
7247
+ }
7248
+ /**
7249
+ * Delete a document from a knowledge base
7250
+ *
7251
+ * @param knowledgeBaseId - Knowledge base ID
7252
+ * @param documentId - Document ID
7253
+ * @returns Promise resolving to { data, error } tuple
7254
+ *
7255
+ * @example
7256
+ * ```typescript
7257
+ * const { data, error } = await client.admin.ai.deleteDocument('kb-uuid', 'doc-uuid')
7258
+ * ```
7259
+ */
7260
+ async deleteDocument(knowledgeBaseId, documentId) {
7261
+ try {
7262
+ await this.fetch.delete(
7263
+ `/api/v1/admin/ai/knowledge-bases/${knowledgeBaseId}/documents/${documentId}`
7264
+ );
7265
+ return { data: null, error: null };
7266
+ } catch (error) {
7267
+ return { data: null, error };
7268
+ }
7269
+ }
7270
+ /**
7271
+ * Search a knowledge base
7272
+ *
7273
+ * @param knowledgeBaseId - Knowledge base ID
7274
+ * @param query - Search query
7275
+ * @param options - Search options
7276
+ * @returns Promise resolving to { data, error } tuple with search results
7277
+ *
7278
+ * @example
7279
+ * ```typescript
7280
+ * const { data, error } = await client.admin.ai.searchKnowledgeBase('kb-uuid', 'how to reset password', {
7281
+ * max_chunks: 5,
7282
+ * threshold: 0.7,
7283
+ * })
7284
+ * if (data) {
7285
+ * console.log('Results:', data.results.map(r => r.content))
7286
+ * }
7287
+ * ```
7288
+ */
7289
+ async searchKnowledgeBase(knowledgeBaseId, query, options) {
7290
+ try {
7291
+ const data = await this.fetch.post(
7292
+ `/api/v1/admin/ai/knowledge-bases/${knowledgeBaseId}/search`,
7293
+ {
7294
+ query,
7295
+ max_chunks: options?.max_chunks,
7296
+ threshold: options?.threshold
7297
+ }
7298
+ );
7299
+ return { data, error: null };
7300
+ } catch (error) {
7301
+ return { data: null, error };
7302
+ }
7303
+ }
7304
+ // ============================================================================
7305
+ // CHATBOT KNOWLEDGE BASE LINKING
7306
+ // ============================================================================
7307
+ /**
7308
+ * List knowledge bases linked to a chatbot
7309
+ *
7310
+ * @param chatbotId - Chatbot ID
7311
+ * @returns Promise resolving to { data, error } tuple with linked knowledge bases
7312
+ *
7313
+ * @example
7314
+ * ```typescript
7315
+ * const { data, error } = await client.admin.ai.listChatbotKnowledgeBases('chatbot-uuid')
7316
+ * if (data) {
7317
+ * console.log('Linked KBs:', data.map(l => l.knowledge_base_id))
7318
+ * }
7319
+ * ```
7320
+ */
7321
+ async listChatbotKnowledgeBases(chatbotId) {
7322
+ try {
7323
+ const response = await this.fetch.get(`/api/v1/admin/ai/chatbots/${chatbotId}/knowledge-bases`);
7324
+ return { data: response.knowledge_bases || [], error: null };
7325
+ } catch (error) {
7326
+ return { data: null, error };
7327
+ }
7328
+ }
7329
+ /**
7330
+ * Link a knowledge base to a chatbot
7331
+ *
7332
+ * @param chatbotId - Chatbot ID
7333
+ * @param request - Link configuration
7334
+ * @returns Promise resolving to { data, error } tuple with link details
7335
+ *
7336
+ * @example
7337
+ * ```typescript
7338
+ * const { data, error } = await client.admin.ai.linkKnowledgeBase('chatbot-uuid', {
7339
+ * knowledge_base_id: 'kb-uuid',
7340
+ * priority: 1,
7341
+ * max_chunks: 5,
7342
+ * similarity_threshold: 0.7,
7343
+ * })
7344
+ * ```
7345
+ */
7346
+ async linkKnowledgeBase(chatbotId, request) {
7347
+ try {
7348
+ const data = await this.fetch.post(
7349
+ `/api/v1/admin/ai/chatbots/${chatbotId}/knowledge-bases`,
7350
+ request
7351
+ );
7352
+ return { data, error: null };
7353
+ } catch (error) {
7354
+ return { data: null, error };
7355
+ }
7356
+ }
7357
+ /**
7358
+ * Update a chatbot-knowledge base link
7359
+ *
7360
+ * @param chatbotId - Chatbot ID
7361
+ * @param knowledgeBaseId - Knowledge base ID
7362
+ * @param updates - Fields to update
7363
+ * @returns Promise resolving to { data, error } tuple with updated link
7364
+ *
7365
+ * @example
7366
+ * ```typescript
7367
+ * const { data, error } = await client.admin.ai.updateChatbotKnowledgeBase(
7368
+ * 'chatbot-uuid',
7369
+ * 'kb-uuid',
7370
+ * { max_chunks: 10, enabled: true }
7371
+ * )
7372
+ * ```
7373
+ */
7374
+ async updateChatbotKnowledgeBase(chatbotId, knowledgeBaseId, updates) {
7375
+ try {
7376
+ const data = await this.fetch.put(
7377
+ `/api/v1/admin/ai/chatbots/${chatbotId}/knowledge-bases/${knowledgeBaseId}`,
7378
+ updates
7379
+ );
7380
+ return { data, error: null };
7381
+ } catch (error) {
7382
+ return { data: null, error };
7383
+ }
7384
+ }
7385
+ /**
7386
+ * Unlink a knowledge base from a chatbot
7387
+ *
7388
+ * @param chatbotId - Chatbot ID
7389
+ * @param knowledgeBaseId - Knowledge base ID
7390
+ * @returns Promise resolving to { data, error } tuple
7391
+ *
7392
+ * @example
7393
+ * ```typescript
7394
+ * const { data, error } = await client.admin.ai.unlinkKnowledgeBase('chatbot-uuid', 'kb-uuid')
7395
+ * ```
7396
+ */
7397
+ async unlinkKnowledgeBase(chatbotId, knowledgeBaseId) {
7398
+ try {
7399
+ await this.fetch.delete(
7400
+ `/api/v1/admin/ai/chatbots/${chatbotId}/knowledge-bases/${knowledgeBaseId}`
7401
+ );
7402
+ return { data: null, error: null };
7403
+ } catch (error) {
7404
+ return { data: null, error };
7405
+ }
7406
+ }
7407
+ };
7408
+
7409
+ // src/admin-rpc.ts
7410
+ var FluxbaseAdminRPC = class {
7411
+ constructor(fetch2) {
7412
+ this.fetch = fetch2;
7413
+ }
7414
+ // ============================================================================
7415
+ // PROCEDURE MANAGEMENT
7416
+ // ============================================================================
7417
+ /**
7418
+ * Sync RPC procedures from filesystem or API payload
7419
+ *
7420
+ * Can sync from:
7421
+ * 1. Filesystem (if no procedures provided) - loads from configured procedures directory
7422
+ * 2. API payload (if procedures array provided) - syncs provided procedure specifications
7423
+ *
7424
+ * Requires service_role or admin authentication.
7425
+ *
7426
+ * @param options - Sync options including namespace and optional procedures array
7427
+ * @returns Promise resolving to { data, error } tuple with sync results
7428
+ *
7429
+ * @example
7430
+ * ```typescript
7431
+ * // Sync from filesystem
7432
+ * const { data, error } = await client.admin.rpc.sync()
7433
+ *
7434
+ * // Sync with provided procedure code
7435
+ * const { data, error } = await client.admin.rpc.sync({
7436
+ * namespace: 'default',
7437
+ * procedures: [{
7438
+ * name: 'get-user-orders',
7439
+ * code: myProcedureSQL,
6413
7440
  * }],
6414
7441
  * options: {
6415
7442
  * delete_missing: false, // Don't remove procedures not in this sync
@@ -6695,6 +7722,230 @@ var FluxbaseAdminRPC = class {
6695
7722
  }
6696
7723
  };
6697
7724
 
7725
+ // src/admin-storage.ts
7726
+ var FluxbaseAdminStorage = class {
7727
+ constructor(fetch2) {
7728
+ this.fetch = fetch2;
7729
+ }
7730
+ // ============================================================================
7731
+ // Bucket Operations
7732
+ // ============================================================================
7733
+ /**
7734
+ * List all storage buckets
7735
+ *
7736
+ * @returns List of buckets
7737
+ *
7738
+ * @example
7739
+ * ```typescript
7740
+ * const { data, error } = await admin.storage.listBuckets();
7741
+ * if (data) {
7742
+ * console.log(`Found ${data.buckets.length} buckets`);
7743
+ * }
7744
+ * ```
7745
+ */
7746
+ async listBuckets() {
7747
+ return wrapAsync(async () => {
7748
+ return await this.fetch.get(
7749
+ "/api/v1/storage/buckets"
7750
+ );
7751
+ });
7752
+ }
7753
+ /**
7754
+ * Create a new storage bucket
7755
+ *
7756
+ * @param name - Bucket name
7757
+ * @returns Success message
7758
+ *
7759
+ * @example
7760
+ * ```typescript
7761
+ * const { error } = await admin.storage.createBucket('my-bucket');
7762
+ * if (!error) {
7763
+ * console.log('Bucket created');
7764
+ * }
7765
+ * ```
7766
+ */
7767
+ async createBucket(name) {
7768
+ return wrapAsync(async () => {
7769
+ return await this.fetch.post(
7770
+ `/api/v1/storage/buckets/${encodeURIComponent(name)}`
7771
+ );
7772
+ });
7773
+ }
7774
+ /**
7775
+ * Delete a storage bucket
7776
+ *
7777
+ * @param name - Bucket name
7778
+ * @returns Success message
7779
+ *
7780
+ * @example
7781
+ * ```typescript
7782
+ * const { error } = await admin.storage.deleteBucket('my-bucket');
7783
+ * if (!error) {
7784
+ * console.log('Bucket deleted');
7785
+ * }
7786
+ * ```
7787
+ */
7788
+ async deleteBucket(name) {
7789
+ return wrapAsync(async () => {
7790
+ return await this.fetch.delete(
7791
+ `/api/v1/storage/buckets/${encodeURIComponent(name)}`
7792
+ );
7793
+ });
7794
+ }
7795
+ // ============================================================================
7796
+ // Object Operations
7797
+ // ============================================================================
7798
+ /**
7799
+ * List objects in a bucket
7800
+ *
7801
+ * @param bucket - Bucket name
7802
+ * @param prefix - Optional path prefix to filter results
7803
+ * @param delimiter - Optional delimiter for hierarchical listing (usually '/')
7804
+ * @returns List of objects and prefixes (folders)
7805
+ *
7806
+ * @example
7807
+ * ```typescript
7808
+ * // List all objects in bucket
7809
+ * const { data } = await admin.storage.listObjects('my-bucket');
7810
+ *
7811
+ * // List objects in a folder
7812
+ * const { data } = await admin.storage.listObjects('my-bucket', 'folder/', '/');
7813
+ * ```
7814
+ */
7815
+ async listObjects(bucket, prefix, delimiter) {
7816
+ return wrapAsync(async () => {
7817
+ const params = new URLSearchParams();
7818
+ if (prefix) params.append("prefix", prefix);
7819
+ if (delimiter) params.append("delimiter", delimiter);
7820
+ const queryString = params.toString();
7821
+ const url = `/api/v1/storage/${encodeURIComponent(bucket)}${queryString ? `?${queryString}` : ""}`;
7822
+ return await this.fetch.get(url);
7823
+ });
7824
+ }
7825
+ /**
7826
+ * Get object metadata
7827
+ *
7828
+ * @param bucket - Bucket name
7829
+ * @param key - Object key (path)
7830
+ * @returns Object metadata
7831
+ *
7832
+ * @example
7833
+ * ```typescript
7834
+ * const { data } = await admin.storage.getObjectMetadata('my-bucket', 'path/to/file.txt');
7835
+ * if (data) {
7836
+ * console.log(`File size: ${data.size} bytes`);
7837
+ * }
7838
+ * ```
7839
+ */
7840
+ async getObjectMetadata(bucket, key) {
7841
+ return wrapAsync(async () => {
7842
+ const encodedKey = key.split("/").map((s) => encodeURIComponent(s)).join("/");
7843
+ return await this.fetch.get(
7844
+ `/api/v1/storage/${encodeURIComponent(bucket)}/${encodedKey}`,
7845
+ {
7846
+ headers: { "X-Metadata-Only": "true" }
7847
+ }
7848
+ );
7849
+ });
7850
+ }
7851
+ /**
7852
+ * Download an object as a Blob
7853
+ *
7854
+ * @param bucket - Bucket name
7855
+ * @param key - Object key (path)
7856
+ * @returns Object data as Blob
7857
+ *
7858
+ * @example
7859
+ * ```typescript
7860
+ * const { data: blob } = await admin.storage.downloadObject('my-bucket', 'file.pdf');
7861
+ * if (blob) {
7862
+ * // Use the blob
7863
+ * const url = URL.createObjectURL(blob);
7864
+ * }
7865
+ * ```
7866
+ */
7867
+ async downloadObject(bucket, key) {
7868
+ return wrapAsync(async () => {
7869
+ const encodedKey = key.split("/").map((s) => encodeURIComponent(s)).join("/");
7870
+ const response = await this.fetch.getBlob(
7871
+ `/api/v1/storage/${encodeURIComponent(bucket)}/${encodedKey}`
7872
+ );
7873
+ return response;
7874
+ });
7875
+ }
7876
+ /**
7877
+ * Delete an object
7878
+ *
7879
+ * @param bucket - Bucket name
7880
+ * @param key - Object key (path)
7881
+ *
7882
+ * @example
7883
+ * ```typescript
7884
+ * const { error } = await admin.storage.deleteObject('my-bucket', 'path/to/file.txt');
7885
+ * if (!error) {
7886
+ * console.log('Object deleted');
7887
+ * }
7888
+ * ```
7889
+ */
7890
+ async deleteObject(bucket, key) {
7891
+ return wrapAsyncVoid(async () => {
7892
+ const encodedKey = key.split("/").map((s) => encodeURIComponent(s)).join("/");
7893
+ await this.fetch.delete(
7894
+ `/api/v1/storage/${encodeURIComponent(bucket)}/${encodedKey}`
7895
+ );
7896
+ });
7897
+ }
7898
+ /**
7899
+ * Create a folder (empty object with directory content type)
7900
+ *
7901
+ * @param bucket - Bucket name
7902
+ * @param folderPath - Folder path (should end with /)
7903
+ *
7904
+ * @example
7905
+ * ```typescript
7906
+ * const { error } = await admin.storage.createFolder('my-bucket', 'new-folder/');
7907
+ * ```
7908
+ */
7909
+ async createFolder(bucket, folderPath) {
7910
+ return wrapAsyncVoid(async () => {
7911
+ const encodedPath = folderPath.split("/").map((s) => encodeURIComponent(s)).join("/");
7912
+ await this.fetch.post(
7913
+ `/api/v1/storage/${encodeURIComponent(bucket)}/${encodedPath}`,
7914
+ null,
7915
+ {
7916
+ headers: { "Content-Type": "application/x-directory" }
7917
+ }
7918
+ );
7919
+ });
7920
+ }
7921
+ /**
7922
+ * Generate a signed URL for temporary access
7923
+ *
7924
+ * @param bucket - Bucket name
7925
+ * @param key - Object key (path)
7926
+ * @param expiresIn - Expiration time in seconds
7927
+ * @returns Signed URL and expiration info
7928
+ *
7929
+ * @example
7930
+ * ```typescript
7931
+ * const { data } = await admin.storage.generateSignedUrl('my-bucket', 'file.pdf', 3600);
7932
+ * if (data) {
7933
+ * console.log(`Download at: ${data.url}`);
7934
+ * console.log(`Expires in: ${data.expires_in} seconds`);
7935
+ * }
7936
+ * ```
7937
+ */
7938
+ async generateSignedUrl(bucket, key, expiresIn) {
7939
+ return wrapAsync(async () => {
7940
+ const encodedKey = key.split("/").map((s) => encodeURIComponent(s)).join("/");
7941
+ return await this.fetch.post(
7942
+ `/api/v1/storage/${encodeURIComponent(bucket)}/${encodedKey}/signed-url`,
7943
+ { expires_in: expiresIn }
7944
+ );
7945
+ });
7946
+ }
7947
+ };
7948
+
6698
7949
  // src/admin.ts
6699
7950
  var FluxbaseAdmin = class {
6700
7951
  constructor(fetch2) {
@@ -6711,6 +7962,7 @@ var FluxbaseAdmin = class {
6711
7962
  this.migrations = new FluxbaseAdminMigrations(fetch2);
6712
7963
  this.ai = new FluxbaseAdminAI(fetch2);
6713
7964
  this.rpc = new FluxbaseAdminRPC(fetch2);
7965
+ this.storage = new FluxbaseAdminStorage(fetch2);
6714
7966
  }
6715
7967
  /**
6716
7968
  * Set admin authentication token
@@ -6733,6 +7985,54 @@ var FluxbaseAdmin = class {
6733
7985
  this.fetch.setAuthToken(null);
6734
7986
  }
6735
7987
  // ============================================================================
7988
+ // Health Check
7989
+ // ============================================================================
7990
+ /**
7991
+ * Get system health status
7992
+ *
7993
+ * @returns Health status including database and realtime service status
7994
+ *
7995
+ * @example
7996
+ * ```typescript
7997
+ * const { data, error } = await admin.getHealth();
7998
+ * if (data) {
7999
+ * console.log('Status:', data.status);
8000
+ * console.log('Database:', data.services.database);
8001
+ * console.log('Realtime:', data.services.realtime);
8002
+ * }
8003
+ * ```
8004
+ */
8005
+ async getHealth() {
8006
+ return wrapAsync(async () => {
8007
+ return await this.fetch.get("/health");
8008
+ });
8009
+ }
8010
+ // ============================================================================
8011
+ // Email
8012
+ // ============================================================================
8013
+ /**
8014
+ * Send an email
8015
+ *
8016
+ * @param request - Email details (to, subject, html/text)
8017
+ *
8018
+ * @example
8019
+ * ```typescript
8020
+ * const { error } = await admin.sendEmail({
8021
+ * to: 'user@example.com',
8022
+ * subject: 'Hello',
8023
+ * html: '<p>Your message here</p>'
8024
+ * });
8025
+ * if (!error) {
8026
+ * console.log('Email sent');
8027
+ * }
8028
+ * ```
8029
+ */
8030
+ async sendEmail(request) {
8031
+ return wrapAsyncVoid(async () => {
8032
+ await this.fetch.post("/api/v1/admin/email/send", request);
8033
+ });
8034
+ }
8035
+ // ============================================================================
6736
8036
  // Admin Authentication
6737
8037
  // ============================================================================
6738
8038
  /**
@@ -7410,7 +8710,111 @@ var FluxbaseAI = class {
7410
8710
  }
7411
8711
  };
7412
8712
 
8713
+ // src/vector.ts
8714
+ var FluxbaseVector = class {
8715
+ constructor(fetch2) {
8716
+ this.fetch = fetch2;
8717
+ }
8718
+ /**
8719
+ * Generate embeddings for text
8720
+ *
8721
+ * @example
8722
+ * ```typescript
8723
+ * // Single text
8724
+ * const { data } = await client.vector.embed({
8725
+ * text: 'Hello world'
8726
+ * })
8727
+ * console.log(data.embeddings[0]) // [0.1, 0.2, ...]
8728
+ *
8729
+ * // Multiple texts
8730
+ * const { data } = await client.vector.embed({
8731
+ * texts: ['Hello', 'World'],
8732
+ * model: 'text-embedding-3-small'
8733
+ * })
8734
+ * ```
8735
+ */
8736
+ async embed(request) {
8737
+ try {
8738
+ const response = await this.fetch.request(
8739
+ "/api/v1/vector/embed",
8740
+ {
8741
+ method: "POST",
8742
+ body: request
8743
+ }
8744
+ );
8745
+ return { data: response, error: null };
8746
+ } catch (error) {
8747
+ return { data: null, error };
8748
+ }
8749
+ }
8750
+ /**
8751
+ * Search for similar vectors with automatic text embedding
8752
+ *
8753
+ * This is a convenience method that:
8754
+ * 1. Embeds the query text automatically (if `query` is provided)
8755
+ * 2. Performs vector similarity search
8756
+ * 3. Returns results with distance scores
8757
+ *
8758
+ * @example
8759
+ * ```typescript
8760
+ * // Search with text query (auto-embedded)
8761
+ * const { data } = await client.vector.search({
8762
+ * table: 'documents',
8763
+ * column: 'embedding',
8764
+ * query: 'How to use TypeScript?',
8765
+ * match_count: 10,
8766
+ * match_threshold: 0.8
8767
+ * })
8768
+ *
8769
+ * // Search with pre-computed vector
8770
+ * const { data } = await client.vector.search({
8771
+ * table: 'documents',
8772
+ * column: 'embedding',
8773
+ * vector: [0.1, 0.2, ...],
8774
+ * metric: 'cosine',
8775
+ * match_count: 10
8776
+ * })
8777
+ *
8778
+ * // With additional filters
8779
+ * const { data } = await client.vector.search({
8780
+ * table: 'documents',
8781
+ * column: 'embedding',
8782
+ * query: 'TypeScript tutorial',
8783
+ * filters: [
8784
+ * { column: 'status', operator: 'eq', value: 'published' }
8785
+ * ],
8786
+ * match_count: 10
8787
+ * })
8788
+ * ```
8789
+ */
8790
+ async search(options) {
8791
+ try {
8792
+ const response = await this.fetch.request(
8793
+ "/api/v1/vector/search",
8794
+ {
8795
+ method: "POST",
8796
+ body: {
8797
+ table: options.table,
8798
+ column: options.column,
8799
+ query: options.query,
8800
+ vector: options.vector,
8801
+ metric: options.metric || "cosine",
8802
+ match_threshold: options.match_threshold,
8803
+ match_count: options.match_count,
8804
+ select: options.select,
8805
+ filters: options.filters
8806
+ }
8807
+ }
8808
+ );
8809
+ return { data: response, error: null };
8810
+ } catch (error) {
8811
+ return { data: null, error };
8812
+ }
8813
+ }
8814
+ };
8815
+
7413
8816
  // src/query-builder.ts
8817
+ var URL_LENGTH_THRESHOLD = 4096;
7414
8818
  var QueryBuilder = class {
7415
8819
  constructor(fetch2, table, schema) {
7416
8820
  this.selectQuery = "*";
@@ -7423,6 +8827,7 @@ var QueryBuilder = class {
7423
8827
  this.maybeSingleRow = false;
7424
8828
  this.operationType = "select";
7425
8829
  this.headOnly = false;
8830
+ this.isCountAggregation = false;
7426
8831
  this.fetch = fetch2;
7427
8832
  this.table = table;
7428
8833
  this.schema = schema;
@@ -7794,6 +9199,80 @@ var QueryBuilder = class {
7794
9199
  });
7795
9200
  return this;
7796
9201
  }
9202
+ /**
9203
+ * Order results by vector similarity (pgvector)
9204
+ * Results are ordered by distance (ascending = closest first)
9205
+ *
9206
+ * @example
9207
+ * ```typescript
9208
+ * // Order by cosine similarity (closest matches first)
9209
+ * const { data } = await client
9210
+ * .from('documents')
9211
+ * .select('id, title, content')
9212
+ * .orderByVector('embedding', queryVector, 'cosine')
9213
+ * .limit(10)
9214
+ * ```
9215
+ *
9216
+ * @param column - The vector column to order by
9217
+ * @param vector - The query vector to compare against
9218
+ * @param metric - Distance metric: 'l2' (euclidean), 'cosine', or 'inner_product'
9219
+ * @param options - Optional: { ascending?: boolean } - defaults to true (closest first)
9220
+ */
9221
+ orderByVector(column, vector, metric = "cosine", options) {
9222
+ const opMap = {
9223
+ l2: "vec_l2",
9224
+ cosine: "vec_cos",
9225
+ inner_product: "vec_ip"
9226
+ };
9227
+ this.orderBys.push({
9228
+ column,
9229
+ direction: options?.ascending === false ? "desc" : "asc",
9230
+ vectorOp: opMap[metric],
9231
+ vectorValue: vector
9232
+ });
9233
+ return this;
9234
+ }
9235
+ /**
9236
+ * Filter by vector similarity (pgvector)
9237
+ * This is a convenience method that adds a vector filter using the specified distance metric.
9238
+ * Typically used with orderByVector() for similarity search.
9239
+ *
9240
+ * @example
9241
+ * ```typescript
9242
+ * // Find the 10 most similar documents
9243
+ * const { data } = await client
9244
+ * .from('documents')
9245
+ * .select('id, title, content')
9246
+ * .vectorSearch('embedding', queryVector, 'cosine')
9247
+ * .limit(10)
9248
+ *
9249
+ * // Combine with other filters
9250
+ * const { data } = await client
9251
+ * .from('documents')
9252
+ * .select('id, title, content')
9253
+ * .eq('status', 'published')
9254
+ * .vectorSearch('embedding', queryVector)
9255
+ * .limit(10)
9256
+ * ```
9257
+ *
9258
+ * @param column - The vector column to search
9259
+ * @param vector - The query vector
9260
+ * @param metric - Distance metric: 'l2' (euclidean), 'cosine', or 'inner_product'
9261
+ */
9262
+ vectorSearch(column, vector, metric = "cosine") {
9263
+ const opMap = {
9264
+ l2: "vec_l2",
9265
+ cosine: "vec_cos",
9266
+ inner_product: "vec_ip"
9267
+ };
9268
+ this.filters.push({
9269
+ column,
9270
+ operator: opMap[metric],
9271
+ value: vector
9272
+ });
9273
+ this.orderByVector(column, vector, metric, { ascending: true });
9274
+ return this;
9275
+ }
7797
9276
  /**
7798
9277
  * Limit number of rows returned
7799
9278
  */
@@ -7898,6 +9377,7 @@ var QueryBuilder = class {
7898
9377
  */
7899
9378
  count(column = "*") {
7900
9379
  this.selectQuery = `count(${column})`;
9380
+ this.isCountAggregation = true;
7901
9381
  return this;
7902
9382
  }
7903
9383
  /**
@@ -8118,6 +9598,9 @@ var QueryBuilder = class {
8118
9598
  statusText: "No Content"
8119
9599
  };
8120
9600
  }
9601
+ if (this.shouldUsePostQuery()) {
9602
+ return this.executePostQuery();
9603
+ }
8121
9604
  const queryString = this.buildQueryString();
8122
9605
  const path = `${this.buildTablePath()}${queryString}`;
8123
9606
  if (this.countType) {
@@ -8179,7 +9662,30 @@ var QueryBuilder = class {
8179
9662
  statusText: "OK"
8180
9663
  };
8181
9664
  }
8182
- const data = await this.fetch.get(path);
9665
+ const data = await this.fetch.get(path);
9666
+ if (this.isCountAggregation && !this.groupByColumns) {
9667
+ if (Array.isArray(data) && data.length === 1) {
9668
+ const countData = data[0];
9669
+ if (countData && typeof countData.count === "number") {
9670
+ return {
9671
+ data,
9672
+ error: null,
9673
+ count: countData.count,
9674
+ status: 200,
9675
+ statusText: "OK"
9676
+ };
9677
+ }
9678
+ }
9679
+ if (Array.isArray(data) && data.length === 0) {
9680
+ return {
9681
+ data,
9682
+ error: null,
9683
+ count: 0,
9684
+ status: 200,
9685
+ statusText: "OK"
9686
+ };
9687
+ }
9688
+ }
8183
9689
  if (this.singleRow) {
8184
9690
  if (Array.isArray(data) && data.length === 0) {
8185
9691
  return {
@@ -8300,32 +9806,43 @@ var QueryBuilder = class {
8300
9806
  `${filter.operator}.${this.formatValue(filter.value)}`
8301
9807
  );
8302
9808
  }
9809
+ const orExpressions = [];
8303
9810
  for (const orFilter of this.orFilters) {
8304
- params.append("or", `(${orFilter})`);
8305
- }
8306
- for (const andFilter of this.andFilters) {
8307
- params.append("and", `(${andFilter})`);
9811
+ orExpressions.push(`or(${orFilter})`);
8308
9812
  }
8309
9813
  for (const bf of this.betweenFilters) {
8310
9814
  const minFormatted = this.formatValue(bf.min);
8311
9815
  const maxFormatted = this.formatValue(bf.max);
8312
9816
  if (bf.negated) {
8313
- params.append(
8314
- "or",
8315
- `(${bf.column}.lt.${minFormatted},${bf.column}.gt.${maxFormatted})`
9817
+ orExpressions.push(
9818
+ `or(${bf.column}.lt.${minFormatted},${bf.column}.gt.${maxFormatted})`
8316
9819
  );
8317
9820
  } else {
8318
9821
  params.append(bf.column, `gte.${minFormatted}`);
8319
9822
  params.append(bf.column, `lte.${maxFormatted}`);
8320
9823
  }
8321
9824
  }
9825
+ if (orExpressions.length === 1) {
9826
+ const expr = orExpressions[0];
9827
+ const inner = expr.replace(/^or\(/, "(");
9828
+ params.append("or", inner);
9829
+ } else if (orExpressions.length > 1) {
9830
+ params.append("and", `(${orExpressions.join(",")})`);
9831
+ }
9832
+ for (const andFilter of this.andFilters) {
9833
+ params.append("and", `(${andFilter})`);
9834
+ }
8322
9835
  if (this.groupByColumns && this.groupByColumns.length > 0) {
8323
9836
  params.append("group_by", this.groupByColumns.join(","));
8324
9837
  }
8325
9838
  if (this.orderBys.length > 0) {
8326
- const orderStr = this.orderBys.map(
8327
- (o) => `${o.column}.${o.direction}${o.nulls ? `.nulls${o.nulls}` : ""}`
8328
- ).join(",");
9839
+ const orderStr = this.orderBys.map((o) => {
9840
+ if (o.vectorOp && o.vectorValue) {
9841
+ const vectorStr = `[${o.vectorValue.join(",")}]`;
9842
+ return `${o.column}.${o.vectorOp}.${vectorStr}.${o.direction}`;
9843
+ }
9844
+ return `${o.column}.${o.direction}${o.nulls ? `.nulls${o.nulls}` : ""}`;
9845
+ }).join(",");
8329
9846
  params.append("order", orderStr);
8330
9847
  }
8331
9848
  if (this.limitValue !== void 0) {
@@ -8401,6 +9918,160 @@ var QueryBuilder = class {
8401
9918
  }
8402
9919
  return null;
8403
9920
  }
9921
+ /**
9922
+ * Check if the query should use POST-based query endpoint
9923
+ * Returns true if the query string would exceed the URL length threshold
9924
+ */
9925
+ shouldUsePostQuery() {
9926
+ const queryString = this.buildQueryString();
9927
+ return queryString.length > URL_LENGTH_THRESHOLD;
9928
+ }
9929
+ /**
9930
+ * Execute a SELECT query using the POST /query endpoint
9931
+ * Used when query parameters would exceed URL length limits
9932
+ */
9933
+ async executePostQuery() {
9934
+ const body = this.buildQueryBody();
9935
+ const path = `${this.buildTablePath()}/query`;
9936
+ if (this.countType) {
9937
+ const response = await this.fetch.postWithHeaders(path, body);
9938
+ const serverCount = this.parseContentRangeCount(response.headers);
9939
+ const data2 = response.data;
9940
+ if (this.headOnly) {
9941
+ return {
9942
+ data: null,
9943
+ error: null,
9944
+ count: serverCount,
9945
+ status: response.status,
9946
+ statusText: "OK"
9947
+ };
9948
+ }
9949
+ if (this.singleRow) {
9950
+ if (Array.isArray(data2) && data2.length === 0) {
9951
+ return {
9952
+ data: null,
9953
+ error: { message: "No rows found", code: "PGRST116" },
9954
+ count: serverCount ?? 0,
9955
+ status: 404,
9956
+ statusText: "Not Found"
9957
+ };
9958
+ }
9959
+ const singleData = Array.isArray(data2) ? data2[0] : data2;
9960
+ return {
9961
+ data: singleData,
9962
+ error: null,
9963
+ count: serverCount ?? 1,
9964
+ status: 200,
9965
+ statusText: "OK"
9966
+ };
9967
+ }
9968
+ if (this.maybeSingleRow) {
9969
+ if (Array.isArray(data2) && data2.length === 0) {
9970
+ return {
9971
+ data: null,
9972
+ error: null,
9973
+ count: serverCount ?? 0,
9974
+ status: 200,
9975
+ statusText: "OK"
9976
+ };
9977
+ }
9978
+ const singleData = Array.isArray(data2) ? data2[0] : data2;
9979
+ return {
9980
+ data: singleData,
9981
+ error: null,
9982
+ count: serverCount ?? 1,
9983
+ status: 200,
9984
+ statusText: "OK"
9985
+ };
9986
+ }
9987
+ return {
9988
+ data: data2,
9989
+ error: null,
9990
+ count: serverCount ?? (Array.isArray(data2) ? data2.length : null),
9991
+ status: 200,
9992
+ statusText: "OK"
9993
+ };
9994
+ }
9995
+ const data = await this.fetch.post(path, body);
9996
+ if (this.singleRow) {
9997
+ if (Array.isArray(data) && data.length === 0) {
9998
+ return {
9999
+ data: null,
10000
+ error: { message: "No rows found", code: "PGRST116" },
10001
+ count: 0,
10002
+ status: 404,
10003
+ statusText: "Not Found"
10004
+ };
10005
+ }
10006
+ const singleData = Array.isArray(data) ? data[0] : data;
10007
+ return {
10008
+ data: singleData,
10009
+ error: null,
10010
+ count: 1,
10011
+ status: 200,
10012
+ statusText: "OK"
10013
+ };
10014
+ }
10015
+ if (this.maybeSingleRow) {
10016
+ if (Array.isArray(data) && data.length === 0) {
10017
+ return {
10018
+ data: null,
10019
+ error: null,
10020
+ count: 0,
10021
+ status: 200,
10022
+ statusText: "OK"
10023
+ };
10024
+ }
10025
+ const singleData = Array.isArray(data) ? data[0] : data;
10026
+ return {
10027
+ data: singleData,
10028
+ error: null,
10029
+ count: 1,
10030
+ status: 200,
10031
+ statusText: "OK"
10032
+ };
10033
+ }
10034
+ return {
10035
+ data,
10036
+ error: null,
10037
+ count: Array.isArray(data) ? data.length : null,
10038
+ status: 200,
10039
+ statusText: "OK"
10040
+ };
10041
+ }
10042
+ /**
10043
+ * Build the request body for POST-based queries
10044
+ * Used when query parameters would exceed URL length limits
10045
+ */
10046
+ buildQueryBody() {
10047
+ return {
10048
+ select: this.selectQuery !== "*" ? this.selectQuery : void 0,
10049
+ filters: this.filters.map((f) => ({
10050
+ column: f.column,
10051
+ operator: f.operator,
10052
+ value: f.value
10053
+ })),
10054
+ orFilters: this.orFilters,
10055
+ andFilters: this.andFilters,
10056
+ betweenFilters: this.betweenFilters.map((bf) => ({
10057
+ column: bf.column,
10058
+ min: bf.min,
10059
+ max: bf.max,
10060
+ negated: bf.negated
10061
+ })),
10062
+ order: this.orderBys.map((o) => ({
10063
+ column: o.column,
10064
+ direction: o.direction,
10065
+ nulls: o.nulls,
10066
+ vectorOp: o.vectorOp,
10067
+ vectorValue: o.vectorValue
10068
+ })),
10069
+ limit: this.limitValue,
10070
+ offset: this.offsetValue,
10071
+ count: this.countType,
10072
+ groupBy: this.groupByColumns
10073
+ };
10074
+ }
8404
10075
  };
8405
10076
 
8406
10077
  // src/schema-query-builder.ts
@@ -8473,6 +10144,23 @@ var FluxbaseClient = class {
8473
10144
  const wsProtocol = fluxbaseUrl.startsWith("https") ? "wss" : "ws";
8474
10145
  const wsBaseUrl = fluxbaseUrl.replace(/^https?:/, wsProtocol + ":");
8475
10146
  this.ai = new FluxbaseAI(this.fetch, wsBaseUrl);
10147
+ this.vector = new FluxbaseVector(this.fetch);
10148
+ const rpcInstance = new FluxbaseRPC(this.fetch);
10149
+ const rpcCallable = async (fn, params) => {
10150
+ const result = await rpcInstance.invoke(fn, params);
10151
+ return {
10152
+ data: result.data?.result ?? null,
10153
+ error: result.error
10154
+ };
10155
+ };
10156
+ Object.assign(rpcCallable, {
10157
+ invoke: rpcInstance.invoke.bind(rpcInstance),
10158
+ list: rpcInstance.list.bind(rpcInstance),
10159
+ getStatus: rpcInstance.getStatus.bind(rpcInstance),
10160
+ getLogs: rpcInstance.getLogs.bind(rpcInstance),
10161
+ waitForCompletion: rpcInstance.waitForCompletion.bind(rpcInstance)
10162
+ });
10163
+ this.rpc = rpcCallable;
8476
10164
  this.setupAuthSync();
8477
10165
  }
8478
10166
  /**
@@ -8512,12 +10200,12 @@ var FluxbaseClient = class {
8512
10200
  *
8513
10201
  * @example
8514
10202
  * ```typescript
8515
- * // Query the jobs.execution_logs table
10203
+ * // Query the logging.entries table
8516
10204
  * const { data } = await client
8517
- * .schema('jobs')
8518
- * .from('execution_logs')
10205
+ * .schema('logging')
10206
+ * .from('entries')
8519
10207
  * .select('*')
8520
- * .eq('job_id', jobId)
10208
+ * .eq('execution_id', executionId)
8521
10209
  * .execute()
8522
10210
  *
8523
10211
  * // Insert into a custom schema table
@@ -8546,7 +10234,10 @@ var FluxbaseClient = class {
8546
10234
  this.realtime.setTokenRefreshCallback(async () => {
8547
10235
  const result = await this.auth.refreshSession();
8548
10236
  if (result.error || !result.data?.session) {
8549
- console.error("[Fluxbase] Failed to refresh token for realtime:", result.error);
10237
+ console.error(
10238
+ "[Fluxbase] Failed to refresh token for realtime:",
10239
+ result.error
10240
+ );
8550
10241
  return null;
8551
10242
  }
8552
10243
  return result.data.session.access_token;
@@ -8660,163 +10351,6 @@ function createClient(fluxbaseUrl, fluxbaseKey, options) {
8660
10351
  return new FluxbaseClient(url, key, options);
8661
10352
  }
8662
10353
 
8663
- // src/rpc.ts
8664
- var FluxbaseRPC = class {
8665
- constructor(fetch2) {
8666
- this.fetch = fetch2;
8667
- }
8668
- /**
8669
- * List available RPC procedures (public, enabled)
8670
- *
8671
- * @param namespace - Optional namespace filter
8672
- * @returns Promise resolving to { data, error } tuple with array of procedure summaries
8673
- */
8674
- async list(namespace) {
8675
- try {
8676
- const params = namespace ? `?namespace=${encodeURIComponent(namespace)}` : "";
8677
- const response = await this.fetch.get(
8678
- `/api/v1/rpc/procedures${params}`
8679
- );
8680
- return { data: response.procedures || [], error: null };
8681
- } catch (error) {
8682
- return { data: null, error };
8683
- }
8684
- }
8685
- /**
8686
- * Invoke an RPC procedure
8687
- *
8688
- * @param name - Procedure name
8689
- * @param params - Optional parameters to pass to the procedure
8690
- * @param options - Optional invocation options
8691
- * @returns Promise resolving to { data, error } tuple with invocation response
8692
- *
8693
- * @example
8694
- * ```typescript
8695
- * // Synchronous invocation
8696
- * const { data, error } = await fluxbase.rpc.invoke('get-user-orders', {
8697
- * user_id: '123',
8698
- * limit: 10
8699
- * });
8700
- * console.log(data.result); // Query results
8701
- *
8702
- * // Asynchronous invocation
8703
- * const { data: asyncData } = await fluxbase.rpc.invoke('generate-report', {
8704
- * year: 2024
8705
- * }, { async: true });
8706
- * console.log(asyncData.execution_id); // Use to poll status
8707
- * ```
8708
- */
8709
- async invoke(name, params, options) {
8710
- try {
8711
- const namespace = options?.namespace || "default";
8712
- const response = await this.fetch.post(
8713
- `/api/v1/rpc/${encodeURIComponent(namespace)}/${encodeURIComponent(name)}`,
8714
- {
8715
- params,
8716
- async: options?.async
8717
- }
8718
- );
8719
- return { data: response, error: null };
8720
- } catch (error) {
8721
- return { data: null, error };
8722
- }
8723
- }
8724
- /**
8725
- * Get execution status (for async invocations or checking history)
8726
- *
8727
- * @param executionId - The execution ID returned from async invoke
8728
- * @returns Promise resolving to { data, error } tuple with execution details
8729
- *
8730
- * @example
8731
- * ```typescript
8732
- * const { data, error } = await fluxbase.rpc.getStatus('execution-uuid');
8733
- * if (data.status === 'completed') {
8734
- * console.log('Result:', data.result);
8735
- * } else if (data.status === 'running') {
8736
- * console.log('Still running...');
8737
- * }
8738
- * ```
8739
- */
8740
- async getStatus(executionId) {
8741
- try {
8742
- const data = await this.fetch.get(
8743
- `/api/v1/rpc/executions/${encodeURIComponent(executionId)}`
8744
- );
8745
- return { data, error: null };
8746
- } catch (error) {
8747
- return { data: null, error };
8748
- }
8749
- }
8750
- /**
8751
- * Get execution logs (for debugging and monitoring)
8752
- *
8753
- * @param executionId - The execution ID
8754
- * @param afterLine - Optional line number to get logs after (for polling)
8755
- * @returns Promise resolving to { data, error } tuple with execution logs
8756
- *
8757
- * @example
8758
- * ```typescript
8759
- * const { data: logs } = await fluxbase.rpc.getLogs('execution-uuid');
8760
- * for (const log of logs) {
8761
- * console.log(`[${log.level}] ${log.message}`);
8762
- * }
8763
- * ```
8764
- */
8765
- async getLogs(executionId, afterLine) {
8766
- try {
8767
- const params = afterLine !== void 0 ? `?after=${afterLine}` : "";
8768
- const response = await this.fetch.get(
8769
- `/api/v1/rpc/executions/${encodeURIComponent(executionId)}/logs${params}`
8770
- );
8771
- return { data: response.logs || [], error: null };
8772
- } catch (error) {
8773
- return { data: null, error };
8774
- }
8775
- }
8776
- /**
8777
- * Poll for execution completion with exponential backoff
8778
- *
8779
- * @param executionId - The execution ID to poll
8780
- * @param options - Polling options
8781
- * @returns Promise resolving to final execution state
8782
- *
8783
- * @example
8784
- * ```typescript
8785
- * const { data: result } = await fluxbase.rpc.invoke('long-task', {}, { async: true });
8786
- * const { data: final } = await fluxbase.rpc.waitForCompletion(result.execution_id, {
8787
- * maxWaitMs: 60000, // Wait up to 1 minute
8788
- * onProgress: (exec) => console.log(`Status: ${exec.status}`)
8789
- * });
8790
- * console.log('Final result:', final.result);
8791
- * ```
8792
- */
8793
- async waitForCompletion(executionId, options) {
8794
- const maxWait = options?.maxWaitMs || 3e4;
8795
- const initialInterval = options?.initialIntervalMs || 500;
8796
- const maxInterval = options?.maxIntervalMs || 5e3;
8797
- const startTime = Date.now();
8798
- let interval = initialInterval;
8799
- while (Date.now() - startTime < maxWait) {
8800
- const { data: execution, error } = await this.getStatus(executionId);
8801
- if (error) {
8802
- return { data: null, error };
8803
- }
8804
- if (!execution) {
8805
- return { data: null, error: new Error("Execution not found") };
8806
- }
8807
- if (options?.onProgress) {
8808
- options.onProgress(execution);
8809
- }
8810
- if (execution.status === "completed" || execution.status === "failed" || execution.status === "cancelled" || execution.status === "timeout") {
8811
- return { data: execution, error: null };
8812
- }
8813
- await new Promise((resolve) => setTimeout(resolve, interval));
8814
- interval = Math.min(interval * 1.5, maxInterval);
8815
- }
8816
- return { data: null, error: new Error("Timeout waiting for execution to complete") };
8817
- }
8818
- };
8819
-
8820
- export { APIKeysManager, AppSettingsManager, AuthSettingsManager, DDLManager, EmailTemplateManager, FluxbaseAI, FluxbaseAIChat, FluxbaseAdmin, FluxbaseAdminAI, FluxbaseAdminFunctions, FluxbaseAdminJobs, FluxbaseAdminMigrations, FluxbaseAdminRPC, FluxbaseAuth, FluxbaseClient, FluxbaseFetch, FluxbaseFunctions, FluxbaseJobs, FluxbaseManagement, FluxbaseOAuth, FluxbaseRPC, FluxbaseRealtime, FluxbaseSettings, FluxbaseStorage, ImpersonationManager, InvitationsManager, OAuthProviderManager, QueryBuilder, RealtimeChannel, SchemaQueryBuilder, SettingsClient, StorageBucket, SystemSettingsManager, WebhooksManager, bundleCode, createClient, denoExternalPlugin, loadImportMap };
10354
+ export { APIKeysManager, AppSettingsManager, AuthSettingsManager, DDLManager, EmailTemplateManager, ExecutionLogsChannel, FluxbaseAI, FluxbaseAIChat, FluxbaseAdmin, FluxbaseAdminAI, FluxbaseAdminFunctions, FluxbaseAdminJobs, FluxbaseAdminMigrations, FluxbaseAdminRPC, FluxbaseAdminStorage, FluxbaseAuth, FluxbaseClient, FluxbaseFetch, FluxbaseFunctions, FluxbaseJobs, FluxbaseManagement, FluxbaseOAuth, FluxbaseRPC, FluxbaseRealtime, FluxbaseSettings, FluxbaseStorage, FluxbaseVector, ImpersonationManager, InvitationsManager, OAuthProviderManager, QueryBuilder, RealtimeChannel, SchemaQueryBuilder, SettingsClient, StorageBucket, SystemSettingsManager, WebhooksManager, bundleCode, createClient, denoExternalPlugin, loadImportMap };
8821
10355
  //# sourceMappingURL=index.js.map
8822
10356
  //# sourceMappingURL=index.js.map