@schmock/faker 2.1.4 → 2.1.5

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.
@@ -1 +1 @@
1
- {"version":3,"file":"field-name-matcher.d.ts","sourceRoot":"","sources":["../src/field-name-matcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGxD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAmBxD;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAsC5E;AAED,UAAU,WAAW;IACnB,OAAO,EAAE,YAAY,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,WAAW,EACnB,QAAQ,GAAE,YAAY,EAAuB,GAC5C,WAAW,GAAG,SAAS,CAuFzB"}
1
+ {"version":3,"file":"field-name-matcher.d.ts","sourceRoot":"","sources":["../src/field-name-matcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGxD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAmBxD;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAsC5E;AAED,UAAU,WAAW;IACnB,OAAO,EAAE,YAAY,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,WAAW,EACnB,QAAQ,GAAE,YAAY,EAAuB,GAC5C,WAAW,GAAG,SAAS,CA6FzB"}
@@ -85,10 +85,16 @@ export function findBestMapping(fieldName, schema, mappings = ALL_FIELD_MAPPINGS
85
85
  };
86
86
  }
87
87
  // Skip if schema already has pattern, enum, faker, or $ref constraint
88
+ // Also skip string mappings when minLength/maxLength is set — name-based
89
+ // mappings (e.g. lorem.word for "label") don't honor JSON Schema length
90
+ // constraints, so they'd produce out-of-range strings ~20% of the time.
91
+ // Mirrors the numeric constraint skip just below.
88
92
  if (schemaAny.pattern ||
89
93
  schemaAny.enum ||
90
94
  schemaAny.faker ||
91
- schemaAny.$ref) {
95
+ schemaAny.$ref ||
96
+ (schemaType === "string" &&
97
+ (schema.minLength !== undefined || schema.maxLength !== undefined))) {
92
98
  return undefined;
93
99
  }
94
100
  // Skip numeric faker mapping when schema already constrains the range
package/dist/index.js CHANGED
@@ -1,122 +1 @@
1
- /// <reference path="../../core/schmock.d.ts" />
2
- import { ResourceLimitError, SchemaGenerationError, SchemaValidationError, } from "@schmock/core";
3
- import { version as packageVersion } from "../package.json";
4
- import { MAX_ARRAY_SIZE, NULLABLE_NULL_PROBABILITY } from "./constants.js";
5
- import { generateWithJsf } from "./jsf-config.js";
6
- import { applyOverrides, determineArrayCount } from "./overrides.js";
7
- import { enhanceSchemaWithSmartMapping } from "./schema-enhancement.js";
8
- import { isJSONSchema7, validateSchema } from "./validation.js";
9
- export function fakerPlugin(options) {
10
- // Validate schema immediately when plugin is created (fail-fast)
11
- validateSchema(options.schema);
12
- return {
13
- name: "faker",
14
- version: packageVersion,
15
- async process(context, response) {
16
- // If response already exists, pass it through
17
- if (response !== undefined && response !== null) {
18
- return { context, response };
19
- }
20
- try {
21
- const generatedResponse = await generateFromSchema({
22
- schema: options.schema,
23
- count: options.count,
24
- overrides: options.overrides,
25
- params: context.params,
26
- state: context.routeState,
27
- query: context.query,
28
- seed: options.seed,
29
- });
30
- return {
31
- context,
32
- response: generatedResponse,
33
- };
34
- }
35
- catch (error) {
36
- // Re-throw schema-specific errors as-is
37
- if (error instanceof SchemaValidationError ||
38
- error instanceof ResourceLimitError) {
39
- throw error;
40
- }
41
- // Wrap other errors
42
- throw new SchemaGenerationError(context.path, error instanceof Error ? error : new Error(String(error)), options.schema);
43
- }
44
- },
45
- };
46
- }
47
- export async function generateFromSchema(options) {
48
- const { schema, count, overrides, params, state, query, seed } = options;
49
- validateSchema(schema);
50
- // Handle array schemas with count
51
- if (schema.type === "array" && schema.items) {
52
- const itemCount = determineArrayCount(schema, count);
53
- // Check for resource limits
54
- if (itemCount > MAX_ARRAY_SIZE) {
55
- throw new ResourceLimitError("array_size", MAX_ARRAY_SIZE, itemCount);
56
- }
57
- const rawItemSchema = Array.isArray(schema.items)
58
- ? schema.items[0]
59
- : schema.items;
60
- if (!rawItemSchema || typeof rawItemSchema === "boolean") {
61
- throw new SchemaValidationError("$.items", "Array schema must have valid items definition");
62
- }
63
- const itemSchema = rawItemSchema;
64
- const enhancedItemSchema = enhanceSchemaWithSmartMapping(itemSchema);
65
- const items = [];
66
- for (let i = 0; i < itemCount; i++) {
67
- let item = await generateWithJsf(enhancedItemSchema, seed);
68
- item = postProcessGenerated(item, enhancedItemSchema);
69
- item = applyOverrides(item, overrides, params, state, query);
70
- items.push(item);
71
- }
72
- return items;
73
- }
74
- // Handle object schemas
75
- const enhancedSchema = enhanceSchemaWithSmartMapping(schema);
76
- let generated = await generateWithJsf(enhancedSchema, seed);
77
- generated = postProcessGenerated(generated, enhancedSchema);
78
- generated = applyOverrides(generated, overrides, params, state, query);
79
- return generated;
80
- }
81
- /**
82
- * Post-process generated data to apply nullable probability and boolean weighting.
83
- * Walks the schema and generated data in parallel, applying:
84
- * - schmockNullable: ~5% chance of null
85
- * - schmockTrueProbability: weighted boolean generation
86
- */
87
- function postProcessGenerated(data, schema) {
88
- if (data === null ||
89
- data === undefined ||
90
- !schema ||
91
- typeof schema !== "object") {
92
- return data;
93
- }
94
- const schemaExt = schema;
95
- // Apply nullable probability at this level
96
- if (schemaExt.schmockNullable === true) {
97
- if (Math.random() < NULLABLE_NULL_PROBABILITY) {
98
- return null;
99
- }
100
- }
101
- // Apply boolean weighting at this level
102
- if (schema.type === "boolean" &&
103
- typeof schemaExt.schmockTrueProbability === "number") {
104
- return Math.random() < schemaExt.schmockTrueProbability;
105
- }
106
- // Recurse into object properties
107
- if (typeof data === "object" && !Array.isArray(data) && schema.properties) {
108
- const record = data;
109
- for (const [key, propSchema] of Object.entries(schema.properties)) {
110
- if (key in record && isJSONSchema7(propSchema)) {
111
- record[key] = postProcessGenerated(record[key], propSchema);
112
- }
113
- }
114
- }
115
- // Recurse into array items
116
- if (Array.isArray(data) && schema.items && isJSONSchema7(schema.items)) {
117
- for (let i = 0; i < data.length; i++) {
118
- data[i] = postProcessGenerated(data[i], schema.items);
119
- }
120
- }
121
- return data;
122
- }
1
+ import{ResourceLimitError as o,SchemaGenerationError as v1,SchemaValidationError as n}from"@schmock/core";var k="2.1.5";var Y=1e4,M=10,V=3,F=3,H=100,E=0.05;import{base as m,en as s,Faker as h}from"@faker-js/faker";import{generate as c}from"json-schema-faker";function g(Q){let $=new h({locale:[s,m]});if(Q!==void 0)$.seed(Q);return $}async function x(Q,$){let Z=$??Math.floor(Math.random()*2147483647);return c(Q,{seed:Z,optionalsProbability:1,alwaysFakeOptionals:!0,useDefaultValue:!0,ignoreMissingRefs:!0,failOnInvalidTypes:!1,failOnInvalidFormat:!1,extensions:{faker:g($)}})}var f=new Set(["__proto__","constructor","prototype"]);function S(Q,$,Z=Math.random){if($!==void 0){if($<0)return 0;return $}if(Q.minItems!==void 0&&Q.maxItems!==void 0){let B=Q.minItems,K=Math.max(B,Q.maxItems);return Math.floor(Z()*(K-B+1))+B}if(Q.minItems!==void 0)return Math.max(Q.minItems,V);if(Q.maxItems!==void 0)return Math.min(Q.maxItems,V);return V}function J(Q,$,Z,B,K){if(!$)return Q;if(typeof Q!=="object"||Q===null)return Q;let j=structuredClone(Q);for(let[z,X]of Object.entries($)){if(f.has(z))continue;if(z.includes("."))t(j,z,X,{params:Z,state:B,query:K});else if(typeof X==="object"&&X!==null&&!Array.isArray(X)){let q=X;if(j[z]&&typeof j[z]==="object")j[z]=J(j[z],q,Z,B,K);else j[z]=J({},q,Z,B,K)}else if(typeof X==="string"&&X.includes("{{"))j[z]=l(X,{params:Z,state:B,query:K});else j[z]=X}return j}function t(Q,$,Z,B){let K=$.split("."),j=Q;for(let X=0;X<K.length-1;X++){let q=K[X];if(f.has(q))return;if(!(q in j)||typeof j[q]!=="object"||j[q]===null)j[q]={};j=j[q]}let z=K[K.length-1];if(f.has(z))return;if(typeof Z==="string"&&Z.includes("{{"))j[z]=l(Z,B);else j[z]=Z}function u(Q,$){let Z=$.trim().split("."),B=Q;for(let K of Z)if(B&&typeof B==="object")B=B[K];else return;return B}function l(Q,$){let Z=Q.match(/^\{\{\s*([^}]+)\s*\}\}$/);if(Z){let B=u($,Z[1]);return B!==void 0?B:Q}return Q.replace(/\{\{\s*([^}]+)\s*\}\}/g,(B,K)=>{let j=u($,K);return j!==void 0?String(j):B})}var a=[{keywords:["email"],fakerMethod:"internet.email",schemaType:"string",minScore:0.6,format:"email"},{keywords:["username","user_name","login","handle"],fakerMethod:"internet.username",schemaType:"string",minScore:0.6},{keywords:["password","passwd","secret"],fakerMethod:"internet.password",schemaType:"string",minScore:0.6},{keywords:["token","api_key","apikey","access_token","refresh_token"],fakerMethod:"string.alphanumeric",schemaType:"string",minScore:0.8}],e=[{keywords:["first_name","firstname","given_name","forename"],fakerMethod:"person.firstName",schemaType:"string",minScore:0.6},{keywords:["last_name","lastname","surname","family_name"],fakerMethod:"person.lastName",schemaType:"string",minScore:0.6},{keywords:["full_name","fullname","display_name","displayname","full_name"],fakerMethod:"person.fullName",schemaType:"string",minScore:0.6},{keywords:["name"],fakerMethod:"person.fullName",schemaType:"string",minScore:0.6},{keywords:["middle_name","middlename"],fakerMethod:"person.middleName",schemaType:"string",minScore:0.8},{keywords:["prefix","honorific","salutation"],fakerMethod:"person.prefix",schemaType:"string",minScore:0.8},{keywords:["suffix"],fakerMethod:"person.suffix",schemaType:"string",minScore:0.9},{keywords:["gender","sex"],fakerMethod:"person.sex",schemaType:"string",minScore:0.8},{keywords:["bio","biography"],fakerMethod:"lorem.paragraph",schemaType:"string",minScore:0.8},{keywords:["job_title","jobtitle","position","occupation","job","role"],fakerMethod:"person.jobTitle",schemaType:"string",minScore:0.6},{keywords:["job_area","department","division","team"],fakerMethod:"person.jobArea",schemaType:"string",minScore:0.8},{keywords:["zodiac","zodiac_sign"],fakerMethod:"person.zodiacSign",schemaType:"string",minScore:0.9}],Q1=[{keywords:["phone","mobile","cell","tel","telephone","cellphone","fax"],fakerMethod:"phone.number",schemaType:"string",minScore:0.6},{keywords:["imei"],fakerMethod:"phone.imei",schemaType:"string",minScore:0.9}],$1=[{keywords:["street","street_address"],fakerMethod:"location.streetAddress",schemaType:"string",minScore:0.6},{keywords:["address","mailing_address","postal_address"],fakerMethod:"location.streetAddress",schemaType:"string",minScore:0.6},{keywords:["city","town"],fakerMethod:"location.city",schemaType:"string",minScore:0.6},{keywords:["state","province","region"],fakerMethod:"location.state",schemaType:"string",minScore:0.8},{keywords:["country"],fakerMethod:"location.country",schemaType:"string",minScore:0.8},{keywords:["country_code","countrycode"],fakerMethod:"location.countryCode",schemaType:"string",minScore:0.8},{keywords:["county"],fakerMethod:"location.county",schemaType:"string",minScore:0.9},{keywords:["continent"],fakerMethod:"location.continent",schemaType:"string",minScore:0.9},{keywords:["zip","zipcode","zip_code","postal","postal_code"],fakerMethod:"location.zipCode",schemaType:"string",minScore:0.6},{keywords:["latitude","lat"],fakerMethod:"location.latitude",schemaType:"number",minScore:0.6},{keywords:["longitude","lng","lon","long"],fakerMethod:"location.longitude",schemaType:"number",minScore:0.6},{keywords:["timezone","time_zone","tz"],fakerMethod:"location.timeZone",schemaType:"string",minScore:0.8},{keywords:["building_number"],fakerMethod:"location.buildingNumber",schemaType:"string",minScore:0.9}],B1=[{keywords:["url","website","link","href","homepage","webpage"],fakerMethod:"internet.url",schemaType:"string",minScore:0.6},{keywords:["avatar","photo_url","profile_image","image_url","picture","thumbnail","icon"],fakerMethod:"image.avatar",schemaType:"string",minScore:0.6},{keywords:["domain","hostname","domain_name"],fakerMethod:"internet.domainName",schemaType:"string",minScore:0.8},{keywords:["ip","ip_address","ipaddress","ipv4"],fakerMethod:"internet.ipv4",schemaType:"string",minScore:0.8},{keywords:["ipv6"],fakerMethod:"internet.ipv6",schemaType:"string",minScore:0.9},{keywords:["mac","mac_address"],fakerMethod:"internet.mac",schemaType:"string",minScore:0.9},{keywords:["user_agent","useragent"],fakerMethod:"internet.userAgent",schemaType:"string",minScore:0.9},{keywords:["port"],fakerMethod:"internet.port",schemaType:"number",minScore:0.9},{keywords:["protocol"],fakerMethod:"internet.protocol",schemaType:"string",minScore:0.9}],Z1=[{keywords:["uuid","guid"],fakerMethod:"string.uuid",schemaType:"string",minScore:0.6},{keywords:["sku"],fakerMethod:"string.alphanumeric",schemaType:"string",minScore:0.9},{keywords:["slug"],fakerMethod:"lorem.slug",schemaType:"string",minScore:0.8},{keywords:["isbn"],fakerMethod:"commerce.isbn",schemaType:"string",minScore:0.9},{keywords:["vin"],fakerMethod:"vehicle.vin",schemaType:"string",minScore:0.9},{keywords:["iban"],fakerMethod:"finance.iban",schemaType:"string",minScore:0.9},{keywords:["bic","swift"],fakerMethod:"finance.bic",schemaType:"string",minScore:0.9},{keywords:["ssn","social_security"],fakerMethod:"string.numeric",schemaType:"string",minScore:0.9}],K1=[{keywords:["description","desc","summary","overview","about","excerpt"],fakerMethod:"lorem.paragraph",schemaType:"string",minScore:0.6},{keywords:["content","body","text","message"],fakerMethod:"lorem.paragraphs",schemaType:"string",minScore:0.6},{keywords:["title","heading","subject","headline"],fakerMethod:"lorem.sentence",schemaType:"string",minScore:0.6},{keywords:["tag","label","category","keyword"],fakerMethod:"lorem.word",schemaType:"string",minScore:0.8},{keywords:["comment","note","remark","feedback","review"],fakerMethod:"lorem.sentence",schemaType:"string",minScore:0.8},{keywords:["sentence"],fakerMethod:"lorem.sentence",schemaType:"string",minScore:0.9},{keywords:["paragraph"],fakerMethod:"lorem.paragraph",schemaType:"string",minScore:0.9},{keywords:["word"],fakerMethod:"lorem.word",schemaType:"string",minScore:0.9}],j1=[{keywords:["company","organization","org","employer","brand"],fakerMethod:"company.name",schemaType:"string",minScore:0.6},{keywords:["product","product_name","item_name"],fakerMethod:"commerce.productName",schemaType:"string",minScore:0.8},{keywords:["price","amount","cost","fee","salary","wage","total","subtotal","balance","revenue","income"],fakerMethod:"commerce.price",minScore:0.6},{keywords:["currency","currency_code"],fakerMethod:"finance.currencyCode",schemaType:"string",minScore:0.8},{keywords:["currency_name"],fakerMethod:"finance.currencyName",schemaType:"string",minScore:0.8},{keywords:["currency_symbol"],fakerMethod:"finance.currencySymbol",schemaType:"string",minScore:0.8},{keywords:["account_number","account_no"],fakerMethod:"finance.accountNumber",schemaType:"string",minScore:0.8},{keywords:["credit_card","card_number"],fakerMethod:"finance.creditCardNumber",schemaType:"string",minScore:0.8},{keywords:["cvv"],fakerMethod:"finance.creditCardCVV",schemaType:"string",minScore:0.9},{keywords:["transaction_type"],fakerMethod:"finance.transactionType",schemaType:"string",minScore:0.9}],X1=[{keywords:["created_at","createdat","creation_date","date_created","createddate"],fakerMethod:"date.recent",schemaType:"string",minScore:0.6,format:"date-time"},{keywords:["updated_at","updatedat","modified_at","modifiedat","date_updated","last_modified","date_modified"],fakerMethod:"date.recent",schemaType:"string",minScore:0.6,format:"date-time"},{keywords:["deleted_at","deletedat"],fakerMethod:"date.recent",schemaType:"string",minScore:0.6,format:"date-time"},{keywords:["published_at","publishedat","publish_date"],fakerMethod:"date.recent",schemaType:"string",minScore:0.6,format:"date-time"},{keywords:["expires_at","expiresat","expiration","expiry"],fakerMethod:"date.future",schemaType:"string",minScore:0.6,format:"date-time"},{keywords:["birthday","date_of_birth","dob","birthdate","born"],fakerMethod:"date.birthdate",schemaType:"string",minScore:0.8},{keywords:["start_date","begin_date"],fakerMethod:"date.past",schemaType:"string",minScore:0.8},{keywords:["end_date","due_date","deadline"],fakerMethod:"date.future",schemaType:"string",minScore:0.8},{keywords:["timestamp","ts"],fakerMethod:"date.recent",schemaType:"string",minScore:0.8}],z1=[{keywords:["color","colour"],fakerMethod:"color.human",schemaType:"string",minScore:0.8},{keywords:["hex_color","hexcolor"],fakerMethod:"color.rgb",schemaType:"string",minScore:0.9}],q1=[{keywords:["age"],fakerMethod:"number.int",schemaType:"number",minScore:0.8,fakerArgs:{min:18,max:80}},{keywords:["rating","score","stars"],fakerMethod:"number.int",schemaType:"number",minScore:0.8,fakerArgs:{min:1,max:5}},{keywords:["count","quantity","qty"],fakerMethod:"number.int",schemaType:"number",minScore:0.8,fakerArgs:{min:1,max:100}},{keywords:["percentage","percent"],fakerMethod:"number.int",schemaType:"number",minScore:0.8,fakerArgs:{min:0,max:100}},{keywords:["weight"],fakerMethod:"number.float",schemaType:"number",minScore:0.8,fakerArgs:{min:0.1,max:200}},{keywords:["height"],fakerMethod:"number.int",schemaType:"number",minScore:0.8,fakerArgs:{min:100,max:220}},{keywords:["width","length","depth"],fakerMethod:"number.int",schemaType:"number",minScore:0.8,fakerArgs:{min:1,max:1000}},{keywords:["priority","order","rank","level"],fakerMethod:"number.int",schemaType:"number",minScore:0.8,fakerArgs:{min:1,max:10}},{keywords:["version"],fakerMethod:"system.semver",schemaType:"string",minScore:0.8}],W1=[{keywords:["is_active","isactive","active","enabled","is_enabled"],fakerMethod:"datatype.boolean",schemaType:"boolean",minScore:0.6,trueProbability:0.9},{keywords:["is_deleted","isdeleted","deleted"],fakerMethod:"datatype.boolean",schemaType:"boolean",minScore:0.6,trueProbability:0.05},{keywords:["is_archived","isarchived","archived"],fakerMethod:"datatype.boolean",schemaType:"boolean",minScore:0.6,trueProbability:0.1},{keywords:["is_verified","isverified","verified","confirmed","is_confirmed"],fakerMethod:"datatype.boolean",schemaType:"boolean",minScore:0.6,trueProbability:0.8},{keywords:["is_admin","isadmin","admin"],fakerMethod:"datatype.boolean",schemaType:"boolean",minScore:0.6,trueProbability:0.1},{keywords:["is_public","ispublic","public","visible","is_visible"],fakerMethod:"datatype.boolean",schemaType:"boolean",minScore:0.6,trueProbability:0.7},{keywords:["is_read","isread","read","seen","is_seen"],fakerMethod:"datatype.boolean",schemaType:"boolean",minScore:0.6,trueProbability:0.6},{keywords:["is_default","isdefault"],fakerMethod:"datatype.boolean",schemaType:"boolean",minScore:0.8,trueProbability:0.1},{keywords:["is_required","isrequired","required","mandatory"],fakerMethod:"datatype.boolean",schemaType:"boolean",minScore:0.6,trueProbability:0.5},{keywords:["is_featured","isfeatured","featured","promoted"],fakerMethod:"datatype.boolean",schemaType:"boolean",minScore:0.8,trueProbability:0.15},{keywords:["available","in_stock"],fakerMethod:"datatype.boolean",schemaType:"boolean",minScore:0.8,trueProbability:0.85}],b1=[{keywords:["filename","file_name"],fakerMethod:"system.fileName",schemaType:"string",minScore:0.8},{keywords:["file_type","filetype","mime_type","mimetype","content_type"],fakerMethod:"system.mimeType",schemaType:"string",minScore:0.8},{keywords:["file_ext","extension"],fakerMethod:"system.fileExt",schemaType:"string",minScore:0.9},{keywords:["file_path","filepath","path"],fakerMethod:"system.filePath",schemaType:"string",minScore:0.9},{keywords:["cron","cron_expression"],fakerMethod:"system.cron",schemaType:"string",minScore:0.9},{keywords:["semver"],fakerMethod:"system.semver",schemaType:"string",minScore:0.9}],U1=[{keywords:["aircraft_type","airplane","aircraft"],fakerMethod:"airline.aircraftType",schemaType:"string",minScore:0.9},{keywords:["flight_number","flight"],fakerMethod:"airline.flightNumber",schemaType:"string",minScore:0.9},{keywords:["record_locator","booking_ref","pnr"],fakerMethod:"airline.recordLocator",schemaType:"string",minScore:0.9},{keywords:["seat","seat_number"],fakerMethod:"airline.seat",schemaType:"string",minScore:0.9}],w1=[{keywords:["animal","animal_type","species"],fakerMethod:"animal.type",schemaType:"string",minScore:0.9},{keywords:["pet_name"],fakerMethod:"animal.petName",schemaType:"string",minScore:0.9},{keywords:["breed"],fakerMethod:"animal.dog",schemaType:"string",minScore:0.9},{keywords:["bird"],fakerMethod:"animal.bird",schemaType:"string",minScore:0.9},{keywords:["fish"],fakerMethod:"animal.fish",schemaType:"string",minScore:0.9},{keywords:["horse"],fakerMethod:"animal.horse",schemaType:"string",minScore:0.9},{keywords:["insect"],fakerMethod:"animal.insect",schemaType:"string",minScore:0.9}],V1=[{keywords:["book_title"],fakerMethod:"book.title",schemaType:"string",minScore:0.9},{keywords:["author","book_author","writer"],fakerMethod:"book.author",schemaType:"string",minScore:0.9},{keywords:["genre","book_genre"],fakerMethod:"book.genre",schemaType:"string",minScore:0.9},{keywords:["publisher","publishing_house"],fakerMethod:"book.publisher",schemaType:"string",minScore:0.9},{keywords:["book_series","series_name"],fakerMethod:"book.series",schemaType:"string",minScore:0.9}],Y1=[{keywords:["dish","meal","recipe_name"],fakerMethod:"food.dish",schemaType:"string",minScore:0.9},{keywords:["ingredient"],fakerMethod:"food.ingredient",schemaType:"string",minScore:0.9},{keywords:["spice"],fakerMethod:"food.spice",schemaType:"string",minScore:0.9},{keywords:["fruit"],fakerMethod:"food.fruit",schemaType:"string",minScore:0.9},{keywords:["vegetable"],fakerMethod:"food.vegetable",schemaType:"string",minScore:0.9},{keywords:["meat"],fakerMethod:"food.meat",schemaType:"string",minScore:0.9},{keywords:["food_description"],fakerMethod:"food.description",schemaType:"string",minScore:0.9},{keywords:["ethnic_category","cuisine"],fakerMethod:"food.ethnicCategory",schemaType:"string",minScore:0.9}],F1=[{keywords:["song","song_name","track","track_name"],fakerMethod:"music.songName",schemaType:"string",minScore:0.9},{keywords:["album","album_name"],fakerMethod:"music.album",schemaType:"string",minScore:0.9},{keywords:["artist","musician","band"],fakerMethod:"music.artist",schemaType:"string",minScore:0.9},{keywords:["music_genre"],fakerMethod:"music.genre",schemaType:"string",minScore:0.9}],C1=[],G1=[{keywords:["vehicle","vehicle_name"],fakerMethod:"vehicle.vehicle",schemaType:"string",minScore:0.9},{keywords:["vehicle_type"],fakerMethod:"vehicle.type",schemaType:"string",minScore:0.9},{keywords:["vehicle_manufacturer","car_make"],fakerMethod:"vehicle.manufacturer",schemaType:"string",minScore:0.9},{keywords:["vehicle_model","car_model"],fakerMethod:"vehicle.model",schemaType:"string",minScore:0.9},{keywords:["fuel","fuel_type"],fakerMethod:"vehicle.fuel",schemaType:"string",minScore:0.9},{keywords:["bicycle"],fakerMethod:"vehicle.bicycle",schemaType:"string",minScore:0.9},{keywords:["vehicle_color","car_color"],fakerMethod:"vehicle.color",schemaType:"string",minScore:0.9}],H1=[{keywords:["branch","branch_name","git_branch"],fakerMethod:"git.branch",schemaType:"string",minScore:0.9},{keywords:["commit_sha","commit_hash","sha"],fakerMethod:"git.commitSha",schemaType:"string",minScore:0.9},{keywords:["commit_message"],fakerMethod:"git.commitMessage",schemaType:"string",minScore:0.9}],J1=[{keywords:["db_column","column_name"],fakerMethod:"database.column",schemaType:"string",minScore:0.9},{keywords:["db_type","column_type"],fakerMethod:"database.type",schemaType:"string",minScore:0.9},{keywords:["db_engine","database_engine"],fakerMethod:"database.engine",schemaType:"string",minScore:0.9},{keywords:["collation"],fakerMethod:"database.collation",schemaType:"string",minScore:0.9},{keywords:["mongodb_id","object_id"],fakerMethod:"database.mongodbObjectId",schemaType:"string",minScore:0.9}],I1=[{keywords:["abbreviation","tech_abbreviation"],fakerMethod:"hacker.abbreviation",schemaType:"string",minScore:0.9},{keywords:["hacker_phrase","tech_phrase"],fakerMethod:"hacker.phrase",schemaType:"string",minScore:0.9}],M1=[{keywords:["adjective"],fakerMethod:"word.adjective",schemaType:"string",minScore:0.8},{keywords:["adverb"],fakerMethod:"word.adverb",schemaType:"string",minScore:0.8},{keywords:["noun"],fakerMethod:"word.noun",schemaType:"string",minScore:0.8},{keywords:["verb"],fakerMethod:"word.verb",schemaType:"string",minScore:0.8}],d=[...a,...e,...Q1,...$1,...B1,...Z1,...K1,...j1,...X1,...z1,...q1,...W1,...b1,...U1,...w1,...V1,...Y1,...F1,...C1,...G1,...H1,...J1,...I1,...M1];function p(Q){let $=Q.split(/[_-]/),Z=[];for(let B of $){if(!B)continue;let K=B.replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z0-9])([A-Z])/g,"$1_$2").split("_");for(let j of K)if(j)Z.push(j.toLowerCase())}return Z}function P1(Q,$){let Z=Q.join(""),B=0;for(let K of $){let j=p(K),z=j.join("");if(Z===z)return 1;if(j.length>0&&j.every((X)=>Q.includes(X))){let q=j.length/Q.length>0.5?0.9:0.65;B=Math.max(B,q)}if(j.length>0&&j.length<=Q.length){if(Q.slice(-j.length).every((q,W)=>q===j[W]))B=Math.max(B,0.8)}if(z.length>=3&&Z.includes(z))B=Math.max(B,0.7)}return B}function i(Q,$,Z=d){let B=$,K=typeof $.type==="string"?$.type:void 0;if(K==="string"&&$.format==="uuid")return{mapping:{keywords:["uuid"],fakerMethod:"string.uuid",schemaType:"string",minScore:0.5},score:1};if(B.pattern||B.enum||B.faker||B.$ref||K==="string"&&($.minLength!==void 0||$.maxLength!==void 0))return;let j=$.minimum!==void 0||$.maximum!==void 0||$.exclusiveMinimum!==void 0||$.exclusiveMaximum!==void 0,z=p(Q),X;for(let q of Z){if(j&&(q.schemaType==="number"||q.schemaType==="integer"))continue;if(K==="object"||K==="array")continue;if(q.schemaType&&K&&q.schemaType!==K){if(!(q.schemaType==="number"&&K==="integer"||q.schemaType==="integer"&&K==="number"))continue}let W=P1(z,q.keywords);if(W>=q.minScore&&(!X||W>X.score))X={mapping:q,score:W}}if(K==="string"){if(z[z.length-1]==="id"&&z.length>1){if(!X||X.score<0.8)X={mapping:{keywords:["id"],fakerMethod:"string.uuid",schemaType:"string",minScore:0.5},score:0.8}}}return X}import{ResourceLimitError as L,SchemaValidationError as U}from"@schmock/core";var _;function b(Q){return typeof Q==="object"&&Q!==null&&!Array.isArray(Q)}function C(Q,$="$"){if(!Q||typeof Q!=="object")throw new U($,"Schema must be a valid JSON Schema object");if(Object.keys(Q).length===0)throw new U($,"Schema cannot be empty");let Z=["object","array","string","number","integer","boolean","null"];if(Q.type&&typeof Q.type==="string"&&!Z.includes(Q.type))throw new U($,`Invalid schema type: "${Q.type}"`,"Supported types are: object, array, string, number, integer, boolean, null");if(Q.type==="object"&&Q.properties){if(typeof Q.properties!=="object"||Array.isArray(Q.properties))throw new U(`${$}.properties`,"Properties must be an object mapping property names to schemas",'Use { "propertyName": { "type": "string" } } format');for(let[B,K]of Object.entries(Q.properties))if(typeof K==="object"&&K!==null){let j="faker"in K?String(K.faker):void 0;if(j)try{y(j)}catch(z){if(z instanceof U){let X=z.context,q="Invalid faker method",W;if(X&&typeof X==="object"){if("issue"in X&&typeof X.issue==="string")q=X.issue;if("suggestion"in X&&typeof X.suggestion==="string")W=X.suggestion}throw new U(`${$}.properties.${B}.faker`,q,W)}if(z instanceof Error)throw z;throw Error(String(z))}C(K,`${$}.properties.${B}`)}}if(Q.type==="array"){if(Q.items===null||Q.items===void 0)throw new U(`${$}.items`,"Array schema must have valid items definition","Define items as a schema object or array of schemas");if(Array.isArray(Q.items)){if(Q.items.length===0)throw new U(`${$}.items`,"Array items cannot be empty array","Provide at least one item schema");Q.items.forEach((B,K)=>{if(typeof B==="object"&&B!==null)C(B,`${$}.items[${K}]`)})}else if(typeof Q.items==="object"&&Q.items!==null)C(Q.items,`${$}.items`)}if($==="$"){if(R(Q))throw new U($,"Schema contains circular references which are not supported");if(Q.$ref==="#")throw new U($,"Self-referencing schemas are not supported");let B=O(Q);if(B>M)throw new L("schema_nesting_depth",M,B);if(B>=F)L1(Q,$);P(Q,$)}}function R(Q,$=new Set){if($.has(Q))return!0;if(Q.$ref==="#")return!0;if($.add(Q),Q.type==="object"&&Q.properties){for(let Z of Object.values(Q.properties))if(b(Z)){if(R(Z,$))return!0}}if(Q.type==="array"&&Q.items){let Z=Array.isArray(Q.items)?Q.items:[Q.items];for(let B of Z)if(b(B)){if(R(B,$))return!0}}return $.delete(Q),!1}function O(Q,$=0){if($>M)return $;let Z=$;if(Q.type==="object"&&Q.properties){for(let B of Object.values(Q.properties))if(b(B))Z=Math.max(Z,O(B,$+1))}if(Q.type==="array"&&Q.items){let B=Array.isArray(Q.items)?Q.items:[Q.items];for(let K of B)if(b(K))Z=Math.max(Z,O(K,$+1))}return Z}function L1(Q,$){function Z(B,K){let j=B.type;if(Array.isArray(j)?j.includes("array"):j==="array"){let X=B.maxItems||V;if(K>=F&&X>=H)throw new L("deep_nesting_memory_risk",F*H,K*X);if(B.items){let q=Array.isArray(B.items)?B.items:[B.items];for(let W of q)if(b(W))Z(W,K+1)}return}if(j==="object"&&B.properties){for(let X of Object.values(B.properties))if(b(X))Z(X,K+1)}}Z(Q,0)}function P(Q,$){if(Q.type==="array"){if(Q.maxItems&&Q.maxItems>Y)throw new L("array_max_items",Y,Q.maxItems);let Z=O(Q),B=Q.maxItems||Q.minItems||V;if(Z>F&&B>H)throw new L("memory_estimation",F*H,Z*B)}if(Q.type==="object"&&Q.properties){for(let[Z,B]of Object.entries(Q.properties))if(b(B))P(B,`${$}.properties.${Z}`)}if(Q.type==="array"&&Q.items){if(Array.isArray(Q.items))Q.items.forEach((Z,B)=>{if(b(Z))P(Z,`${$}.items[${B}]`)});else if(b(Q.items))P(Q.items,`${$}.items`)}}function y(Q){let $=Q.split(".");if($.length<2)throw new U("$.faker",`Invalid faker method format: "${Q}"`,"Use format like 'person.firstName' or 'internet.email'");if(!_)_=g();let B=_;for(let K of $)if(B&&typeof B==="object"&&K in B)B=B[K];else throw new U("$.faker",`Invalid faker method: "${Q}"`,"Check faker.js documentation for valid methods");if(typeof B!=="function")throw new U("$.faker",`Invalid faker method: "${Q}" is not a function`,"Check faker.js documentation for valid methods")}function w(Q){if(!Q||typeof Q!=="object")return Q;let $={...Q};if($.properties){$.properties={...$.properties};for(let[Z,B]of Object.entries($.properties))if(b(B))$.properties[Z]=O1(Z,B)}for(let Z of["allOf","anyOf","oneOf"]){let B=$[Z];if(Array.isArray(B))$[Z]=B.map((K)=>b(K)?w(K):K)}if($.items){if(Array.isArray($.items))$.items=$.items.map((Z)=>b(Z)?w(Z):Z);else if(b($.items))$.items=w($.items)}if($.additionalProperties&&typeof $.additionalProperties==="object"&&!Array.isArray($.additionalProperties))$.additionalProperties=w($.additionalProperties);return $}function O1(Q,$){let Z={...$};if(Z.faker)return y(Z.faker),Z;let B=Z.allOf||Z.anyOf||Z.oneOf;if(Z.properties||B||Z.items){let j=w(Z);Object.assign(Z,j)}if(B)return Z;if(Z.const!==void 0||Z.enum)return Z;let K=i(Q,Z);if(K){if(Z.faker=K.mapping.fakerMethod,K.mapping.format)Z.format=K.mapping.format;if(K.mapping.trueProbability!==void 0)Z.schmockTrueProbability=K.mapping.trueProbability}return Z}function a1(Q){return C(Q.schema),{name:"faker",version:k,async process($,Z){if(Z!==void 0&&Z!==null)return{context:$,response:Z};try{let B=await D1({schema:Q.schema,count:Q.count,overrides:Q.overrides,params:$.params,state:$.routeState,query:$.query,seed:Q.seed});return{context:$,response:B}}catch(B){if(B instanceof n||B instanceof o)throw B;throw new v1($.path,B instanceof Error?B:Error(String(B)),Q.schema)}}}}async function D1(Q){let{schema:$,count:Z,overrides:B,params:K,state:j,query:z,seed:X}=Q;if(C($),$.type==="array"&&$.items){let I=S($,Z);if(I>Y)throw new o("array_size",Y,I);let D=Array.isArray($.items)?$.items[0]:$.items;if(!D||typeof D==="boolean")throw new n("$.items","Array schema must have valid items definition");let A=w(D),N=[];for(let T=0;T<I;T++){let G=await x(A,X);G=v(G,A),G=J(G,B,K,j,z),N.push(G)}return N}let q=w($),W=await x(q,X);return W=v(W,q),W=J(W,B,K,j,z),W}function v(Q,$){if(Q===null||Q===void 0||!$||typeof $!=="object")return Q;let Z=$;if(Z.schmockNullable===!0){if(Math.random()<E)return null}if($.type==="boolean"&&typeof Z.schmockTrueProbability==="number")return Math.random()<Z.schmockTrueProbability;if(typeof Q==="object"&&!Array.isArray(Q)&&$.properties){let B=Q;for(let[K,j]of Object.entries($.properties))if(K in B&&b(j))B[K]=v(B[K],j)}if(Array.isArray(Q)&&$.items&&b($.items))for(let B=0;B<Q.length;B++)Q[B]=v(Q[B],$.items);return Q}export{D1 as generateFromSchema,a1 as fakerPlugin};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@schmock/faker",
3
3
  "description": "Faker-powered automatic data generation for Schmock",
4
- "version": "2.1.4",
4
+ "version": "2.1.5",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -34,7 +34,7 @@
34
34
  "json-schema-faker": "^0.6.1"
35
35
  },
36
36
  "peerDependencies": {
37
- "@schmock/core": "^2.1.4"
37
+ "@schmock/core": "^2.1.5"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@amiceli/vitest-cucumber": "^6.5.0",
@@ -228,6 +228,46 @@ describe("findBestMapping", () => {
228
228
  const result = findBestMapping("email", schema);
229
229
  expect(result).toBeUndefined();
230
230
  });
231
+
232
+ // Regression: name-based mappings (e.g. lorem.word for 'label') don't
233
+ // honor JSON Schema length constraints, so they'd produce out-of-range
234
+ // strings ~20% of the time when the schema asked for a specific length.
235
+ // Skip the mapping and let json-schema-faker generate a length-respecting
236
+ // string instead. Mirrors the numeric constraint skip in the loop below.
237
+ it("skips string mapping when schema has minLength", () => {
238
+ const result = findBestMapping("label", {
239
+ type: "string",
240
+ minLength: 3,
241
+ });
242
+ expect(result).toBeUndefined();
243
+ });
244
+
245
+ it("skips string mapping when schema has maxLength", () => {
246
+ const result = findBestMapping("label", {
247
+ type: "string",
248
+ maxLength: 20,
249
+ });
250
+ expect(result).toBeUndefined();
251
+ });
252
+
253
+ it("skips string mapping when both length constraints are set", () => {
254
+ const result = findBestMapping("label", {
255
+ type: "string",
256
+ minLength: 3,
257
+ maxLength: 20,
258
+ });
259
+ expect(result).toBeUndefined();
260
+ });
261
+
262
+ it("does not skip non-string types when minLength happens to be set", () => {
263
+ // minLength is a string-only keyword; on a non-string schema it's
264
+ // meaningless. The number mapping for 'age' should still apply.
265
+ const result = findBestMapping("age", {
266
+ type: "number",
267
+ minLength: 3, // nonsensical on a number, but shouldn't block the mapping
268
+ } as any);
269
+ expect(result).toBeDefined();
270
+ });
231
271
  });
232
272
 
233
273
  it("does not map unrecognized fields", () => {
@@ -110,11 +110,17 @@ export function findBestMapping(
110
110
  }
111
111
 
112
112
  // Skip if schema already has pattern, enum, faker, or $ref constraint
113
+ // Also skip string mappings when minLength/maxLength is set — name-based
114
+ // mappings (e.g. lorem.word for "label") don't honor JSON Schema length
115
+ // constraints, so they'd produce out-of-range strings ~20% of the time.
116
+ // Mirrors the numeric constraint skip just below.
113
117
  if (
114
118
  schemaAny.pattern ||
115
119
  schemaAny.enum ||
116
120
  schemaAny.faker ||
117
- schemaAny.$ref
121
+ schemaAny.$ref ||
122
+ (schemaType === "string" &&
123
+ (schema.minLength !== undefined || schema.maxLength !== undefined))
118
124
  ) {
119
125
  return undefined;
120
126
  }
@@ -175,10 +175,19 @@ describe("Performance and Memory", () => {
175
175
  : (sorted[mid - 1] + sorted[mid]) / 2;
176
176
  };
177
177
 
178
+ const medNarrow = median(narrowTimes);
178
179
  const medWide = median(wideTimes);
179
180
 
180
- // 100-property object should still generate quickly (under 50ms)
181
- expect(medWide).toBeLessThan(50);
181
+ // Scale assertion (machine-speed-independent): wide has 5x the
182
+ // properties of narrow, so its generation time should scale roughly
183
+ // with that. Allow 20x headroom for measurement noise + small superlinear
184
+ // behavior; this catches order-of-magnitude regressions without flaking
185
+ // on shared CI runners where absolute wall-clock thresholds are unreliable.
186
+ if (medNarrow > 0.1) {
187
+ // Only check scaling if we have measurable timing
188
+ expect(medWide).toBeGreaterThan(medNarrow * 0.5); // sanity: more work, not less
189
+ expect(medWide).toBeLessThan(medNarrow * 20); // catches >4x-per-property regressions
190
+ }
182
191
  });
183
192
  });
184
193