nuabase 1.0.0 → 1.0.1

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 CHANGED
@@ -1,26 +1,25 @@
1
- # Nuabase TypeScript client
1
+ ## Nuabase TypeScript SDK
2
2
 
3
- Nuabase turns LLM prompts into type-safe functions in under five lines.
3
+ Nuabase turns LLM prompts into type-safe functions in under five lines of code. Set up your free account now at [Nuabase](https://nuabase.com).
4
+
5
+ With Nuabase, you write a prompt, specify the output schema, and get back a well-typed function. You can invoke it with input data, like any other code in your application, and receive data that matches the exact schema you specified.
4
6
 
5
7
  Example use-cases:
6
- * Enrich a list of food items with nutrition facts
7
- * Tag sales leads with more details about the company
8
- * Convert free-form instructions into precise order forms—directly inside your application.
9
8
 
10
- You can describe the transformation once, and get back a well-typed function, and call it like any other code in your application.
9
+ - Provide free-form text input for faster filling of complex HTML forms
10
+ - Enrich a list of food items with nutrition facts
11
+ - Tag sales leads with more details about the company
11
12
 
12
- You don't have to do any custom custom orchestration, or run polling, or schema validation.
13
+ Behind the scenes, Nuabase runs the LLM transformation as async jobs, and once ready, call your server API with the results using Webhooks. It can also stream the result directly to your front-end using SSE. You can also see all requests and responses as they happen in the [Nuabase Console](https://console.nuabase.com). All outputs are guaranteed to match the schema you specified.
13
14
 
14
- Behind the scenes, Nuabase runs the async jobs, SSE streaming, webhooks, logging, caching, cost attribution, and strict JSON-schema enforcement. You also get an observability dashboard for every request so you always know what the LLM produced.
15
+ One major reason I built Nuabase is the need for granular, row-level caching. For example, let's say you want to classify your bank transaction entries, and map them to your chart of accounts. With Nuabase's row-level caching, it will only send new entries to the LLM. Any specific entry that it has seen before will be returned from the cache. This also means you can make LLM requests multiple times with identical values, and after the first time, they will return immediately without needing to go through the LLM.
15
16
 
16
- ## At a Glance
17
+ ## Usage at a glance
17
18
 
18
19
  **1. Input.** Start with the data you want to send to the LLM:
19
20
 
20
21
  ```ts
21
- const leads = [
22
- { id: 'lead-101', notes: 'Growth-stage SaaS, ~80 employees, wants a demo.' },
23
- ];
22
+ const leads = [{ id: 'lead-101', notes: 'Growth-stage SaaS, ~80 employees, wants a demo.' }];
24
23
  ```
25
24
 
26
25
  **2. Desired shape.** Describe the structure you expect back:
@@ -35,10 +34,12 @@ const LeadInsights = z.object({
35
34
  **3. Declare the prompt.** Turn it into a typed function:
36
35
 
37
36
  ```ts
38
- const classifyLeads = new Nua().map({
39
- name: 'insights',
40
- output: LeadInsights,
37
+ const classifyLeads = new Nua().createArrayFn({
41
38
  prompt: 'Classify each lead with industry and company_size bucket.',
39
+ output: {
40
+ name: 'leadInsights',
41
+ schema: LeadInsights,
42
+ },
42
43
  });
43
44
  ```
44
45
 
@@ -78,11 +79,13 @@ const LeadInsights = z.object({
78
79
  recommended_follow_up: z.enum(['Call', 'Email', 'Event']),
79
80
  });
80
81
 
81
- const enrichLeads = nua.map({
82
- name: 'leadInsights',
83
- output: LeadInsights,
82
+ const enrichLeads = nua.createArrayFn({
84
83
  prompt:
85
84
  'Summarize each inbound lead by extracting company_name, industry, company_size bucket (SMB, Mid-market, Enterprise), and the recommended_follow_up channel (Call, Email, or Event) based on their notes.',
85
+ output: {
86
+ name: 'leadInsights',
87
+ schema: LeadInsights,
88
+ },
86
89
  });
87
90
 
88
91
  const rows = [
@@ -83,22 +83,46 @@ class NuabaseAPIClient {
83
83
  }
84
84
  }
85
85
 
86
- const zs_MapResponseRecord = (primaryKeyName, outputKey, outputSchema) => {
86
+ const zs_NuaQueuedResponse = zod.z.object({
87
+ id: zod.z.string(),
88
+ sseUrl: zod.z.string().min(1)
89
+ }).strict();
90
+
91
+ const validateArrayRequestParams = (data, primaryKeyName) => {
92
+ if (!Array.isArray(data)) {
93
+ throw new Error("`data` must be an array of objects.");
94
+ }
95
+ if (!primaryKeyName || typeof primaryKeyName !== "string") {
96
+ throw new Error("`primaryKeyName` must be a non-empty string.");
97
+ }
98
+ data.forEach((item, index) => {
99
+ if (item === null || typeof item !== "object" || Array.isArray(item)) {
100
+ throw new Error(`data[${index}] must be a non-null object.`);
101
+ }
102
+ if (!(primaryKeyName in item)) {
103
+ throw new Error(`data[${index}] must contain property '${primaryKeyName}'.`);
104
+ }
105
+ });
106
+ };
107
+
108
+ const zs_ResponseRecord = (primaryKeyName, outputKey, outputSchema) => {
87
109
  const primaryKeySchema = zod.z.any();
88
- return zod.z.object({
110
+ const shape = {
89
111
  [primaryKeyName]: primaryKeySchema,
90
112
  [outputKey]: outputSchema
91
- }).strict();
113
+ };
114
+ return zod.z.object(shape).strict();
92
115
  };
93
- const zs_MapSuccessDataResult = (primaryKeyName, outputKey, outputSchema) => {
94
- const recordSchema = zs_MapResponseRecord(primaryKeyName, outputKey, outputSchema);
95
- return zod.z.array(recordSchema);
96
- };
97
- const zs_MapRequestApiResponse_Success = (primaryKeyName, outputKey, outputSchema) => {
98
- const dataSchema = zs_MapSuccessDataResult(primaryKeyName, outputKey, outputSchema);
116
+ const createNuaApiResponseCastArray = (primaryKeyName, outputKey, outputSchema) => {
117
+ const recordSchema = zs_ResponseRecord(
118
+ primaryKeyName,
119
+ outputKey,
120
+ outputSchema
121
+ );
122
+ const dataSchema = zod.z.array(recordSchema);
99
123
  return zod.z.object({
100
124
  llmRequestId: zod.z.string(),
101
- kind: zod.z.literal("map"),
125
+ kind: zod.z.literal("cast/array"),
102
126
  data: dataSchema,
103
127
  cacheHits: zod.z.number(),
104
128
  rowsWithNoResults: zod.z.array(zod.z.string()),
@@ -106,72 +130,202 @@ const zs_MapRequestApiResponse_Success = (primaryKeyName, outputKey, outputSchem
106
130
  isError: zod.z.optional(zod.z.literal(false))
107
131
  }).strict();
108
132
  };
109
- class Nua {
110
- constructor(config) {
111
- this.apiClient = new NuabaseAPIClient(config);
133
+ const zs_NuaApiResponse_CastArray = createNuaApiResponseCastArray;
134
+
135
+ const zs_ArrayFnDef = zod.z.object({
136
+ prompt: zod.z.string(),
137
+ output: zod.z.object({
138
+ name: zod.z.string(),
139
+ schema: zod.z.custom(
140
+ (value) => value instanceof zod.z.ZodType,
141
+ "Expected a Zod schema"
142
+ )
143
+ })
144
+ });
145
+ const validateArrayFnDef = (fnDef) => {
146
+ const parsed = zs_ArrayFnDef.safeParse(fnDef);
147
+ if (!parsed.success) {
148
+ const message = zod.z.prettifyError(parsed.error);
149
+ throw new TypeError(`Invalid array function definition: ${message}`);
150
+ }
151
+ return parsed.data;
152
+ };
153
+
154
+ const toCastArrayApiRequest = (prompt, data, primaryKeyName, outputName, outputSchema) => ({
155
+ input: {
156
+ prompt,
157
+ data,
158
+ primaryKey: primaryKeyName
159
+ },
160
+ output: {
161
+ schema: outputSchema,
162
+ name: outputName
112
163
  }
113
- // Some of the checks here mirror type validation, but since our code could be run from
114
- // untyped JavaScript, we still want runtime checks
115
- static validateMapRequestParams(data, primaryKeyName) {
116
- if (!Array.isArray(data)) {
117
- throw new Error("`data` must be an array of objects.");
164
+ });
165
+ const createArrayFn = (client, inputFnDef) => {
166
+ const fnDef = validateArrayFnDef(inputFnDef);
167
+ const outputJsonSchema = zod.z.toJSONSchema(fnDef.output.schema);
168
+ const toNuabaseError = (response) => {
169
+ if (response && typeof response === "object" && "error" in response)
170
+ return { error: String(response.error), isError: true };
171
+ return void 0;
172
+ };
173
+ const baseFn = async (data, primaryKeyName) => {
174
+ validateArrayRequestParams(data, primaryKeyName);
175
+ const response = await client.request(
176
+ "cast/array",
177
+ toCastArrayApiRequest(
178
+ fnDef.prompt,
179
+ data,
180
+ primaryKeyName,
181
+ fnDef.output.name,
182
+ outputJsonSchema
183
+ )
184
+ );
185
+ const error = toNuabaseError(response);
186
+ if (error) return error;
187
+ const parsedResponse = zs_NuaQueuedResponse.safeParse(response);
188
+ if (!parsedResponse.success) {
189
+ const emsg = zod.z.prettifyError(parsedResponse.error);
190
+ return { error: `Invalid queued response received from Nuabase API: ${emsg}`, isError: true };
118
191
  }
119
- if (!primaryKeyName || typeof primaryKeyName !== "string") {
120
- throw new Error("`primaryKeyName` must be a non-empty string.");
192
+ return parsedResponse.data;
193
+ };
194
+ const nowFn = async (data, primaryKeyName) => {
195
+ validateArrayRequestParams(data, primaryKeyName);
196
+ const response = await client.request(
197
+ "cast/array/now",
198
+ toCastArrayApiRequest(
199
+ fnDef.prompt,
200
+ data,
201
+ primaryKeyName,
202
+ fnDef.output.name,
203
+ outputJsonSchema
204
+ )
205
+ );
206
+ const error = toNuabaseError(response);
207
+ if (error) return error;
208
+ const outputKey = fnDef.output.name;
209
+ const successSchema = zs_NuaApiResponse_CastArray(primaryKeyName, outputKey, fnDef.output.schema);
210
+ const parsedResponse = successSchema.safeParse(response);
211
+ if (!parsedResponse.success) {
212
+ const emsg = zod.z.prettifyError(parsedResponse.error);
213
+ return { error: `Invalid data received from Nuabase API: ${emsg}`, isError: true };
121
214
  }
122
- data.forEach((item, index) => {
123
- if (item === null || typeof item !== "object" || Array.isArray(item)) {
124
- throw new Error(`data[${index}] must be a non-null object.`);
125
- }
126
- if (!(primaryKeyName in item)) {
127
- throw new Error(`data[${index}] must contain property '${primaryKeyName}'.`);
128
- }
129
- });
130
- }
131
- map(fnDef) {
132
- const client = this.apiClient;
133
- const outputJsonSchema = zod.z.toJSONSchema(fnDef.output);
134
- const makeRequest = async (path, data, primaryKeyName) => {
135
- Nua.validateMapRequestParams(data, primaryKeyName);
136
- const response = await client.request(path, {
137
- input: {
138
- prompt: fnDef.prompt,
139
- data,
140
- primaryKey: primaryKeyName
141
- },
142
- output: {
143
- schema: outputJsonSchema,
144
- name: fnDef.name
145
- }
146
- });
147
- if (response && typeof response === "object" && "error" in response)
148
- return { error: String(response.error), isError: true };
149
- const outputKey = fnDef.name;
150
- const successSchema = zs_MapRequestApiResponse_Success(primaryKeyName, outputKey, fnDef.output);
151
- const parsedResponse = successSchema.safeParse(response);
152
- if (!parsedResponse.success) {
153
- const emsg = zod.z.prettifyError(parsedResponse.error);
154
- return { error: `Invalid data received from Nuabase API: ${emsg}`, isError: true };
155
- }
156
- return {
157
- isSuccess: true,
158
- llmRequestId: parsedResponse.data.llmRequestId,
159
- kind: parsedResponse.data.kind,
160
- data: parsedResponse.data.data,
161
- rowsWithNoResults: parsedResponse.data.rowsWithNoResults,
162
- cacheHits: parsedResponse.data.cacheHits
163
- };
215
+ return {
216
+ isSuccess: true,
217
+ llmRequestId: parsedResponse.data.llmRequestId,
218
+ kind: parsedResponse.data.kind,
219
+ data: parsedResponse.data.data,
220
+ rowsWithNoResults: parsedResponse.data.rowsWithNoResults,
221
+ cacheHits: parsedResponse.data.cacheHits
164
222
  };
165
- const baseInvoker = async (data, primaryKeyName) => {
166
- return await makeRequest("map", data, primaryKeyName);
167
- };
168
- const nowInvoker = async (data, primaryKeyName) => {
169
- return await makeRequest("map/now", data, primaryKeyName);
223
+ };
224
+ return Object.assign(baseFn, {
225
+ now: nowFn
226
+ });
227
+ };
228
+
229
+ const createNuaApiResponseCastValue = (outputKey, outputSchema) => {
230
+ return zod.z.object({
231
+ llmRequestId: zod.z.string(),
232
+ kind: zod.z.literal("cast/value"),
233
+ data: outputSchema,
234
+ isCacheHit: zod.z.boolean(),
235
+ isSuccess: zod.z.literal(true),
236
+ isError: zod.z.optional(zod.z.literal(false))
237
+ }).strict();
238
+ };
239
+ const zs_NuaApiResponse_CastValue = createNuaApiResponseCastValue;
240
+
241
+ const zs_ValueFnDef = zod.z.object({
242
+ prompt: zod.z.string(),
243
+ output: zod.z.object({
244
+ name: zod.z.string(),
245
+ schema: zod.z.custom(
246
+ (value) => value instanceof zod.z.ZodType,
247
+ "Expected a Zod schema for `output.schema`"
248
+ )
249
+ })
250
+ });
251
+ const validateValueFnDef = (fnDef) => {
252
+ const parsed = zs_ValueFnDef.safeParse(fnDef);
253
+ if (!parsed.success) {
254
+ const message = zod.z.prettifyError(parsed.error);
255
+ throw new TypeError(`Invalid value function definition: ${message}`);
256
+ }
257
+ return parsed.data;
258
+ };
259
+
260
+ const toCastValueApiRequest = (prompt, data, outputName, outputSchema) => ({
261
+ input: {
262
+ prompt,
263
+ data
264
+ },
265
+ output: {
266
+ schema: outputSchema,
267
+ name: outputName
268
+ }
269
+ });
270
+ const createValueFn = (client, inputFnDef) => {
271
+ const fnDef = validateValueFnDef(inputFnDef);
272
+ const outputJsonSchema = zod.z.toJSONSchema(fnDef.output.schema);
273
+ const toNuabaseError = (response) => {
274
+ if (response && typeof response === "object" && "error" in response)
275
+ return { error: String(response.error), isError: true };
276
+ return void 0;
277
+ };
278
+ const sendRequest = async (path, data) => client.request(
279
+ path,
280
+ toCastValueApiRequest(fnDef.prompt, data, fnDef.output.name, outputJsonSchema)
281
+ );
282
+ const baseFn = async (data) => {
283
+ const response = await sendRequest("cast/value", data);
284
+ const error = toNuabaseError(response);
285
+ if (error) return error;
286
+ const parsedResponse = zs_NuaQueuedResponse.safeParse(response);
287
+ if (!parsedResponse.success) {
288
+ const emsg = zod.z.prettifyError(parsedResponse.error);
289
+ return { error: `Invalid queued response received from Nuabase API: ${emsg}`, isError: true };
290
+ }
291
+ return parsedResponse.data;
292
+ };
293
+ const nowFn = async (data) => {
294
+ const response = await sendRequest("cast/value/now", data);
295
+ const error = toNuabaseError(response);
296
+ if (error) return error;
297
+ const outputKey = fnDef.output.name;
298
+ const successSchema = zs_NuaApiResponse_CastValue(
299
+ outputKey,
300
+ fnDef.output.schema
301
+ );
302
+ const parsedResponse = successSchema.safeParse(response);
303
+ if (!parsedResponse.success) {
304
+ const emsg = zod.z.prettifyError(parsedResponse.error);
305
+ return { error: `Invalid data received from Nuabase API: ${emsg}`, isError: true };
306
+ }
307
+ return {
308
+ isSuccess: true,
309
+ llmRequestId: parsedResponse.data.llmRequestId,
310
+ kind: parsedResponse.data.kind,
311
+ data: parsedResponse.data.data,
312
+ isCacheHit: parsedResponse.data.isCacheHit
170
313
  };
171
- const fnReturnValue = Object.assign(baseInvoker, {
172
- now: nowInvoker
173
- });
174
- return fnReturnValue;
314
+ };
315
+ return Object.assign(baseFn, {
316
+ now: nowFn
317
+ });
318
+ };
319
+
320
+ class Nua {
321
+ constructor(config) {
322
+ this.apiClient = new NuabaseAPIClient(config);
323
+ }
324
+ createArrayFn(fnDef) {
325
+ return createArrayFn(this.apiClient, fnDef);
326
+ }
327
+ createFn(fnDef) {
328
+ return createValueFn(this.apiClient, fnDef);
175
329
  }
176
330
  }
177
331
 
@@ -81,22 +81,46 @@ class NuabaseAPIClient {
81
81
  }
82
82
  }
83
83
 
84
- const zs_MapResponseRecord = (primaryKeyName, outputKey, outputSchema) => {
84
+ const zs_NuaQueuedResponse = z.object({
85
+ id: z.string(),
86
+ sseUrl: z.string().min(1)
87
+ }).strict();
88
+
89
+ const validateArrayRequestParams = (data, primaryKeyName) => {
90
+ if (!Array.isArray(data)) {
91
+ throw new Error("`data` must be an array of objects.");
92
+ }
93
+ if (!primaryKeyName || typeof primaryKeyName !== "string") {
94
+ throw new Error("`primaryKeyName` must be a non-empty string.");
95
+ }
96
+ data.forEach((item, index) => {
97
+ if (item === null || typeof item !== "object" || Array.isArray(item)) {
98
+ throw new Error(`data[${index}] must be a non-null object.`);
99
+ }
100
+ if (!(primaryKeyName in item)) {
101
+ throw new Error(`data[${index}] must contain property '${primaryKeyName}'.`);
102
+ }
103
+ });
104
+ };
105
+
106
+ const zs_ResponseRecord = (primaryKeyName, outputKey, outputSchema) => {
85
107
  const primaryKeySchema = z.any();
86
- return z.object({
108
+ const shape = {
87
109
  [primaryKeyName]: primaryKeySchema,
88
110
  [outputKey]: outputSchema
89
- }).strict();
111
+ };
112
+ return z.object(shape).strict();
90
113
  };
91
- const zs_MapSuccessDataResult = (primaryKeyName, outputKey, outputSchema) => {
92
- const recordSchema = zs_MapResponseRecord(primaryKeyName, outputKey, outputSchema);
93
- return z.array(recordSchema);
94
- };
95
- const zs_MapRequestApiResponse_Success = (primaryKeyName, outputKey, outputSchema) => {
96
- const dataSchema = zs_MapSuccessDataResult(primaryKeyName, outputKey, outputSchema);
114
+ const createNuaApiResponseCastArray = (primaryKeyName, outputKey, outputSchema) => {
115
+ const recordSchema = zs_ResponseRecord(
116
+ primaryKeyName,
117
+ outputKey,
118
+ outputSchema
119
+ );
120
+ const dataSchema = z.array(recordSchema);
97
121
  return z.object({
98
122
  llmRequestId: z.string(),
99
- kind: z.literal("map"),
123
+ kind: z.literal("cast/array"),
100
124
  data: dataSchema,
101
125
  cacheHits: z.number(),
102
126
  rowsWithNoResults: z.array(z.string()),
@@ -104,72 +128,202 @@ const zs_MapRequestApiResponse_Success = (primaryKeyName, outputKey, outputSchem
104
128
  isError: z.optional(z.literal(false))
105
129
  }).strict();
106
130
  };
107
- class Nua {
108
- constructor(config) {
109
- this.apiClient = new NuabaseAPIClient(config);
131
+ const zs_NuaApiResponse_CastArray = createNuaApiResponseCastArray;
132
+
133
+ const zs_ArrayFnDef = z.object({
134
+ prompt: z.string(),
135
+ output: z.object({
136
+ name: z.string(),
137
+ schema: z.custom(
138
+ (value) => value instanceof z.ZodType,
139
+ "Expected a Zod schema"
140
+ )
141
+ })
142
+ });
143
+ const validateArrayFnDef = (fnDef) => {
144
+ const parsed = zs_ArrayFnDef.safeParse(fnDef);
145
+ if (!parsed.success) {
146
+ const message = z.prettifyError(parsed.error);
147
+ throw new TypeError(`Invalid array function definition: ${message}`);
148
+ }
149
+ return parsed.data;
150
+ };
151
+
152
+ const toCastArrayApiRequest = (prompt, data, primaryKeyName, outputName, outputSchema) => ({
153
+ input: {
154
+ prompt,
155
+ data,
156
+ primaryKey: primaryKeyName
157
+ },
158
+ output: {
159
+ schema: outputSchema,
160
+ name: outputName
110
161
  }
111
- // Some of the checks here mirror type validation, but since our code could be run from
112
- // untyped JavaScript, we still want runtime checks
113
- static validateMapRequestParams(data, primaryKeyName) {
114
- if (!Array.isArray(data)) {
115
- throw new Error("`data` must be an array of objects.");
162
+ });
163
+ const createArrayFn = (client, inputFnDef) => {
164
+ const fnDef = validateArrayFnDef(inputFnDef);
165
+ const outputJsonSchema = z.toJSONSchema(fnDef.output.schema);
166
+ const toNuabaseError = (response) => {
167
+ if (response && typeof response === "object" && "error" in response)
168
+ return { error: String(response.error), isError: true };
169
+ return void 0;
170
+ };
171
+ const baseFn = async (data, primaryKeyName) => {
172
+ validateArrayRequestParams(data, primaryKeyName);
173
+ const response = await client.request(
174
+ "cast/array",
175
+ toCastArrayApiRequest(
176
+ fnDef.prompt,
177
+ data,
178
+ primaryKeyName,
179
+ fnDef.output.name,
180
+ outputJsonSchema
181
+ )
182
+ );
183
+ const error = toNuabaseError(response);
184
+ if (error) return error;
185
+ const parsedResponse = zs_NuaQueuedResponse.safeParse(response);
186
+ if (!parsedResponse.success) {
187
+ const emsg = z.prettifyError(parsedResponse.error);
188
+ return { error: `Invalid queued response received from Nuabase API: ${emsg}`, isError: true };
116
189
  }
117
- if (!primaryKeyName || typeof primaryKeyName !== "string") {
118
- throw new Error("`primaryKeyName` must be a non-empty string.");
190
+ return parsedResponse.data;
191
+ };
192
+ const nowFn = async (data, primaryKeyName) => {
193
+ validateArrayRequestParams(data, primaryKeyName);
194
+ const response = await client.request(
195
+ "cast/array/now",
196
+ toCastArrayApiRequest(
197
+ fnDef.prompt,
198
+ data,
199
+ primaryKeyName,
200
+ fnDef.output.name,
201
+ outputJsonSchema
202
+ )
203
+ );
204
+ const error = toNuabaseError(response);
205
+ if (error) return error;
206
+ const outputKey = fnDef.output.name;
207
+ const successSchema = zs_NuaApiResponse_CastArray(primaryKeyName, outputKey, fnDef.output.schema);
208
+ const parsedResponse = successSchema.safeParse(response);
209
+ if (!parsedResponse.success) {
210
+ const emsg = z.prettifyError(parsedResponse.error);
211
+ return { error: `Invalid data received from Nuabase API: ${emsg}`, isError: true };
119
212
  }
120
- data.forEach((item, index) => {
121
- if (item === null || typeof item !== "object" || Array.isArray(item)) {
122
- throw new Error(`data[${index}] must be a non-null object.`);
123
- }
124
- if (!(primaryKeyName in item)) {
125
- throw new Error(`data[${index}] must contain property '${primaryKeyName}'.`);
126
- }
127
- });
128
- }
129
- map(fnDef) {
130
- const client = this.apiClient;
131
- const outputJsonSchema = z.toJSONSchema(fnDef.output);
132
- const makeRequest = async (path, data, primaryKeyName) => {
133
- Nua.validateMapRequestParams(data, primaryKeyName);
134
- const response = await client.request(path, {
135
- input: {
136
- prompt: fnDef.prompt,
137
- data,
138
- primaryKey: primaryKeyName
139
- },
140
- output: {
141
- schema: outputJsonSchema,
142
- name: fnDef.name
143
- }
144
- });
145
- if (response && typeof response === "object" && "error" in response)
146
- return { error: String(response.error), isError: true };
147
- const outputKey = fnDef.name;
148
- const successSchema = zs_MapRequestApiResponse_Success(primaryKeyName, outputKey, fnDef.output);
149
- const parsedResponse = successSchema.safeParse(response);
150
- if (!parsedResponse.success) {
151
- const emsg = z.prettifyError(parsedResponse.error);
152
- return { error: `Invalid data received from Nuabase API: ${emsg}`, isError: true };
153
- }
154
- return {
155
- isSuccess: true,
156
- llmRequestId: parsedResponse.data.llmRequestId,
157
- kind: parsedResponse.data.kind,
158
- data: parsedResponse.data.data,
159
- rowsWithNoResults: parsedResponse.data.rowsWithNoResults,
160
- cacheHits: parsedResponse.data.cacheHits
161
- };
213
+ return {
214
+ isSuccess: true,
215
+ llmRequestId: parsedResponse.data.llmRequestId,
216
+ kind: parsedResponse.data.kind,
217
+ data: parsedResponse.data.data,
218
+ rowsWithNoResults: parsedResponse.data.rowsWithNoResults,
219
+ cacheHits: parsedResponse.data.cacheHits
162
220
  };
163
- const baseInvoker = async (data, primaryKeyName) => {
164
- return await makeRequest("map", data, primaryKeyName);
165
- };
166
- const nowInvoker = async (data, primaryKeyName) => {
167
- return await makeRequest("map/now", data, primaryKeyName);
221
+ };
222
+ return Object.assign(baseFn, {
223
+ now: nowFn
224
+ });
225
+ };
226
+
227
+ const createNuaApiResponseCastValue = (outputKey, outputSchema) => {
228
+ return z.object({
229
+ llmRequestId: z.string(),
230
+ kind: z.literal("cast/value"),
231
+ data: outputSchema,
232
+ isCacheHit: z.boolean(),
233
+ isSuccess: z.literal(true),
234
+ isError: z.optional(z.literal(false))
235
+ }).strict();
236
+ };
237
+ const zs_NuaApiResponse_CastValue = createNuaApiResponseCastValue;
238
+
239
+ const zs_ValueFnDef = z.object({
240
+ prompt: z.string(),
241
+ output: z.object({
242
+ name: z.string(),
243
+ schema: z.custom(
244
+ (value) => value instanceof z.ZodType,
245
+ "Expected a Zod schema for `output.schema`"
246
+ )
247
+ })
248
+ });
249
+ const validateValueFnDef = (fnDef) => {
250
+ const parsed = zs_ValueFnDef.safeParse(fnDef);
251
+ if (!parsed.success) {
252
+ const message = z.prettifyError(parsed.error);
253
+ throw new TypeError(`Invalid value function definition: ${message}`);
254
+ }
255
+ return parsed.data;
256
+ };
257
+
258
+ const toCastValueApiRequest = (prompt, data, outputName, outputSchema) => ({
259
+ input: {
260
+ prompt,
261
+ data
262
+ },
263
+ output: {
264
+ schema: outputSchema,
265
+ name: outputName
266
+ }
267
+ });
268
+ const createValueFn = (client, inputFnDef) => {
269
+ const fnDef = validateValueFnDef(inputFnDef);
270
+ const outputJsonSchema = z.toJSONSchema(fnDef.output.schema);
271
+ const toNuabaseError = (response) => {
272
+ if (response && typeof response === "object" && "error" in response)
273
+ return { error: String(response.error), isError: true };
274
+ return void 0;
275
+ };
276
+ const sendRequest = async (path, data) => client.request(
277
+ path,
278
+ toCastValueApiRequest(fnDef.prompt, data, fnDef.output.name, outputJsonSchema)
279
+ );
280
+ const baseFn = async (data) => {
281
+ const response = await sendRequest("cast/value", data);
282
+ const error = toNuabaseError(response);
283
+ if (error) return error;
284
+ const parsedResponse = zs_NuaQueuedResponse.safeParse(response);
285
+ if (!parsedResponse.success) {
286
+ const emsg = z.prettifyError(parsedResponse.error);
287
+ return { error: `Invalid queued response received from Nuabase API: ${emsg}`, isError: true };
288
+ }
289
+ return parsedResponse.data;
290
+ };
291
+ const nowFn = async (data) => {
292
+ const response = await sendRequest("cast/value/now", data);
293
+ const error = toNuabaseError(response);
294
+ if (error) return error;
295
+ const outputKey = fnDef.output.name;
296
+ const successSchema = zs_NuaApiResponse_CastValue(
297
+ outputKey,
298
+ fnDef.output.schema
299
+ );
300
+ const parsedResponse = successSchema.safeParse(response);
301
+ if (!parsedResponse.success) {
302
+ const emsg = z.prettifyError(parsedResponse.error);
303
+ return { error: `Invalid data received from Nuabase API: ${emsg}`, isError: true };
304
+ }
305
+ return {
306
+ isSuccess: true,
307
+ llmRequestId: parsedResponse.data.llmRequestId,
308
+ kind: parsedResponse.data.kind,
309
+ data: parsedResponse.data.data,
310
+ isCacheHit: parsedResponse.data.isCacheHit
168
311
  };
169
- const fnReturnValue = Object.assign(baseInvoker, {
170
- now: nowInvoker
171
- });
172
- return fnReturnValue;
312
+ };
313
+ return Object.assign(baseFn, {
314
+ now: nowFn
315
+ });
316
+ };
317
+
318
+ class Nua {
319
+ constructor(config) {
320
+ this.apiClient = new NuabaseAPIClient(config);
321
+ }
322
+ createArrayFn(fnDef) {
323
+ return createArrayFn(this.apiClient, fnDef);
324
+ }
325
+ createFn(fnDef) {
326
+ return createValueFn(this.apiClient, fnDef);
173
327
  }
174
328
  }
175
329
 
@@ -0,0 +1,12 @@
1
+ import z from 'zod';
2
+ export declare const processedSchema: z.ZodObject<{
3
+ processed_id: z.ZodString;
4
+ }, z.core.$strip>;
5
+ export declare const sseResponseSchema: z.ZodObject<{
6
+ isSuccess: z.ZodLiteral<true>;
7
+ llmRequestId: z.ZodString;
8
+ kind: z.ZodLiteral<"cast/value">;
9
+ data: z.ZodUnknown;
10
+ isCacheHit: z.ZodOptional<z.ZodBoolean>;
11
+ }, z.core.$loose>;
12
+ export declare const waitForSuccessfulSseMessage: (sseUrl: string, timeoutMs?: number) => Promise<Record<string, unknown>>;
@@ -0,0 +1,4 @@
1
+ import { NuabaseAPIClient } from '../../lib/api-client';
2
+ import { ArrayFn, ArrayFnDef } from './types';
3
+ import { z } from 'zod';
4
+ export declare const createArrayFn: <OutputName extends string, OutputZodSchema extends z.ZodTypeAny>(client: NuabaseAPIClient, inputFnDef: ArrayFnDef<OutputName, OutputZodSchema>) => ArrayFn<OutputZodSchema, OutputName>;
@@ -0,0 +1 @@
1
+ export declare const validateArrayRequestParams: (data: unknown, primaryKeyName: unknown) => void;
@@ -0,0 +1,17 @@
1
+ import { z } from 'zod';
2
+ export type UserDataPKValue = string | number;
3
+ export type PrimaryKeyedInputRecord<PrimaryKeyName extends string> = Record<PrimaryKeyName, UserDataPKValue> & Record<string, unknown>;
4
+ type ResponseRow<OutputZodSchema extends z.ZodTypeAny, OutputName extends string, PrimaryKeyName extends string, InputRecord extends PrimaryKeyedInputRecord<PrimaryKeyName>> = Pick<InputRecord, PrimaryKeyName> & Record<OutputName, z.infer<OutputZodSchema>>;
5
+ type NuaApiResponseShape<OutputZodSchema extends z.ZodTypeAny, OutputName extends string, PrimaryKeyName extends string, InputRecord extends PrimaryKeyedInputRecord<PrimaryKeyName>> = {
6
+ llmRequestId: string;
7
+ kind: 'cast/array';
8
+ data: Array<ResponseRow<OutputZodSchema, OutputName, PrimaryKeyName, InputRecord>>;
9
+ cacheHits: number;
10
+ rowsWithNoResults: string[];
11
+ isSuccess: true;
12
+ isError?: false;
13
+ };
14
+ type NuaApiResponseCastArrayBuilder = <OutputZodSchema extends z.ZodTypeAny, OutputName extends string, PrimaryKeyName extends string, InputRecord extends PrimaryKeyedInputRecord<PrimaryKeyName>>(primaryKeyName: PrimaryKeyName, outputKey: OutputName, outputSchema: OutputZodSchema) => z.ZodType<NuaApiResponseShape<OutputZodSchema, OutputName, PrimaryKeyName, InputRecord>>;
15
+ export declare const zs_NuaApiResponse_CastArray: NuaApiResponseCastArrayBuilder;
16
+ export type NuaApiResponse_CastArray<OutputZodSchema extends z.ZodTypeAny, OutputName extends string, PrimaryKeyName extends string, InputRecord extends PrimaryKeyedInputRecord<PrimaryKeyName>> = z.infer<ReturnType<typeof zs_NuaApiResponse_CastArray<OutputZodSchema, OutputName, PrimaryKeyName, InputRecord>>>;
17
+ export {};
@@ -0,0 +1,18 @@
1
+ import { NuabaseError } from '../../lib/error-response';
2
+ import { NuaQueuedResponse } from '../common/queued-response';
3
+ import type { NuaApiResponse_CastArray, PrimaryKeyedInputRecord } from './response-schema';
4
+ import { z } from 'zod';
5
+ export type ArrayFnDef<OutputName extends string, OutputZodSchema extends z.ZodTypeAny> = {
6
+ prompt: string;
7
+ output: {
8
+ name: OutputName;
9
+ schema: OutputZodSchema;
10
+ };
11
+ };
12
+ export declare const validateArrayFnDef: <OutputName extends string, OutputZodSchema extends z.ZodTypeAny>(fnDef: ArrayFnDef<OutputName, OutputZodSchema>) => ArrayFnDef<OutputName, OutputZodSchema>;
13
+ export type ArrayFnResult<OutputZodSchema extends z.ZodTypeAny, OutputName extends string, PrimaryKeyName extends string, InputRecord extends PrimaryKeyedInputRecord<PrimaryKeyName>> = NuabaseError | NuaApiResponse_CastArray<OutputZodSchema, OutputName, PrimaryKeyName, InputRecord>;
14
+ export type ArrayFnQueuedResult = NuabaseError | NuaQueuedResponse;
15
+ export type ArrayFn<OutputZodSchema extends z.ZodTypeAny, OutputName extends string> = {
16
+ <PrimaryKeyName extends string, InputRecord extends PrimaryKeyedInputRecord<PrimaryKeyName>>(data: InputRecord[], primaryKeyName: PrimaryKeyName): Promise<ArrayFnQueuedResult>;
17
+ now: <PrimaryKeyName extends string, InputRecord extends PrimaryKeyedInputRecord<PrimaryKeyName>>(data: InputRecord[], primaryKeyName: PrimaryKeyName) => Promise<ArrayFnResult<OutputZodSchema, OutputName, PrimaryKeyName, InputRecord>>;
18
+ };
@@ -0,0 +1,4 @@
1
+ import { NuabaseAPIClient } from '../../lib/api-client';
2
+ import { ValueFn, ValueFnDef } from './types';
3
+ import { z } from 'zod';
4
+ export declare const createValueFn: <OutputName extends string, OutputZodSchema extends z.ZodTypeAny>(client: NuabaseAPIClient, inputFnDef: ValueFnDef<OutputName, OutputZodSchema>) => ValueFn<OutputZodSchema, OutputName>;
@@ -0,0 +1,13 @@
1
+ import { z } from 'zod';
2
+ type NuaApiResponseShape<OutputZodSchema extends z.ZodTypeAny> = {
3
+ llmRequestId: string;
4
+ kind: 'cast/value';
5
+ data: z.infer<OutputZodSchema>;
6
+ isCacheHit: boolean;
7
+ isSuccess: true;
8
+ isError?: false;
9
+ };
10
+ type NuaApiResponseCastValueBuilder = <OutputZodSchema extends z.ZodTypeAny, OutputName extends string>(outputKey: OutputName, outputSchema: OutputZodSchema) => z.ZodType<NuaApiResponseShape<OutputZodSchema>>;
11
+ export declare const zs_NuaApiResponse_CastValue: NuaApiResponseCastValueBuilder;
12
+ export type NuaApiResponse_CastValue<OutputZodSchema extends z.ZodTypeAny, OutputName extends string> = z.infer<ReturnType<typeof zs_NuaApiResponse_CastValue<OutputZodSchema, OutputName>>>;
13
+ export {};
@@ -0,0 +1,18 @@
1
+ import { NuabaseError } from '../../lib/error-response';
2
+ import { NuaQueuedResponse } from '../common/queued-response';
3
+ import { NuaApiResponse_CastValue } from './response-schema';
4
+ import { z } from 'zod';
5
+ export type ValueFnDef<OutputName extends string, OutputZodSchema extends z.ZodTypeAny> = {
6
+ prompt: string;
7
+ output: {
8
+ name: OutputName;
9
+ schema: OutputZodSchema;
10
+ };
11
+ };
12
+ export declare const validateValueFnDef: <OutputName extends string, OutputZodSchema extends z.ZodTypeAny>(fnDef: ValueFnDef<OutputName, OutputZodSchema>) => ValueFnDef<OutputName, OutputZodSchema>;
13
+ export type ValueFnResult<OutputZodSchema extends z.ZodTypeAny, OutputName extends string> = NuabaseError | NuaApiResponse_CastValue<OutputZodSchema, OutputName>;
14
+ export type ValueFnQueuedResult = NuabaseError | NuaQueuedResponse;
15
+ export type ValueFn<OutputZodSchema extends z.ZodTypeAny, OutputName extends string> = {
16
+ (data: unknown): Promise<ValueFnQueuedResult>;
17
+ now: (data: unknown) => Promise<ValueFnResult<OutputZodSchema, OutputName>>;
18
+ };
@@ -0,0 +1,6 @@
1
+ import { z } from 'zod';
2
+ export declare const zs_NuaQueuedResponse: z.ZodObject<{
3
+ id: z.ZodString;
4
+ sseUrl: z.ZodString;
5
+ }, z.core.$strict>;
6
+ export type NuaQueuedResponse = z.infer<typeof zs_NuaQueuedResponse>;
@@ -0,0 +1,10 @@
1
+ import { NuabaseAPIClient, NuabaseConfig } from '../lib/api-client';
2
+ import { ArrayFn, ArrayFnDef } from './cast_array/types';
3
+ import { ValueFn, ValueFnDef } from './cast_value/types';
4
+ import { z } from 'zod';
5
+ export declare class Nua {
6
+ apiClient: NuabaseAPIClient;
7
+ constructor(config?: NuabaseConfig);
8
+ createArrayFn<OutputName extends string, OutputZodSchema extends z.ZodTypeAny>(fnDef: ArrayFnDef<OutputName, OutputZodSchema>): ArrayFn<OutputZodSchema, OutputName>;
9
+ createFn<OutputName extends string, OutputZodSchema extends z.ZodTypeAny>(fnDef: ValueFnDef<OutputName, OutputZodSchema>): ValueFn<OutputZodSchema, OutputName>;
10
+ }
@@ -0,0 +1,4 @@
1
+ export { Nua } from './core';
2
+ export { createArrayFn } from './cast_array/factory';
3
+ export { validateArrayRequestParams } from './cast_array/request-validation';
4
+ export type { ArrayFn, ArrayFnDef } from './cast_array/types';
@@ -1,33 +1,3 @@
1
- import { NuabaseAPIClient, NuabaseConfig } from './lib/api-client';
2
- import { NuabaseError } from './lib/error-response';
3
- import { z } from 'zod';
4
- type MappingFnDef<OutputName extends string, OutputZodSchema extends z.ZodTypeAny> = {
5
- name: OutputName;
6
- prompt: string;
7
- output: OutputZodSchema;
8
- };
9
- type MapResponseRecord<OutputZodSchema extends z.ZodTypeAny, OutputName extends string, InputRecord extends Record<string, unknown>, PrimaryKeyName extends keyof InputRecord & string> = {
10
- [Key in PrimaryKeyName | OutputName]: Key extends PrimaryKeyName ? InputRecord[PrimaryKeyName] : z.infer<OutputZodSchema>;
11
- };
12
- type MapSuccessDataResult<OutputZodSchema extends z.ZodTypeAny, OutputName extends string, InputRecord extends Record<string, unknown>, PrimaryKeyName extends keyof InputRecord & string> = Array<MapResponseRecord<OutputZodSchema, OutputName, InputRecord, PrimaryKeyName>>;
13
- type MapRequestApiResponse_Success<OutputZodSchema extends z.ZodTypeAny, OutputName extends string, InputRecord extends Record<string, unknown>, PrimaryKeyName extends keyof InputRecord & string> = {
14
- data: MapSuccessDataResult<OutputZodSchema, OutputName, InputRecord, PrimaryKeyName>;
15
- cacheHits: number;
16
- llmRequestId: string;
17
- kind: 'map';
18
- rowsWithNoResults: string[];
19
- isSuccess: true;
20
- isError?: false;
21
- };
22
- type MapResult<OutputZodSchema extends z.ZodTypeAny, OutputName extends string, InputRecord extends Record<string, unknown>, PrimaryKeyName extends keyof InputRecord & string> = NuabaseError | MapRequestApiResponse_Success<OutputZodSchema, OutputName, InputRecord, PrimaryKeyName>;
23
- type MapInvoker<OutputZodSchema extends z.ZodTypeAny, OutputName extends string> = {
24
- <InputRecord extends Record<string, unknown>, PrimaryKeyName extends keyof InputRecord & string>(data: InputRecord[], primaryKeyName: PrimaryKeyName): Promise<MapResult<OutputZodSchema, OutputName, InputRecord, PrimaryKeyName>>;
25
- now: <InputRecord extends Record<string, unknown>, PrimaryKeyName extends keyof InputRecord & string>(data: InputRecord[], primaryKeyName: PrimaryKeyName) => Promise<MapResult<OutputZodSchema, OutputName, InputRecord, PrimaryKeyName>>;
26
- };
27
- export declare class Nua {
28
- apiClient: NuabaseAPIClient;
29
- constructor(config?: NuabaseConfig);
30
- static validateMapRequestParams(data: unknown, primaryKeyName: unknown): void;
31
- map<OutputName extends string, OutputZodSchema extends z.ZodTypeAny>(fnDef: MappingFnDef<OutputName, OutputZodSchema>): MapInvoker<OutputZodSchema, OutputName>;
32
- }
33
- export {};
1
+ export { Nua } from './nua/index';
2
+ export { createArrayFn, validateArrayRequestParams } from './nua/index';
3
+ export type { ArrayFn, ArrayFnDef } from './nua/index';
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "nuabase",
3
3
  "description": "Nuabase TypeScript client to create typed functions from LLM prompts",
4
4
  "packageManager": "yarn@4.10.3+sha512.c38cafb5c7bb273f3926d04e55e1d8c9dfa7d9c3ea1f36a4868fa028b9e5f72298f0b7f401ad5eb921749eb012eb1c3bb74bf7503df3ee43fd600d14a018266f",
5
- "version": "1.0.0",
5
+ "version": "1.0.1",
6
6
  "type": "module",
7
7
  "author": "Jasim A Basheer <jasim@nuabase.com>",
8
8
  "engines": {
@@ -69,6 +69,7 @@
69
69
  "chalk": "^5.6.2",
70
70
  "esbuild": "^0.25.9",
71
71
  "eslint": "^9.35.0",
72
+ "eventsource": "^4.0.0",
72
73
  "fs-extra": "^11.3.1",
73
74
  "jest": "^30.1.3",
74
75
  "knip": "^5.63.1",