@jaypie/fabric 0.1.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.
Files changed (166) hide show
  1. package/README.md +677 -0
  2. package/dist/cjs/commander/FabricCommander.d.ts +94 -0
  3. package/dist/cjs/commander/createCommanderOptions.d.ts +25 -0
  4. package/dist/cjs/commander/fabricCommand.d.ts +43 -0
  5. package/dist/cjs/commander/index.cjs +1487 -0
  6. package/dist/cjs/commander/index.cjs.map +1 -0
  7. package/dist/cjs/commander/index.d.ts +6 -0
  8. package/dist/cjs/commander/parseCommanderOptions.d.ts +32 -0
  9. package/dist/cjs/commander/registerServiceCommand.d.ts +43 -0
  10. package/dist/cjs/commander/types.d.ts +107 -0
  11. package/dist/cjs/constants.d.ts +12 -0
  12. package/dist/cjs/convert-date.d.ts +47 -0
  13. package/dist/cjs/convert.d.ts +69 -0
  14. package/dist/cjs/data/FabricData.d.ts +42 -0
  15. package/dist/cjs/data/index.cjs +1575 -0
  16. package/dist/cjs/data/index.cjs.map +1 -0
  17. package/dist/cjs/data/index.d.ts +5 -0
  18. package/dist/cjs/data/services/archive.d.ts +8 -0
  19. package/dist/cjs/data/services/create.d.ts +8 -0
  20. package/dist/cjs/data/services/delete.d.ts +8 -0
  21. package/dist/cjs/data/services/execute.d.ts +8 -0
  22. package/dist/cjs/data/services/index.d.ts +7 -0
  23. package/dist/cjs/data/services/list.d.ts +8 -0
  24. package/dist/cjs/data/services/read.d.ts +8 -0
  25. package/dist/cjs/data/services/update.d.ts +8 -0
  26. package/dist/cjs/data/transforms.d.ts +80 -0
  27. package/dist/cjs/data/types.d.ts +190 -0
  28. package/dist/cjs/express/FabricRouter.d.ts +29 -0
  29. package/dist/cjs/express/fabricExpress.d.ts +16 -0
  30. package/dist/cjs/express/index.cjs +505 -0
  31. package/dist/cjs/express/index.cjs.map +1 -0
  32. package/dist/cjs/express/index.d.ts +3 -0
  33. package/dist/cjs/express/types.d.ts +51 -0
  34. package/dist/cjs/helpers/fallback.d.ts +21 -0
  35. package/dist/cjs/helpers/index.d.ts +3 -0
  36. package/dist/cjs/helpers/resolvedName.d.ts +24 -0
  37. package/dist/cjs/http/FabricHttpServer.d.ts +31 -0
  38. package/dist/cjs/http/authorization.d.ts +30 -0
  39. package/dist/cjs/http/cors.d.ts +40 -0
  40. package/dist/cjs/http/fabricHttp.d.ts +28 -0
  41. package/dist/cjs/http/httpTransform.d.ts +36 -0
  42. package/dist/cjs/http/index.cjs +1820 -0
  43. package/dist/cjs/http/index.cjs.map +1 -0
  44. package/dist/cjs/http/index.d.ts +10 -0
  45. package/dist/cjs/http/stream.d.ts +185 -0
  46. package/dist/cjs/http/types.d.ts +343 -0
  47. package/dist/cjs/index/index.d.ts +8 -0
  48. package/dist/cjs/index/keyBuilder.d.ts +81 -0
  49. package/dist/cjs/index/registry.d.ts +56 -0
  50. package/dist/cjs/index/types.d.ts +54 -0
  51. package/dist/cjs/index.cjs +1674 -0
  52. package/dist/cjs/index.cjs.map +1 -0
  53. package/dist/cjs/index.d.ts +18 -0
  54. package/dist/cjs/lambda/createLambdaService.d.ts +33 -0
  55. package/dist/cjs/lambda/fabricLambda.d.ts +36 -0
  56. package/dist/cjs/lambda/index.cjs +967 -0
  57. package/dist/cjs/lambda/index.cjs.map +1 -0
  58. package/dist/cjs/lambda/index.d.ts +2 -0
  59. package/dist/cjs/lambda/types.d.ts +68 -0
  60. package/dist/cjs/llm/createLlmTool.d.ts +40 -0
  61. package/dist/cjs/llm/fabricTool.d.ts +40 -0
  62. package/dist/cjs/llm/index.cjs +1107 -0
  63. package/dist/cjs/llm/index.cjs.map +1 -0
  64. package/dist/cjs/llm/index.d.ts +3 -0
  65. package/dist/cjs/llm/inputToJsonSchema.d.ts +32 -0
  66. package/dist/cjs/llm/types.d.ts +61 -0
  67. package/dist/cjs/mcp/fabricMcp.d.ts +38 -0
  68. package/dist/cjs/mcp/index.cjs +938 -0
  69. package/dist/cjs/mcp/index.cjs.map +1 -0
  70. package/dist/cjs/mcp/index.d.ts +2 -0
  71. package/dist/cjs/mcp/registerMcpTool.d.ts +38 -0
  72. package/dist/cjs/mcp/types.d.ts +60 -0
  73. package/dist/cjs/models/base.d.ts +209 -0
  74. package/dist/cjs/resolve-date.d.ts +47 -0
  75. package/dist/cjs/resolve.d.ts +69 -0
  76. package/dist/cjs/resolveService.d.ts +49 -0
  77. package/dist/cjs/service.d.ts +13 -0
  78. package/dist/cjs/status.d.ts +30 -0
  79. package/dist/cjs/types/elementaryTypes.d.ts +84 -0
  80. package/dist/cjs/types/fieldCategory.d.ts +20 -0
  81. package/dist/cjs/types/fieldDefinition.d.ts +46 -0
  82. package/dist/cjs/types/index.d.ts +4 -0
  83. package/dist/cjs/types.d.ts +56 -0
  84. package/dist/esm/commander/FabricCommander.d.ts +94 -0
  85. package/dist/esm/commander/createCommanderOptions.d.ts +25 -0
  86. package/dist/esm/commander/fabricCommand.d.ts +43 -0
  87. package/dist/esm/commander/index.d.ts +6 -0
  88. package/dist/esm/commander/index.js +1482 -0
  89. package/dist/esm/commander/index.js.map +1 -0
  90. package/dist/esm/commander/parseCommanderOptions.d.ts +32 -0
  91. package/dist/esm/commander/registerServiceCommand.d.ts +43 -0
  92. package/dist/esm/commander/types.d.ts +107 -0
  93. package/dist/esm/constants.d.ts +12 -0
  94. package/dist/esm/convert-date.d.ts +47 -0
  95. package/dist/esm/convert.d.ts +69 -0
  96. package/dist/esm/data/FabricData.d.ts +42 -0
  97. package/dist/esm/data/index.d.ts +5 -0
  98. package/dist/esm/data/index.js +1548 -0
  99. package/dist/esm/data/index.js.map +1 -0
  100. package/dist/esm/data/services/archive.d.ts +8 -0
  101. package/dist/esm/data/services/create.d.ts +8 -0
  102. package/dist/esm/data/services/delete.d.ts +8 -0
  103. package/dist/esm/data/services/execute.d.ts +8 -0
  104. package/dist/esm/data/services/index.d.ts +7 -0
  105. package/dist/esm/data/services/list.d.ts +8 -0
  106. package/dist/esm/data/services/read.d.ts +8 -0
  107. package/dist/esm/data/services/update.d.ts +8 -0
  108. package/dist/esm/data/transforms.d.ts +80 -0
  109. package/dist/esm/data/types.d.ts +190 -0
  110. package/dist/esm/express/FabricRouter.d.ts +29 -0
  111. package/dist/esm/express/fabricExpress.d.ts +16 -0
  112. package/dist/esm/express/index.d.ts +3 -0
  113. package/dist/esm/express/index.js +500 -0
  114. package/dist/esm/express/index.js.map +1 -0
  115. package/dist/esm/express/types.d.ts +51 -0
  116. package/dist/esm/helpers/fallback.d.ts +21 -0
  117. package/dist/esm/helpers/index.d.ts +3 -0
  118. package/dist/esm/helpers/resolvedName.d.ts +24 -0
  119. package/dist/esm/http/FabricHttpServer.d.ts +31 -0
  120. package/dist/esm/http/authorization.d.ts +30 -0
  121. package/dist/esm/http/cors.d.ts +40 -0
  122. package/dist/esm/http/fabricHttp.d.ts +28 -0
  123. package/dist/esm/http/httpTransform.d.ts +36 -0
  124. package/dist/esm/http/index.d.ts +10 -0
  125. package/dist/esm/http/index.js +1775 -0
  126. package/dist/esm/http/index.js.map +1 -0
  127. package/dist/esm/http/stream.d.ts +185 -0
  128. package/dist/esm/http/types.d.ts +343 -0
  129. package/dist/esm/index/index.d.ts +8 -0
  130. package/dist/esm/index/keyBuilder.d.ts +81 -0
  131. package/dist/esm/index/registry.d.ts +56 -0
  132. package/dist/esm/index/types.d.ts +54 -0
  133. package/dist/esm/index.d.ts +18 -0
  134. package/dist/esm/index.js +1606 -0
  135. package/dist/esm/index.js.map +1 -0
  136. package/dist/esm/lambda/createLambdaService.d.ts +33 -0
  137. package/dist/esm/lambda/fabricLambda.d.ts +36 -0
  138. package/dist/esm/lambda/index.d.ts +2 -0
  139. package/dist/esm/lambda/index.js +965 -0
  140. package/dist/esm/lambda/index.js.map +1 -0
  141. package/dist/esm/lambda/types.d.ts +68 -0
  142. package/dist/esm/llm/createLlmTool.d.ts +40 -0
  143. package/dist/esm/llm/fabricTool.d.ts +40 -0
  144. package/dist/esm/llm/index.d.ts +3 -0
  145. package/dist/esm/llm/index.js +1104 -0
  146. package/dist/esm/llm/index.js.map +1 -0
  147. package/dist/esm/llm/inputToJsonSchema.d.ts +32 -0
  148. package/dist/esm/llm/types.d.ts +61 -0
  149. package/dist/esm/mcp/fabricMcp.d.ts +38 -0
  150. package/dist/esm/mcp/index.d.ts +2 -0
  151. package/dist/esm/mcp/index.js +936 -0
  152. package/dist/esm/mcp/index.js.map +1 -0
  153. package/dist/esm/mcp/registerMcpTool.d.ts +38 -0
  154. package/dist/esm/mcp/types.d.ts +60 -0
  155. package/dist/esm/models/base.d.ts +209 -0
  156. package/dist/esm/resolve-date.d.ts +47 -0
  157. package/dist/esm/resolve.d.ts +69 -0
  158. package/dist/esm/resolveService.d.ts +49 -0
  159. package/dist/esm/service.d.ts +13 -0
  160. package/dist/esm/status.d.ts +30 -0
  161. package/dist/esm/types/elementaryTypes.d.ts +84 -0
  162. package/dist/esm/types/fieldCategory.d.ts +20 -0
  163. package/dist/esm/types/fieldDefinition.d.ts +46 -0
  164. package/dist/esm/types/index.d.ts +4 -0
  165. package/dist/esm/types.d.ts +56 -0
  166. package/package.json +122 -0
@@ -0,0 +1,936 @@
1
+ import { BadRequestError } from '@jaypie/errors';
2
+
3
+ /**
4
+ * Meta-modeling Constants
5
+ */
6
+ // =============================================================================
7
+ // Constants
8
+ // =============================================================================
9
+ /** Root organizational unit */
10
+ /** Fabric version - used to identify pre-instantiated Services */
11
+ const FABRIC_VERSION = "0.1.0";
12
+
13
+ /**
14
+ * Date Type Conversion for @jaypie/fabric
15
+ *
16
+ * Adds Date as a supported type in the fabric type system.
17
+ * Follows the same conversion patterns as String, Number, Boolean.
18
+ */
19
+ /**
20
+ * Convert a value to a Date
21
+ *
22
+ * Supported inputs:
23
+ * - Date: returned as-is (validated)
24
+ * - Number: treated as Unix timestamp (milliseconds)
25
+ * - String: parsed via Date constructor (ISO 8601, etc.)
26
+ * - Object with value property: unwrapped and converted
27
+ *
28
+ * @throws BadRequestError if value cannot be converted to valid Date
29
+ */
30
+ function fabricDate(value) {
31
+ // Already a Date
32
+ if (value instanceof Date) {
33
+ if (Number.isNaN(value.getTime())) {
34
+ throw new BadRequestError("Invalid Date value");
35
+ }
36
+ return value;
37
+ }
38
+ // Null/undefined
39
+ if (value === null || value === undefined) {
40
+ throw new BadRequestError("Cannot convert null or undefined to Date");
41
+ }
42
+ // Object with value property (fabric pattern)
43
+ if (typeof value === "object" && value !== null && "value" in value) {
44
+ return fabricDate(value.value);
45
+ }
46
+ // Number (timestamp in milliseconds)
47
+ if (typeof value === "number") {
48
+ if (Number.isNaN(value)) {
49
+ throw new BadRequestError("Cannot convert NaN to Date");
50
+ }
51
+ const date = new Date(value);
52
+ if (Number.isNaN(date.getTime())) {
53
+ throw new BadRequestError(`Cannot convert ${value} to Date`);
54
+ }
55
+ return date;
56
+ }
57
+ // String (ISO 8601 or parseable format)
58
+ if (typeof value === "string") {
59
+ // Empty string is invalid
60
+ if (value.trim() === "") {
61
+ throw new BadRequestError("Cannot convert empty string to Date");
62
+ }
63
+ const date = new Date(value);
64
+ if (Number.isNaN(date.getTime())) {
65
+ throw new BadRequestError(`Cannot convert "${value}" to Date`);
66
+ }
67
+ return date;
68
+ }
69
+ // Boolean cannot be converted to Date
70
+ if (typeof value === "boolean") {
71
+ throw new BadRequestError("Cannot convert boolean to Date");
72
+ }
73
+ // Arrays - attempt single element extraction
74
+ if (Array.isArray(value)) {
75
+ if (value.length === 1) {
76
+ return fabricDate(value[0]);
77
+ }
78
+ throw new BadRequestError(`Cannot convert array with ${value.length} elements to Date`);
79
+ }
80
+ throw new BadRequestError(`Cannot convert ${typeof value} to Date`);
81
+ }
82
+ /**
83
+ * Type guard for Date type in schema definitions
84
+ */
85
+ function isDateType(type) {
86
+ return type === Date;
87
+ }
88
+
89
+ // Fabric functions for @jaypie/fabric
90
+ /**
91
+ * Try to parse a string as JSON if it looks like JSON
92
+ * Returns the parsed value or the original string if not JSON
93
+ */
94
+ function tryParseJson(value) {
95
+ const trimmed = value.trim();
96
+ if ((trimmed.startsWith("{") && trimmed.endsWith("}")) ||
97
+ (trimmed.startsWith("[") && trimmed.endsWith("]"))) {
98
+ try {
99
+ return JSON.parse(trimmed);
100
+ }
101
+ catch {
102
+ // Not valid JSON, return original
103
+ return value;
104
+ }
105
+ }
106
+ return value;
107
+ }
108
+ /**
109
+ * Unwrap arrays and objects to get to the scalar value
110
+ * - Single-element arrays unwrap to their element
111
+ * - Objects with value property unwrap to that value
112
+ * - Recursively unwraps nested structures
113
+ */
114
+ function unwrapToScalar(value) {
115
+ if (value === undefined || value === null) {
116
+ return value;
117
+ }
118
+ // Unwrap single-element arrays
119
+ if (Array.isArray(value)) {
120
+ if (value.length === 0) {
121
+ return undefined;
122
+ }
123
+ if (value.length === 1) {
124
+ return unwrapToScalar(value[0]);
125
+ }
126
+ throw new BadRequestError("Cannot convert multi-value array to scalar");
127
+ }
128
+ // Unwrap objects with value property
129
+ if (typeof value === "object") {
130
+ const obj = value;
131
+ if ("value" in obj) {
132
+ return unwrapToScalar(obj.value);
133
+ }
134
+ throw new BadRequestError("Object must have a value attribute");
135
+ }
136
+ return value;
137
+ }
138
+ /**
139
+ * Prepare a value for scalar conversion by parsing JSON strings and unwrapping
140
+ */
141
+ function prepareForScalarConversion(value) {
142
+ if (value === undefined || value === null) {
143
+ return value;
144
+ }
145
+ // Try to parse JSON strings
146
+ if (typeof value === "string") {
147
+ const parsed = tryParseJson(value);
148
+ if (parsed !== value) {
149
+ // Successfully parsed, unwrap the result
150
+ return unwrapToScalar(parsed);
151
+ }
152
+ return value;
153
+ }
154
+ // Unwrap arrays and objects
155
+ if (Array.isArray(value) || typeof value === "object") {
156
+ return unwrapToScalar(value);
157
+ }
158
+ return value;
159
+ }
160
+ /**
161
+ * Convert a value to a boolean
162
+ * - Arrays, objects, and JSON strings are unwrapped first
163
+ * - String "true" becomes true
164
+ * - String "false" becomes false
165
+ * - Strings that parse to numbers: positive = true, zero or negative = false
166
+ * - Numbers: positive = true, zero or negative = false
167
+ * - Boolean passes through
168
+ */
169
+ function fabricBoolean(value) {
170
+ // Prepare value by parsing JSON and unwrapping arrays/objects
171
+ const prepared = prepareForScalarConversion(value);
172
+ if (prepared === undefined || prepared === null) {
173
+ return undefined;
174
+ }
175
+ if (typeof prepared === "boolean") {
176
+ return prepared;
177
+ }
178
+ if (typeof prepared === "string") {
179
+ if (prepared === "") {
180
+ return undefined;
181
+ }
182
+ const lower = prepared.toLowerCase();
183
+ if (lower === "true") {
184
+ return true;
185
+ }
186
+ if (lower === "false") {
187
+ return false;
188
+ }
189
+ // Try to parse as number
190
+ const num = parseFloat(prepared);
191
+ if (isNaN(num)) {
192
+ throw new BadRequestError(`Cannot convert "${prepared}" to Boolean`);
193
+ }
194
+ return num > 0;
195
+ }
196
+ if (typeof prepared === "number") {
197
+ if (isNaN(prepared)) {
198
+ throw new BadRequestError("Cannot convert NaN to Boolean");
199
+ }
200
+ return prepared > 0;
201
+ }
202
+ throw new BadRequestError(`Cannot convert ${typeof prepared} to Boolean`);
203
+ }
204
+ /**
205
+ * Convert a value to a number
206
+ * - Arrays, objects, and JSON strings are unwrapped first
207
+ * - String "" becomes undefined
208
+ * - String "true" becomes 1
209
+ * - String "false" becomes 0
210
+ * - Strings that parse to numbers use those values
211
+ * - Strings that parse to NaN throw BadRequestError
212
+ * - Boolean true becomes 1, false becomes 0
213
+ * - Number passes through
214
+ */
215
+ function fabricNumber(value) {
216
+ // Prepare value by parsing JSON and unwrapping arrays/objects
217
+ const prepared = prepareForScalarConversion(value);
218
+ if (prepared === undefined || prepared === null) {
219
+ return undefined;
220
+ }
221
+ if (typeof prepared === "number") {
222
+ if (isNaN(prepared)) {
223
+ throw new BadRequestError("Cannot convert NaN to Number");
224
+ }
225
+ return prepared;
226
+ }
227
+ if (typeof prepared === "boolean") {
228
+ return prepared ? 1 : 0;
229
+ }
230
+ if (typeof prepared === "string") {
231
+ if (prepared === "") {
232
+ return undefined;
233
+ }
234
+ const lower = prepared.toLowerCase();
235
+ if (lower === "true") {
236
+ return 1;
237
+ }
238
+ if (lower === "false") {
239
+ return 0;
240
+ }
241
+ const num = parseFloat(prepared);
242
+ if (isNaN(num)) {
243
+ throw new BadRequestError(`Cannot convert "${prepared}" to Number`);
244
+ }
245
+ return num;
246
+ }
247
+ throw new BadRequestError(`Cannot convert ${typeof prepared} to Number`);
248
+ }
249
+ /**
250
+ * Convert a value to a string
251
+ * - Arrays, objects, and JSON strings are unwrapped first
252
+ * - String "" becomes undefined
253
+ * - Boolean true becomes "true", false becomes "false"
254
+ * - Number converts to string representation
255
+ * - String passes through
256
+ */
257
+ function fabricString(value) {
258
+ // Prepare value by parsing JSON and unwrapping arrays/objects
259
+ const prepared = prepareForScalarConversion(value);
260
+ if (prepared === undefined || prepared === null) {
261
+ return undefined;
262
+ }
263
+ if (typeof prepared === "string") {
264
+ if (prepared === "") {
265
+ return undefined;
266
+ }
267
+ return prepared;
268
+ }
269
+ if (typeof prepared === "boolean") {
270
+ return prepared ? "true" : "false";
271
+ }
272
+ if (typeof prepared === "number") {
273
+ if (isNaN(prepared)) {
274
+ throw new BadRequestError("Cannot convert NaN to String");
275
+ }
276
+ return String(prepared);
277
+ }
278
+ throw new BadRequestError(`Cannot convert ${typeof prepared} to String`);
279
+ }
280
+ /**
281
+ * Convert a value to an array
282
+ * - Non-arrays become arrays containing that value
283
+ * - Arrays of a single value become that value (unwrapped)
284
+ * - Multi-value arrays throw BadRequestError
285
+ * - undefined/null become undefined
286
+ */
287
+ function fabricArray(value) {
288
+ if (value === undefined || value === null) {
289
+ return undefined;
290
+ }
291
+ if (Array.isArray(value)) {
292
+ // Arrays pass through (single-element unwrapping happens when converting FROM array)
293
+ return value;
294
+ }
295
+ // Non-arrays become single-element arrays
296
+ return [value];
297
+ }
298
+ /**
299
+ * Convert a value to an object with a value property
300
+ * - Scalars become { value: scalar }
301
+ * - Arrays become { value: array }
302
+ * - Objects with a value attribute pass through
303
+ * - Objects without a value attribute throw BadRequestError
304
+ * - undefined/null become undefined
305
+ */
306
+ function fabricObject(value) {
307
+ if (value === undefined || value === null) {
308
+ return undefined;
309
+ }
310
+ // Check if already an object (but not an array)
311
+ if (typeof value === "object" && !Array.isArray(value)) {
312
+ const obj = value;
313
+ if ("value" in obj) {
314
+ return obj;
315
+ }
316
+ throw new BadRequestError("Object must have a value attribute");
317
+ }
318
+ // Scalars and arrays become { value: ... }
319
+ return { value };
320
+ }
321
+ /**
322
+ * Check if a type is a typed array (e.g., [String], [Number], [], etc.)
323
+ */
324
+ function isTypedArrayType(type) {
325
+ return Array.isArray(type);
326
+ }
327
+ /**
328
+ * Split a string on comma or tab delimiters for typed array conversion.
329
+ * Only splits if the string contains commas or tabs.
330
+ * Returns the original value if not a string or no delimiters found.
331
+ */
332
+ function splitStringForArray(value) {
333
+ if (typeof value !== "string") {
334
+ return value;
335
+ }
336
+ // Check for comma or tab delimiters
337
+ if (value.includes(",")) {
338
+ return value.split(",").map((s) => s.trim());
339
+ }
340
+ if (value.includes("\t")) {
341
+ return value.split("\t").map((s) => s.trim());
342
+ }
343
+ return value;
344
+ }
345
+ /**
346
+ * Try to parse a string as JSON for array context.
347
+ * Returns parsed value if it's an array, otherwise returns original.
348
+ */
349
+ function tryParseJsonArray(value) {
350
+ if (typeof value !== "string") {
351
+ return value;
352
+ }
353
+ const trimmed = value.trim();
354
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
355
+ try {
356
+ const parsed = JSON.parse(trimmed);
357
+ if (Array.isArray(parsed)) {
358
+ return parsed;
359
+ }
360
+ }
361
+ catch {
362
+ // Not valid JSON, fall through
363
+ }
364
+ }
365
+ return value;
366
+ }
367
+ /**
368
+ * Get the element type from a typed array type
369
+ * Returns undefined for untyped arrays ([])
370
+ */
371
+ function getArrayElementType(type) {
372
+ if (type.length === 0) {
373
+ return undefined; // Untyped array
374
+ }
375
+ const elementType = type[0];
376
+ // Handle constructor types
377
+ if (elementType === Boolean)
378
+ return "boolean";
379
+ if (elementType === Number)
380
+ return "number";
381
+ if (elementType === String)
382
+ return "string";
383
+ if (elementType === Object)
384
+ return "object";
385
+ // Handle string types
386
+ if (elementType === "boolean")
387
+ return "boolean";
388
+ if (elementType === "number")
389
+ return "number";
390
+ if (elementType === "string")
391
+ return "string";
392
+ if (elementType === "object")
393
+ return "object";
394
+ // Handle shorthand types
395
+ if (elementType === "")
396
+ return "string"; // "" shorthand for String
397
+ if (typeof elementType === "object" &&
398
+ elementType !== null &&
399
+ Object.keys(elementType).length === 0) {
400
+ return "object"; // {} shorthand for Object
401
+ }
402
+ throw new BadRequestError(`Unknown array element type: ${String(elementType)}`);
403
+ }
404
+ /**
405
+ * Convert a value to a typed array
406
+ * - Tries to parse JSON arrays first
407
+ * - Splits strings on comma/tab if present
408
+ * - Wraps non-arrays in an array
409
+ * - Converts each element to the specified element type
410
+ */
411
+ function fabricTypedArray(value, elementType) {
412
+ // Try to parse JSON array first
413
+ let processed = tryParseJsonArray(value);
414
+ // If still a string, try to split on comma/tab
415
+ processed = splitStringForArray(processed);
416
+ // Convert to array (wraps non-arrays)
417
+ const array = fabricArray(processed);
418
+ if (array === undefined) {
419
+ return undefined;
420
+ }
421
+ // If no element type specified, return as-is
422
+ if (elementType === undefined) {
423
+ return array;
424
+ }
425
+ // Convert each element to the element type
426
+ return array.map((element, index) => {
427
+ try {
428
+ switch (elementType) {
429
+ case "boolean":
430
+ return fabricBoolean(element);
431
+ case "number":
432
+ return fabricNumber(element);
433
+ case "object":
434
+ return fabricObject(element);
435
+ case "string":
436
+ return fabricString(element);
437
+ default:
438
+ throw new BadRequestError(`Unknown element type: ${elementType}`);
439
+ }
440
+ }
441
+ catch (error) {
442
+ if (error instanceof BadRequestError) {
443
+ throw new BadRequestError(`Cannot convert array element at index ${index}: ${error.message}`);
444
+ }
445
+ throw error;
446
+ }
447
+ });
448
+ }
449
+ /**
450
+ * Fabric a value to the specified type
451
+ */
452
+ function fabric(value, type) {
453
+ // Check for Date type first
454
+ if (isDateType(type)) {
455
+ return fabricDate(value);
456
+ }
457
+ // Check for typed array types
458
+ if (isTypedArrayType(type)) {
459
+ const elementType = getArrayElementType(type);
460
+ return fabricTypedArray(value, elementType);
461
+ }
462
+ const normalizedType = normalizeType(type);
463
+ switch (normalizedType) {
464
+ case "array":
465
+ return fabricArray(value);
466
+ case "boolean":
467
+ return fabricBoolean(value);
468
+ case "number":
469
+ return fabricNumber(value);
470
+ case "object":
471
+ return fabricObject(value);
472
+ case "string":
473
+ return fabricString(value);
474
+ default:
475
+ throw new BadRequestError(`Unknown type: ${String(type)}`);
476
+ }
477
+ }
478
+ /**
479
+ * Normalize type to string representation
480
+ */
481
+ function normalizeType(type) {
482
+ if (type === Array || type === "array") {
483
+ return "array";
484
+ }
485
+ if (type === Boolean || type === "boolean") {
486
+ return "boolean";
487
+ }
488
+ if (type === Number || type === "number") {
489
+ return "number";
490
+ }
491
+ if (type === Object || type === "object") {
492
+ return "object";
493
+ }
494
+ if (type === String || type === "string") {
495
+ return "string";
496
+ }
497
+ throw new BadRequestError(`Unknown type: ${String(type)}`);
498
+ }
499
+
500
+ // Service for @jaypie/fabric
501
+ /**
502
+ * Check if a single-element array is a typed array type constructor.
503
+ */
504
+ function isTypedArrayConstructor(element) {
505
+ return (element === Boolean ||
506
+ element === Number ||
507
+ element === String ||
508
+ element === Object ||
509
+ element === "boolean" ||
510
+ element === "number" ||
511
+ element === "string" ||
512
+ element === "object" ||
513
+ element === "" ||
514
+ (typeof element === "object" &&
515
+ element !== null &&
516
+ !(element instanceof RegExp) &&
517
+ Object.keys(element).length === 0));
518
+ }
519
+ /**
520
+ * Check if a type is a validated string type (array of string literals and/or RegExp).
521
+ * Distinguishes from typed arrays like [String], [Number], etc.
522
+ */
523
+ function isValidatedStringType(type) {
524
+ if (!Array.isArray(type)) {
525
+ return false;
526
+ }
527
+ // Empty array is untyped array, not validated string
528
+ if (type.length === 0) {
529
+ return false;
530
+ }
531
+ // Single-element arrays with type constructors are typed arrays
532
+ if (type.length === 1 && isTypedArrayConstructor(type[0])) {
533
+ return false;
534
+ }
535
+ // Check that all elements are strings or RegExp
536
+ return type.every((item) => typeof item === "string" || item instanceof RegExp);
537
+ }
538
+ /**
539
+ * Check if a type is a validated number type (array of number literals).
540
+ * Distinguishes from typed arrays like [Number], etc.
541
+ */
542
+ function isValidatedNumberType(type) {
543
+ if (!Array.isArray(type)) {
544
+ return false;
545
+ }
546
+ // Empty array is untyped array, not validated number
547
+ if (type.length === 0) {
548
+ return false;
549
+ }
550
+ // Single-element arrays with type constructors are typed arrays
551
+ if (type.length === 1 && isTypedArrayConstructor(type[0])) {
552
+ return false;
553
+ }
554
+ // Check that all elements are numbers
555
+ return type.every((item) => typeof item === "number");
556
+ }
557
+ /**
558
+ * Parse input string as JSON if it's a string
559
+ */
560
+ function parseInput(input) {
561
+ if (input === undefined || input === null) {
562
+ return {};
563
+ }
564
+ if (typeof input === "string") {
565
+ if (input === "") {
566
+ return {};
567
+ }
568
+ try {
569
+ const parsed = JSON.parse(input);
570
+ if (typeof parsed !== "object" ||
571
+ parsed === null ||
572
+ Array.isArray(parsed)) {
573
+ throw new BadRequestError("Input must be an object");
574
+ }
575
+ return parsed;
576
+ }
577
+ catch (error) {
578
+ if (error instanceof BadRequestError) {
579
+ throw error;
580
+ }
581
+ throw new BadRequestError("Invalid JSON input");
582
+ }
583
+ }
584
+ if (typeof input === "object" && !Array.isArray(input)) {
585
+ return input;
586
+ }
587
+ throw new BadRequestError("Input must be an object or JSON string");
588
+ }
589
+ /**
590
+ * Run validation on a value (supports async validators)
591
+ */
592
+ async function runValidation(value, validate, fieldName) {
593
+ if (typeof validate === "function") {
594
+ const result = await validate(value);
595
+ if (result === false) {
596
+ throw new BadRequestError(`Validation failed for field "${fieldName}"`);
597
+ }
598
+ }
599
+ else if (validate instanceof RegExp) {
600
+ if (typeof value !== "string" || !validate.test(value)) {
601
+ throw new BadRequestError(`Validation failed for field "${fieldName}"`);
602
+ }
603
+ }
604
+ else if (Array.isArray(validate)) {
605
+ // Check if value matches any item in the array
606
+ for (const item of validate) {
607
+ if (item instanceof RegExp) {
608
+ if (typeof value === "string" && item.test(value)) {
609
+ return; // Match found
610
+ }
611
+ }
612
+ else if (typeof item === "function") {
613
+ try {
614
+ const result = await item(value);
615
+ if (result !== false) {
616
+ return; // Match found
617
+ }
618
+ }
619
+ catch {
620
+ // Continue to next item
621
+ }
622
+ }
623
+ else if (value === item) {
624
+ return; // Scalar match found
625
+ }
626
+ }
627
+ throw new BadRequestError(`Validation failed for field "${fieldName}"`);
628
+ }
629
+ }
630
+ /**
631
+ * Check if a field is required
632
+ * A field is required unless it has a default OR required is explicitly false
633
+ */
634
+ function isFieldRequired(definition) {
635
+ if (definition.required === false) {
636
+ return false;
637
+ }
638
+ if (definition.default !== undefined) {
639
+ return false;
640
+ }
641
+ return true;
642
+ }
643
+ /**
644
+ * Process a single field through conversion and validation
645
+ */
646
+ async function processField(fieldName, value, definition) {
647
+ // Apply default if value is undefined
648
+ let processedValue = value;
649
+ if (processedValue === undefined && definition.default !== undefined) {
650
+ processedValue = definition.default;
651
+ }
652
+ // Determine actual type and validation
653
+ let actualType = definition.type;
654
+ let validation = definition.validate;
655
+ // Handle bare RegExp shorthand: /regex/
656
+ if (definition.type instanceof RegExp) {
657
+ actualType = String;
658
+ validation = definition.type; // The RegExp becomes the validation
659
+ }
660
+ // Handle validated string shorthand: ["value1", "value2"] or [/regex/]
661
+ else if (isValidatedStringType(definition.type)) {
662
+ actualType = String;
663
+ validation = definition.type; // The array becomes the validation
664
+ }
665
+ // Handle validated number shorthand: [1, 2, 3]
666
+ else if (isValidatedNumberType(definition.type)) {
667
+ actualType = Number;
668
+ validation = definition.type; // The array becomes the validation
669
+ }
670
+ // Fabric to target type
671
+ const convertedValue = fabric(processedValue, actualType);
672
+ // Check if required field is missing
673
+ if (convertedValue === undefined && isFieldRequired(definition)) {
674
+ throw new BadRequestError(`Missing required field "${fieldName}"`);
675
+ }
676
+ // Run validation if provided
677
+ if (validation !== undefined && convertedValue !== undefined) {
678
+ await runValidation(convertedValue, validation, fieldName);
679
+ }
680
+ return convertedValue;
681
+ }
682
+ /**
683
+ * Fabric a service function
684
+ *
685
+ * Service builds a function that initiates a "controller" step that:
686
+ * - Parses the input if it is a string to object
687
+ * - Fabrics each input field to its type
688
+ * - Calls the validation function or regular expression or checks the array
689
+ * - Calls the service function and returns the response
690
+ *
691
+ * The returned function has config properties for introspection.
692
+ */
693
+ function fabricService(config) {
694
+ const { input: inputDefinitions, service } = config;
695
+ const handler = async (rawInput, context) => {
696
+ // Parse input (handles string JSON)
697
+ const parsedInput = parseInput(rawInput);
698
+ // If no input definitions, pass through to service or return parsed input
699
+ if (!inputDefinitions) {
700
+ if (service) {
701
+ return service(parsedInput, context);
702
+ }
703
+ return parsedInput;
704
+ }
705
+ // Process all fields in parallel
706
+ const entries = Object.entries(inputDefinitions);
707
+ const processedValues = await Promise.all(entries.map(([fieldName, definition]) => processField(fieldName, parsedInput[fieldName], definition)));
708
+ // Build processed input object
709
+ const processedInput = {};
710
+ entries.forEach(([fieldName], index) => {
711
+ processedInput[fieldName] = processedValues[index];
712
+ });
713
+ // Return processed input if no service, otherwise call service
714
+ if (service) {
715
+ return service(processedInput, context);
716
+ }
717
+ return processedInput;
718
+ };
719
+ // Attach config properties directly to handler for flat access
720
+ const typedHandler = handler;
721
+ typedHandler.$fabric = FABRIC_VERSION;
722
+ if (config.alias !== undefined)
723
+ typedHandler.alias = config.alias;
724
+ if (config.description !== undefined)
725
+ typedHandler.description = config.description;
726
+ if (config.input !== undefined)
727
+ typedHandler.input = config.input;
728
+ if (config.service !== undefined)
729
+ typedHandler.service = config.service;
730
+ return typedHandler;
731
+ }
732
+
733
+ // Resolve inline service definitions to full Service objects
734
+ /**
735
+ * Type guard to check if a value is a pre-instantiated Service
736
+ * A Service is a function with the `$fabric` property set by fabricService
737
+ */
738
+ function isService(value) {
739
+ return typeof value === "function" && "$fabric" in value;
740
+ }
741
+ /**
742
+ * Resolve a service configuration to a full Service object
743
+ *
744
+ * Supports two patterns:
745
+ * 1. Inline service definition - pass a plain function as `service` along with
746
+ * `alias`, `description`, and `input` in the config
747
+ * 2. Pre-instantiated Service - pass a Service object as `service`
748
+ *
749
+ * When a pre-instantiated Service is passed, config fields act as overrides:
750
+ * - `alias` overrides service.alias
751
+ * - `description` overrides service.description
752
+ * - `input` overrides service.input
753
+ *
754
+ * The original Service is never mutated - a new Service is created when overrides
755
+ * are applied.
756
+ *
757
+ * @example
758
+ * ```typescript
759
+ * // Inline service definition
760
+ * const service = resolveService({
761
+ * alias: "greet",
762
+ * description: "Greet a user",
763
+ * input: { name: { type: String } },
764
+ * service: ({ name }) => `Hello, ${name}!`,
765
+ * });
766
+ *
767
+ * // Pre-instantiated with override
768
+ * const baseService = fabricService({ alias: "foo", service: (x) => x });
769
+ * const overridden = resolveService({
770
+ * alias: "bar", // Override alias
771
+ * service: baseService,
772
+ * });
773
+ * ```
774
+ */
775
+ function resolveService(config) {
776
+ const { alias, description, input, service } = config;
777
+ if (isService(service)) {
778
+ // Service is pre-instantiated - config fields act as overrides
779
+ // Create new Service with merged properties (config overrides service)
780
+ return fabricService({
781
+ alias: alias ?? service.alias,
782
+ description: description ?? service.description,
783
+ input: input ?? service.input,
784
+ service: service.service,
785
+ });
786
+ }
787
+ // Service is an inline function - create Service from config
788
+ return fabricService({
789
+ alias,
790
+ description,
791
+ input,
792
+ service,
793
+ });
794
+ }
795
+
796
+ // Fabric a service as an MCP tool
797
+ /**
798
+ * Format a value as a string for MCP response
799
+ */
800
+ function formatResult(value) {
801
+ if (value === undefined || value === null) {
802
+ return "";
803
+ }
804
+ if (typeof value === "string") {
805
+ return value;
806
+ }
807
+ if (typeof value === "object") {
808
+ return JSON.stringify(value, null, 2);
809
+ }
810
+ return String(value);
811
+ }
812
+ /**
813
+ * Fabric a service as an MCP tool
814
+ *
815
+ * This function registers a service with an MCP server.
816
+ * It automatically:
817
+ * - Uses service.alias as the tool name (or custom name)
818
+ * - Uses service.description as the tool description (or custom)
819
+ * - Delegates validation to the service
820
+ * - Wraps the service and formats the response
821
+ *
822
+ * @param config - Configuration including service, server, and optional overrides
823
+ * @returns An object containing the fabricated tool name
824
+ *
825
+ * @example
826
+ * ```typescript
827
+ * import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
828
+ * import { fabricService } from "@jaypie/fabric";
829
+ * import { fabricMcp } from "@jaypie/fabric/mcp";
830
+ *
831
+ * const myService = fabricService({
832
+ * alias: "greet",
833
+ * description: "Greet a user by name",
834
+ * input: {
835
+ * userName: { type: String, description: "The user's name" },
836
+ * loud: { type: Boolean, default: false, description: "Shout the greeting" },
837
+ * },
838
+ * service: ({ userName, loud }) => {
839
+ * const greeting = `Hello, ${userName}!`;
840
+ * return loud ? greeting.toUpperCase() : greeting;
841
+ * },
842
+ * });
843
+ *
844
+ * const server = new McpServer({ name: "my-server", version: "1.0.0" });
845
+ * fabricMcp({ server, service: myService });
846
+ * ```
847
+ */
848
+ function fabricMcp(config) {
849
+ const { alias, description, input, name, onComplete, onError, onFatal, onMessage, server, service: serviceOrFunction, } = config;
850
+ // Resolve inline service or apply overrides to pre-instantiated service
851
+ const service = resolveService({
852
+ alias,
853
+ description,
854
+ input,
855
+ service: serviceOrFunction,
856
+ });
857
+ // Determine tool name (priority: name > service.alias > "tool")
858
+ const toolName = name ?? service.alias ?? "tool";
859
+ // Determine tool description
860
+ const toolDescription = service.description ?? "";
861
+ // Create context callbacks that wrap with error swallowing
862
+ const sendMessage = onMessage
863
+ ? async (msg) => {
864
+ try {
865
+ await onMessage(msg);
866
+ }
867
+ catch {
868
+ // Swallow errors - callback failures should not halt execution
869
+ }
870
+ }
871
+ : undefined;
872
+ const contextOnError = onError
873
+ ? async (error) => {
874
+ try {
875
+ await onError(error);
876
+ }
877
+ catch {
878
+ // Swallow errors - callback failures should not halt execution
879
+ }
880
+ }
881
+ : undefined;
882
+ const contextOnFatal = onFatal
883
+ ? async (error) => {
884
+ try {
885
+ await onFatal(error);
886
+ }
887
+ catch {
888
+ // Swallow errors - callback failures should not halt execution
889
+ }
890
+ }
891
+ : undefined;
892
+ // Create context for the service
893
+ const context = {
894
+ onError: contextOnError,
895
+ onFatal: contextOnFatal,
896
+ sendMessage,
897
+ };
898
+ // Register the tool with the MCP server
899
+ // Use empty schema - service validates inputs
900
+ server.tool(toolName, toolDescription, {}, async (args) => {
901
+ try {
902
+ const result = await service(args, context);
903
+ // Call onComplete if provided
904
+ if (onComplete) {
905
+ try {
906
+ await onComplete(result);
907
+ }
908
+ catch {
909
+ // Swallow errors - callback failures should not halt execution
910
+ }
911
+ }
912
+ return {
913
+ content: [
914
+ {
915
+ text: formatResult(result),
916
+ type: "text",
917
+ },
918
+ ],
919
+ };
920
+ }
921
+ catch (error) {
922
+ // Any thrown error is fatal
923
+ if (onFatal) {
924
+ await onFatal(error);
925
+ }
926
+ else if (onError) {
927
+ await onError(error);
928
+ }
929
+ throw error;
930
+ }
931
+ });
932
+ return { name: toolName };
933
+ }
934
+
935
+ export { fabricMcp };
936
+ //# sourceMappingURL=index.js.map