nuabase 1.1.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 +105 -56
- package/dist/cjs/index.cjs +302 -361
- package/dist/esm/index.mjs +299 -361
- 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 -11
- 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 -17
- 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 -13
- package/dist/types/nua/cast_value/types.d.ts +0 -18
- 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,398 +179,210 @@ function getErrorMessageFromException(error) {
|
|
|
53
179
|
return String(error);
|
|
54
180
|
}
|
|
55
181
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
182
|
+
function isErrorResponse(response) {
|
|
183
|
+
return Boolean(response && typeof response === "object" && "error" in response);
|
|
184
|
+
}
|
|
185
|
+
class GatewayBackend {
|
|
186
|
+
constructor(config) {
|
|
59
187
|
this.baseUrl = resolveConfigValue(config, "baseUrl", "NUABASE_API_URL") || "https://api.nuabase.com";
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
188
|
+
this.authManager = new AuthTokenManager(config);
|
|
189
|
+
}
|
|
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 };
|
|
199
|
+
}
|
|
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 };
|
|
230
|
+
}
|
|
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" };
|
|
64
259
|
}
|
|
260
|
+
const apiResponse = response;
|
|
261
|
+
return { success: true, jobId: apiResponse.jobId, source: "gateway" };
|
|
65
262
|
}
|
|
66
|
-
async
|
|
67
|
-
const
|
|
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" };
|
|
273
|
+
}
|
|
274
|
+
async post(path, body) {
|
|
275
|
+
const url = `${this.baseUrl}/${path}`;
|
|
68
276
|
let response;
|
|
69
|
-
const hasBody = method !== "GET" && params !== void 0;
|
|
70
277
|
const headers = {
|
|
71
|
-
Authorization: `Bearer ${this.
|
|
278
|
+
Authorization: `Bearer ${await this.authManager.getToken()}`,
|
|
279
|
+
"Content-Type": "application/json"
|
|
72
280
|
};
|
|
73
|
-
if (hasBody) {
|
|
74
|
-
headers["Content-Type"] = "application/json";
|
|
75
|
-
}
|
|
76
281
|
try {
|
|
77
282
|
response = await fetch(url, {
|
|
78
|
-
method,
|
|
283
|
+
method: "POST",
|
|
79
284
|
headers,
|
|
80
|
-
body:
|
|
285
|
+
body: JSON.stringify(body)
|
|
81
286
|
});
|
|
82
287
|
} catch (e) {
|
|
83
288
|
return { error: `Error calling Nuabase API: ${getErrorMessageFromException(e)}` };
|
|
84
289
|
}
|
|
85
|
-
let result;
|
|
86
290
|
if (!response.ok) {
|
|
87
|
-
return await parseErrorResponse(response,
|
|
291
|
+
return await parseErrorResponse(response, path);
|
|
88
292
|
}
|
|
89
293
|
try {
|
|
90
|
-
|
|
294
|
+
return await response.json();
|
|
91
295
|
} catch {
|
|
92
296
|
return { error: "Invalid response received from Nuabase API call, unable to parse" };
|
|
93
297
|
}
|
|
94
|
-
return result;
|
|
95
298
|
}
|
|
96
299
|
}
|
|
97
300
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}).strict();
|
|
102
|
-
|
|
103
|
-
const validateArrayRequestParams = (data, primaryKeyName) => {
|
|
104
|
-
if (!Array.isArray(data)) {
|
|
105
|
-
throw new Error("`data` must be an array of objects.");
|
|
301
|
+
class Nua {
|
|
302
|
+
constructor(backend) {
|
|
303
|
+
this.backend = backend;
|
|
106
304
|
}
|
|
107
|
-
|
|
108
|
-
|
|
305
|
+
static gateway(config) {
|
|
306
|
+
return new Nua(new GatewayBackend(config));
|
|
109
307
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
throw new Error(`data[${index}] must be a non-null object.`);
|
|
113
|
-
}
|
|
114
|
-
if (!(primaryKeyName in item)) {
|
|
115
|
-
throw new Error(`data[${index}] must contain property '${primaryKeyName}'.`);
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
const zs_ResponseRecord = (primaryKeyName, outputKey, outputSchema) => {
|
|
121
|
-
const primaryKeySchema = z.any();
|
|
122
|
-
const shape = {
|
|
123
|
-
[primaryKeyName]: primaryKeySchema,
|
|
124
|
-
[outputKey]: outputSchema
|
|
125
|
-
};
|
|
126
|
-
return z.object(shape).strict();
|
|
127
|
-
};
|
|
128
|
-
const createNuaApiResponseCastArray = (primaryKeyName, outputKey, outputSchema) => {
|
|
129
|
-
const recordSchema = zs_ResponseRecord(
|
|
130
|
-
primaryKeyName,
|
|
131
|
-
outputKey,
|
|
132
|
-
outputSchema
|
|
133
|
-
);
|
|
134
|
-
const dataSchema = z.array(recordSchema);
|
|
135
|
-
return z.object({
|
|
136
|
-
llmRequestId: z.string(),
|
|
137
|
-
kind: z.literal("cast/array"),
|
|
138
|
-
data: dataSchema,
|
|
139
|
-
cacheHits: z.number(),
|
|
140
|
-
rowsWithNoResults: z.array(z.string()),
|
|
141
|
-
isSuccess: z.literal(true),
|
|
142
|
-
isError: z.optional(z.literal(false))
|
|
143
|
-
}).strict();
|
|
144
|
-
};
|
|
145
|
-
const zs_NuaApiResponse_CastArray = createNuaApiResponseCastArray;
|
|
146
|
-
|
|
147
|
-
const zs_ArrayFnDef = z.object({
|
|
148
|
-
prompt: z.string(),
|
|
149
|
-
output: z.object({
|
|
150
|
-
name: z.string(),
|
|
151
|
-
schema: z.custom(
|
|
152
|
-
(value) => value instanceof z.ZodType,
|
|
153
|
-
"Expected a Zod schema"
|
|
154
|
-
)
|
|
155
|
-
})
|
|
156
|
-
});
|
|
157
|
-
const validateArrayFnDef = (fnDef) => {
|
|
158
|
-
const parsed = zs_ArrayFnDef.safeParse(fnDef);
|
|
159
|
-
if (!parsed.success) {
|
|
160
|
-
const message = z.prettifyError(parsed.error);
|
|
161
|
-
throw new TypeError(`Invalid array function definition: ${message}`);
|
|
308
|
+
static direct(config) {
|
|
309
|
+
return new Nua(new DirectBackend(config));
|
|
162
310
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const lookup = /* @__PURE__ */ new Map();
|
|
168
|
-
for (const row of inputRows) {
|
|
169
|
-
lookup.set(row[primaryKeyName], row);
|
|
170
|
-
}
|
|
171
|
-
return outputRows.map((row) => {
|
|
172
|
-
const primaryKeyValue = row[primaryKeyName];
|
|
173
|
-
const sourceRow = lookup.get(primaryKeyValue);
|
|
174
|
-
if (!sourceRow) {
|
|
175
|
-
throw new Error(
|
|
176
|
-
`Unable to locate source row for primary key "${String(primaryKeyValue)}" in cast array response.`
|
|
177
|
-
);
|
|
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");
|
|
178
315
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
data,
|
|
189
|
-
primaryKey: primaryKeyName
|
|
190
|
-
},
|
|
191
|
-
output: {
|
|
192
|
-
schema: outputSchema,
|
|
193
|
-
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
|
+
});
|
|
194
325
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const toNuabaseError = (response) => {
|
|
200
|
-
if (response && typeof response === "object" && "error" in response)
|
|
201
|
-
return { error: String(response.error), isError: true };
|
|
202
|
-
return void 0;
|
|
203
|
-
};
|
|
204
|
-
const baseFn = async (data, primaryKeyName) => {
|
|
205
|
-
validateArrayRequestParams(data, primaryKeyName);
|
|
206
|
-
const response = await client.request(
|
|
207
|
-
"POST",
|
|
208
|
-
"cast/array",
|
|
209
|
-
toCastArrayApiRequest(
|
|
210
|
-
fnDef.prompt,
|
|
211
|
-
data,
|
|
212
|
-
primaryKeyName,
|
|
213
|
-
fnDef.output.name,
|
|
214
|
-
outputJsonSchema
|
|
215
|
-
)
|
|
216
|
-
);
|
|
217
|
-
const error = toNuabaseError(response);
|
|
218
|
-
if (error) return error;
|
|
219
|
-
const parsedResponse = zs_NuaQueuedResponse.safeParse(response);
|
|
220
|
-
if (!parsedResponse.success) {
|
|
221
|
-
const emsg = z.prettifyError(parsedResponse.error);
|
|
222
|
-
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");
|
|
223
330
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const nowFn = async (data, primaryKeyName) => {
|
|
227
|
-
validateArrayRequestParams(data, primaryKeyName);
|
|
228
|
-
const response = await client.request(
|
|
229
|
-
"POST",
|
|
230
|
-
"cast/array/now",
|
|
231
|
-
toCastArrayApiRequest(
|
|
232
|
-
fnDef.prompt,
|
|
233
|
-
data,
|
|
234
|
-
primaryKeyName,
|
|
235
|
-
fnDef.output.name,
|
|
236
|
-
outputJsonSchema
|
|
237
|
-
)
|
|
238
|
-
);
|
|
239
|
-
const error = toNuabaseError(response);
|
|
240
|
-
if (error) return error;
|
|
241
|
-
const outputKey = fnDef.output.name;
|
|
242
|
-
const successSchema = zs_NuaApiResponse_CastArray(primaryKeyName, outputKey, fnDef.output.schema);
|
|
243
|
-
const parsedResponse = successSchema.safeParse(response);
|
|
244
|
-
if (!parsedResponse.success) {
|
|
245
|
-
const emsg = z.prettifyError(parsedResponse.error);
|
|
246
|
-
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()");
|
|
247
333
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
};
|
|
260
|
-
};
|
|
261
|
-
return Object.assign(baseFn, {
|
|
262
|
-
now: nowFn
|
|
263
|
-
});
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
const createNuaApiResponseCastValue = (outputKey, outputSchema) => {
|
|
267
|
-
return z.object({
|
|
268
|
-
llmRequestId: z.string(),
|
|
269
|
-
kind: z.literal("cast/value"),
|
|
270
|
-
data: outputSchema,
|
|
271
|
-
isCacheHit: z.boolean(),
|
|
272
|
-
isSuccess: z.literal(true),
|
|
273
|
-
isError: z.optional(z.literal(false))
|
|
274
|
-
}).strict();
|
|
275
|
-
};
|
|
276
|
-
const zs_NuaApiResponse_CastValue = createNuaApiResponseCastValue;
|
|
277
|
-
|
|
278
|
-
const zs_ValueFnDef = z.object({
|
|
279
|
-
prompt: z.string(),
|
|
280
|
-
output: z.object({
|
|
281
|
-
name: z.string(),
|
|
282
|
-
schema: z.custom(
|
|
283
|
-
(value) => value instanceof z.ZodType,
|
|
284
|
-
"Expected a Zod schema for `output.schema`"
|
|
285
|
-
)
|
|
286
|
-
})
|
|
287
|
-
});
|
|
288
|
-
const validateValueFnDef = (fnDef) => {
|
|
289
|
-
const parsed = zs_ValueFnDef.safeParse(fnDef);
|
|
290
|
-
if (!parsed.success) {
|
|
291
|
-
const message = z.prettifyError(parsed.error);
|
|
292
|
-
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
|
+
});
|
|
293
345
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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
|
+
});
|
|
305
363
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
const toNuabaseError = (response) => {
|
|
311
|
-
if (response && typeof response === "object" && "error" in response)
|
|
312
|
-
return { error: String(response.error), isError: true };
|
|
313
|
-
return void 0;
|
|
314
|
-
};
|
|
315
|
-
const sendRequest = async (path, data) => client.request(
|
|
316
|
-
"POST",
|
|
317
|
-
path,
|
|
318
|
-
toCastValueApiRequest(fnDef.prompt, data, fnDef.output.name, outputJsonSchema)
|
|
319
|
-
);
|
|
320
|
-
const baseFn = async (data) => {
|
|
321
|
-
const response = await sendRequest("cast/value", data);
|
|
322
|
-
const error = toNuabaseError(response);
|
|
323
|
-
if (error) return error;
|
|
324
|
-
const parsedResponse = zs_NuaQueuedResponse.safeParse(response);
|
|
325
|
-
if (!parsedResponse.success) {
|
|
326
|
-
const emsg = z.prettifyError(parsedResponse.error);
|
|
327
|
-
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");
|
|
328
368
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
const nowFn = async (data) => {
|
|
332
|
-
const response = await sendRequest("cast/value/now", data);
|
|
333
|
-
const error = toNuabaseError(response);
|
|
334
|
-
if (error) return error;
|
|
335
|
-
const outputKey = fnDef.output.name;
|
|
336
|
-
const successSchema = zs_NuaApiResponse_CastValue(
|
|
337
|
-
outputKey,
|
|
338
|
-
fnDef.output.schema
|
|
339
|
-
);
|
|
340
|
-
const parsedResponse = successSchema.safeParse(response);
|
|
341
|
-
if (!parsedResponse.success) {
|
|
342
|
-
const emsg = z.prettifyError(parsedResponse.error);
|
|
343
|
-
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");
|
|
344
371
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
llmRequestId: parsedResponse.data.llmRequestId,
|
|
348
|
-
kind: parsedResponse.data.kind,
|
|
349
|
-
data: parsedResponse.data.data,
|
|
350
|
-
isCacheHit: parsedResponse.data.isCacheHit
|
|
351
|
-
};
|
|
352
|
-
};
|
|
353
|
-
return Object.assign(baseFn, {
|
|
354
|
-
now: nowFn
|
|
355
|
-
});
|
|
356
|
-
};
|
|
357
|
-
|
|
358
|
-
const errorParseFailed = {
|
|
359
|
-
kind: "internal-error",
|
|
360
|
-
message: "Unable to parse stored LLM result"
|
|
361
|
-
};
|
|
362
|
-
const dateFromIso = z.preprocess(
|
|
363
|
-
(value) => {
|
|
364
|
-
if (value == null) return null;
|
|
365
|
-
if (value instanceof Date) return value;
|
|
366
|
-
if (typeof value === "string") {
|
|
367
|
-
const parsed = new Date(value);
|
|
368
|
-
if (!Number.isNaN(parsed.getTime())) return parsed;
|
|
372
|
+
if (!Array.isArray(options.input)) {
|
|
373
|
+
throw new TypeError("Input must be an array for queueList()");
|
|
369
374
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
);
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
}, z.date());
|
|
382
|
-
const apiResponseSchema = z.object({
|
|
383
|
-
id: z.string(),
|
|
384
|
-
requestType: z.string(),
|
|
385
|
-
llmStatus: z.union([
|
|
386
|
-
z.literal("pending"),
|
|
387
|
-
z.literal("processing"),
|
|
388
|
-
z.literal("success"),
|
|
389
|
-
z.literal("failed")
|
|
390
|
-
]),
|
|
391
|
-
sseStatus: z.union([
|
|
392
|
-
z.literal("n/a"),
|
|
393
|
-
z.literal("pending"),
|
|
394
|
-
z.literal("sent"),
|
|
395
|
-
z.literal("failed")
|
|
396
|
-
]),
|
|
397
|
-
webhookStatus: z.union([
|
|
398
|
-
z.literal("n/a"),
|
|
399
|
-
z.literal("pending"),
|
|
400
|
-
z.literal("sent"),
|
|
401
|
-
z.literal("failed")
|
|
402
|
-
]),
|
|
403
|
-
input: z.object({
|
|
404
|
-
prompt: z.union([z.string(), z.null()]),
|
|
405
|
-
data: z.union([z.string(), z.null()]),
|
|
406
|
-
primaryKey: z.union([z.string(), z.null()])
|
|
407
|
-
}),
|
|
408
|
-
output: z.object({
|
|
409
|
-
name: z.string(),
|
|
410
|
-
schema: z.string(),
|
|
411
|
-
effectiveSchema: z.string()
|
|
412
|
-
}),
|
|
413
|
-
result: z.union([z.record(z.string(), z.unknown()), z.null()]).optional(),
|
|
414
|
-
error: z.union([z.string(), z.null()]),
|
|
415
|
-
fullPrompt: z.string(),
|
|
416
|
-
model: z.string(),
|
|
417
|
-
provider: z.string(),
|
|
418
|
-
startedAt: dateFromIso,
|
|
419
|
-
finishedAt: dateFromIso,
|
|
420
|
-
createdAt: mandatoryDateFromIso,
|
|
421
|
-
updatedAt: mandatoryDateFromIso
|
|
422
|
-
});
|
|
423
|
-
const isNuabaseError = (value) => Boolean(value && typeof value === "object" && "isError" in value && value.isError);
|
|
424
|
-
async function getRequest(client, llmRequestId) {
|
|
425
|
-
const response = await client.request("GET", `requests/${encodeURIComponent(llmRequestId)}`);
|
|
426
|
-
if (isNuabaseError(response)) {
|
|
427
|
-
return response;
|
|
428
|
-
}
|
|
429
|
-
const parsedResponse = apiResponseSchema.safeParse(response);
|
|
430
|
-
if (!parsedResponse.success) {
|
|
431
|
-
return errorParseFailed;
|
|
432
|
-
}
|
|
433
|
-
return parsedResponse.data;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
class Nua {
|
|
437
|
-
constructor(config) {
|
|
438
|
-
this.apiClient = new NuabaseAPIClient(config);
|
|
439
|
-
}
|
|
440
|
-
createArrayFn(fnDef) {
|
|
441
|
-
return createArrayFn(this.apiClient, fnDef);
|
|
442
|
-
}
|
|
443
|
-
createFn(fnDef) {
|
|
444
|
-
return createValueFn(this.apiClient, fnDef);
|
|
445
|
-
}
|
|
446
|
-
async getRequest(llmRequestId) {
|
|
447
|
-
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
|
+
});
|
|
448
386
|
}
|
|
449
387
|
}
|
|
450
388
|
|