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