nuabase 1.2.0 → 2.0.0
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/README.md +100 -58
- package/dist/cjs/index.cjs +298 -405
- package/dist/esm/index.mjs +295 -405
- package/dist/types/__tests__/helpers/client-factory.d.ts +17 -0
- package/dist/types/__tests__/helpers/index.d.ts +1 -0
- package/dist/types/backend/direct.d.ts +17 -0
- package/dist/types/backend/gateway.d.ts +23 -0
- package/dist/types/backend/types.d.ts +61 -0
- package/dist/types/index.d.cts +5 -2
- package/dist/types/index.d.mts +5 -2
- package/dist/types/lib/auth.d.ts +12 -0
- package/dist/types/nua.d.ts +34 -3
- package/package.json +19 -17
- package/dist/types/__tests__/utils/sse.d.ts +0 -12
- package/dist/types/lib/api-client.d.ts +0 -19
- package/dist/types/nua/cast_array/factory.d.ts +0 -4
- package/dist/types/nua/cast_array/request-validation.d.ts +0 -1
- package/dist/types/nua/cast_array/response-schema.d.ts +0 -19
- package/dist/types/nua/cast_array/types.d.ts +0 -33
- package/dist/types/nua/cast_value/factory.d.ts +0 -4
- package/dist/types/nua/cast_value/response-schema.d.ts +0 -15
- package/dist/types/nua/cast_value/types.d.ts +0 -18
- package/dist/types/nua/common/normalized-usage.d.ts +0 -7
- package/dist/types/nua/common/queued-response.d.ts +0 -6
- package/dist/types/nua/core.d.ts +0 -12
- package/dist/types/nua/get-request/index.d.ts +0 -6
- package/dist/types/nua/get-request/types.d.ts +0 -34
- package/dist/types/nua/index.d.ts +0 -5
package/dist/esm/index.mjs
CHANGED
|
@@ -1,4 +1,78 @@
|
|
|
1
|
+
import { NuaLlmClient, wrapArraySchema } from 'nua-llm-core';
|
|
1
2
|
import { z } from 'zod';
|
|
3
|
+
export { z } from 'zod';
|
|
4
|
+
|
|
5
|
+
const normalizedUsageZero = {
|
|
6
|
+
promptTokens: 0,
|
|
7
|
+
completionTokens: 0,
|
|
8
|
+
totalTokens: 0
|
|
9
|
+
};
|
|
10
|
+
class DirectBackend {
|
|
11
|
+
constructor(config) {
|
|
12
|
+
if (!config.model) {
|
|
13
|
+
throw new Error("model is required for direct mode");
|
|
14
|
+
}
|
|
15
|
+
this.model = config.model;
|
|
16
|
+
this.client = new NuaLlmClient({ providers: config.providers });
|
|
17
|
+
}
|
|
18
|
+
async castValue(params) {
|
|
19
|
+
const startTime = Date.now();
|
|
20
|
+
const result = await this.client.castValue({
|
|
21
|
+
model: this.model,
|
|
22
|
+
input: { prompt: params.prompt, data: params.data },
|
|
23
|
+
output: { name: params.outputName, effectiveSchema: params.outputSchema }
|
|
24
|
+
});
|
|
25
|
+
const latencyMs = Date.now() - startTime;
|
|
26
|
+
if (!result.success) {
|
|
27
|
+
return {
|
|
28
|
+
success: false,
|
|
29
|
+
error: result.error || "Unknown error",
|
|
30
|
+
source: "direct",
|
|
31
|
+
latencyMs
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
success: true,
|
|
36
|
+
data: result.data,
|
|
37
|
+
usage: result.usage || normalizedUsageZero,
|
|
38
|
+
model: this.model,
|
|
39
|
+
latencyMs,
|
|
40
|
+
source: "direct",
|
|
41
|
+
meta: {}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
async castArray(params) {
|
|
45
|
+
const startTime = Date.now();
|
|
46
|
+
const wrappedSchema = wrapArraySchema(params.outputSchema, {
|
|
47
|
+
primaryKey: params.primaryKey,
|
|
48
|
+
outputName: params.outputName
|
|
49
|
+
});
|
|
50
|
+
const result = await this.client.castArray({
|
|
51
|
+
model: this.model,
|
|
52
|
+
data: params.data,
|
|
53
|
+
input: { prompt: params.prompt, primaryKey: params.primaryKey },
|
|
54
|
+
output: { name: params.outputName, effectiveSchema: wrappedSchema }
|
|
55
|
+
});
|
|
56
|
+
const latencyMs = Date.now() - startTime;
|
|
57
|
+
if (!result.success) {
|
|
58
|
+
return {
|
|
59
|
+
success: false,
|
|
60
|
+
error: result.error || "Unknown error",
|
|
61
|
+
source: "direct",
|
|
62
|
+
latencyMs
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
success: true,
|
|
67
|
+
data: result.data,
|
|
68
|
+
usage: result.usage || normalizedUsageZero,
|
|
69
|
+
model: this.model,
|
|
70
|
+
latencyMs,
|
|
71
|
+
source: "direct",
|
|
72
|
+
meta: {}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
2
76
|
|
|
3
77
|
function resolveConfigValue(config, key, envVar) {
|
|
4
78
|
if (config[key]) return config[key];
|
|
@@ -10,6 +84,50 @@ function resolveConfigValue(config, key, envVar) {
|
|
|
10
84
|
return void 0;
|
|
11
85
|
}
|
|
12
86
|
|
|
87
|
+
const isBrowserEnvironment = () => typeof window !== "undefined" && !!window.document;
|
|
88
|
+
class AuthTokenManager {
|
|
89
|
+
constructor(config) {
|
|
90
|
+
this.apiKey = resolveConfigValue(config, "apiKey", "NUABASE_API_KEY") || void 0;
|
|
91
|
+
this.fetchToken = config.fetchToken;
|
|
92
|
+
if (this.apiKey && this.fetchToken) {
|
|
93
|
+
throw new Error("Provide either an apiKey or a fetchToken function, but not both.");
|
|
94
|
+
}
|
|
95
|
+
if (!this.apiKey && !this.fetchToken) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
"Authentication is required. Provide config.apiKey/NUABASE_API_KEY or a fetchToken function."
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
if (this.apiKey && isBrowserEnvironment()) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
"found config.apiKey. It is a private secret that must only be used in server environments."
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async getToken() {
|
|
107
|
+
if (this.apiKey) {
|
|
108
|
+
return this.apiKey;
|
|
109
|
+
}
|
|
110
|
+
if (!this.fetchToken) {
|
|
111
|
+
throw new Error("Unable to resolve API token because no fetchToken function is configured.");
|
|
112
|
+
}
|
|
113
|
+
if (this.apiToken) {
|
|
114
|
+
return this.apiToken;
|
|
115
|
+
}
|
|
116
|
+
if (!this.pendingTokenPromise) {
|
|
117
|
+
this.pendingTokenPromise = this.fetchToken().then((token) => {
|
|
118
|
+
if (!token) {
|
|
119
|
+
throw new Error("fetchToken must resolve a non-empty API token.");
|
|
120
|
+
}
|
|
121
|
+
this.apiToken = token;
|
|
122
|
+
return token;
|
|
123
|
+
}).finally(() => {
|
|
124
|
+
this.pendingTokenPromise = void 0;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return this.pendingTokenPromise;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
13
131
|
async function parseErrorResponse(response, urlPath) {
|
|
14
132
|
const fallbackMessage = `Request to ${urlPath} failed with status ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`;
|
|
15
133
|
let responseBody = "";
|
|
@@ -38,9 +156,17 @@ function safeParseJson(payload) {
|
|
|
38
156
|
}
|
|
39
157
|
}
|
|
40
158
|
function extractErrorMessage(body) {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
159
|
+
const parts = [];
|
|
160
|
+
const error = body["error"];
|
|
161
|
+
if (typeof error === "string" && error.trim()) {
|
|
162
|
+
parts.push(error.trim());
|
|
163
|
+
}
|
|
164
|
+
const message = body["message"];
|
|
165
|
+
if (typeof message === "string" && message.trim()) {
|
|
166
|
+
parts.push(message.trim());
|
|
167
|
+
}
|
|
168
|
+
if (parts.length === 0) return void 0;
|
|
169
|
+
return parts.join(". ");
|
|
44
170
|
}
|
|
45
171
|
|
|
46
172
|
function isErrorWithMessage(error) {
|
|
@@ -53,446 +179,210 @@ function getErrorMessageFromException(error) {
|
|
|
53
179
|
return String(error);
|
|
54
180
|
}
|
|
55
181
|
|
|
56
|
-
|
|
57
|
-
|
|
182
|
+
function isErrorResponse(response) {
|
|
183
|
+
return Boolean(response && typeof response === "object" && "error" in response);
|
|
184
|
+
}
|
|
185
|
+
class GatewayBackend {
|
|
58
186
|
constructor(config) {
|
|
59
|
-
this.
|
|
60
|
-
this.
|
|
61
|
-
if (this.apiKey && this.fetchToken) {
|
|
62
|
-
throw new Error("Provide either an apiKey or a fetchToken function, but not both.");
|
|
63
|
-
}
|
|
64
|
-
if (!this.apiKey && !this.fetchToken) {
|
|
65
|
-
throw new Error(
|
|
66
|
-
"Authentication is required. Provide config.apiKey/NUABASE_API_KEY or a fetchToken function."
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
if (this.apiKey && isBrowserEnvironment()) {
|
|
70
|
-
throw new Error(
|
|
71
|
-
"found config.apiKey. It is a private secret that must only be used in server environments."
|
|
72
|
-
);
|
|
73
|
-
}
|
|
187
|
+
this.baseUrl = resolveConfigValue(config, "baseUrl", "NUABASE_API_URL") || "https://api.nuabase.com";
|
|
188
|
+
this.authManager = new AuthTokenManager(config);
|
|
74
189
|
}
|
|
75
|
-
async
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
190
|
+
async castValue(params) {
|
|
191
|
+
const startTime = Date.now();
|
|
192
|
+
const response = await this.post("cast/value/now", {
|
|
193
|
+
input: { prompt: params.prompt, data: params.data },
|
|
194
|
+
output: { name: params.outputName, schema: params.outputSchema }
|
|
195
|
+
});
|
|
196
|
+
const latencyMs = Date.now() - startTime;
|
|
197
|
+
if (isErrorResponse(response)) {
|
|
198
|
+
return { success: false, error: response.error, source: "gateway", latencyMs };
|
|
81
199
|
}
|
|
82
|
-
|
|
83
|
-
|
|
200
|
+
const apiResponse = response;
|
|
201
|
+
const usage = {
|
|
202
|
+
promptTokens: apiResponse.llmUsage.promptTokens + apiResponse.cacheUsage.promptTokens,
|
|
203
|
+
completionTokens: apiResponse.llmUsage.completionTokens + apiResponse.cacheUsage.completionTokens,
|
|
204
|
+
totalTokens: apiResponse.llmUsage.totalTokens + apiResponse.cacheUsage.totalTokens
|
|
205
|
+
};
|
|
206
|
+
return {
|
|
207
|
+
success: true,
|
|
208
|
+
data: apiResponse.data,
|
|
209
|
+
usage,
|
|
210
|
+
model: apiResponse.model ?? "unknown",
|
|
211
|
+
latencyMs,
|
|
212
|
+
source: "gateway",
|
|
213
|
+
meta: {
|
|
214
|
+
requestId: apiResponse.llmRequestId,
|
|
215
|
+
cached: apiResponse.isCacheHit,
|
|
216
|
+
llmUsage: apiResponse.llmUsage,
|
|
217
|
+
cacheUsage: apiResponse.cacheUsage
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
async castArray(params) {
|
|
222
|
+
const startTime = Date.now();
|
|
223
|
+
const response = await this.post("cast/array/now", {
|
|
224
|
+
input: { prompt: params.prompt, data: params.data, primaryKey: params.primaryKey },
|
|
225
|
+
output: { name: params.outputName, schema: params.outputSchema }
|
|
226
|
+
});
|
|
227
|
+
const latencyMs = Date.now() - startTime;
|
|
228
|
+
if (isErrorResponse(response)) {
|
|
229
|
+
return { success: false, error: response.error, source: "gateway", latencyMs };
|
|
84
230
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
231
|
+
const apiResponse = response;
|
|
232
|
+
const usage = {
|
|
233
|
+
promptTokens: apiResponse.llmUsage.promptTokens + apiResponse.cacheUsage.promptTokens,
|
|
234
|
+
completionTokens: apiResponse.llmUsage.completionTokens + apiResponse.cacheUsage.completionTokens,
|
|
235
|
+
totalTokens: apiResponse.llmUsage.totalTokens + apiResponse.cacheUsage.totalTokens
|
|
236
|
+
};
|
|
237
|
+
return {
|
|
238
|
+
success: true,
|
|
239
|
+
data: apiResponse.data,
|
|
240
|
+
usage,
|
|
241
|
+
model: apiResponse.model ?? "unknown",
|
|
242
|
+
latencyMs,
|
|
243
|
+
source: "gateway",
|
|
244
|
+
meta: {
|
|
245
|
+
requestId: apiResponse.llmRequestId,
|
|
246
|
+
cached: apiResponse.cacheHits === apiResponse.data.length,
|
|
247
|
+
llmUsage: apiResponse.llmUsage,
|
|
248
|
+
cacheUsage: apiResponse.cacheUsage
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
async queueCastValue(params) {
|
|
253
|
+
const response = await this.post("cast/value", {
|
|
254
|
+
input: { prompt: params.prompt, data: params.data },
|
|
255
|
+
output: { name: params.outputName, schema: params.outputSchema }
|
|
256
|
+
});
|
|
257
|
+
if (isErrorResponse(response)) {
|
|
258
|
+
return { success: false, error: response.error, source: "gateway" };
|
|
95
259
|
}
|
|
96
|
-
|
|
260
|
+
const apiResponse = response;
|
|
261
|
+
return { success: true, jobId: apiResponse.jobId, source: "gateway" };
|
|
97
262
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
263
|
+
async queueCastArray(params) {
|
|
264
|
+
const response = await this.post("cast/array", {
|
|
265
|
+
input: { prompt: params.prompt, data: params.data, primaryKey: params.primaryKey },
|
|
266
|
+
output: { name: params.outputName, schema: params.outputSchema }
|
|
267
|
+
});
|
|
268
|
+
if (isErrorResponse(response)) {
|
|
269
|
+
return { success: false, error: response.error, source: "gateway" };
|
|
270
|
+
}
|
|
271
|
+
const apiResponse = response;
|
|
272
|
+
return { success: true, jobId: apiResponse.jobId, source: "gateway" };
|
|
103
273
|
}
|
|
104
|
-
async
|
|
105
|
-
const url = `${this.baseUrl}/${
|
|
274
|
+
async post(path, body) {
|
|
275
|
+
const url = `${this.baseUrl}/${path}`;
|
|
106
276
|
let response;
|
|
107
|
-
const hasBody = method !== "GET" && params !== void 0;
|
|
108
277
|
const headers = {
|
|
109
|
-
Authorization: `Bearer ${await this.
|
|
278
|
+
Authorization: `Bearer ${await this.authManager.getToken()}`,
|
|
279
|
+
"Content-Type": "application/json"
|
|
110
280
|
};
|
|
111
|
-
if (hasBody) {
|
|
112
|
-
headers["Content-Type"] = "application/json";
|
|
113
|
-
}
|
|
114
281
|
try {
|
|
115
282
|
response = await fetch(url, {
|
|
116
|
-
method,
|
|
283
|
+
method: "POST",
|
|
117
284
|
headers,
|
|
118
|
-
body:
|
|
285
|
+
body: JSON.stringify(body)
|
|
119
286
|
});
|
|
120
287
|
} catch (e) {
|
|
121
288
|
return { error: `Error calling Nuabase API: ${getErrorMessageFromException(e)}` };
|
|
122
289
|
}
|
|
123
|
-
let result;
|
|
124
290
|
if (!response.ok) {
|
|
125
|
-
return await parseErrorResponse(response,
|
|
291
|
+
return await parseErrorResponse(response, path);
|
|
126
292
|
}
|
|
127
293
|
try {
|
|
128
|
-
|
|
294
|
+
return await response.json();
|
|
129
295
|
} catch {
|
|
130
296
|
return { error: "Invalid response received from Nuabase API call, unable to parse" };
|
|
131
297
|
}
|
|
132
|
-
return result;
|
|
133
298
|
}
|
|
134
299
|
}
|
|
135
300
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}).strict();
|
|
140
|
-
|
|
141
|
-
const validateArrayRequestParams = (data, primaryKeyName) => {
|
|
142
|
-
if (!Array.isArray(data)) {
|
|
143
|
-
throw new Error("`data` must be an array of objects.");
|
|
144
|
-
}
|
|
145
|
-
if (!primaryKeyName || typeof primaryKeyName !== "string") {
|
|
146
|
-
throw new Error("`primaryKeyName` must be a non-empty string.");
|
|
301
|
+
class Nua {
|
|
302
|
+
constructor(backend) {
|
|
303
|
+
this.backend = backend;
|
|
147
304
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
throw new Error(`data[${index}] must be a non-null object.`);
|
|
151
|
-
}
|
|
152
|
-
if (!(primaryKeyName in item)) {
|
|
153
|
-
throw new Error(`data[${index}] must contain property '${primaryKeyName}'.`);
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
const zs_NormalizedUsage = z.object({
|
|
159
|
-
promptTokens: z.number(),
|
|
160
|
-
completionTokens: z.number(),
|
|
161
|
-
totalTokens: z.number()
|
|
162
|
-
}).strict();
|
|
163
|
-
|
|
164
|
-
const zs_ResponseRecord = (primaryKeyName, outputKey, outputSchema) => {
|
|
165
|
-
const primaryKeySchema = z.any();
|
|
166
|
-
const shape = {
|
|
167
|
-
[primaryKeyName]: primaryKeySchema,
|
|
168
|
-
[outputKey]: outputSchema
|
|
169
|
-
};
|
|
170
|
-
return z.object(shape).strict();
|
|
171
|
-
};
|
|
172
|
-
const createNuaApiResponseCastArray = (primaryKeyName, outputKey, outputSchema) => {
|
|
173
|
-
const recordSchema = zs_ResponseRecord(
|
|
174
|
-
primaryKeyName,
|
|
175
|
-
outputKey,
|
|
176
|
-
outputSchema
|
|
177
|
-
);
|
|
178
|
-
const dataSchema = z.array(recordSchema);
|
|
179
|
-
return z.object({
|
|
180
|
-
llmRequestId: z.string(),
|
|
181
|
-
kind: z.literal("cast/array"),
|
|
182
|
-
data: dataSchema,
|
|
183
|
-
cacheHits: z.number(),
|
|
184
|
-
rowsWithNoResults: z.array(z.string()),
|
|
185
|
-
isSuccess: z.literal(true),
|
|
186
|
-
isError: z.optional(z.literal(false)),
|
|
187
|
-
usage: zs_NormalizedUsage
|
|
188
|
-
}).strict();
|
|
189
|
-
};
|
|
190
|
-
const zs_NuaApiResponse_CastArray = createNuaApiResponseCastArray;
|
|
191
|
-
|
|
192
|
-
const zs_ArrayFnDef = z.object({
|
|
193
|
-
prompt: z.string(),
|
|
194
|
-
output: z.object({
|
|
195
|
-
name: z.string(),
|
|
196
|
-
schema: z.custom(
|
|
197
|
-
(value) => value instanceof z.ZodType,
|
|
198
|
-
"Expected a Zod schema"
|
|
199
|
-
)
|
|
200
|
-
})
|
|
201
|
-
});
|
|
202
|
-
const validateArrayFnDef = (fnDef) => {
|
|
203
|
-
const parsed = zs_ArrayFnDef.safeParse(fnDef);
|
|
204
|
-
if (!parsed.success) {
|
|
205
|
-
const message = z.prettifyError(parsed.error);
|
|
206
|
-
throw new TypeError(`Invalid array function definition: ${message}`);
|
|
305
|
+
static gateway(config) {
|
|
306
|
+
return new Nua(new GatewayBackend(config));
|
|
207
307
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
function addOriginalInputRows(inputRows, outputRows, primaryKeyName) {
|
|
212
|
-
const lookup = /* @__PURE__ */ new Map();
|
|
213
|
-
for (const row of inputRows) {
|
|
214
|
-
lookup.set(row[primaryKeyName], row);
|
|
308
|
+
static direct(config) {
|
|
309
|
+
return new Nua(new DirectBackend(config));
|
|
215
310
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
throw new Error(
|
|
221
|
-
`Unable to locate source row for primary key "${String(primaryKeyValue)}" in cast array response.`
|
|
222
|
-
);
|
|
311
|
+
// Implementation
|
|
312
|
+
async get(prompt, options) {
|
|
313
|
+
if (typeof prompt !== "string" || prompt.trim() === "") {
|
|
314
|
+
throw new TypeError("Prompt must be a non-empty string");
|
|
223
315
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
data,
|
|
234
|
-
primaryKey: primaryKeyName
|
|
235
|
-
},
|
|
236
|
-
output: {
|
|
237
|
-
schema: outputSchema,
|
|
238
|
-
name: outputName
|
|
316
|
+
const outputSchema = options?.output?.schema ?? z.string();
|
|
317
|
+
const outputName = options?.output?.name ?? "result";
|
|
318
|
+
const jsonSchema = z.toJSONSchema(outputSchema);
|
|
319
|
+
return this.backend.castValue({
|
|
320
|
+
prompt,
|
|
321
|
+
data: options?.input ?? null,
|
|
322
|
+
outputName,
|
|
323
|
+
outputSchema: jsonSchema
|
|
324
|
+
});
|
|
239
325
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const toNuabaseError = (response) => {
|
|
245
|
-
if (response && typeof response === "object" && "error" in response)
|
|
246
|
-
return { error: String(response.error), isError: true };
|
|
247
|
-
return void 0;
|
|
248
|
-
};
|
|
249
|
-
const queueFn = async (data, primaryKeyName) => {
|
|
250
|
-
validateArrayRequestParams(data, primaryKeyName);
|
|
251
|
-
const response = await client.request(
|
|
252
|
-
"POST",
|
|
253
|
-
"cast/array",
|
|
254
|
-
toCastArrayApiRequest(
|
|
255
|
-
fnDef.prompt,
|
|
256
|
-
data,
|
|
257
|
-
primaryKeyName,
|
|
258
|
-
fnDef.output.name,
|
|
259
|
-
outputJsonSchema
|
|
260
|
-
)
|
|
261
|
-
);
|
|
262
|
-
const error = toNuabaseError(response);
|
|
263
|
-
if (error) return error;
|
|
264
|
-
const parsedResponse = zs_NuaQueuedResponse.safeParse(response);
|
|
265
|
-
if (!parsedResponse.success) {
|
|
266
|
-
const emsg = z.prettifyError(parsedResponse.error);
|
|
267
|
-
return { error: `Invalid queued response received from Nuabase API: ${emsg}`, isError: true };
|
|
326
|
+
// ========== list() - Array Operations ==========
|
|
327
|
+
async list(prompt, options) {
|
|
328
|
+
if (typeof prompt !== "string" || prompt.trim() === "") {
|
|
329
|
+
throw new TypeError("Prompt must be a non-empty string");
|
|
268
330
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
const baseNowFn = async (data, primaryKeyName) => {
|
|
272
|
-
validateArrayRequestParams(data, primaryKeyName);
|
|
273
|
-
const response = await client.request(
|
|
274
|
-
"POST",
|
|
275
|
-
"cast/array/now",
|
|
276
|
-
toCastArrayApiRequest(
|
|
277
|
-
fnDef.prompt,
|
|
278
|
-
data,
|
|
279
|
-
primaryKeyName,
|
|
280
|
-
fnDef.output.name,
|
|
281
|
-
outputJsonSchema
|
|
282
|
-
)
|
|
283
|
-
);
|
|
284
|
-
const error = toNuabaseError(response);
|
|
285
|
-
if (error) return error;
|
|
286
|
-
const outputKey = fnDef.output.name;
|
|
287
|
-
const successSchema = zs_NuaApiResponse_CastArray(primaryKeyName, outputKey, fnDef.output.schema);
|
|
288
|
-
const parsedResponse = successSchema.safeParse(response);
|
|
289
|
-
if (!parsedResponse.success) {
|
|
290
|
-
const emsg = z.prettifyError(parsedResponse.error);
|
|
291
|
-
return { error: `Invalid data received from Nuabase API: ${emsg}`, isError: true };
|
|
331
|
+
if (!Array.isArray(options.input)) {
|
|
332
|
+
throw new TypeError("Input must be an array for list()");
|
|
292
333
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
usage: parsedResponse.data.usage
|
|
305
|
-
};
|
|
306
|
-
};
|
|
307
|
-
return Object.assign(baseNowFn, {
|
|
308
|
-
queue: queueFn
|
|
309
|
-
});
|
|
310
|
-
};
|
|
311
|
-
|
|
312
|
-
const createNuaApiResponseCastValue = (outputKey, outputSchema) => {
|
|
313
|
-
return z.object({
|
|
314
|
-
llmRequestId: z.string(),
|
|
315
|
-
kind: z.literal("cast/value"),
|
|
316
|
-
data: outputSchema,
|
|
317
|
-
isCacheHit: z.boolean(),
|
|
318
|
-
isSuccess: z.literal(true),
|
|
319
|
-
isError: z.optional(z.literal(false)),
|
|
320
|
-
usage: zs_NormalizedUsage
|
|
321
|
-
}).strict();
|
|
322
|
-
};
|
|
323
|
-
const zs_NuaApiResponse_CastValue = createNuaApiResponseCastValue;
|
|
324
|
-
|
|
325
|
-
const zs_ValueFnDef = z.object({
|
|
326
|
-
prompt: z.string(),
|
|
327
|
-
output: z.object({
|
|
328
|
-
name: z.string(),
|
|
329
|
-
schema: z.custom(
|
|
330
|
-
(value) => value instanceof z.ZodType,
|
|
331
|
-
"Expected a Zod schema for `output.schema`"
|
|
332
|
-
)
|
|
333
|
-
})
|
|
334
|
-
});
|
|
335
|
-
const validateValueFnDef = (fnDef) => {
|
|
336
|
-
const parsed = zs_ValueFnDef.safeParse(fnDef);
|
|
337
|
-
if (!parsed.success) {
|
|
338
|
-
const message = z.prettifyError(parsed.error);
|
|
339
|
-
throw new TypeError(`Invalid value function definition: ${message}`);
|
|
334
|
+
if (typeof options.primaryKey !== "string" || options.primaryKey.trim() === "") {
|
|
335
|
+
throw new TypeError("primaryKey must be a non-empty string");
|
|
336
|
+
}
|
|
337
|
+
const jsonSchema = z.toJSONSchema(options.output.schema);
|
|
338
|
+
return this.backend.castArray({
|
|
339
|
+
prompt,
|
|
340
|
+
data: options.input,
|
|
341
|
+
primaryKey: options.primaryKey,
|
|
342
|
+
outputName: options.output.name,
|
|
343
|
+
outputSchema: jsonSchema
|
|
344
|
+
});
|
|
340
345
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
346
|
+
// Implementation
|
|
347
|
+
async queueGet(prompt, options) {
|
|
348
|
+
if (typeof prompt !== "string" || prompt.trim() === "") {
|
|
349
|
+
throw new TypeError("Prompt must be a non-empty string");
|
|
350
|
+
}
|
|
351
|
+
if (!this.backend.queueCastValue) {
|
|
352
|
+
throw new Error("Queue operations are only supported in gateway mode");
|
|
353
|
+
}
|
|
354
|
+
const outputSchema = options?.output?.schema ?? z.string();
|
|
355
|
+
const outputName = options?.output?.name ?? "result";
|
|
356
|
+
const jsonSchema = z.toJSONSchema(outputSchema);
|
|
357
|
+
return this.backend.queueCastValue({
|
|
358
|
+
prompt,
|
|
359
|
+
data: options?.input ?? null,
|
|
360
|
+
outputName,
|
|
361
|
+
outputSchema: jsonSchema
|
|
362
|
+
});
|
|
352
363
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
const toNuabaseError = (response) => {
|
|
358
|
-
if (response && typeof response === "object" && "error" in response)
|
|
359
|
-
return { error: String(response.error), isError: true };
|
|
360
|
-
return void 0;
|
|
361
|
-
};
|
|
362
|
-
const sendRequest = async (path, data) => client.request(
|
|
363
|
-
"POST",
|
|
364
|
-
path,
|
|
365
|
-
toCastValueApiRequest(fnDef.prompt, data, fnDef.output.name, outputJsonSchema)
|
|
366
|
-
);
|
|
367
|
-
const queueFn = async (data) => {
|
|
368
|
-
const response = await sendRequest("cast/value", data);
|
|
369
|
-
const error = toNuabaseError(response);
|
|
370
|
-
if (error) return error;
|
|
371
|
-
const parsedResponse = zs_NuaQueuedResponse.safeParse(response);
|
|
372
|
-
if (!parsedResponse.success) {
|
|
373
|
-
const emsg = z.prettifyError(parsedResponse.error);
|
|
374
|
-
return { error: `Invalid queued response received from Nuabase API: ${emsg}`, isError: true };
|
|
364
|
+
// ========== queueList() - Async Array Operations ==========
|
|
365
|
+
async queueList(prompt, options) {
|
|
366
|
+
if (typeof prompt !== "string" || prompt.trim() === "") {
|
|
367
|
+
throw new TypeError("Prompt must be a non-empty string");
|
|
375
368
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
const baseNowFn = async (data) => {
|
|
379
|
-
const response = await sendRequest("cast/value/now", data);
|
|
380
|
-
const error = toNuabaseError(response);
|
|
381
|
-
if (error) return error;
|
|
382
|
-
const outputKey = fnDef.output.name;
|
|
383
|
-
const successSchema = zs_NuaApiResponse_CastValue(
|
|
384
|
-
outputKey,
|
|
385
|
-
fnDef.output.schema
|
|
386
|
-
);
|
|
387
|
-
const parsedResponse = successSchema.safeParse(response);
|
|
388
|
-
if (!parsedResponse.success) {
|
|
389
|
-
const emsg = z.prettifyError(parsedResponse.error);
|
|
390
|
-
return { error: `Invalid data received from Nuabase API: ${emsg}`, isError: true };
|
|
369
|
+
if (!this.backend.queueCastArray) {
|
|
370
|
+
throw new Error("Queue operations are only supported in gateway mode");
|
|
391
371
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
llmRequestId: parsedResponse.data.llmRequestId,
|
|
395
|
-
kind: parsedResponse.data.kind,
|
|
396
|
-
data: parsedResponse.data.data,
|
|
397
|
-
isCacheHit: parsedResponse.data.isCacheHit,
|
|
398
|
-
usage: parsedResponse.data.usage
|
|
399
|
-
};
|
|
400
|
-
};
|
|
401
|
-
return Object.assign(baseNowFn, {
|
|
402
|
-
queue: queueFn
|
|
403
|
-
});
|
|
404
|
-
};
|
|
405
|
-
|
|
406
|
-
const errorParseFailed = {
|
|
407
|
-
kind: "internal-error",
|
|
408
|
-
message: "Unable to parse stored LLM result"
|
|
409
|
-
};
|
|
410
|
-
const dateFromIso = z.preprocess(
|
|
411
|
-
(value) => {
|
|
412
|
-
if (value == null) return null;
|
|
413
|
-
if (value instanceof Date) return value;
|
|
414
|
-
if (typeof value === "string") {
|
|
415
|
-
const parsed = new Date(value);
|
|
416
|
-
if (!Number.isNaN(parsed.getTime())) return parsed;
|
|
372
|
+
if (!Array.isArray(options.input)) {
|
|
373
|
+
throw new TypeError("Input must be an array for queueList()");
|
|
417
374
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
);
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
}, z.date());
|
|
430
|
-
const apiResponseSchema = z.object({
|
|
431
|
-
id: z.string(),
|
|
432
|
-
requestType: z.string(),
|
|
433
|
-
llmStatus: z.union([
|
|
434
|
-
z.literal("pending"),
|
|
435
|
-
z.literal("processing"),
|
|
436
|
-
z.literal("success"),
|
|
437
|
-
z.literal("failed")
|
|
438
|
-
]),
|
|
439
|
-
sseStatus: z.union([
|
|
440
|
-
z.literal("n/a"),
|
|
441
|
-
z.literal("pending"),
|
|
442
|
-
z.literal("sent"),
|
|
443
|
-
z.literal("failed")
|
|
444
|
-
]),
|
|
445
|
-
webhookStatus: z.union([
|
|
446
|
-
z.literal("n/a"),
|
|
447
|
-
z.literal("pending"),
|
|
448
|
-
z.literal("sent"),
|
|
449
|
-
z.literal("failed")
|
|
450
|
-
]),
|
|
451
|
-
input: z.object({
|
|
452
|
-
prompt: z.union([z.string(), z.null()]),
|
|
453
|
-
data: z.union([z.string(), z.null()]),
|
|
454
|
-
primaryKey: z.union([z.string(), z.null()])
|
|
455
|
-
}),
|
|
456
|
-
output: z.object({
|
|
457
|
-
name: z.string(),
|
|
458
|
-
schema: z.string(),
|
|
459
|
-
effectiveSchema: z.string()
|
|
460
|
-
}),
|
|
461
|
-
result: z.union([z.record(z.string(), z.unknown()), z.null()]).optional(),
|
|
462
|
-
error: z.union([z.string(), z.null()]),
|
|
463
|
-
fullPrompt: z.string(),
|
|
464
|
-
model: z.string(),
|
|
465
|
-
provider: z.string(),
|
|
466
|
-
startedAt: dateFromIso,
|
|
467
|
-
finishedAt: dateFromIso,
|
|
468
|
-
createdAt: mandatoryDateFromIso,
|
|
469
|
-
updatedAt: mandatoryDateFromIso
|
|
470
|
-
});
|
|
471
|
-
const isNuabaseError = (value) => Boolean(value && typeof value === "object" && "isError" in value && value.isError);
|
|
472
|
-
async function getRequest(client, llmRequestId) {
|
|
473
|
-
const response = await client.request("GET", `requests/${encodeURIComponent(llmRequestId)}`);
|
|
474
|
-
if (isNuabaseError(response)) {
|
|
475
|
-
return response;
|
|
476
|
-
}
|
|
477
|
-
const parsedResponse = apiResponseSchema.safeParse(response);
|
|
478
|
-
if (!parsedResponse.success) {
|
|
479
|
-
return errorParseFailed;
|
|
480
|
-
}
|
|
481
|
-
return parsedResponse.data;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
class Nua {
|
|
485
|
-
constructor(config) {
|
|
486
|
-
this.apiClient = new NuabaseAPIClient(config);
|
|
487
|
-
}
|
|
488
|
-
createArrayFn(fnDef) {
|
|
489
|
-
return createArrayFn(this.apiClient, fnDef);
|
|
490
|
-
}
|
|
491
|
-
createFn(fnDef) {
|
|
492
|
-
return createValueFn(this.apiClient, fnDef);
|
|
493
|
-
}
|
|
494
|
-
async getRequest(llmRequestId) {
|
|
495
|
-
return getRequest(this.apiClient, llmRequestId);
|
|
375
|
+
if (typeof options.primaryKey !== "string" || options.primaryKey.trim() === "") {
|
|
376
|
+
throw new TypeError("primaryKey must be a non-empty string");
|
|
377
|
+
}
|
|
378
|
+
const jsonSchema = z.toJSONSchema(options.output.schema);
|
|
379
|
+
return this.backend.queueCastArray({
|
|
380
|
+
prompt,
|
|
381
|
+
data: options.input,
|
|
382
|
+
primaryKey: options.primaryKey,
|
|
383
|
+
outputName: options.output.name,
|
|
384
|
+
outputSchema: jsonSchema
|
|
385
|
+
});
|
|
496
386
|
}
|
|
497
387
|
}
|
|
498
388
|
|