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.
- package/dist/auto-codegen.d.ts +1 -1
- package/dist/auto-codegen.js +234 -17
- package/dist/auto-register.js +1 -1
- package/dist/express.js +13 -0
- package/dist/observe-register.js +450 -41
- package/dist/trace-var.d.ts +44 -0
- package/dist/trace-var.js +219 -0
- package/dist/type-inference.js +9 -1
- package/dist/vite-plugin.d.ts +5 -2
- package/dist/vite-plugin.js +385 -25
- package/dist/wrap.js +4 -12
- package/package.json +10 -3
- package/src/auto-codegen.ts +226 -18
- package/src/auto-register.ts +1 -1
- package/src/express.d.ts +387 -0
- package/src/express.ts +14 -0
- package/src/observe-register.ts +420 -41
- package/src/trace-var.ts +202 -0
- package/src/type-inference.ts +11 -1
- package/src/vite-plugin.ts +444 -24
- package/src/wrap.ts +4 -12
package/src/auto-codegen.ts
CHANGED
|
@@ -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
|
-
|
|
721
|
+
const lastContentByModule = new Map<string, string>();
|
|
722
|
+
let tsconfigPatched = false;
|
|
711
723
|
|
|
712
724
|
/**
|
|
713
|
-
*
|
|
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
|
-
//
|
|
733
|
-
const
|
|
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 ===
|
|
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
|
-
|
|
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
|
-
|
|
1261
|
+
// Recursive search using cached file index
|
|
1262
|
+
if (!fileIndex) {
|
|
1263
|
+
fileIndex = buildFileIndex();
|
|
1264
|
+
}
|
|
1265
|
+
return fileIndex.get(moduleName) || null;
|
|
1058
1266
|
}
|
package/src/auto-register.ts
CHANGED
|
@@ -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 .
|
|
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();
|
package/src/express.d.ts
ADDED
|
@@ -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;
|