schemock 0.0.4-alpha.16 → 0.0.4-alpha.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -609,6 +609,71 @@ export const HealthEndpoint = defineEndpoint('/api/health', {
609
609
  - Easier debugging with meaningful stack traces
610
610
  - Cleaner generated code
611
611
 
612
+ ### Inline Resolver Limitations
613
+
614
+ When using inline `mockResolver` functions with local helper functions, be aware of these limitations:
615
+
616
+ #### 1. Only Directly-Used Functions Are Copied
617
+
618
+ Local functions are only copied if they are **directly called** in a resolver. Transitive dependencies (functions called by other functions) are not automatically detected.
619
+
620
+ ```typescript
621
+ // ❌ Problem: innerHelper won't be copied
622
+ function innerHelper(x: string) { return x.toUpperCase(); }
623
+ function outerHelper(x: string) { return innerHelper(x); }
624
+
625
+ export const MyEndpoint = defineEndpoint('/api/test', {
626
+ mockResolver: async ({ body }) => ({
627
+ result: outerHelper(body.input) // Only outerHelper is detected
628
+ })
629
+ });
630
+
631
+ // ✅ Solution: Use named exported function
632
+ export async function myResolver({ body, db }) {
633
+ return { result: outerHelper(body.input) };
634
+ }
635
+
636
+ export const MyEndpoint = defineEndpoint('/api/test', {
637
+ mockResolver: myResolver // Function is imported, not serialized
638
+ });
639
+ ```
640
+
641
+ #### 2. Arrow Functions Need Parentheses
642
+
643
+ Arrow functions without parentheses around parameters are not detected:
644
+
645
+ ```typescript
646
+ // ❌ Not detected
647
+ const double = x => x * 2;
648
+
649
+ // ✅ Detected
650
+ const double = (x) => x * 2;
651
+ ```
652
+
653
+ #### 3. Recommended: Use Named Functions for Complex Logic
654
+
655
+ For resolvers with helper functions, use named exported functions instead of inline resolvers:
656
+
657
+ ```typescript
658
+ // Recommended approach
659
+ export async function loginResolver({ body, db }) {
660
+ const user = db.user.findFirst({ where: { email: { equals: body.email } } });
661
+ if (!verifyPassword(body.password, user.hash)) {
662
+ throw new Error('Invalid credentials');
663
+ }
664
+ return { token: generateToken(user.id), user };
665
+ }
666
+
667
+ export const LoginEndpoint = defineEndpoint('/api/auth/login', {
668
+ mockResolver: loginResolver // ✅ Imported directly, full IDE support
669
+ });
670
+ ```
671
+
672
+ Benefits of named functions:
673
+ - All dependencies are available (no serialization)
674
+ - Full IDE support (go-to-definition, refactoring)
675
+ - Better stack traces for debugging
676
+
612
677
  ### Row-Level Security (RLS)
613
678
 
614
679
  **Scope-based RLS (simple - recommended):**
@@ -423,6 +423,15 @@ interface ResolverDependency {
423
423
  /** Whether it's a default import */
424
424
  isDefault?: boolean;
425
425
  }
426
+ /**
427
+ * Local function definition used by inline resolver
428
+ */
429
+ interface LocalFunction {
430
+ /** Function name */
431
+ name: string;
432
+ /** Full function source code */
433
+ source: string;
434
+ }
426
435
  /**
427
436
  * Fully analyzed endpoint with all computed properties
428
437
  */
@@ -455,6 +464,8 @@ interface AnalyzedEndpoint {
455
464
  sourceFile?: string;
456
465
  /** Dependencies detected in inline resolver (functions used but not defined locally) */
457
466
  resolverDependencies?: ResolverDependency[];
467
+ /** Local functions used by inline resolver */
468
+ localFunctions?: LocalFunction[];
458
469
  /** Description */
459
470
  description?: string;
460
471
  }
@@ -1068,4 +1079,4 @@ declare function generateFetchClient(schemas: AnalyzedSchema[], config: FetchAda
1068
1079
  */
1069
1080
  declare function generateFormSchemas(schemas: AnalyzedSchema[]): string;
1070
1081
 
1071
- export { type AnalyzedComputed, type AnalyzedEndpoint, type AnalyzedEndpointField, type AnalyzedField, type AnalyzedIndex, type AnalyzedRLS, type AnalyzedRPC, type AnalyzedRelation, type AnalyzedSchema, type AuthProviderConfig, CodeBuilder, type FakerMapping, type FetchAdapterConfig, type FirebaseAdapterConfig, type FrameworkType, type GenerateOptions, type GenerateSQLOptions, type GenerationTarget, type GraphQLAdapterConfig, type MockAdapterConfig, type PGliteAdapterConfig, type PluralizeConfig, RLSBypass, RLSConfig, RLSScopeMapping, type ResolverDependency, type SQLGeneratorResult, type SchemockConfig, type SupabaseAdapterConfig, type TargetMiddlewareConfig, type TargetType, analyzeSchemas, defineConfig, discoverSchemas, fieldToFakerCall, fieldToTsType, generate, generateClaudeMd, generateCursorRules, generateFetchClient, generateFirebaseClient, generateFormSchemas, generateHooks, generateMockClient, generateMockDb, generateMockHandlers, generateSchemockSection, generateSeed, generateSupabaseClient, generateTypes, getDefaultConfig, getRelativePath, loadConfig, mergeClaudeMdContent, pluralize, primitiveToTs, setupAI, toCamelCase, toPascalCase };
1082
+ export { type AnalyzedComputed, type AnalyzedEndpoint, type AnalyzedEndpointField, type AnalyzedField, type AnalyzedIndex, type AnalyzedRLS, type AnalyzedRPC, type AnalyzedRelation, type AnalyzedSchema, type AuthProviderConfig, CodeBuilder, type FakerMapping, type FetchAdapterConfig, type FirebaseAdapterConfig, type FrameworkType, type GenerateOptions, type GenerateSQLOptions, type GenerationTarget, type GraphQLAdapterConfig, type LocalFunction, type MockAdapterConfig, type PGliteAdapterConfig, type PluralizeConfig, RLSBypass, RLSConfig, RLSScopeMapping, type ResolverDependency, type SQLGeneratorResult, type SchemockConfig, type SupabaseAdapterConfig, type TargetMiddlewareConfig, type TargetType, analyzeSchemas, defineConfig, discoverSchemas, fieldToFakerCall, fieldToTsType, generate, generateClaudeMd, generateCursorRules, generateFetchClient, generateFirebaseClient, generateFormSchemas, generateHooks, generateMockClient, generateMockDb, generateMockHandlers, generateSchemockSection, generateSeed, generateSupabaseClient, generateTypes, getDefaultConfig, getRelativePath, loadConfig, mergeClaudeMdContent, pluralize, primitiveToTs, setupAI, toCamelCase, toPascalCase };
@@ -423,6 +423,15 @@ interface ResolverDependency {
423
423
  /** Whether it's a default import */
424
424
  isDefault?: boolean;
425
425
  }
426
+ /**
427
+ * Local function definition used by inline resolver
428
+ */
429
+ interface LocalFunction {
430
+ /** Function name */
431
+ name: string;
432
+ /** Full function source code */
433
+ source: string;
434
+ }
426
435
  /**
427
436
  * Fully analyzed endpoint with all computed properties
428
437
  */
@@ -455,6 +464,8 @@ interface AnalyzedEndpoint {
455
464
  sourceFile?: string;
456
465
  /** Dependencies detected in inline resolver (functions used but not defined locally) */
457
466
  resolverDependencies?: ResolverDependency[];
467
+ /** Local functions used by inline resolver */
468
+ localFunctions?: LocalFunction[];
458
469
  /** Description */
459
470
  description?: string;
460
471
  }
@@ -1068,4 +1079,4 @@ declare function generateFetchClient(schemas: AnalyzedSchema[], config: FetchAda
1068
1079
  */
1069
1080
  declare function generateFormSchemas(schemas: AnalyzedSchema[]): string;
1070
1081
 
1071
- export { type AnalyzedComputed, type AnalyzedEndpoint, type AnalyzedEndpointField, type AnalyzedField, type AnalyzedIndex, type AnalyzedRLS, type AnalyzedRPC, type AnalyzedRelation, type AnalyzedSchema, type AuthProviderConfig, CodeBuilder, type FakerMapping, type FetchAdapterConfig, type FirebaseAdapterConfig, type FrameworkType, type GenerateOptions, type GenerateSQLOptions, type GenerationTarget, type GraphQLAdapterConfig, type MockAdapterConfig, type PGliteAdapterConfig, type PluralizeConfig, RLSBypass, RLSConfig, RLSScopeMapping, type ResolverDependency, type SQLGeneratorResult, type SchemockConfig, type SupabaseAdapterConfig, type TargetMiddlewareConfig, type TargetType, analyzeSchemas, defineConfig, discoverSchemas, fieldToFakerCall, fieldToTsType, generate, generateClaudeMd, generateCursorRules, generateFetchClient, generateFirebaseClient, generateFormSchemas, generateHooks, generateMockClient, generateMockDb, generateMockHandlers, generateSchemockSection, generateSeed, generateSupabaseClient, generateTypes, getDefaultConfig, getRelativePath, loadConfig, mergeClaudeMdContent, pluralize, primitiveToTs, setupAI, toCamelCase, toPascalCase };
1082
+ export { type AnalyzedComputed, type AnalyzedEndpoint, type AnalyzedEndpointField, type AnalyzedField, type AnalyzedIndex, type AnalyzedRLS, type AnalyzedRPC, type AnalyzedRelation, type AnalyzedSchema, type AuthProviderConfig, CodeBuilder, type FakerMapping, type FetchAdapterConfig, type FirebaseAdapterConfig, type FrameworkType, type GenerateOptions, type GenerateSQLOptions, type GenerationTarget, type GraphQLAdapterConfig, type LocalFunction, type MockAdapterConfig, type PGliteAdapterConfig, type PluralizeConfig, RLSBypass, RLSConfig, RLSScopeMapping, type ResolverDependency, type SQLGeneratorResult, type SchemockConfig, type SupabaseAdapterConfig, type TargetMiddlewareConfig, type TargetType, analyzeSchemas, defineConfig, discoverSchemas, fieldToFakerCall, fieldToTsType, generate, generateClaudeMd, generateCursorRules, generateFetchClient, generateFirebaseClient, generateFormSchemas, generateHooks, generateMockClient, generateMockDb, generateMockHandlers, generateSchemockSection, generateSeed, generateSupabaseClient, generateTypes, getDefaultConfig, getRelativePath, loadConfig, mergeClaudeMdContent, pluralize, primitiveToTs, setupAI, toCamelCase, toPascalCase };
package/dist/cli/index.js CHANGED
@@ -1263,6 +1263,83 @@ function parseImportsFromFile(filePath) {
1263
1263
  importCache.set(filePath, imports);
1264
1264
  return imports;
1265
1265
  }
1266
+ var localFunctionCache = /* @__PURE__ */ new Map();
1267
+ function parseLocalFunctions(filePath) {
1268
+ if (localFunctionCache.has(filePath)) {
1269
+ return localFunctionCache.get(filePath);
1270
+ }
1271
+ const functions = /* @__PURE__ */ new Map();
1272
+ try {
1273
+ const content = fs.readFileSync(filePath, "utf-8");
1274
+ const funcDeclRegex = /^(export\s+)?function\s+(\w+)\s*\([^)]*\)[^{]*\{/gm;
1275
+ let match;
1276
+ while ((match = funcDeclRegex.exec(content)) !== null) {
1277
+ const name = match[2];
1278
+ const source = extractFunctionBody2(content, match.index);
1279
+ if (source) functions.set(name, source);
1280
+ }
1281
+ const arrowRegex = /^(export\s+)?(const|let)\s+(\w+)\s*=\s*(async\s*)?\([^)]*\)[^=]*=>/gm;
1282
+ while ((match = arrowRegex.exec(content)) !== null) {
1283
+ const name = match[3];
1284
+ const source = extractArrowFunction(content, match.index);
1285
+ if (source) functions.set(name, source);
1286
+ }
1287
+ } catch {
1288
+ console.warn(`Warning: Could not parse local functions from ${filePath}`);
1289
+ }
1290
+ localFunctionCache.set(filePath, functions);
1291
+ return functions;
1292
+ }
1293
+ function extractFunctionBody2(content, startIndex) {
1294
+ let braceCount = 0;
1295
+ let started = false;
1296
+ for (let i = startIndex; i < content.length; i++) {
1297
+ if (content[i] === "{") {
1298
+ braceCount++;
1299
+ started = true;
1300
+ } else if (content[i] === "}") {
1301
+ braceCount--;
1302
+ if (started && braceCount === 0) {
1303
+ return content.slice(startIndex, i + 1).trim();
1304
+ }
1305
+ }
1306
+ }
1307
+ return null;
1308
+ }
1309
+ function extractArrowFunction(content, startIndex) {
1310
+ const arrowIndex = content.indexOf("=>", startIndex);
1311
+ if (arrowIndex === -1) return null;
1312
+ let i = arrowIndex + 2;
1313
+ while (i < content.length && /\s/.test(content[i])) i++;
1314
+ if (content[i] === "{") {
1315
+ let braceCount = 0;
1316
+ let started = false;
1317
+ for (let j = i; j < content.length; j++) {
1318
+ if (content[j] === "{") {
1319
+ braceCount++;
1320
+ started = true;
1321
+ } else if (content[j] === "}") {
1322
+ braceCount--;
1323
+ if (started && braceCount === 0) {
1324
+ return content.slice(startIndex, j + 1).trim();
1325
+ }
1326
+ }
1327
+ }
1328
+ return null;
1329
+ } else {
1330
+ let end = i;
1331
+ let parenCount = 0;
1332
+ while (end < content.length) {
1333
+ if (content[end] === "(") parenCount++;
1334
+ else if (content[end] === ")") parenCount--;
1335
+ else if ((content[end] === ";" || content[end] === "\n") && parenCount === 0) {
1336
+ break;
1337
+ }
1338
+ end++;
1339
+ }
1340
+ return content.slice(startIndex, end).trim() + ";";
1341
+ }
1342
+ }
1266
1343
  function detectUsedIdentifiers(functionSource) {
1267
1344
  const withoutStrings = functionSource.replace(/'[^']*'/g, "").replace(/"[^"]*"/g, "").replace(/`[^`]*`/g, "");
1268
1345
  const identifiers = /* @__PURE__ */ new Set();
@@ -1382,6 +1459,7 @@ function analyzeEndpoint(endpoint, endpointFiles) {
1382
1459
  let mockResolverName;
1383
1460
  let mockResolverImportPath;
1384
1461
  let resolverDependencies;
1462
+ let localFunctions;
1385
1463
  if (isNamedFunction) {
1386
1464
  mockResolverName = resolverName;
1387
1465
  if (sourceFile) {
@@ -1391,16 +1469,26 @@ function analyzeEndpoint(endpoint, endpointFiles) {
1391
1469
  const usedIdentifiers = detectUsedIdentifiers(mockResolverSource);
1392
1470
  if (usedIdentifiers.length > 0) {
1393
1471
  const fileImports = parseImportsFromFile(sourceFile);
1472
+ const localFuncs = parseLocalFunctions(sourceFile);
1394
1473
  const deps = [];
1474
+ const locals = [];
1395
1475
  for (const identifier of usedIdentifiers) {
1396
1476
  const importPath = fileImports.get(identifier);
1397
1477
  if (importPath) {
1398
1478
  deps.push({ name: identifier, from: importPath });
1479
+ } else {
1480
+ const localSource = localFuncs.get(identifier);
1481
+ if (localSource) {
1482
+ locals.push({ name: identifier, source: localSource });
1483
+ }
1399
1484
  }
1400
1485
  }
1401
1486
  if (deps.length > 0) {
1402
1487
  resolverDependencies = deps;
1403
1488
  }
1489
+ if (locals.length > 0) {
1490
+ localFunctions = locals;
1491
+ }
1404
1492
  }
1405
1493
  }
1406
1494
  return {
@@ -1417,6 +1505,7 @@ function analyzeEndpoint(endpoint, endpointFiles) {
1417
1505
  mockResolverImportPath,
1418
1506
  sourceFile,
1419
1507
  resolverDependencies,
1508
+ localFunctions,
1420
1509
  description: endpoint.description
1421
1510
  };
1422
1511
  }
@@ -2952,6 +3041,18 @@ function generateRouteHelpers(code, schemas) {
2952
3041
  }
2953
3042
 
2954
3043
  // src/cli/generators/mock/endpoints.ts
3044
+ function injectResolverTypeAnnotation(source, contextType) {
3045
+ const match = source.match(/^(async\s*)?\(([^)]*)\)\s*=>/);
3046
+ if (match) {
3047
+ const [fullMatch, asyncPart = "", params] = match;
3048
+ if (params.includes(":")) {
3049
+ return source;
3050
+ }
3051
+ const replacement = `${asyncPart}(${params}: ${contextType}) =>`;
3052
+ return source.replace(fullMatch, replacement);
3053
+ }
3054
+ return source;
3055
+ }
2955
3056
  function generateEndpointTypes(endpoints) {
2956
3057
  const code = new CodeBuilder();
2957
3058
  code.line();
@@ -3322,6 +3423,24 @@ function generateEndpointResolvers(endpoints, outputDir) {
3322
3423
  code.line(`import { ${names.join(", ")} } from '${resolvedPath}';`);
3323
3424
  }
3324
3425
  }
3426
+ const allLocalFunctions = /* @__PURE__ */ new Map();
3427
+ for (const endpoint of endpoints) {
3428
+ if (endpoint.localFunctions) {
3429
+ for (const fn of endpoint.localFunctions) {
3430
+ if (!allLocalFunctions.has(fn.name)) {
3431
+ allLocalFunctions.set(fn.name, fn.source);
3432
+ }
3433
+ }
3434
+ }
3435
+ }
3436
+ if (allLocalFunctions.size > 0) {
3437
+ code.line();
3438
+ code.comment("Local helper functions (copied from source files)");
3439
+ for (const [, source] of allLocalFunctions) {
3440
+ code.line(source);
3441
+ code.line();
3442
+ }
3443
+ }
3325
3444
  code.line();
3326
3445
  code.comment("Typed resolver function types");
3327
3446
  for (const endpoint of endpoints) {
@@ -3348,7 +3467,11 @@ function generateEndpointResolvers(endpoints, outputDir) {
3348
3467
  if (endpoint.mockResolverName) {
3349
3468
  code.line(`${endpoint.name}: ${endpoint.mockResolverName} as ${endpoint.pascalName}ResolverFn,`);
3350
3469
  } else {
3351
- code.line(`${endpoint.name}: (${endpoint.mockResolverSource}) as ${endpoint.pascalName}ResolverFn,`);
3470
+ const typedSource = injectResolverTypeAnnotation(
3471
+ endpoint.mockResolverSource,
3472
+ `${endpoint.pascalName}ResolverContext`
3473
+ );
3474
+ code.line(`${endpoint.name}: ${typedSource},`);
3352
3475
  }
3353
3476
  code.line();
3354
3477
  }