@nubase/core 0.1.0 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,4 +1,269 @@
1
- // schema/base-schema.ts
1
+ // ../frontend/src/http/http-client.ts
2
+ import axios, { AxiosError } from "axios";
3
+
4
+ // ../frontend/src/utils/network-errors.ts
5
+ var NetworkError = class extends Error {
6
+ endpoint;
7
+ method;
8
+ timestamp;
9
+ constructor(message, options) {
10
+ super(message);
11
+ this.name = this.constructor.name;
12
+ this.endpoint = options.endpoint;
13
+ this.method = options.method;
14
+ this.timestamp = /* @__PURE__ */ new Date();
15
+ if (Error.captureStackTrace) {
16
+ Error.captureStackTrace(this, this.constructor);
17
+ }
18
+ }
19
+ };
20
+ var ClientNetworkError = class _ClientNetworkError extends NetworkError {
21
+ // Custom inspect method for Node.js and some browser consoles
22
+ [/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
23
+ return this.toJSON();
24
+ }
25
+ // For browsers that support console formatting
26
+ get [Symbol.toStringTag]() {
27
+ return JSON.stringify(this.toJSON(), null, 2);
28
+ }
29
+ phase;
30
+ parseErrors;
31
+ originalError;
32
+ zodError;
33
+ constructor(message, options) {
34
+ super(message, options);
35
+ this.phase = options.phase;
36
+ this.parseErrors = options.parseErrors;
37
+ this.originalError = options.originalError;
38
+ this.zodError = options.zodError;
39
+ }
40
+ toString() {
41
+ const details = [];
42
+ details.push(`${this.name}: ${this.message}`);
43
+ details.push(` Endpoint: ${this.method} ${this.endpoint}`);
44
+ details.push(` Phase: ${this.phase}`);
45
+ details.push(` Timestamp: ${this.timestamp.toISOString()}`);
46
+ if (this.zodError) {
47
+ details.push(` Zod Error: ${this.zodError.toString()}`);
48
+ if (this.zodError.issues.length > 0) {
49
+ details.push(" Validation Issues:");
50
+ this.zodError.issues.forEach((issue, index) => {
51
+ const path = issue.path.length > 0 ? issue.path.join(".") : "root";
52
+ details.push(
53
+ ` ${index + 1}. Path: ${path}, Message: ${issue.message}, Code: ${issue.code}`
54
+ );
55
+ });
56
+ }
57
+ }
58
+ if (this.parseErrors && this.parseErrors.length > 0) {
59
+ details.push(" Parse Errors:");
60
+ this.parseErrors.forEach((error, index) => {
61
+ details.push(
62
+ ` ${index + 1}. Path: ${error.path}, Message: ${error.message}, Code: ${error.code}`
63
+ );
64
+ if (error.expected) details.push(` Expected: ${error.expected}`);
65
+ if (error.received) details.push(` Received: ${error.received}`);
66
+ });
67
+ }
68
+ if (this.originalError) {
69
+ const errorStr = this.originalError instanceof Error ? this.originalError.toString() : String(this.originalError);
70
+ details.push(` Original Error: ${errorStr}`);
71
+ }
72
+ return details.join("\n");
73
+ }
74
+ toJSON() {
75
+ const result = {
76
+ name: this.name,
77
+ message: this.message,
78
+ endpoint: this.endpoint,
79
+ method: this.method,
80
+ phase: this.phase,
81
+ timestamp: this.timestamp.toISOString()
82
+ };
83
+ if (this.zodError) {
84
+ result.zodError = this.zodError.issues;
85
+ }
86
+ if (this.parseErrors && this.parseErrors.length > 0) {
87
+ result.parseErrors = this.parseErrors;
88
+ }
89
+ if (this.originalError) {
90
+ result.originalError = this.originalError instanceof Error ? {
91
+ name: this.originalError.name,
92
+ message: this.originalError.message
93
+ } : this.originalError;
94
+ }
95
+ return result;
96
+ }
97
+ /**
98
+ * Creates a ClientNetworkError for network failures (no response from server)
99
+ */
100
+ static fromNetworkFailure(error, options) {
101
+ const message = error instanceof Error ? `Network request failed for ${options.method} ${options.endpoint}: ${error.message}` : `Network request failed for ${options.method} ${options.endpoint}`;
102
+ return new _ClientNetworkError(message, {
103
+ ...options,
104
+ phase: "network-failure",
105
+ originalError: error
106
+ });
107
+ }
108
+ };
109
+ var ServerNetworkError = class _ServerNetworkError extends NetworkError {
110
+ // Custom inspect method for Node.js and some browser consoles
111
+ [/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
112
+ return this.toJSON();
113
+ }
114
+ // For browsers that support console formatting
115
+ get [Symbol.toStringTag]() {
116
+ return JSON.stringify(this.toJSON(), null, 2);
117
+ }
118
+ statusCode;
119
+ responseText;
120
+ responseData;
121
+ constructor(message, options) {
122
+ super(message, options);
123
+ this.statusCode = options.statusCode;
124
+ this.responseText = options.responseText;
125
+ this.responseData = options.responseData;
126
+ }
127
+ toString() {
128
+ const details = [];
129
+ details.push(`${this.name}: ${this.message}`);
130
+ details.push(` Endpoint: ${this.method} ${this.endpoint}`);
131
+ details.push(` Status Code: ${this.statusCode}`);
132
+ details.push(` Timestamp: ${this.timestamp.toISOString()}`);
133
+ if (this.responseText) {
134
+ details.push(` Response Text: ${this.responseText}`);
135
+ }
136
+ if (this.responseData) {
137
+ try {
138
+ const dataStr = typeof this.responseData === "string" ? this.responseData : JSON.stringify(this.responseData, null, 2);
139
+ details.push(` Response Data: ${dataStr}`);
140
+ } catch {
141
+ details.push(` Response Data: ${String(this.responseData)}`);
142
+ }
143
+ }
144
+ return details.join("\n");
145
+ }
146
+ toJSON() {
147
+ const result = {
148
+ name: this.name,
149
+ message: this.message,
150
+ endpoint: this.endpoint,
151
+ method: this.method,
152
+ statusCode: this.statusCode,
153
+ timestamp: this.timestamp.toISOString()
154
+ };
155
+ if (this.responseText) {
156
+ result.responseText = this.responseText;
157
+ }
158
+ if (this.responseData) {
159
+ result.responseData = this.responseData;
160
+ }
161
+ return result;
162
+ }
163
+ /**
164
+ * Creates a ServerNetworkError from an HTTP response
165
+ */
166
+ static fromResponse(response, responseText, responseData) {
167
+ const message = `Server error ${response.status} for ${response.url}: ${response.statusText}`;
168
+ return new _ServerNetworkError(message, {
169
+ endpoint: response.url,
170
+ method: "GET",
171
+ // Will be overridden by caller with actual method
172
+ statusCode: response.status,
173
+ responseText,
174
+ responseData
175
+ });
176
+ }
177
+ /**
178
+ * Checks if this is a specific type of server error
179
+ */
180
+ isNotFound() {
181
+ return this.statusCode === 404;
182
+ }
183
+ isUnauthorized() {
184
+ return this.statusCode === 401;
185
+ }
186
+ isForbidden() {
187
+ return this.statusCode === 403;
188
+ }
189
+ isServerError() {
190
+ return this.statusCode >= 500;
191
+ }
192
+ isClientError() {
193
+ return this.statusCode >= 400 && this.statusCode < 500;
194
+ }
195
+ };
196
+
197
+ // ../frontend/src/http/http-client.ts
198
+ var HttpClient = class {
199
+ baseUrl;
200
+ constructor({ baseUrl = "" }) {
201
+ this.baseUrl = baseUrl;
202
+ }
203
+ async request(url, method, data, config = {}) {
204
+ const fullUrl = this.baseUrl ? `${this.baseUrl}${url}` : url;
205
+ try {
206
+ const response = await axios({
207
+ url: fullUrl,
208
+ method,
209
+ data,
210
+ headers: config.headers,
211
+ timeout: config.timeout,
212
+ params: config.params,
213
+ withCredentials: true
214
+ // Required for sending cookies in cross-origin requests
215
+ });
216
+ return {
217
+ status: response.status,
218
+ data: response.data
219
+ };
220
+ } catch (error) {
221
+ if (error instanceof AxiosError) {
222
+ if (error.response) {
223
+ throw new ServerNetworkError(
224
+ `Server error ${error.response.status} for ${method} ${url}: ${error.response.statusText}`,
225
+ {
226
+ endpoint: url,
227
+ method,
228
+ statusCode: error.response.status,
229
+ responseText: error.response.statusText,
230
+ responseData: error.response.data
231
+ }
232
+ );
233
+ }
234
+ if (error.request) {
235
+ throw ClientNetworkError.fromNetworkFailure(error, {
236
+ endpoint: url,
237
+ method
238
+ });
239
+ }
240
+ throw ClientNetworkError.fromNetworkFailure(error, {
241
+ endpoint: url,
242
+ method
243
+ });
244
+ }
245
+ throw error;
246
+ }
247
+ }
248
+ async get(url, config) {
249
+ return this.request(url, "GET", void 0, config);
250
+ }
251
+ async post(url, data, config) {
252
+ return this.request(url, "POST", data, config);
253
+ }
254
+ async put(url, data, config) {
255
+ return this.request(url, "PUT", data, config);
256
+ }
257
+ async patch(url, data, config) {
258
+ return this.request(url, "PATCH", data, config);
259
+ }
260
+ async delete(url, data, config) {
261
+ return this.request(url, "DELETE", data, config);
262
+ }
263
+ };
264
+
265
+ // src/schema/schema.ts
266
+ import { z } from "zod";
2
267
  var BaseSchema = class {
3
268
  /**
4
269
  * Phantom property used for TypeScript type inference.
@@ -12,105 +277,457 @@ var BaseSchema = class {
12
277
  * @param meta The new metadata object.
13
278
  * @returns The schema instance for chaining.
14
279
  */
15
- meta(meta) {
280
+ withMeta(meta) {
16
281
  this._meta = meta;
17
282
  return this;
18
283
  }
19
- // We could also add a safeParse method like Zod if needed
20
- // safeParse(data: any): { success: true; data: Output } | { success: false; error: SchemaError };
284
+ /**
285
+ * Add metadata with validation functions to the schema.
286
+ * @param meta The metadata object with validation functions.
287
+ * @returns The schema instance for chaining.
288
+ */
289
+ withMetadata(meta) {
290
+ this._meta = { ...this._meta, ...meta };
291
+ return this;
292
+ }
293
+ /**
294
+ * Makes this schema optional, allowing undefined values.
295
+ * @returns An OptionalSchema wrapping this schema.
296
+ */
297
+ optional() {
298
+ return new OptionalSchema(this);
299
+ }
21
300
  };
22
-
23
- // schema/types.ts
24
301
  var BooleanSchema = class extends BaseSchema {
25
- parse(data) {
26
- if (typeof data !== "boolean") {
27
- throw new Error(`Expected boolean, received ${typeof data}`);
28
- }
29
- return data;
302
+ type = "boolean";
303
+ toZod() {
304
+ return z.boolean();
30
305
  }
31
306
  };
32
307
  var StringSchema = class extends BaseSchema {
33
- parse(data) {
34
- if (typeof data !== "string") {
35
- throw new Error(`Expected string, received ${typeof data}`);
36
- }
37
- return data;
38
- }
308
+ type = "string";
39
309
  // Add string-specific validation methods here (e.g., minLength, pattern)
310
+ toZod() {
311
+ return z.string();
312
+ }
40
313
  };
41
314
  var NumberSchema = class extends BaseSchema {
42
- parse(data) {
43
- if (typeof data !== "number" || !Number.isFinite(data)) {
44
- throw new Error(`Expected number, received ${typeof data}`);
45
- }
46
- return data;
47
- }
315
+ type = "number";
48
316
  // Add number-specific validation methods here (e.g., min, max)
317
+ toZod() {
318
+ return z.number();
319
+ }
320
+ };
321
+ var OptionalSchema = class extends BaseSchema {
322
+ type = "optional";
323
+ _wrapped;
324
+ constructor(wrapped) {
325
+ super();
326
+ this._wrapped = wrapped;
327
+ }
328
+ /**
329
+ * Get the underlying wrapped schema.
330
+ * @returns The wrapped schema.
331
+ */
332
+ unwrap() {
333
+ return this._wrapped;
334
+ }
335
+ toZod() {
336
+ return this._wrapped.toZod().nullable().optional();
337
+ }
49
338
  };
50
- var ObjectSchema = class extends BaseSchema {
339
+ var ObjectSchema = class _ObjectSchema extends BaseSchema {
340
+ type = "object";
51
341
  _shape;
52
- constructor(shape) {
342
+ _computedMeta = {};
343
+ _layouts = {};
344
+ _idField = "id";
345
+ _catchall = null;
346
+ constructor(shape, catchall) {
53
347
  super();
54
348
  this._shape = shape;
349
+ if (catchall) {
350
+ this._catchall = catchall;
351
+ }
352
+ }
353
+ /**
354
+ * Allow additional properties beyond the defined shape, validated against the provided schema.
355
+ * Similar to Zod's .catchall() method.
356
+ *
357
+ * @example
358
+ * const chartDataSchema = nu.object({ category: nu.string() }).catchall(nu.number());
359
+ * // Validates: { category: "Jan", desktop: 186, mobile: 80 }
360
+ *
361
+ * @param schema The schema to validate additional properties against.
362
+ * @returns A new ObjectSchema that allows additional properties.
363
+ */
364
+ catchall(schema) {
365
+ const newSchema = new _ObjectSchema(
366
+ this._shape,
367
+ schema
368
+ );
369
+ newSchema._meta = { ...this._meta };
370
+ newSchema._computedMeta = { ...this._computedMeta };
371
+ newSchema._layouts = { ...this._layouts };
372
+ newSchema._idField = this._idField;
373
+ return newSchema;
374
+ }
375
+ /**
376
+ * Add computed metadata to the object schema.
377
+ * @param computedMeta Object mapping property keys to computed metadata functions.
378
+ * @returns The schema instance for chaining.
379
+ */
380
+ withComputed(computedMeta) {
381
+ this._computedMeta = computedMeta;
382
+ return this;
383
+ }
384
+ /**
385
+ * Add layouts to the object schema.
386
+ * @param layouts Object mapping layout names to layout configurations.
387
+ * @returns The schema instance for chaining.
388
+ * @deprecated Use withFormLayouts or withTableLayouts instead
389
+ */
390
+ withLayouts(layouts) {
391
+ this._layouts = layouts;
392
+ return this;
393
+ }
394
+ /**
395
+ * Add form layouts to the object schema.
396
+ * @param layouts Object mapping layout names to form layout configurations.
397
+ * @returns The schema instance for chaining.
398
+ */
399
+ withFormLayouts(layouts) {
400
+ this._layouts = { ...this._layouts, ...layouts };
401
+ return this;
402
+ }
403
+ /**
404
+ * Add table layouts to the object schema.
405
+ * @param layouts Object mapping layout names to table layout configurations with fields and optional linkFields.
406
+ * @returns The schema instance for chaining.
407
+ */
408
+ withTableLayouts(layouts) {
409
+ const tableLayouts = {};
410
+ for (const [name, tableConfig] of Object.entries(layouts)) {
411
+ tableLayouts[name] = {
412
+ type: "table",
413
+ groups: [
414
+ {
415
+ fields: tableConfig.fields
416
+ }
417
+ ],
418
+ className: tableConfig.className,
419
+ config: tableConfig.config,
420
+ metadata: tableConfig.metadata
421
+ };
422
+ }
423
+ this._layouts = { ...this._layouts, ...tableLayouts };
424
+ return this;
55
425
  }
56
- parse(data) {
57
- if (typeof data !== "object" || data === null) {
58
- throw new Error(`Expected object, received ${typeof data}`);
426
+ /**
427
+ * Specify which field serves as the unique identifier for this object schema.
428
+ * This is used by resource operations to determine how to identify specific records.
429
+ * @param idField The field name or a selector function that returns the ID field.
430
+ * @returns The schema instance for chaining.
431
+ */
432
+ withId(idField) {
433
+ if (typeof idField === "function") {
434
+ throw new Error(
435
+ "Function form of withId is not yet supported. Please pass the field name directly."
436
+ );
437
+ }
438
+ this._idField = idField;
439
+ return this;
440
+ }
441
+ /**
442
+ * Get the ID field name for this object schema.
443
+ * @returns The field name that serves as the unique identifier.
444
+ */
445
+ getIdField() {
446
+ return this._idField;
447
+ }
448
+ /**
449
+ * Get a specific layout by name.
450
+ * @param layoutName The name of the layout to retrieve.
451
+ * @returns The layout configuration or undefined if not found.
452
+ */
453
+ getLayout(layoutName) {
454
+ return this._layouts[layoutName];
455
+ }
456
+ /**
457
+ * Get all available layout names.
458
+ * @returns Array of layout names.
459
+ */
460
+ getLayoutNames() {
461
+ return Object.keys(this._layouts);
462
+ }
463
+ /**
464
+ * Check if a layout exists.
465
+ * @param layoutName The name of the layout to check.
466
+ * @returns True if the layout exists, false otherwise.
467
+ */
468
+ hasLayout(layoutName) {
469
+ return layoutName in this._layouts;
470
+ }
471
+ /**
472
+ * Get the computed metadata for a specific property.
473
+ * @param key The property key.
474
+ * @param data The parsed object data to pass to computed functions.
475
+ * @returns Promise resolving to the computed metadata.
476
+ */
477
+ async getComputedMeta(key, data) {
478
+ const computedMeta = this._computedMeta[key];
479
+ if (!computedMeta) {
480
+ return {};
59
481
  }
60
482
  const result = {};
61
- const errors = [];
483
+ if (computedMeta.label) {
484
+ result.label = await computedMeta.label(data);
485
+ }
486
+ if (computedMeta.description) {
487
+ result.description = await computedMeta.description(data);
488
+ }
489
+ if (computedMeta.defaultValue) {
490
+ result.defaultValue = await computedMeta.defaultValue(data);
491
+ }
492
+ return result;
493
+ }
494
+ /**
495
+ * Get all computed metadata for all properties.
496
+ * @param data The parsed object data to pass to computed functions.
497
+ * @returns Promise resolving to a map of property keys to computed metadata.
498
+ */
499
+ async getAllComputedMeta(data) {
500
+ const result = {};
62
501
  for (const key in this._shape) {
63
- if (Object.prototype.hasOwnProperty.call(this._shape, key)) {
64
- const schema = this._shape[key];
65
- const value = data[key];
66
- try {
67
- if (schema) {
68
- result[key] = schema.parse(value);
502
+ result[key] = await this.getComputedMeta(key, data);
503
+ }
504
+ return result;
505
+ }
506
+ /**
507
+ * Get merged metadata (static + computed) for all properties.
508
+ * @param data The current form data to pass to computed functions.
509
+ * @returns Promise resolving to a map of property keys to merged metadata.
510
+ */
511
+ async getAllMergedMeta(data) {
512
+ const result = {};
513
+ for (const key in this._shape) {
514
+ if (Object.hasOwn(this._shape, key)) {
515
+ let schema = this._shape[key];
516
+ if (schema) {
517
+ if (schema instanceof OptionalSchema) {
518
+ schema = schema._wrapped;
519
+ }
520
+ result[key] = { ...schema?._meta || {} };
521
+ }
522
+ }
523
+ }
524
+ if (Object.keys(this._computedMeta).length > 0) {
525
+ try {
526
+ const completeData = {};
527
+ for (const key in this._shape) {
528
+ if (Object.hasOwn(this._shape, key)) {
529
+ const schema = this._shape[key];
530
+ if (schema) {
531
+ const actualSchema = schema instanceof OptionalSchema ? schema._wrapped : schema;
532
+ if (data[key] !== void 0) {
533
+ completeData[key] = data[key];
534
+ } else if (actualSchema._meta.defaultValue !== void 0) {
535
+ completeData[key] = actualSchema._meta.defaultValue;
536
+ } else {
537
+ if (actualSchema instanceof StringSchema) {
538
+ completeData[key] = "";
539
+ } else if (actualSchema instanceof NumberSchema) {
540
+ completeData[key] = 0;
541
+ } else if (actualSchema instanceof BooleanSchema) {
542
+ completeData[key] = false;
543
+ } else {
544
+ completeData[key] = null;
545
+ }
546
+ }
547
+ }
548
+ }
549
+ }
550
+ const computedMeta = await this.getAllComputedMeta(
551
+ completeData
552
+ );
553
+ for (const key in computedMeta) {
554
+ if (computedMeta[key]) {
555
+ result[key] = {
556
+ ...result[key],
557
+ ...computedMeta[key]
558
+ };
69
559
  }
70
- } catch (e) {
71
- errors.push(`Property "${key}": ${e.message}`);
72
560
  }
561
+ } catch (error) {
562
+ console.warn("Failed to compute metadata:", error);
73
563
  }
74
564
  }
75
- for (const key in data) {
76
- if (Object.prototype.hasOwnProperty.call(data, key) && !Object.prototype.hasOwnProperty.call(this._shape, key)) {
565
+ return result;
566
+ }
567
+ /**
568
+ * Create a new ObjectSchema with certain properties omitted.
569
+ * @param keys The property keys to omit from the schema.
570
+ * @returns A new ObjectSchema without the specified properties.
571
+ */
572
+ omit(...keys) {
573
+ const newShape = {};
574
+ const keysToOmit = new Set(keys);
575
+ for (const key in this._shape) {
576
+ if (Object.hasOwn(this._shape, key) && !keysToOmit.has(key)) {
577
+ newShape[key] = this._shape[key];
578
+ }
579
+ }
580
+ const newSchema = new _ObjectSchema(newShape);
581
+ newSchema._meta = { ...this._meta };
582
+ const newComputedMeta = {};
583
+ for (const key in this._computedMeta) {
584
+ if (!keysToOmit.has(key)) {
585
+ newComputedMeta[key] = this._computedMeta[key];
77
586
  }
78
587
  }
79
- if (errors.length > 0) {
80
- throw new Error(`Object validation failed:
81
- ${errors.join("\n")}`);
588
+ newSchema._computedMeta = newComputedMeta;
589
+ const newLayouts = {};
590
+ for (const layoutName in this._layouts) {
591
+ const originalLayout = this._layouts[layoutName];
592
+ if (originalLayout) {
593
+ const newGroups = originalLayout.groups.map((group) => ({
594
+ ...group,
595
+ fields: group.fields.filter(
596
+ (field) => !keysToOmit.has(field.name)
597
+ )
598
+ }));
599
+ newLayouts[layoutName] = {
600
+ ...originalLayout,
601
+ groups: newGroups
602
+ };
603
+ }
82
604
  }
83
- return result;
605
+ newSchema._layouts = newLayouts;
606
+ return newSchema;
607
+ }
608
+ /**
609
+ * Create a new ObjectSchema with additional or modified properties.
610
+ * @param shape The new properties to add or existing properties to modify.
611
+ * @returns A new ObjectSchema with the extended shape.
612
+ */
613
+ extend(shape) {
614
+ const newShape = { ...this._shape, ...shape };
615
+ const newSchema = new _ObjectSchema(newShape);
616
+ newSchema._meta = { ...this._meta };
617
+ newSchema._computedMeta = { ...this._computedMeta };
618
+ newSchema._layouts = { ...this._layouts };
619
+ return newSchema;
620
+ }
621
+ /**
622
+ * Create a new ObjectSchema where all top-level properties become optional.
623
+ * This wraps each field in an OptionalSchema, similar to how Zod handles partial.
624
+ * @returns A new ObjectSchema where all properties are optional.
625
+ */
626
+ partial() {
627
+ const partialShape = {};
628
+ for (const key in this._shape) {
629
+ if (Object.hasOwn(this._shape, key)) {
630
+ const fieldSchema = this._shape[key];
631
+ if (fieldSchema) {
632
+ if (fieldSchema instanceof OptionalSchema) {
633
+ partialShape[key] = fieldSchema;
634
+ } else {
635
+ partialShape[key] = fieldSchema.optional();
636
+ }
637
+ }
638
+ }
639
+ }
640
+ const partialSchema = new _ObjectSchema(partialShape);
641
+ partialSchema._meta = { ...this._meta };
642
+ partialSchema._computedMeta = { ...this._computedMeta };
643
+ partialSchema._layouts = { ...this._layouts };
644
+ return partialSchema;
645
+ }
646
+ toZod() {
647
+ const zodShape = {};
648
+ for (const key in this._shape) {
649
+ if (Object.hasOwn(this._shape, key) && this._shape[key]) {
650
+ const fieldSchema = this._shape[key];
651
+ zodShape[key] = fieldSchema.toZod();
652
+ }
653
+ }
654
+ const baseZod = z.object(zodShape);
655
+ if (this._catchall) {
656
+ return baseZod.catchall(this._catchall.toZod());
657
+ }
658
+ return baseZod;
659
+ }
660
+ /**
661
+ * Returns a Zod schema with URL coercion enabled.
662
+ * This automatically converts string values from URL parameters to the expected types:
663
+ * - "37" → 37 (number)
664
+ * - "true" → true (boolean)
665
+ * - "hello" → "hello" (string, unchanged)
666
+ *
667
+ * Use this when parsing URL search params that arrive as strings but need to be typed.
668
+ *
669
+ * @returns A Zod schema with coercion for URL parameter parsing
670
+ */
671
+ toZodWithCoercion() {
672
+ const zodShape = {};
673
+ for (const key in this._shape) {
674
+ if (Object.hasOwn(this._shape, key) && this._shape[key]) {
675
+ const fieldSchema = this._shape[key];
676
+ const baseZodSchema = fieldSchema.toZod();
677
+ const wrappedType = fieldSchema.type === "optional" ? fieldSchema._wrapped.type : null;
678
+ if (fieldSchema.type === "number" || wrappedType === "number") {
679
+ const coercedSchema = z.coerce.number();
680
+ if (fieldSchema.type === "optional") {
681
+ zodShape[key] = coercedSchema.nullable().optional();
682
+ } else {
683
+ zodShape[key] = coercedSchema;
684
+ }
685
+ } else if (fieldSchema.type === "boolean" || wrappedType === "boolean") {
686
+ const customBooleanCoercion = z.preprocess((val) => {
687
+ if (typeof val === "string") {
688
+ const lower = val.toLowerCase();
689
+ if (lower === "true" || lower === "1") return true;
690
+ if (lower === "false" || lower === "0") return false;
691
+ }
692
+ return val;
693
+ }, z.boolean());
694
+ if (fieldSchema.type === "optional") {
695
+ zodShape[key] = customBooleanCoercion.nullable().optional();
696
+ } else {
697
+ zodShape[key] = customBooleanCoercion;
698
+ }
699
+ } else {
700
+ zodShape[key] = baseZodSchema;
701
+ }
702
+ }
703
+ }
704
+ return z.object(zodShape);
84
705
  }
85
706
  };
86
707
  var ArraySchema = class extends BaseSchema {
708
+ type = "array";
87
709
  _element;
88
710
  constructor(elementSchema) {
89
711
  super();
90
712
  this._element = elementSchema;
91
713
  }
92
- parse(data) {
93
- if (!Array.isArray(data)) {
94
- throw new Error(`Expected array, received ${typeof data}`);
95
- }
96
- const result = [];
97
- const errors = [];
98
- for (let i = 0; i < data.length; i++) {
99
- try {
100
- result[i] = this._element.parse(data[i]);
101
- } catch (e) {
102
- errors.push(`Element at index ${i}: ${e.message}`);
103
- }
104
- }
105
- if (errors.length > 0) {
106
- throw new Error(`Array validation failed:
107
- ${errors.join("\n")}`);
108
- }
109
- return result;
714
+ toZod() {
715
+ return z.array(this._element.toZod());
716
+ }
717
+ };
718
+ var RecordSchema = class extends BaseSchema {
719
+ type = "record";
720
+ _valueSchema;
721
+ constructor(valueSchema) {
722
+ super();
723
+ this._valueSchema = valueSchema;
724
+ }
725
+ toZod() {
726
+ return z.record(z.string(), this._valueSchema.toZod());
110
727
  }
111
728
  };
112
729
 
113
- // schema/nu.ts
730
+ // src/schema/nu.ts
114
731
  var nu = {
115
732
  boolean: () => new BooleanSchema(),
116
733
  /**
@@ -130,16 +747,206 @@ var nu = {
130
747
  * Creates an array schema with a defined element schema.
131
748
  * @param elementSchema The schema for elements within the array.
132
749
  */
133
- array: (elementSchema) => new ArraySchema(elementSchema)
134
- // Add more factory methods here (boolean, union, literal, etc.)
750
+ array: (elementSchema) => new ArraySchema(elementSchema),
751
+ /**
752
+ * Creates a record schema (dictionary) with string keys and values of a specific type.
753
+ * Similar to TypeScript's Record<string, T>.
754
+ *
755
+ * @example
756
+ * const scoresSchema = nu.record(nu.number());
757
+ * // Represents: { [key: string]: number }
758
+ *
759
+ * @param valueSchema The schema for values in the record.
760
+ */
761
+ record: (valueSchema) => new RecordSchema(valueSchema)
762
+ };
763
+
764
+ // src/schema/empty-schema.ts
765
+ var emptySchema = nu.object({});
766
+ var idNumberSchema = nu.object({
767
+ id: nu.number()
768
+ });
769
+ var idStringSchema = nu.object({
770
+ id: nu.string()
771
+ });
772
+ var successSchema = nu.object({
773
+ success: nu.boolean()
774
+ });
775
+ var successMessageSchema = nu.object({
776
+ success: nu.boolean(),
777
+ message: nu.string().optional()
778
+ });
779
+ var successErrorSchema = nu.object({
780
+ success: nu.boolean(),
781
+ message: nu.string().optional(),
782
+ errors: nu.array(
783
+ nu.object({
784
+ field: nu.string().optional(),
785
+ message: nu.string()
786
+ })
787
+ ).optional()
788
+ });
789
+
790
+ // src/schema/error-schema.ts
791
+ var errorSchema = nu.object({
792
+ errorCode: nu.string(),
793
+ // required by default
794
+ errorMessage: nu.string()
795
+ // required by default
796
+ });
797
+
798
+ // src/schema/errors.ts
799
+ var NubaseSchemaError = class _NubaseSchemaError extends Error {
800
+ errors;
801
+ constructor(errors) {
802
+ const message = `Schema validation failed:
803
+ ${errors.map((e) => ` - ${e.path}: ${e.message}`).join("\n")}`;
804
+ super(message);
805
+ this.name = "NubaseSchemaError";
806
+ this.errors = errors;
807
+ if (Error.captureStackTrace) {
808
+ Error.captureStackTrace(this, this.constructor);
809
+ }
810
+ }
811
+ /**
812
+ * Creates a NubaseSchemaError from a list of error messages
813
+ */
814
+ static fromMessages(errorMessages) {
815
+ const errors = errorMessages.map((message) => {
816
+ const match = message.match(/^Property '([^']+)':\s*(.+)$/);
817
+ if (match?.[1] && match[2]) {
818
+ return {
819
+ path: match[1],
820
+ message: match[2]
821
+ };
822
+ }
823
+ return {
824
+ path: "root",
825
+ message
826
+ };
827
+ });
828
+ return new _NubaseSchemaError(errors);
829
+ }
830
+ /**
831
+ * Type guard to check if an error is a NubaseSchemaError
832
+ */
833
+ static isNubaseSchemaError(error) {
834
+ return error instanceof _NubaseSchemaError;
835
+ }
135
836
  };
837
+
838
+ // src/schema/widget-data-schema.ts
839
+ var seriesConfigSchema = nu.object({
840
+ /** The data key field names that represent numeric series (e.g., ["desktop", "mobile"]) */
841
+ keys: nu.array(nu.string()),
842
+ /** Optional labels for each key (e.g., { desktop: "Desktop Users" }) */
843
+ labels: nu.object({}).optional(),
844
+ /** Optional colors for each key (e.g., { desktop: "var(--chart1)" }) */
845
+ colors: nu.object({}).optional()
846
+ });
847
+ var seriesDataPointSchema = nu.object({
848
+ category: nu.string()
849
+ }).catchall(nu.number());
850
+ var seriesDataSchema = nu.object({
851
+ type: nu.string(),
852
+ // "series"
853
+ config: seriesConfigSchema,
854
+ data: nu.array(seriesDataPointSchema)
855
+ });
856
+ var proportionalDataItemSchema = nu.object({
857
+ label: nu.string(),
858
+ value: nu.number()
859
+ });
860
+ var proportionalDataSchema = nu.object({
861
+ type: nu.string(),
862
+ // "proportional"
863
+ data: nu.array(proportionalDataItemSchema)
864
+ });
865
+ var kpiDataSchema = nu.object({
866
+ type: nu.string(),
867
+ // "kpi"
868
+ value: nu.string(),
869
+ label: nu.string().optional(),
870
+ trend: nu.string().optional(),
871
+ trendDirection: nu.string().optional()
872
+ // "up" | "down" | "neutral"
873
+ });
874
+ var tableColumnSchema = nu.object({
875
+ key: nu.string(),
876
+ label: nu.string(),
877
+ width: nu.string().optional()
878
+ });
879
+ var tableDataSchema = nu.object({
880
+ type: nu.string(),
881
+ // "table"
882
+ columns: nu.array(tableColumnSchema),
883
+ rows: nu.array(nu.object({}))
884
+ // Record<string, unknown>[]
885
+ });
886
+
887
+ // src/schema/widget-request-schema.ts
888
+ function createSeriesWidgetEndpoint(path, requestParams) {
889
+ return {
890
+ method: "GET",
891
+ path,
892
+ requestParams: requestParams || emptySchema,
893
+ responseBody: seriesDataSchema
894
+ };
895
+ }
896
+ function createProportionalWidgetEndpoint(path, requestParams) {
897
+ return {
898
+ method: "GET",
899
+ path,
900
+ requestParams: requestParams || emptySchema,
901
+ responseBody: proportionalDataSchema
902
+ };
903
+ }
904
+ function createKpiWidgetEndpoint(path, requestParams) {
905
+ return {
906
+ method: "GET",
907
+ path,
908
+ requestParams: requestParams || emptySchema,
909
+ responseBody: kpiDataSchema
910
+ };
911
+ }
912
+ function createTableWidgetEndpoint(path, requestParams) {
913
+ return {
914
+ method: "GET",
915
+ path,
916
+ requestParams: requestParams || emptySchema,
917
+ responseBody: tableDataSchema
918
+ };
919
+ }
136
920
  export {
137
921
  ArraySchema,
138
922
  BaseSchema,
139
923
  BooleanSchema,
924
+ HttpClient,
925
+ NubaseSchemaError,
140
926
  NumberSchema,
141
927
  ObjectSchema,
928
+ OptionalSchema,
929
+ RecordSchema,
142
930
  StringSchema,
143
- nu
931
+ createKpiWidgetEndpoint,
932
+ createProportionalWidgetEndpoint,
933
+ createSeriesWidgetEndpoint,
934
+ createTableWidgetEndpoint,
935
+ emptySchema,
936
+ errorSchema,
937
+ idNumberSchema,
938
+ idStringSchema,
939
+ kpiDataSchema,
940
+ nu,
941
+ proportionalDataItemSchema,
942
+ proportionalDataSchema,
943
+ seriesConfigSchema,
944
+ seriesDataPointSchema,
945
+ seriesDataSchema,
946
+ successErrorSchema,
947
+ successMessageSchema,
948
+ successSchema,
949
+ tableColumnSchema,
950
+ tableDataSchema
144
951
  };
145
952
  //# sourceMappingURL=index.mjs.map