trickle-observe 0.2.2 → 0.2.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.
@@ -258,6 +258,12 @@ function typeToTS(node: TypeNode, ext: Extracted[], parent: string, prop: string
258
258
  case 'object': {
259
259
  const keys = Object.keys(node.properties || {});
260
260
  if (keys.length === 0) return 'Record<string, never>';
261
+ // Recognize special marker objects from type-inference
262
+ if (keys.length === 1 && keys[0] === '__date') return 'Date';
263
+ if (keys.length === 1 && keys[0] === '__buffer') return 'Buffer';
264
+ if (keys.length === 1 && keys[0] === '__typedArray') return 'TypedArray';
265
+ if (keys.includes('__regexp')) return 'RegExp';
266
+ if (keys.includes('__error')) return 'Error';
261
267
  if (keys.length > 2 && prop) {
262
268
  const iName = toPascalCase(parent) + toPascalCase(prop);
263
269
  if (!ext.some(e => e.name === iName)) ext.push({ name: iName, node });
@@ -563,6 +569,11 @@ function typeToJSDoc(node: TypeNode): string {
563
569
  const props = node.properties || {};
564
570
  const keys = Object.keys(props);
565
571
  if (keys.length === 0) return 'Object';
572
+ // Recognize special marker objects from type-inference
573
+ if (keys.length === 1 && keys[0] === '__date') return 'Date';
574
+ if (keys.length === 1 && keys[0] === '__buffer') return 'Buffer';
575
+ if (keys.includes('__regexp')) return 'RegExp';
576
+ if (keys.includes('__error')) return 'Error';
566
577
  const entries = keys.map(k => {
567
578
  const { isOptional, innerType } = extractOptional(props[k]);
568
579
  return isOptional ? `${k}?: ${typeToJSDoc(innerType)}` : `${k}: ${typeToJSDoc(innerType)}`;
@@ -707,10 +718,147 @@ export function injectTypes(): number {
707
718
  // ── Public API ──
708
719
 
709
720
  let lastSize = 0;
710
- let lastContent = '';
721
+ const lastContentByModule = new Map<string, string>();
722
+ let tsconfigPatched = false;
711
723
 
712
724
  /**
713
- * Read observations and generate .d.ts files next to source files.
725
+ * Generate a typed Express route definitions file from route observations.
726
+ * Each route (e.g., "GET /users") becomes a typed interface.
727
+ */
728
+ function generateRoutesDts(routeFunctions: FunctionData[]): string {
729
+ const sections: string[] = [
730
+ '// Auto-generated by trickle — Express route types from runtime observations',
731
+ `// Generated at ${new Date().toISOString()}`,
732
+ '// Do not edit — types update automatically as your code runs',
733
+ '',
734
+ ];
735
+
736
+ const ext: Extracted[] = [];
737
+
738
+ for (const fn of routeFunctions) {
739
+ // Route name like "GET /users" → GetUsers
740
+ const routeName = fn.name;
741
+ const baseName = toPascalCase(routeName);
742
+
743
+ // Input type (body, params, query)
744
+ const inputProps = fn.argsType.kind === 'object' ? fn.argsType.properties || {} : {};
745
+ const hasInput = Object.keys(inputProps).length > 0;
746
+
747
+ if (hasInput) {
748
+ sections.push(renderInterface(`${baseName}Input`, fn.argsType, ext));
749
+ sections.push('');
750
+ }
751
+
752
+ // Output type
753
+ if (fn.returnType.kind === 'object' && Object.keys(fn.returnType.properties || {}).length > 0) {
754
+ sections.push(renderInterface(`${baseName}Output`, fn.returnType, ext));
755
+ } else {
756
+ sections.push(`export type ${baseName}Output = ${typeToTS(fn.returnType, ext, baseName, undefined, 0)};`);
757
+ }
758
+ sections.push('');
759
+ }
760
+
761
+ // Emit extracted interfaces
762
+ const emitted = new Set<string>();
763
+ const extLines: string[] = [];
764
+ for (const iface of ext) {
765
+ if (emitted.has(iface.name)) continue;
766
+ emitted.add(iface.name);
767
+ extLines.push(renderInterface(iface.name, iface.node, ext));
768
+ extLines.push('');
769
+ }
770
+ if (extLines.length > 0) {
771
+ // Insert extracted interfaces after the header, before route types
772
+ sections.splice(4, 0, ...extLines);
773
+ }
774
+
775
+ // Route map type for typed middleware/client usage
776
+ sections.push('/** All observed routes with their input/output types */');
777
+ sections.push('export interface TrickleRoutes {');
778
+ for (const fn of routeFunctions) {
779
+ const baseName = toPascalCase(fn.name);
780
+ const inputProps = fn.argsType.kind === 'object' ? fn.argsType.properties || {} : {};
781
+ const hasInput = Object.keys(inputProps).length > 0;
782
+ const inputType = hasInput ? `${baseName}Input` : 'Record<string, never>';
783
+ sections.push(` '${fn.name}': { input: ${inputType}; output: ${baseName}Output };`);
784
+ }
785
+ sections.push('}');
786
+ sections.push('');
787
+
788
+ return sections.join('\n');
789
+ }
790
+
791
+ /**
792
+ * Ensure .trickle/types/ directory exists.
793
+ */
794
+ function ensureTypesDir(trickleDir: string): string {
795
+ const typesDir = path.join(trickleDir, 'types');
796
+ try {
797
+ fs.mkdirSync(typesDir, { recursive: true });
798
+ } catch { /* already exists */ }
799
+ return typesDir;
800
+ }
801
+
802
+ /**
803
+ * Auto-patch tsconfig.json to include .trickle/types so generated
804
+ * types are visible in VSCode and tsc. Only runs once per process.
805
+ */
806
+ function patchTsConfig(): void {
807
+ if (tsconfigPatched) return;
808
+ tsconfigPatched = true;
809
+
810
+ const tsconfigPath = path.join(process.cwd(), 'tsconfig.json');
811
+ try {
812
+ if (!fs.existsSync(tsconfigPath)) return;
813
+
814
+ const raw = fs.readFileSync(tsconfigPath, 'utf-8');
815
+ // Strip JSON comments safely (skip strings to avoid breaking paths like "@/*")
816
+ let stripped = '';
817
+ let i = 0;
818
+ while (i < raw.length) {
819
+ if (raw[i] === '"') {
820
+ // Skip string content
821
+ let j = i + 1;
822
+ while (j < raw.length && raw[j] !== '"') {
823
+ if (raw[j] === '\\') j++; // skip escaped char
824
+ j++;
825
+ }
826
+ stripped += raw.slice(i, j + 1);
827
+ i = j + 1;
828
+ } else if (raw[i] === '/' && i + 1 < raw.length && raw[i + 1] === '/') {
829
+ // Line comment — skip to end of line
830
+ while (i < raw.length && raw[i] !== '\n') i++;
831
+ } else if (raw[i] === '/' && i + 1 < raw.length && raw[i + 1] === '*') {
832
+ // Block comment — skip to */
833
+ i += 2;
834
+ while (i < raw.length - 1 && !(raw[i] === '*' && raw[i + 1] === '/')) i++;
835
+ i += 2;
836
+ } else {
837
+ stripped += raw[i];
838
+ i++;
839
+ }
840
+ }
841
+ // Also strip trailing commas (common in tsconfig)
842
+ const cleaned = stripped.replace(/,(\s*[}\]])/g, '$1');
843
+ const config = JSON.parse(cleaned);
844
+
845
+ const include = config.include as string[] | undefined;
846
+ if (include && include.some((p: string) => p === '.trickle' || p.startsWith('.trickle/'))) {
847
+ return; // Already configured
848
+ }
849
+
850
+ if (include) {
851
+ include.push('.trickle/types');
852
+ } else {
853
+ config.include = ['.trickle/types'];
854
+ }
855
+
856
+ fs.writeFileSync(tsconfigPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
857
+ } catch { /* don't crash — tsconfig patching is best-effort */ }
858
+ }
859
+
860
+ /**
861
+ * Read observations and generate .d.ts type files in .trickle/types/.
714
862
  * Returns the number of functions typed.
715
863
  */
716
864
  export function generateTypes(): number {
@@ -729,36 +877,55 @@ export function generateTypes(): number {
729
877
  const functions = readAndMerge(jsonlPath);
730
878
  if (functions.length === 0) return 0;
731
879
 
732
- // Group by module and generate .d.ts next to source files
733
- const byModule = new Map<string, FunctionData[]>();
880
+ // Separate Express route observations from regular functions
881
+ const routeFunctions: FunctionData[] = [];
882
+ const regularFunctions: FunctionData[] = [];
734
883
  for (const fn of functions) {
884
+ if (fn.module === 'express' && /^(GET|POST|PUT|DELETE|PATCH|ALL)\s/.test(fn.name)) {
885
+ routeFunctions.push(fn);
886
+ } else {
887
+ regularFunctions.push(fn);
888
+ }
889
+ }
890
+
891
+ // Group regular functions by module
892
+ const byModule = new Map<string, FunctionData[]>();
893
+ for (const fn of regularFunctions) {
735
894
  const mod = fn.module || '_default';
736
895
  if (!byModule.has(mod)) byModule.set(mod, []);
737
896
  byModule.get(mod)!.push(fn);
738
897
  }
739
898
 
899
+ // Write all types to .trickle/types/ so TypeScript picks them up via tsconfig include
900
+ const typesDir = ensureTypesDir(trickleDir);
901
+ patchTsConfig();
902
+
740
903
  let totalFunctions = 0;
904
+
905
+ // Generate Express route types
906
+ if (routeFunctions.length > 0) {
907
+ const routesDts = generateRoutesDts(routeFunctions);
908
+ if (routesDts !== lastContentByModule.get('__routes')) {
909
+ const routesDtsPath = path.join(typesDir, 'routes.d.ts');
910
+ try {
911
+ fs.writeFileSync(routesDtsPath, routesDts, 'utf-8');
912
+ lastContentByModule.set('__routes', routesDts);
913
+ totalFunctions += routeFunctions.length;
914
+ } catch { /* don't crash */ }
915
+ }
916
+ }
917
+
741
918
  for (const [mod, fns] of byModule) {
742
919
  // Skip HTTP route observations (module is hostname like "localhost")
743
920
  if (mod.includes('.') && !mod.includes('/') && !mod.includes('\\')) continue;
744
921
 
745
922
  const dts = generateDts(fns);
746
- if (dts === lastContent) continue;
747
-
748
- // Find source file for this module
749
- const sourceFile = findSourceFile(mod);
750
- if (!sourceFile) continue;
751
-
752
- const ext = path.extname(sourceFile);
753
- const dir = path.dirname(sourceFile);
754
- const baseName = path.basename(sourceFile, ext);
755
- // For .ts/.tsx files, use .trickle.d.ts to avoid conflicts (TS ignores .d.ts next to .ts)
756
- const isTs = ext === '.ts' || ext === '.tsx';
757
- const dtsPath = path.join(dir, `${baseName}${isTs ? '.trickle' : ''}.d.ts`);
923
+ if (dts === lastContentByModule.get(mod)) continue;
758
924
 
925
+ const dtsPath = path.join(typesDir, `${mod}.d.ts`);
759
926
  try {
760
927
  fs.writeFileSync(dtsPath, dts, 'utf-8');
761
- lastContent = dts;
928
+ lastContentByModule.set(mod, dts);
762
929
  totalFunctions += fns.length;
763
930
  } catch { /* don't crash user's app */ }
764
931
  }
@@ -902,6 +1069,10 @@ function typeToCompact(node: TypeNode, depth = 0): string {
902
1069
  const props = node.properties || {};
903
1070
  const keys = Object.keys(props);
904
1071
  if (keys.length === 0) return '{}';
1072
+ if (keys.length === 1 && keys[0] === '__date') return 'Date';
1073
+ if (keys.length === 1 && keys[0] === '__buffer') return 'Buffer';
1074
+ if (keys.includes('__regexp')) return 'RegExp';
1075
+ if (keys.includes('__error')) return 'Error';
905
1076
  if (keys.length > 4) {
906
1077
  const shown = keys.slice(0, 3).map(k => `${k}: ${typeToCompact(props[k], depth + 1)}`);
907
1078
  return `{ ${shown.join(', ')}, ... }`;
@@ -1028,6 +1199,39 @@ export function generateTypeSummary(): string | null {
1028
1199
  return lines.join('\n');
1029
1200
  }
1030
1201
 
1202
+ /**
1203
+ * Cached recursive file index: maps basename (without ext) → full path.
1204
+ * Built once on first call, then reused.
1205
+ */
1206
+ let fileIndex: Map<string, string> | null = null;
1207
+
1208
+ function buildFileIndex(): Map<string, string> {
1209
+ const cwd = process.cwd();
1210
+ const exts = new Set(['.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs']);
1211
+ const index = new Map<string, string>();
1212
+
1213
+ // Common source directories to scan
1214
+ const dirs = ['src', 'dist', 'lib', 'app', '.'];
1215
+ for (const dir of dirs) {
1216
+ const absDir = path.join(cwd, dir);
1217
+ try {
1218
+ const entries = fs.readdirSync(absDir, { recursive: true, withFileTypes: true });
1219
+ for (const entry of entries) {
1220
+ if (!entry.isFile()) continue;
1221
+ const ext = path.extname(entry.name);
1222
+ if (!exts.has(ext)) continue;
1223
+ const baseName = entry.name.replace(/\.[^.]+$/, '');
1224
+ const fullPath = path.join(absDir, (entry as any).parentPath ? path.relative(absDir, (entry as any).parentPath) : '', entry.name);
1225
+ // Prefer src/ over dist/ for the same basename
1226
+ if (!index.has(baseName) || fullPath.includes('/src/')) {
1227
+ index.set(baseName, fullPath);
1228
+ }
1229
+ }
1230
+ } catch { /* dir doesn't exist */ }
1231
+ }
1232
+ return index;
1233
+ }
1234
+
1031
1235
  /**
1032
1236
  * Try to find the source file for a given module name.
1033
1237
  */
@@ -1054,5 +1258,9 @@ function findSourceFile(moduleName: string): string | null {
1054
1258
  if (fs.existsSync(candidate)) return candidate;
1055
1259
  }
1056
1260
 
1057
- return null;
1261
+ // Recursive search using cached file index
1262
+ if (!fileIndex) {
1263
+ fileIndex = buildFileIndex();
1264
+ }
1265
+ return fileIndex.get(moduleName) || null;
1058
1266
  }
@@ -48,7 +48,7 @@ function runGeneration(isFinal: boolean): void {
48
48
  }
49
49
 
50
50
  if (isFinal && lastFunctionCount > 0) {
51
- console.log(`[trickle/auto] ${lastFunctionCount} function type(s) written to .d.ts`);
51
+ console.log(`[trickle/auto] ${lastFunctionCount} function type(s) written to .trickle/types/`);
52
52
  // Inject JSDoc into source files if TRICKLE_INJECT=1
53
53
  try {
54
54
  const injected = injectTypes();
@@ -0,0 +1,387 @@
1
+ // Auto-generated by trickle from runtime type observations
2
+ // Generated at 2026-03-12T09:51:12.160Z
3
+ // Do not edit manually — re-run `trickle codegen` to update
4
+
5
+ export interface GetApiProductsOutputProducts {
6
+ id: number;
7
+ name: string;
8
+ price: number;
9
+ category: string;
10
+ }
11
+
12
+ /**
13
+ * Output type for `GET /api/products` — express module, observed in node, 40m ago
14
+ */
15
+ export interface GetApiProductsOutput {
16
+ products: GetApiProductsOutputProducts[];
17
+ total: number;
18
+ page: number;
19
+ }
20
+
21
+ /**
22
+ * @example
23
+ * // Sample input:
24
+ * {}
25
+ * // Sample output:
26
+ * {
27
+ * "products": [
28
+ * {
29
+ * "id": "[truncated]",
30
+ * "name": "[truncated]",
31
+ * "price": "[truncated]",
32
+ * "category": "[truncated]"
33
+ * },
34
+ * {
35
+ * "id": "[truncated]",
36
+ * "name": "[truncated]",
37
+ * "price": "[truncated]",
38
+ * "category": "[truncated]"
39
+ * },
40
+ * {
41
+ * "id": "[truncated]",
42
+ * "name": "[truncated]",
43
+ * "price": "[truncated]",
44
+ * "category": "[truncated]"
45
+ * }
46
+ * ],
47
+ * "total": 3,
48
+ * "page": 1
49
+ * }
50
+ */
51
+ export declare function getApiProducts(): GetApiProductsOutput;
52
+
53
+ /**
54
+ * Input type for `GET /api/products/:id` — express module, observed in node, 40m ago
55
+ */
56
+ export interface GetApiProductsIdInput {
57
+ id: string;
58
+ }
59
+
60
+ /**
61
+ * Output type for `GET /api/products/:id` — express module, observed in node, 40m ago
62
+ */
63
+ export interface GetApiProductsIdOutput {
64
+ id: number;
65
+ name: string;
66
+ price: number;
67
+ category: string;
68
+ description: string;
69
+ reviews: {
70
+ rating: number;
71
+ comment: string;
72
+ }[];
73
+ }
74
+
75
+ /**
76
+ * @example
77
+ * // Sample input:
78
+ * {
79
+ * "params": {
80
+ * "id": "1"
81
+ * }
82
+ * }
83
+ * // Sample output:
84
+ * {
85
+ * "id": 1,
86
+ * "name": "Widget",
87
+ * "price": 29.99,
88
+ * "category": "electronics",
89
+ * "description": "A fine widget for all your widgeting needs",
90
+ * "reviews": [
91
+ * {
92
+ * "rating": "[truncated]",
93
+ * "comment": "[truncated]"
94
+ * },
95
+ * {
96
+ * "rating": "[truncated]",
97
+ * "comment": "[truncated]"
98
+ * }
99
+ * ]
100
+ * }
101
+ */
102
+ export declare function getApiProductsId(input: GetApiProductsIdInput): GetApiProductsIdOutput;
103
+
104
+ export interface PostApiCartAddOutputItems {
105
+ productId: number;
106
+ quantity: number;
107
+ price: number;
108
+ }
109
+
110
+ /**
111
+ * Input type for `POST /api/cart/add` — express module, observed in node, 40m ago
112
+ */
113
+ export interface PostApiCartAddInput {
114
+ productId: number;
115
+ quantity: number;
116
+ }
117
+
118
+ /**
119
+ * Output type for `POST /api/cart/add` — express module, observed in node, 40m ago
120
+ */
121
+ export interface PostApiCartAddOutput {
122
+ cartId: string;
123
+ items: PostApiCartAddOutputItems[];
124
+ subtotal: number;
125
+ }
126
+
127
+ /**
128
+ * @example
129
+ * // Sample input:
130
+ * {
131
+ * "body": {
132
+ * "productId": 1,
133
+ * "quantity": 2
134
+ * }
135
+ * }
136
+ * // Sample output:
137
+ * {
138
+ * "cartId": "CART-12345",
139
+ * "items": [
140
+ * {
141
+ * "productId": "[truncated]",
142
+ * "quantity": "[truncated]",
143
+ * "price": "[truncated]"
144
+ * }
145
+ * ],
146
+ * "subtotal": 59.98
147
+ * }
148
+ */
149
+ export declare function postApiCartAdd(input: PostApiCartAddInput): PostApiCartAddOutput;
150
+
151
+ /**
152
+ * Input type for `DELETE /api/cart/:cartId` — express module, observed in node, 40m ago
153
+ */
154
+ export interface DeleteApiCartCartIdInput {
155
+ cartId: string;
156
+ }
157
+
158
+ /**
159
+ * Output type for `DELETE /api/cart/:cartId` — express module, observed in node, 40m ago
160
+ */
161
+ export interface DeleteApiCartCartIdOutput {
162
+ deleted: boolean;
163
+ cartId: string;
164
+ }
165
+
166
+ /**
167
+ * @example
168
+ * // Sample input:
169
+ * {
170
+ * "params": {
171
+ * "cartId": "CART-999"
172
+ * }
173
+ * }
174
+ * // Sample output:
175
+ * {
176
+ * "deleted": true,
177
+ * "cartId": "CART-999"
178
+ * }
179
+ */
180
+ export declare function deleteApiCartCartId(input: DeleteApiCartCartIdInput): DeleteApiCartCartIdOutput;
181
+
182
+ export interface GetApiUsersOutputUsers {
183
+ id: number;
184
+ name: string;
185
+ email: string;
186
+ role: string;
187
+ }
188
+
189
+ /**
190
+ * Output type for `GET /api/users` — express module, observed in node, 41m ago
191
+ */
192
+ export interface GetApiUsersOutput {
193
+ users: GetApiUsersOutputUsers[];
194
+ total: number;
195
+ }
196
+
197
+ /**
198
+ * @example
199
+ * // Sample input:
200
+ * {}
201
+ * // Sample output:
202
+ * {
203
+ * "users": [
204
+ * {
205
+ * "id": "[truncated]",
206
+ * "name": "[truncated]",
207
+ * "email": "[truncated]",
208
+ * "role": "[truncated]"
209
+ * },
210
+ * {
211
+ * "id": "[truncated]",
212
+ * "name": "[truncated]",
213
+ * "email": "[truncated]",
214
+ * "role": "[truncated]"
215
+ * }
216
+ * ],
217
+ * "total": 2
218
+ * }
219
+ */
220
+ export declare function getApiUsers(): GetApiUsersOutput;
221
+
222
+ /**
223
+ * Input type for `GET /api/users/:id` — express module, observed in node, 41m ago
224
+ */
225
+ export interface GetApiUsersIdInput {
226
+ id: string;
227
+ }
228
+
229
+ /**
230
+ * Output type for `GET /api/users/:id` — express module, observed in node, 41m ago
231
+ */
232
+ export interface GetApiUsersIdOutput {
233
+ id: number;
234
+ name: string;
235
+ email: string;
236
+ role: string;
237
+ metadata: {
238
+ lastLogin: string;
239
+ loginCount: number;
240
+ };
241
+ }
242
+
243
+ /**
244
+ * @example
245
+ * // Sample input:
246
+ * {
247
+ * "params": {
248
+ * "id": "1"
249
+ * }
250
+ * }
251
+ * // Sample output:
252
+ * {
253
+ * "id": 1,
254
+ * "name": "Alice",
255
+ * "email": "alice@example.com",
256
+ * "role": "admin",
257
+ * "metadata": {
258
+ * "lastLogin": "2026-03-10T10:00:00Z",
259
+ * "loginCount": 42
260
+ * }
261
+ * }
262
+ */
263
+ export declare function getApiUsersId(input: GetApiUsersIdInput): GetApiUsersIdOutput;
264
+
265
+ export interface PostApiOrdersInputItems {
266
+ name: string;
267
+ price: number;
268
+ quantity: number;
269
+ }
270
+
271
+ /**
272
+ * Input type for `POST /api/orders` — express module, observed in node, 41m ago
273
+ */
274
+ export interface PostApiOrdersInput {
275
+ customer: string;
276
+ items: PostApiOrdersInputItems[];
277
+ }
278
+
279
+ /**
280
+ * Output type for `POST /api/orders` — express module, observed in node, 41m ago
281
+ */
282
+ export interface PostApiOrdersOutput {
283
+ orderId: string;
284
+ customer: string;
285
+ total: number;
286
+ status: string;
287
+ }
288
+
289
+ /**
290
+ * @example
291
+ * // Sample input:
292
+ * {
293
+ * "body": {
294
+ * "customer": "Alice",
295
+ * "items": [
296
+ * "[truncated]",
297
+ * "[truncated]"
298
+ * ]
299
+ * }
300
+ * }
301
+ * // Sample output:
302
+ * {
303
+ * "orderId": "ORD-1773306555867",
304
+ * "customer": "Alice",
305
+ * "total": 109.97,
306
+ * "status": "created"
307
+ * }
308
+ */
309
+ export declare function postApiOrders(input: PostApiOrdersInput): PostApiOrdersOutput;
310
+
311
+ export interface PutApiUsersIdBody {
312
+ name: string;
313
+ email: string;
314
+ }
315
+
316
+ export interface PutApiUsersIdParams {
317
+ id: string;
318
+ }
319
+
320
+ /**
321
+ * Output type for `PUT /api/users/:id` — express module, observed in node, 41m ago
322
+ */
323
+ export interface PutApiUsersIdOutput {
324
+ id: number;
325
+ name: string;
326
+ email: string;
327
+ updated: boolean;
328
+ }
329
+
330
+ /**
331
+ * @example
332
+ * // Sample input:
333
+ * {
334
+ * "body": {
335
+ * "name": "Alice Updated",
336
+ * "email": "alice2@example.com"
337
+ * },
338
+ * "params": {
339
+ * "id": "1"
340
+ * }
341
+ * }
342
+ * // Sample output:
343
+ * {
344
+ * "id": 1,
345
+ * "name": "Alice Updated",
346
+ * "email": "alice2@example.com",
347
+ * "updated": true
348
+ * }
349
+ */
350
+ export declare function putApiUsersId(body: PutApiUsersIdBody, params: PutApiUsersIdParams): PutApiUsersIdOutput;
351
+
352
+ /**
353
+ * Input type for `POST /api/cart` — express module, observed in node, 41m ago
354
+ */
355
+ export interface PostApiCartInput {
356
+ productId: number;
357
+ quantity: number;
358
+ }
359
+
360
+ /**
361
+ * Output type for `POST /api/cart` — express module, observed in node, 41m ago
362
+ */
363
+ export interface PostApiCartOutput {
364
+ cartId: string;
365
+ productId: number;
366
+ quantity: number;
367
+ added: boolean;
368
+ }
369
+
370
+ /**
371
+ * @example
372
+ * // Sample input:
373
+ * {
374
+ * "body": {
375
+ * "productId": 1,
376
+ * "quantity": 3
377
+ * }
378
+ * }
379
+ * // Sample output:
380
+ * {
381
+ * "cartId": "CART-1773306558041",
382
+ * "productId": 1,
383
+ * "quantity": 3,
384
+ * "added": true
385
+ * }
386
+ */
387
+ export declare function postApiCart(input: PostApiCartInput): PostApiCartOutput;