fakelab 0.0.24 → 0.0.25

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
@@ -273,8 +273,15 @@ This is useful when you want to:
273
273
  ### Snapshot Options
274
274
 
275
275
  ```ts
276
+ export type SnapshotDataSource = {
277
+ url: string;
278
+ name: string;
279
+ headers?: SourceHeaders;
280
+ };
281
+
276
282
  export type SnapshotOptions = {
277
283
  enabled: boolean;
284
+ sources?: SnapshotDataSource[];
278
285
  };
279
286
  ```
280
287
 
@@ -287,10 +294,11 @@ npx fakelab snapshot [url] [options]
287
294
 
288
295
  ### Options
289
296
 
290
- | Option | Alias | Description |
291
- | ----------------- | ----- | --------------------------------- |
292
- | `--name <string>` | `-n` | name for the captured type |
293
- | `--update` | `-u` | flag to force update the snapshot |
297
+ | Option | Alias | Description |
298
+ | -------------------- | ----- | ------------------------------ |
299
+ | `--source <string>` | `-s` | specify snapshot source name |
300
+ | `--refresh <string>` | `-r` | refresh the specified snapshot |
301
+ | `--delete <string>` | `-d` | delete the specified snapshot |
294
302
 
295
303
  ### Examples
296
304
 
@@ -298,14 +306,16 @@ npx fakelab snapshot [url] [options]
298
306
  # Basic usage
299
307
  npx fakelab snapshot https://jsonplaceholder.typicode.com/todos
300
308
 
301
- # consider a name for captured type
302
- npx fakelab snapshot https://jsonplaceholder.typicode.com/todos --name Todo
309
+ # specify a name for captured source
310
+ npx fakelab snapshot https://jsonplaceholder.typicode.com/todos --source Todo
303
311
 
304
- # add --update flag to force update the existing snapshot
305
- npx fakelab snapshot https://jsonplaceholder.typicode.com/todos --name Todo --update
312
+ # refresh the existing snapshot
313
+ npx fakelab snapshot --refresh Todo
306
314
 
315
+ # delete the existing snapshot
316
+ npx fakelab snapshot --delete Todo
307
317
 
308
- # update all existing snapshots
318
+ # update all snapshots
309
319
  npx fakelab snapshot
310
320
  ```
311
321
 
@@ -362,6 +372,7 @@ npx fakelab serve [options]
362
372
  | `--pathPrefix <prefix>` | `-x` | Prefix for all generated API routes |
363
373
  | `--locale <locale>` | `-l` | Locale used for fake data generation |
364
374
  | `--port <number>` | `-p` | Port to run the server on |
375
+ | `--fresh-snapshots` | `-f` | capture or refresh all snapshots |
365
376
 
366
377
  ### Examples
367
378
 
@@ -374,6 +385,9 @@ npx fakelab serve -s ./types -p 4000
374
385
 
375
386
  # Custom API prefix and locale
376
387
  npx fakelab serve --pathPrefix /v1 --locale fr
388
+
389
+ # refresh existing snapshots
390
+ npx fakelab serve --fresh-snapshots
377
391
  ```
378
392
 
379
393
  ## Related
package/lib/cli.js CHANGED
@@ -1,10 +1,10 @@
1
- import m from'path';import d from'fs-extra';import {Command}from'commander';import R from'express';import X from'cors';import Z from'express-ejs-layouts';import tt from'http';import {Project}from'ts-morph';import y from'picocolors';import {JSONFilePreset}from'lowdb/node';import {fileURLToPath}from'url';import q from'qs';import et from'figlet';import {bundleRequire}from'bundle-require';import V from'joycon';var M=new Intl.ListFormat("en",{style:"long",type:"unit"}),p=class{static label(t){switch(t){case "info":return y.blueBright(t.toUpperCase());case "error":return y.redBright(t.toUpperCase());case "warn":return y.yellowBright(t.toUpperCase())}}static log(t,e){return [y.dim(`[${new Date().toISOString()}]`),this.label(t),e].join(" ")}static info(t,...e){console.log(this.log("info",t),...e);}static warn(t,...e){console.log(this.log("warn",t),...e);}static error(t,...e){console.error(this.log("error",t),...e);}static debug(t,...e){typeof process>"u"||!process.env.DEBUG||console.log(this.log("info",t),...e);}static list(t){return M.format(t)}};var w=class{constructor(t){this.faker=t;}JSDOC_FAKER_FIELD="faker";FAKER_TAG_REGEX=/^([a-zA-Z0-9._]+)(?:\((.*)\))?$/;boolMapping={true:true,false:false};string(t){return this.execute(t,this.faker.word.noun)}int(t){return this.execute(t,this.faker.number.int)}bool(t){return this.execute(t,this.faker.datatype.boolean)}bigInt(t){return this.execute(t,this.faker.number.bigInt)}litbool(t){return this.boolMapping[t]}async object(t,e){let s={};return await Promise.all(t.map(async r=>{let a=r.getTypeAtLocation(r.getValueDeclarationOrThrow());s[r.getName()]=await e(a,this,this.readJSDocTags(r));})),s}async union(t){let e=await Promise.all(t);return e[Math.floor(Math.random()*e.length)]}async intersection(t){let e=await Promise.all(t);return e[Math.floor(Math.random()*e.length)]}evalArgs(t){if(!(!t||!t.trim()))return Function(`"use strict"; return (${t});`)()}readJSDocTags(t){let e=t.getJsDocTags().filter(s=>s.getName()===this.JSDOC_FAKER_FIELD);return e.length===0?[]:e.map(s=>{let[r]=s.getText();if(!r)return;let a=r.text.trim().match(this.FAKER_TAG_REGEX);if(!a)return;let[,i,o]=a,c=this.evalArgs(o);return {path:i,args:c}})}execute(t,e){if(!t)return e();let s=t.path.split("."),r=this.faker;for(let a of s)r=r[a],r||(p.error("Invalid faker module path: (%s)",t.path),process.exit(1));typeof r!="function"&&(p.error("Unresolvable faker function. (%s)",t.path),process.exit(1));try{return t.args?r(t.args):r()}catch{return p.error("Passed invalid arguments to faker function."),r()}}};var h=m.dirname(fileURLToPath(import.meta.url)),f=process.cwd();var v=class{constructor(t,e){this.files=t;this.database=e;let r=new Project({tsConfigFilePath:"tsconfig.json"}).addSourceFilesAtPaths(t);this.__targets=r.flatMap(a=>{let i=a.getInterfaces(),o=a.getTypeAliases(),c=a.getExportDeclarations().flatMap(l=>l.getNamedExports().flatMap(u=>u.getLocalTargetDeclarations()));return [...i,...o,...c]}),this.generateInFileEntitiyMap(this.__targets);}__targets;async run(t){return await t()}normalizePath(t){return t.split(m.sep).join(m.posix.sep)}generateInFileEntitiyMap(t){let s=`
1
+ import d from'path';import u from'fs-extra';import {Command}from'commander';import T from'express';import K from'cors';import X from'http';import Y from'express-ejs-layouts';import {Project}from'ts-morph';import b from'picocolors';import {JSONFilePreset}from'lowdb/node';import {fileURLToPath}from'url';import q from'qs';import Z from'figlet';import {bundleRequire}from'bundle-require';import te from'joycon';var $=new Intl.ListFormat("en",{style:"long",type:"unit"}),p=class{static label(e){switch(e){case "info":return b.blueBright(e.toUpperCase());case "error":return b.redBright(e.toUpperCase());case "warn":return b.yellowBright(e.toUpperCase());case "success":return b.greenBright(e.toUpperCase())}}static log(e,t){return [b.dim(`[${new Date().toISOString()}]`),this.label(e),t].join(" ")}static info(e,...t){console.log(this.log("info",e),...t);}static warn(e,...t){console.log(this.log("warn",e),...t);}static error(e,...t){console.error(this.log("error",e),...t);}static success(e,...t){console.log(this.log("success",e),...t);}static debug(e,...t){typeof process>"u"||!process.env.DEBUG||console.log(this.log("info",e),...t);}static list(e){return $.format(e)}};var v=class{constructor(e){this.faker=e;}JSDOC_FAKER_FIELD="faker";FAKER_TAG_REGEX=/^([a-zA-Z0-9._]+)(?:\((.*)\))?$/;boolMapping={true:true,false:false};string(e){return this.execute(e,this.faker.word.noun)}int(e){return this.execute(e,this.faker.number.int)}bool(e){return this.execute(e,this.faker.datatype.boolean)}bigInt(e){return this.execute(e,this.faker.number.bigInt)}litbool(e){return this.boolMapping[e]}async object(e,t){let s={};return await Promise.all(e.map(async r=>{let a=r.getTypeAtLocation(r.getValueDeclarationOrThrow());s[r.getName()]=await t(a,this,this.readJSDocTags(r));})),s}async union(e){let t=await Promise.all(e);return t[Math.floor(Math.random()*t.length)]}async intersection(e){let t=await Promise.all(e);return t[Math.floor(Math.random()*t.length)]}evalArgs(e){if(!(!e||!e.trim()))return Function(`"use strict"; return (${e});`)()}readJSDocTags(e){let t=e.getJsDocTags().filter(s=>s.getName()===this.JSDOC_FAKER_FIELD);return t.length===0?[]:t.map(s=>{let[r]=s.getText();if(!r)return;let a=r.text.trim().match(this.FAKER_TAG_REGEX);if(!a)return;let[,i,o]=a,c=this.evalArgs(o);return {path:i,args:c}})}execute(e,t){if(!e)return t();let s=e.path.split("."),r=this.faker;for(let a of s)r=r[a],r||(p.error("Invalid faker module path: (%s)",e.path),process.exit(1));typeof r!="function"&&(p.error("Unresolvable faker function. (%s)",e.path),process.exit(1));try{return e.args?r(e.args):r()}catch{return p.error("Passed invalid arguments to faker function."),r()}}};var f=d.dirname(fileURLToPath(import.meta.url)),m=process.cwd();var x=class{constructor(e,t){this.files=e;this.database=t;let r=new Project({tsConfigFilePath:"tsconfig.json"}).addSourceFilesAtPaths(e);this.__targets=r.flatMap(a=>{let i=a.getInterfaces(),o=a.getTypeAliases(),c=a.getExportDeclarations().flatMap(l=>l.getNamedExports().flatMap(h=>h.getLocalTargetDeclarations()));return [...i,...o,...c]}),this.generateInFileEntitiyMap(this.__targets);}__targets;async run(e){return await e()}normalizePath(e){return e.split(d.sep).join(d.posix.sep)}generateInFileEntitiyMap(e){let s=`
2
2
  interface Runtime$ {
3
- ${[...new Set(t.map(r=>{let a=r.getName(),i=r.getSourceFile().getFilePath();return `${a}: import("${i}").${a}`}))].join(`
3
+ ${[...new Set(e.map(r=>{let a=r.getName(),i=r.getSourceFile().getFilePath();return `${a}: import("${i}").${a}`}))].join(`
4
4
  `)}
5
- }`;d.appendFile(m.resolve(h,"runtime.d.ts"),s);}address(t,e){let s=this.normalizePath(f),a=`${t.replace(s,"")}/${e}`;return a.startsWith("/.fakelab")&&(a=a.replace("/.fakelab","")),a}async entities(){let t=await Promise.all(this.__targets.map(async e=>{let s=e.getName().toLowerCase(),r=e.getType(),a=this.normalizePath(e.getSourceFile().getDirectoryPath()),i=e.getSourceFile().getBaseName(),o=this.address(a,i),c=m.resolve(this.database.DATABASE_DIR,`${s}.json`),l=this.address(this.normalizePath(this.database.DATABASE_DIR),m.basename(c)),u=a.includes("/.fakelab/snapshots"),N=await JSONFilePreset(c,[]);return [s,{type:r,filepath:o,snapshot:u,table:N,tablepath:l}]}));return new Map(t.sort((e,s)=>Number(e[1].snapshot)-Number(s[1].snapshot)))}async initFakerLibrary(t){let{faker:e}=await import(`@faker-js/faker/locale/${t.locale}`);return e}};async function g(n,t,e=[],s=0){if(n.isString())return t.string(e[s]);if(n.isNumber())return t.int(e[s]);if(n.isBoolean())return t.bool(e[s]);if(n.isBigInt())return t.bigInt(e[s]);if(n.isBooleanLiteral())return t.litbool(n.getText());if(n.isLiteral())return n.getLiteralValue();if(!n.isUndefined()){if(n.isUnion()){let r=n.getUnionTypes();return await t.union(r.map((a,i)=>g(a,t,e,i)))}if(n.isIntersection()){let r=n.getIntersectionTypes();return await t.intersection(r.map((a,i)=>g(a,t,e,i)))}if(n.isArray()){let r=n.getArrayElementTypeOrThrow();return [await g(r,t,e,s)]}if(n.isObject()){let r=n.getProperties();return await t.object(r,(a,i,o)=>g(a,i,o,s))}return null}}function J({each:n}){return {resolve:async e=>await Promise.all(Array.from({length:e},n))}}async function _(n,t,e){let s=await n.files(t.source),r=new v(s,e),a=await r.entities(),i=await r.initFakerLibrary(n.options.faker(t.locale)),o=new w(i);async function c(l,u){let N=J({each:()=>g(l,o)}),P=await(u.count?N.resolve(parseInt(u.count)):g(l,o)),O=JSON.stringify(P,null,2);return {data:P,json:O}}return {entities:a,build:c}}var x=class{constructor(t,e,s){this.builder=t;this.database=e;this.pkg=s;}async handleQueries(t){let e=t.query.count;return e?{count:e.toString()}:{}}index(){return (t,e)=>{e.render("index",{name:null,entities:this.builder.entities,version:this.pkg.version,enabled:this.database.enabled()});}}preview(t){return async(e,s)=>{let r=`${e.protocol}://${e.host}/`,a=e.params.name,i=await this.handleQueries(e),o=q.stringify(i,{addQueryPrefix:true}),c=this.builder.entities.get(a.toLowerCase());if(c){let{json:l}=await this.builder.build(c.type,i),u=c.filepath;s.render("preview",{name:a,suffix:c.snapshot?"(snapshot)":"",filepath:u,address:r,search:o,json:l,prefix:t,entities:this.builder.entities,version:this.pkg.version,enabled:this.database.enabled()});}else s.redirect("/");}}db(){return (t,e)=>{this.database.enabled()?e.render("database",{name:null,entities:this.builder.entities,version:this.pkg.version}):e.redirect("/");}}table(t){return async(e,s)=>{let r=`${e.protocol}://${e.host}/`,a=e.params.name,i=this.builder.entities.get(a.toLowerCase());if(!this.database.enabled())s.redirect("/");else if(i){await i.table.read();let c=i.table.data.length>0,l=JSON.stringify(i.table.data,null,2),u=i.filepath;s.render("table",{name:a,filepath:u,address:r,prefix:t,json:l,hasData:c,entities:this.builder.entities,version:this.pkg.version});}else s.redirect("/database");}}};var k=class{constructor(t,e,s){this.builder=t;this.network=e;this.database=s;}SEED_MERGE_THRESHOLD=1e3;async handleQueries(t){let e=t.query.count;return e?{count:e.toString()}:{}}async applyNetworkHandlers(t){if(this.network.offline()){let{status:e,message:s}=this.network.state("offline");return t.status(e).json({message:s}),true}if(await this.network.wait(),this.network.timeout())return true;if(this.network.error()){let{status:e,message:s}=this.network.state("error");return t.status(e).json({message:s}),true}return false}entity(){return async(t,e)=>{try{if(await this.applyNetworkHandlers(e))return;let s=t.params.name,r=await this.handleQueries(t),a=this.builder.entities.get(s.toLowerCase());if(a){let{data:i}=await this.builder.build(a.type,r);e.status(200).json(i);}else e.status(400).json({message:"The entity is not exists"});}catch(s){e.status(500).send(s);}}}getTable(){return async(t,e)=>{try{if(!this.database.enabled())return e.status(403).json({message:"database is not enabled or initialized."});if(await this.applyNetworkHandlers(e))return;let s=t.params.name,r=this.builder.entities.get(s.toLowerCase());r?(await r.table.read(),e.status(200).json(r.table.data)):e.status(400).json({message:`${s} table is not exists`});}catch(s){e.status(500).send(s);}}}updateTable(){return async(t,e)=>{try{if(!this.database.enabled())return e.status(403).json({message:"database is not enabled or initialized."});if(await this.applyNetworkHandlers(e))return;let s=t.params.name,r=await this.handleQueries(t),a=this.builder.entities.get(s.toLowerCase());if(a){let{data:i}=await this.builder.build(a.type,r);await a.table.update(o=>o.push(...Array.isArray(i)?i:[i])),e.status(200).json({success:!0});}else e.status(400).json({success:!1,message:`${s} table is not exists`});}catch(s){e.status(500).send(s);}}}insert(){return async(t,e)=>{try{if(!this.database.enabled())return e.status(403).json({message:"database is not enabled or initialized."});let s=t.body?.count||1,r=t.body?.strategy||"reset",a=t.params.name,i=this.builder.entities.get(a.toLowerCase());if(i){let{data:o}=await this.builder.build(i.type,{count:s});if(await i.table.read(),r==="once"&&i.table.data.length>0)return e.status(200).json({message:`${a} entity was seeded once before.`});await i.table.update(c=>{r!=="merge"&&(c.length=0);let l=Array.isArray(o)?o:[o];c.length+l.length<this.SEED_MERGE_THRESHOLD&&c.push(...l);}),e.status(200).json({success:!0});}else e.status(400).json({success:!1,message:"The table is not exists"});}catch(s){e.status(500).send(s);}}}flush(){return async(t,e)=>{try{if(!this.database.enabled())return e.status(403).json({message:"database is not enabled or initialized."});let s=t.params.name,r=this.builder.entities.get(s.toLowerCase());r?(await r.table.update(a=>a.length=0),e.status(200).json({success:!0})):e.status(400).json({success:!1,message:"The table is not exists"});}catch(s){console.log({error:s}),e.status(500).send(s);}}}_update(){return async(t,e)=>{try{if(!this.database.enabled())return e.status(403).json({message:"database is not enabled or initialized."});let s=t.params.name,r=await this.handleQueries(t),a=this.builder.entities.get(s.toLowerCase());if(a){let{data:i}=await this.builder.build(a.type,r);await a.table.update(o=>o.push(i)),e.status(301).redirect(`/database/${s.toLowerCase()}`);}else e.status(400).redirect("/database");}catch{e.status(500).redirect("/database");}}}_clear(){return async(t,e)=>{try{if(!this.database.enabled())return e.status(403).json({message:"database is not enabled or initialized."});let s=t.params.name,r=this.builder.entities.get(s.toLowerCase());r?(await r.table.read(),r.table.data.length>0&&await r.table.update(a=>a.length=0),e.status(301).redirect(`/database/${s.toLowerCase()}`)):e.status(400).redirect("/database");}catch{e.status(500).redirect("/database");}}}};var K=d.readJSONSync(m.join(h,"../package.json")),A=class{constructor(t,e,s,r,a){this.router=t;this.config=e;this.network=s;this.database=r;this.serverCLIOptions=a;let{pathPrefix:i}=this.config.options.server(this.serverCLIOptions.pathPrefix,this.serverCLIOptions.port);this.prefix=i;}prefix;async register(){let t=await _(this.config,this.serverCLIOptions,this.database),e=new x(t,this.database,K),s=new k(t,this.network,this.database);this.router.get("/",e.index()),this.router.get("/entities/:name",e.preview(this.prefix)),this.router.get("/database",e.db()),this.router.get("/database/:name",e.table(this.prefix)),this.router.get(`/${this.prefix}/:name`,s.entity()),this.router.get(`/${this.prefix}/database/:name`,s.getTable()),this.router.post(`/${this.prefix}/database/:name`,s.updateTable()),this.router.post(`/${this.prefix}/database/insert/:name`,s.insert()),this.router.post(`/${this.prefix}/database/flush/:name`,s.flush()),this.router.post("/__update/:name",s._update()),this.router.post("/__delete/:name",s._clear());}};var S=class n{constructor(t){this.config=t;this.options=this.config.options.network(),this.timeout=this.timeout.bind(this),this.error=this.error.bind(this),this.state=this.state.bind(this),this.middleware=this.middleware.bind(this),this.wait=this.wait.bind(this),this.offline=this.offline.bind(this);}options;static initHandlers(t){return new n(t)}timeout(){let t=this.chance(this.options?.timeoutRate);return t&&p.debug("Network timeout..."),t}error(){let t=this.chance(this.options?.errorRate);return t&&p.debug("Network error..."),t}state(t){switch(t){case "error":return {status:500,message:"Network error"};case "offline":return {status:503,message:"Network offline"};default:return {status:500,message:"Unknown error"}}}async wait(){let t=this.resolveDelay();t>0&&(p.debug("Network waiting (%d ms)...",t),await this.sleep(t));}offline(){return this.options?.offline??false}middleware(t,e,s){let r=this.options?.errorRate||0,a=this.options?.timeoutRate||0,i=this.options?.offline??false,o=`delay=${this.resolveDelay()},error=${r},timeout=${a},offline=${i}`;e.setHeader("X-Fakelab-Network",o),s();}resolveDelay(){let t=this.options?.delay;return typeof t=="number"?Math.round(t):Array.isArray(t)?this.random(t):0}chance(t=0){return Math.random()<t}random([t,e]){return Math.round(Math.random()*(e-t)+t)}sleep(t){return new Promise(e=>setTimeout(e,t))}};async function D(){try{let t=await new V().resolve({files:["fakelab.config.ts"]});return t||(p.error("No fakelab config file is detected."),process.exit(1)),(await bundleRequire({filepath:t})).mod.default}catch{p.error("Could not load the config file."),process.exit(1);}}var E=class n{constructor(t){this.config=t;this.enabled=this.enabled.bind(this),this.options=this.config.options.database(),this.options.enabled||d.rmSync(this.DATABASE_DIR,{force:true,recursive:true});}options;DATABASE_DIR=m.resolve(f,".fakelab/db");static register(t){return new n(t)}enabled(){return this.options.enabled??false}async initialize(){if(this.enabled())try{await d.ensureDir(this.DATABASE_DIR),await this.modifyGitignoreFile(".fakelab/*");}catch{p.error("Could not create database.");}}async modifyGitignoreFile(t){try{let e=m.resolve(f,".gitignore");if((await d.readFile(e,{encoding:"utf8"})).split(`
6
- `).some(r=>r.trim()===t.trim()))return;await d.appendFile(e,`
7
- ${t}`);}catch{}}};var L=class n{constructor(t){this.serverCLIOptions=t;this.start=this.start.bind(this),this.xPoweredMiddleware=this.xPoweredMiddleware.bind(this),this.setupApplication=this.setupApplication.bind(this),this.setupTemplateEngine=this.setupTemplateEngine.bind(this),this.loadLocalEnv();}static init(t){return new n(t)}async start(){let t=R(),e=R.Router(),s=tt.createServer(t),r=await D(),a=S.initHandlers(r),i=E.register(r);this.setupApplication(t,a),this.setupTemplateEngine(t),await r.generateInFileRuntimeConfig(h,this.serverCLIOptions),await i.initialize(),await new A(e,r,a,i,this.serverCLIOptions).register(),t.use(e),this.run(s,r,i,this.serverCLIOptions);}setupApplication(t,e){t.disable("x-powered-by"),t.use(R.json()),t.use(X({methods:"GET"})),t.use(R.static(h+"/public")),t.use(this.xPoweredMiddleware),t.use(e.middleware);}setupTemplateEngine(t){t.set("views",m.join(h,"views")),t.set("view engine","ejs"),t.use(Z),t.set("layout","layouts/main");}listen(t,e){t.enabled()&&p.info("database: %s",t.DATABASE_DIR),p.info("server listening to http://localhost:%d",e),console.log(et.textSync("FAKELAB"));}run(t,e,s,r){let{port:a}=e.options.server(r.pathPrefix,r.port);t.listen(a,"localhost",()=>this.listen(s,a));}xPoweredMiddleware(t,e,s){e.setHeader("x-powered-by","fakelab"),s();}loadLocalEnv(){}};var C=class n{constructor(t,e){this.url=t;this.options=e;}TARGET_LANGUAGE="typescript";DEFAULT_TYPE_NAME="Interface";SNAPSHOT_DIR=m.resolve(f,".fakelab/snapshots");static init(t,e){return new n(t,e)}async fetch(t,e){this.invalidUrl(t)&&(p.error("snapshot url is invalid."),process.exit(1));let r=await(await fetch(t)).text();this.invalidJSON(r)&&(p.error("snapshot response data is invalid json."),process.exit(1));let a=await this.readSnapshotMeta(),i=await import('quicktype-core'),o=i.jsonInputForTargetLanguage(this.TARGET_LANGUAGE);await o.addSource({name:e||this.suffix((a?.snapshot?.sources||[]).length||0),samples:[r]});let c=new i.InputData;c.addInput(o);let{lines:l}=await i.quicktype({inputData:c,lang:this.TARGET_LANGUAGE,rendererOptions:{"just-types":true}});return l.join(`
8
- `)}async capture(){if(!(await D()).options.snapshot().enabled)return;if(!this.url)return await this.refetch();let e=await this.fetch(this.url,this.options?.name);await this.save(this.url,e,this.options),await this.modifyGitignoreFile(".fakelab/*");}async save(t,e,s){try{if(await d.ensureDir(this.SNAPSHOT_DIR),await d.ensureFile(m.resolve(this.SNAPSHOT_DIR,"__meta.json")),(await this.readSnapshotMeta()).snapshot.sources.some(a=>a.url===t.trim())){if(s?.update){p.info("Updating %s snapshot...",this.snapshotName(t,!1));let a=await this.fetch(t,s.name);await this.write(t,a,s?.name);return}p.warn("%s snapshot is already exists. Use --update flag to update.",t);return}p.info("Saving %s snapshot...",this.snapshotName(t,!1)),await this.write(t,e,s?.name);}catch{}}async write(t,e,s){await d.writeFile(m.resolve(this.SNAPSHOT_DIR,this.snapshotName(t)),e),await this.updateSnapshotMeta(t,s);}async refetch(){let t=await this.readSnapshotMeta(),e=[...new Set(t.snapshot.sources||[])];p.info("Updating all snapshots..."),await Promise.all(e.map(async s=>{let r=m.resolve(this.SNAPSHOT_DIR,this.snapshotName(s.url));if(await d.exists(r)){let i=await this.fetch(s.url,s.name);await d.writeFile(r,i);}else {let i=await this.fetch(s.url,s.name);await this.save(s.url,i,{});}}));}async readSnapshotMeta(){let t={snapshot:{sources:[]}};try{t=await d.readJSON(m.resolve(this.SNAPSHOT_DIR,"__meta.json"));}catch{}return t.snapshot||(t.snapshot={sources:[]}),t.snapshot.sources||(t.snapshot.sources=[]),t}async updateSnapshotMeta(t,e){let s=await this.readSnapshotMeta(),r=s.snapshot.sources||[],a=r.findIndex(i=>i.url===t);a===-1?r.push({name:e||this.suffix(r.length),url:t}):r.splice(a,1,{...r[a],name:e||r[a].name}),s.snapshot.sources=r,await d.writeJSON(m.resolve(this.SNAPSHOT_DIR,"__meta.json"),s);}snapshotName(t,e=true){return t.replace(/^https?:\/\//,"").replace(/[\/:?.&=#]/g,"_")+(e?".ts":"")}suffix(t){return `${this.DEFAULT_TYPE_NAME}${t.toString().toUpperCase()}`}invalidUrl(t){try{return new URL(t),!1}catch{return true}}invalidJSON(t){try{return JSON.parse(t),!1}catch{return true}}async modifyGitignoreFile(t){try{let e=m.resolve(f,".gitignore");if((await d.readFile(e,{encoding:"utf8"})).split(`
9
- `).some(r=>r.trim()===t.trim()))return;await d.appendFile(e,`
10
- ${t}`);}catch{}}};var I=new Command,F=d.readJSONSync(m.join(h,"../package.json"));I.name(F.name).description(F.description).version(F.version);I.command("serve").description("start server").option("-s, --source <char>","config source path").option("-x, --pathPrefix <char>","server url path prefix").option("-p, --port <number>","server port number",parseInt).option("-l, --locale <char>","faker custom locale").action(async n=>{L.init(n).start();});I.command("snapshot").description("snapshot a endpoint json response to a typescript type").argument("[string]","url to snapshot").option("-n, --name <char>","snapshot type name").option("-u, --update","force update the snapshot").action(async(n,t)=>{C.init(n,t).capture();});I.parse();
5
+ }`;u.appendFile(d.resolve(f,"runtime.d.ts"),s);}address(e,t){let s=this.normalizePath(m);return `${e.replace(s,"")}/${t}`}async entities(){let e=await Promise.all(this.__targets.map(async t=>{let s=t.getName().toLowerCase(),r=t.getType(),a=this.normalizePath(t.getSourceFile().getDirectoryPath()),i=t.getSourceFile().getBaseName(),o=this.address(a,i),c=d.resolve(this.database.DATABASE_DIR,`${s}.json`),l=this.address(this.normalizePath(this.database.DATABASE_DIR),d.basename(c)),h=a.includes("/.fakelab/snapshots"),I=await JSONFilePreset(c,[]);return [s,{type:r,filepath:o,snapshot:h,table:I,tablepath:l}]}));return new Map(e.sort((t,s)=>Number(t[1].snapshot)-Number(s[1].snapshot)))}async initFakerLibrary(e){let{faker:t}=await import(`@faker-js/faker/locale/${e.locale}`);return t}};async function g(n,e,t=[],s=0){if(n.isString())return e.string(t[s]);if(n.isNumber())return e.int(t[s]);if(n.isBoolean())return e.bool(t[s]);if(n.isBigInt())return e.bigInt(t[s]);if(n.isBooleanLiteral())return e.litbool(n.getText());if(n.isLiteral())return n.getLiteralValue();if(!n.isUndefined()){if(n.isUnion()){let r=n.getUnionTypes();return await e.union(r.map((a,i)=>g(a,e,t,i)))}if(n.isIntersection()){let r=n.getIntersectionTypes();return await e.intersection(r.map((a,i)=>g(a,e,t,i)))}if(n.isArray()){let r=n.getArrayElementTypeOrThrow();return [await g(r,e,t,s)]}if(n.isObject()){let r=n.getProperties();return await e.object(r,(a,i,o)=>g(a,i,o,s))}return null}}function J({each:n}){return {resolve:async t=>await Promise.all(Array.from({length:t},n))}}async function P(n,e,t){let s=await n.files(e.source),r=new x(s,t),a=await r.entities(),i=await r.initFakerLibrary(n.options.faker(e.locale)),o=new v(i);async function c(l,h){let I=J({each:()=>g(l,o)}),_=await(h.count?I.resolve(parseInt(h.count)):g(l,o)),j=JSON.stringify(_,null,2);return {data:_,json:j}}return {entities:a,build:c}}var S=class{constructor(e,t,s){this.builder=e;this.database=t;this.pkg=s;}async handleQueries(e){let t=e.query.count;return t?{count:t.toString()}:{}}index(){return (e,t)=>{t.render("index",{name:null,entities:this.builder.entities,version:this.pkg.version,enabled:this.database.enabled()});}}preview(e){return async(t,s)=>{let r=`${t.protocol}://${t.host}/`,a=t.params.name,i=await this.handleQueries(t),o=q.stringify(i,{addQueryPrefix:true}),c=this.builder.entities.get(a.toLowerCase());if(c){let{json:l}=await this.builder.build(c.type,i),h=c.filepath;s.render("preview",{name:a,suffix:c.snapshot?"(snapshot)":"",filepath:h,address:r,search:o,json:l,prefix:e,entities:this.builder.entities,version:this.pkg.version,enabled:this.database.enabled()});}else s.redirect("/");}}db(){return (e,t)=>{this.database.enabled()?t.render("database",{name:null,entities:this.builder.entities,version:this.pkg.version}):t.redirect("/");}}table(e){return async(t,s)=>{let r=`${t.protocol}://${t.host}/`,a=t.params.name,i=this.builder.entities.get(a.toLowerCase());if(!this.database.enabled())s.redirect("/");else if(i){await i.table.read();let c=i.table.data.length>0,l=JSON.stringify(i.table.data,null,2),h=i.filepath;s.render("table",{name:a,filepath:h,address:r,prefix:e,json:l,hasData:c,entities:this.builder.entities,version:this.pkg.version});}else s.redirect("/database");}}};var k=class{constructor(e,t,s){this.builder=e;this.network=t;this.database=s;}SEED_MERGE_THRESHOLD=1e3;async handleQueries(e){let t=e.query.count;return t?{count:t.toString()}:{}}async applyNetworkHandlers(e){if(this.network.offline()){let{status:t,message:s}=this.network.state("offline");return e.status(t).json({message:s}),true}if(await this.network.wait(),this.network.timeout())return true;if(this.network.error()){let{status:t,message:s}=this.network.state("error");return e.status(t).json({message:s}),true}return false}entity(){return async(e,t)=>{try{if(await this.applyNetworkHandlers(t))return;let s=e.params.name,r=await this.handleQueries(e),a=this.builder.entities.get(s.toLowerCase());if(a){let{data:i}=await this.builder.build(a.type,r);t.status(200).json(i);}else t.status(400).json({message:"The entity is not exists"});}catch(s){t.status(500).send(s);}}}getTable(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});if(await this.applyNetworkHandlers(t))return;let s=e.params.name,r=this.builder.entities.get(s.toLowerCase());r?(await r.table.read(),t.status(200).json(r.table.data)):t.status(400).json({message:`${s} table is not exists`});}catch(s){t.status(500).send(s);}}}updateTable(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});if(await this.applyNetworkHandlers(t))return;let s=e.params.name,r=await this.handleQueries(e),a=this.builder.entities.get(s.toLowerCase());if(a){let{data:i}=await this.builder.build(a.type,r);await a.table.update(o=>o.push(...Array.isArray(i)?i:[i])),t.status(200).json({success:!0});}else t.status(400).json({success:!1,message:`${s} table is not exists`});}catch(s){t.status(500).send(s);}}}insert(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let s=e.body?.count||1,r=e.body?.strategy||"reset",a=e.params.name,i=this.builder.entities.get(a.toLowerCase());if(i){let{data:o}=await this.builder.build(i.type,{count:s});if(await i.table.read(),r==="once"&&i.table.data.length>0)return t.status(200).json({message:`${a} entity was seeded once before.`});await i.table.update(c=>{r!=="merge"&&(c.length=0);let l=Array.isArray(o)?o:[o];c.length+l.length<this.SEED_MERGE_THRESHOLD&&c.push(...l);}),t.status(200).json({success:!0});}else t.status(400).json({success:!1,message:"The table is not exists"});}catch(s){t.status(500).send(s);}}}flush(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let s=e.params.name,r=this.builder.entities.get(s.toLowerCase());r?(await r.table.update(a=>a.length=0),t.status(200).json({success:!0})):t.status(400).json({success:!1,message:"The table is not exists"});}catch(s){console.log({error:s}),t.status(500).send(s);}}}_update(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let s=e.params.name,r=await this.handleQueries(e),a=this.builder.entities.get(s.toLowerCase());if(a){let{data:i}=await this.builder.build(a.type,r);await a.table.update(o=>o.push(i)),t.status(301).redirect(`/database/${s.toLowerCase()}`);}else t.status(400).redirect("/database");}catch{t.status(500).redirect("/database");}}}_clear(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let s=e.params.name,r=this.builder.entities.get(s.toLowerCase());r?(await r.table.read(),r.table.data.length>0&&await r.table.update(a=>a.length=0),t.status(301).redirect(`/database/${s.toLowerCase()}`)):t.status(400).redirect("/database");}catch{t.status(500).redirect("/database");}}}};var V=u.readJSONSync(d.join(f,"../package.json")),A=class{constructor(e,t,s,r,a){this.router=e;this.config=t;this.network=s;this.database=r;this.serverCLIOptions=a;let{pathPrefix:i}=this.config.options.server(this.serverCLIOptions.pathPrefix,this.serverCLIOptions.port);this.prefix=i;}prefix;async register(){let e=await P(this.config,this.serverCLIOptions,this.database),t=new S(e,this.database,V),s=new k(e,this.network,this.database);this.router.get("/",t.index()),this.router.get("/entities/:name",t.preview(this.prefix)),this.router.get("/database",t.db()),this.router.get("/database/:name",t.table(this.prefix)),this.router.get(`/${this.prefix}/:name`,s.entity()),this.router.get(`/${this.prefix}/database/:name`,s.getTable()),this.router.post(`/${this.prefix}/database/:name`,s.updateTable()),this.router.post(`/${this.prefix}/database/insert/:name`,s.insert()),this.router.post(`/${this.prefix}/database/flush/:name`,s.flush()),this.router.post("/__update/:name",s._update()),this.router.post("/__delete/:name",s._clear());}};var D=class n{constructor(e){this.config=e;this.options=this.config.options.network(),this.timeout=this.timeout.bind(this),this.error=this.error.bind(this),this.state=this.state.bind(this),this.middleware=this.middleware.bind(this),this.wait=this.wait.bind(this),this.offline=this.offline.bind(this);}options;static initHandlers(e){return new n(e)}timeout(){let e=this.chance(this.options?.timeoutRate);return e&&p.debug("Network timeout..."),e}error(){let e=this.chance(this.options?.errorRate);return e&&p.debug("Network error..."),e}state(e){switch(e){case "error":return {status:500,message:"Network error"};case "offline":return {status:503,message:"Network offline"};default:return {status:500,message:"Unknown error"}}}async wait(){let e=this.resolveDelay();e>0&&(p.debug("Network waiting (%d ms)...",e),await this.sleep(e));}offline(){return this.options?.offline??false}middleware(e,t,s){let r=this.options?.errorRate||0,a=this.options?.timeoutRate||0,i=this.options?.offline??false,o=`delay=${this.resolveDelay()},error=${r},timeout=${a},offline=${i}`;t.setHeader("X-Fakelab-Network",o),s();}resolveDelay(){let e=this.options?.delay;return typeof e=="number"?Math.round(e):Array.isArray(e)?this.random(e):0}chance(e=0){return Math.random()<e}random([e,t]){return Math.round(Math.random()*(t-e)+e)}sleep(e){return new Promise(t=>setTimeout(t,e))}};var R=class n{constructor(e){this.config=e;this.enabled=this.enabled.bind(this),this.options=this.config.options.database(),this.options.enabled||u.rmSync(this.DATABASE_DIR,{force:true,recursive:true});}options;DATABASE_DIR=d.resolve(m,".fakelab/db");static register(e){return new n(e)}enabled(){return this.options.enabled??false}async initialize(){if(this.enabled())try{await u.ensureDir(this.DATABASE_DIR),await this.modifyGitignoreFile(".fakelab/*");}catch{p.error("Could not create database.");}}async modifyGitignoreFile(e){try{let t=d.resolve(m,".gitignore");if((await u.readFile(t,{encoding:"utf8"})).split(`
6
+ `).some(r=>r.trim()===e.trim()))return;await u.appendFile(t,`
7
+ ${e}`);}catch{}}};var C=class n{constructor(e,t){this.serverCLIOptions=e;this.config=t;this.start=this.start.bind(this),this.xPoweredMiddleware=this.xPoweredMiddleware.bind(this),this.setupApplication=this.setupApplication.bind(this),this.setupTemplateEngine=this.setupTemplateEngine.bind(this),this.loadLocalEnv();}static init(e,t){return new n(e,t)}async start(){let e=T(),t=T.Router(),s=X.createServer(e),r=D.initHandlers(this.config),a=R.register(this.config);this.setupApplication(e,r),this.setupTemplateEngine(e),await this.config.initializeRuntimeConfig(f,this.serverCLIOptions),await a.initialize(),await new A(t,this.config,r,a,this.serverCLIOptions).register(),e.use(t),this.run(s,a,this.serverCLIOptions);}setupApplication(e,t){e.disable("x-powered-by"),e.use(T.json()),e.use(K({methods:"GET"})),e.use(T.static(f+"/public")),e.use(this.xPoweredMiddleware),e.use(t.middleware);}setupTemplateEngine(e){e.set("views",d.join(f,"views")),e.set("view engine","ejs"),e.use(Y),e.set("layout","layouts/main");}listen(e,t){e.enabled()&&p.info("database: %s",e.DATABASE_DIR),p.info("server listening to http://localhost:%d",t),console.log(Z.textSync("FAKELAB"));}run(e,t,s){let{port:r}=this.config.options.server(s.pathPrefix,s.port);e.listen(r,"localhost",()=>this.listen(t,r));}xPoweredMiddleware(e,t,s){t.setHeader("x-powered-by","fakelab"),s();}loadLocalEnv(){}};async function N(){try{let e=await new te().resolve({files:["fakelab.config.ts"]});return e||(p.error("No fakelab config file is detected."),process.exit(1)),(await bundleRequire({filepath:e})).mod.default}catch{p.error("Could not load the config file."),process.exit(1);}}var w=class n{constructor(e,t){this.snapshotCLIOptions=e;this.config=t;this.capture=this.capture.bind(this);}TARGET_LANGUAGE="typescript";DEFAULT_TYPE_NAME="Fakelab";SNAPSHOT_DIR=d.resolve(m,".fakelab/snapshots");static _instance;static async init(e){let t=await N();return this._instance||(this._instance=new n(e,t)),this._instance}static async prepare(e){let t=await N(),s=this._instance||new n({},t),{enabled:r,sources:a}=t.options.snapshot();return r&&e.freshSnapshots&&await s.updateAll(a,true),s}async capture(e){let{enabled:t,sources:s}=this.config.options.snapshot();if(!t)return;await u.ensureDir(this.SNAPSHOT_DIR),await u.ensureFile(d.resolve(this.SNAPSHOT_DIR,"__schema.json"));let r=await this.readSnapshotSchema();if(await this.duplicateExists(r)&&(p.error("Snapshot source names must be unique."),process.exit(1)),!e)return this.snapshotCLIOptions.refresh?await this.refresh(s,this.snapshotCLIOptions.refresh,r):this.snapshotCLIOptions.delete?await this.delete(s,this.snapshotCLIOptions.delete,r):await this.updateAll(s);let a=this.suffix(r.sources);this.snapshotCLIOptions?.refresh&&p.warn("--refresh flag has no effect when used with url. Refresh skipped."),this.snapshotCLIOptions?.delete&&p.warn("--delete flag has no effect when used with url. Delete skipped.");let i=await this.fetch({url:e,name:this.snapshotCLIOptions?.source||a});await this.save({url:e,name:this.snapshotCLIOptions?.source||a},i,r),await this.modifyGitignoreFile(".fakelab/*");}async save(e,t,s){try{let r=s.sources.find(i=>i.url===e.url);if(r){p.warn("%s snapshot is already captured. Use \x1B[36mnpx fakelab snapshot --refresh %s\x1B[0m to update.",e.url,r.name);return}let a=this.snapshotName(e.url,!1);p.info("Capturing %s snapshot...",a),e.name||this.snapshotCLIOptions?.source||p.warn("Snapshot source name not found. Auto-generating a name."),await this.write(e.url,t,e.name||this.snapshotCLIOptions?.source),p.success("Snapshot \x1B[34m%s\x1B[0m captured successfully.",a);}catch(r){console.log({error:r});}}async refresh(e,t,s){let r=e.find(o=>o.name===t.trim());if(r||(r=(s.sources||[]).find(o=>o.name===t)),!r){p.warn("Snapshot source not found. Refresh skipped.");return}p.info("Refreshing \x1B[34m%s\x1B[0m snapshot source...",r.name);let a=d.resolve(this.SNAPSHOT_DIR,this.snapshotName(r.url));if(await u.exists(a)){let o=await this.fetch(r);await u.writeFile(a,o);}else {let o=await this.fetch(r);await this.save(r,o,s);}p.success("Snapshot source \x1B[34m%s\x1B[0m refreshed successfully.",r.name);}async delete(e,t,s){let r=e.find(i=>i.name===t.trim());if(r||(r=(s.sources||[]).find(i=>i.name===t)),!r){p.warn("Snapshot source not found. Delete skipped.");return}p.info("Deleting \x1B[34m%s\x1B[0m snapshot source...",r.name);let a=d.resolve(this.SNAPSHOT_DIR,this.snapshotName(r.url));await u.rm(a,{force:true}),await this.updateSnapshotSchema({url:r.url,delete:true}),p.success("Snapshot source \x1B[34m%s\x1B[0m deleted successfully.",r.name);}async updateAll(e,t=false){p.info(t?"Refreshing all snapshots...":"Updating all snapshots...");let s=await this.readSnapshotSchema();try{await Promise.all(e.map(async r=>{let a=d.resolve(this.SNAPSHOT_DIR,this.snapshotName(r.url));if(await u.exists(a)){let o=await this.fetch(r);await u.writeFile(a,o);}else {let o=await this.fetch(r);await this.save(r,o,s);}})),p.success("All snapshots are updated.");}catch(r){p.error("Failed to update.",r),process.exit(1);}}async fetch({name:e,url:t,headers:s}){this.isValidUrl(t)||(p.error("Invalid snapshot URL. Please provide a valid http URL."),process.exit(1));let a=await(await fetch(t,{headers:s})).text();this.isValidJSON(a)||(p.error("Invalid snapshot response format. Expected JSON but received non-JSON data."),process.exit(1));let i=await import('quicktype-core'),o=i.jsonInputForTargetLanguage(this.TARGET_LANGUAGE);await o.addSource({name:e,samples:[a]});let c=new i.InputData;c.addInput(o);let{lines:l}=await i.quicktype({inputData:c,lang:this.TARGET_LANGUAGE,rendererOptions:{"just-types":true}});return l.join(`
8
+ `)}async write(e,t,s,r){await u.writeFile(d.resolve(this.SNAPSHOT_DIR,this.snapshotName(e)),t),await this.updateSnapshotSchema({url:e,name:s,headers:r});}async readSnapshotSchema(){let e={sources:[]};try{e=await u.readJSON(d.resolve(this.SNAPSHOT_DIR,"__schema.json"));}catch{}return e.sources||(e.sources=[]),e}async updateSnapshotSchema(e){let t=await this.readSnapshotSchema(),s=t.sources||[],r=s.findIndex(a=>a.url===e.url);if(e.delete)s.splice(r,1);else if(r===-1)s.push({...e,name:e.name||this.suffix(s)});else {let a=s[r].name,i=s[r].headers;s.splice(r,1,{...s[r],name:e.name||a,headers:e.headers||i});}t.sources=s,await u.writeJSON(d.resolve(this.SNAPSHOT_DIR,"__schema.json"),t);}snapshotName(e,t=true){return e.replace(/^https?:\/\//,"").replace(/[\/:?.&=#]/g,"_")+(t?".ts":"")}suffix(e){let t=e.filter(s=>s.name.startsWith(this.DEFAULT_TYPE_NAME));return `${this.DEFAULT_TYPE_NAME}${t.length}`}isValidUrl(e){try{return new URL(e),!0}catch{return false}}isValidJSON(e){try{return JSON.parse(e),!0}catch{return false}}async duplicateExists(e){let t=e.sources.map(s=>s.name);return new Set(t).size!==t.length}async modifyGitignoreFile(e){try{let t=d.resolve(m,".gitignore");if((await u.readFile(t,{encoding:"utf8"})).split(`
9
+ `).some(r=>r.trim()===e.trim()))return;await u.appendFile(t,`
10
+ ${e}`);}catch{}}};var E=new Command,O=u.readJSONSync(d.join(f,"../package.json"));E.name(O.name).description(O.description).version(O.version);E.command("serve").description("start server").option("-s, --source <char>","config source path").option("-x, --pathPrefix <char>","server url path prefix").option("-p, --port <number>","server port number",parseInt).option("-l, --locale <char>","faker custom locale").option("-f, --fresh-snapshots","capture or refresh all snapshots").action(async n=>{let e=await w.prepare(n);C.init(n,e.config).start();});E.command("snapshot").description("capture a url response to a fakelab entity").argument("[string]","url to capture").option("-s, --source <string>","specify snapshot source name").option("-r, --refresh <string>","refresh the specified snapshot").option("-d, --delete <string>","delete the specified snapshot").action(async(n,e)=>{(await w.init(e)).capture(n);});E.parse();
package/lib/main.d.ts CHANGED
@@ -65,11 +65,21 @@ type NetworkOptions = NetworkBehaviourOptions & {
65
65
  */
66
66
  presets?: Record<string, NetworkBehaviourOptions>;
67
67
  };
68
+ type SourceHeaders = NonNullable<Parameters<typeof fetch>[1]>["headers"];
69
+ type SnapshotDataSource = {
70
+ url: string;
71
+ name: string;
72
+ headers?: SourceHeaders;
73
+ };
68
74
  type SnapshotOptions = {
69
75
  /**
70
- * Enables snapshot data sources.
76
+ * Enables snapshot.
71
77
  */
72
78
  enabled: boolean;
79
+ /**
80
+ * Predefined snapshot sources
81
+ */
82
+ sources?: SnapshotDataSource[];
73
83
  };
74
84
  type ConfigOptions = {
75
85
  /**
@@ -107,6 +117,7 @@ type ServerCLIOptions = {
107
117
  pathPrefix?: string;
108
118
  port?: number;
109
119
  locale?: string;
120
+ freshSnapshots?: boolean;
110
121
  };
111
122
 
112
123
  declare class Config {
@@ -128,7 +139,7 @@ declare class Config {
128
139
  private _snapshotOptions;
129
140
  private _fakerOptions;
130
141
  files(_sourcePath?: string): Promise<string[]>;
131
- generateInFileRuntimeConfig(dirname: string, options: ServerCLIOptions): Promise<void>;
142
+ initializeRuntimeConfig(dirname: string, options: ServerCLIOptions): Promise<void>;
132
143
  private getSnapshotSourceFiles;
133
144
  private tryStat;
134
145
  private isReadable;
@@ -138,4 +149,4 @@ declare class Config {
138
149
 
139
150
  declare function defineConfig(options: ConfigOptions): Config;
140
151
 
141
- export { defineConfig };
152
+ export { type ConfigOptions, defineConfig };
package/lib/main.js CHANGED
@@ -1,4 +1,4 @@
1
- import f from'fast-glob';import a from'path';import _ from'fs-extra';import {stat,access,constants}from'fs/promises';import b from'is-glob';import i from'picocolors';import {transform}from'esbuild';import {fileURLToPath}from'url';var g=new Intl.ListFormat("en",{style:"long",type:"unit"}),s=class{static label(t){switch(t){case "info":return i.blueBright(t.toUpperCase());case "error":return i.redBright(t.toUpperCase());case "warn":return i.yellowBright(t.toUpperCase())}}static log(t,e){return [i.dim(`[${new Date().toISOString()}]`),this.label(t),e].join(" ")}static info(t,...e){console.log(this.log("info",t),...e);}static warn(t,...e){console.log(this.log("warn",t),...e);}static error(t,...e){console.error(this.log("error",t),...e);}static debug(t,...e){typeof process>"u"||!process.env.DEBUG||console.log(this.log("info",t),...e);}static list(t){return g.format(t)}};var l=["af","ar","az","bn","cs","cy","da","de","dv","el","en","eo","es","fa","fi","fr","he","hr","hu","hy","id","it","ja","ka","ko","ku","lv","mk","nb","ne","nl","pl","pt","ro"];function h(){let n=Intl.DateTimeFormat().resolvedOptions().locale;if(!n)return "en";let[t]=n.split("-"),e=t.toLowerCase();return l.includes(e)?e:"en"}var d=`let fl = {};
1
+ import u from'fast-glob';import i from'path';import _ from'fs-extra';import {stat,access,constants}from'fs/promises';import m from'is-glob';import a from'picocolors';import {transform}from'esbuild';import {fileURLToPath}from'url';var g=new Intl.ListFormat("en",{style:"long",type:"unit"}),s=class{static label(t){switch(t){case "info":return a.blueBright(t.toUpperCase());case "error":return a.redBright(t.toUpperCase());case "warn":return a.yellowBright(t.toUpperCase());case "success":return a.greenBright(t.toUpperCase())}}static log(t,e){return [a.dim(`[${new Date().toISOString()}]`),this.label(t),e].join(" ")}static info(t,...e){console.log(this.log("info",t),...e);}static warn(t,...e){console.log(this.log("warn",t),...e);}static error(t,...e){console.error(this.log("error",t),...e);}static success(t,...e){console.log(this.log("success",t),...e);}static debug(t,...e){typeof process>"u"||!process.env.DEBUG||console.log(this.log("info",t),...e);}static list(t){return g.format(t)}};var f=["af","ar","az","bn","cs","cy","da","de","dv","el","en","eo","es","fa","fi","fr","he","hr","hu","hy","id","it","ja","ka","ko","ku","lv","mk","nb","ne","nl","pl","pt","ro"];function h(){let n=Intl.DateTimeFormat().resolvedOptions().locale;if(!n)return "en";let[t]=n.split("-"),e=t.toLowerCase();return f.includes(e)?e:"en"}var d=`let fl = {};
2
2
  let db = {};
3
3
  fl.url = () => "http://localhost:PORT/PREFIX/";
4
4
  fl.fetch = async function (name, count) {
@@ -50,4 +50,4 @@ const fakelab = Object.freeze(fl);
50
50
  const database = Object.freeze(db);
51
51
 
52
52
  export { fakelab, database };
53
- `;var p=class n{constructor(t,e,r){this.port=t;this.prefix=e;this.dbOptions=r;this.enabled=this.dbOptions?.enabled??true?"true":"false";}enabled;transformOptions={minify:true,platform:"browser",target:"es2022"};static init(t,e,r){return new n(t,e,r)}replacer(t,e){let r=t;for(let o in e)r=r.replace(new RegExp(o,"g"),e[o].toString().trim());return r}async prepareSource(){let t=this.replacer(d,{PORT:this.port,PREFIX:this.prefix,ENABLED:this.enabled});try{let{code:e}=await transform(t,this.transformOptions);return e}catch(e){return e instanceof Error&&s.warn(e.message),t}}};a.dirname(fileURLToPath(import.meta.url));var m=process.cwd();var c=class{constructor(t){this.configOptions=t;this.files=this.files.bind(this),this._serverOptions=this._serverOptions.bind(this),this._databaseOptions=this._databaseOptions.bind(this),this._networkOptions=this._networkOptions.bind(this),this._snapshotOptions=this._snapshotOptions.bind(this),this._fakerOptions=this._fakerOptions.bind(this),this.NETWORK_DEFAULT_OPTIONS=Object.freeze({delay:this.configOptions.network?.delay||0,errorRate:this.configOptions.network?.errorRate||0,timeoutRate:this.configOptions.network?.timeoutRate||0,offline:this.configOptions.network?.offline??false});}RUNTIME_SOURCE_FILENAME="runtime.js";FAKELAB_PERSIST_DIR=".fakelab";NETWORK_DEFAULT_OPTIONS;get options(){return {server:this._serverOptions,database:this._databaseOptions,network:this._networkOptions,snapshot:this._snapshotOptions,faker:this._fakerOptions}}_serverOptions(t,e){return {pathPrefix:t||this.configOptions.server?.pathPrefix||"api",port:e||this.configOptions.server?.port||5200,includeSnapshots:this.configOptions.server?.includeSnapshots??true}}_databaseOptions(){return {enabled:this.configOptions.database?.enabled??false}}_networkOptions(){let t=this.configOptions.network?.preset,e=this.configOptions.network?.presets??{};return !t||!e[t]?this.NETWORK_DEFAULT_OPTIONS:{...e[t],...this.configOptions.network??{}}}_snapshotOptions(){return {enabled:this.configOptions.snapshot?.enabled??false}}_fakerOptions(t){let e=(t||this.configOptions.faker?.locale)?.toLowerCase();return e&&l.includes(e)?{locale:e}:{locale:h()}}async files(t){let e=this.resolveSourcePath(t||this.configOptions.sourcePath),r=Array.from(new Set((await Promise.all(e.map(o=>this.resolveTSFiles(o)))).flat()));if(this._serverOptions().includeSnapshots){let o=await this.getSnapshotSourceFiles();r.push(...o);}return r.length===0&&(s.error("No Typescript files found in: %s",s.list(e.map(o=>a.basename(o)))),process.exit(1)),r}async generateInFileRuntimeConfig(t,e){let{port:r,pathPrefix:o}=this._serverOptions(e.pathPrefix,e.port),u=a.resolve(t,this.RUNTIME_SOURCE_FILENAME),O=await p.init(r,o,this.configOptions.database).prepareSource();await _.writeFile(u,O);}async getSnapshotSourceFiles(){let t=await f(".fakelab/snapshots/**/*.ts",{absolute:true,ignore:["**/*.d.ts"],cwd:m});return t.length>0&&s.info("snapshot(s): %s",s.list(t.map(e=>a.parse(e).name))),t}async tryStat(t){try{return await stat(t)}catch{return null}}async isReadable(t){try{return await access(t,constants.R_OK),!0}catch{return false}}resolveSourcePath(t){return (Array.isArray(t)?t:[t]).map(r=>b(r,{strict:true})?r:a.resolve(r))}async resolveTSFiles(t){if(b(t,{strict:true}))return s.info("source: %s",t),f(t,{absolute:true,ignore:["**/*.d.ts"]});let e=a.resolve(t),r=e.endsWith(".ts")?e:e+".ts";return (await this.tryStat(r))?.isFile()?(await this.isReadable(r)||(s.error("Cannot read file: %s",r),process.exit(1)),s.info("source: %s",r),[r]):(await this.tryStat(e))?.isDirectory()?(s.info("source: %s",e),f("**/*.ts",{cwd:e,absolute:true,ignore:["**/*.d.ts"]})):(s.warn("invalid source: [REDACTED]/%s",a.basename(r)),[])}};function R(n){return new c(n)}export{R as defineConfig};
53
+ `;var c=class n{constructor(t,e,r){this.port=t;this.prefix=e;this.dbOptions=r;this.enabled=this.dbOptions?.enabled??true?"true":"false";}enabled;transformOptions={minify:true,platform:"browser",target:"es2022"};static init(t,e,r){return new n(t,e,r)}replacer(t,e){let r=t;for(let o in e)r=r.replace(new RegExp(o,"g"),e[o].toString().trim());return r}async prepareSource(){let t=this.replacer(d,{PORT:this.port,PREFIX:this.prefix,ENABLED:this.enabled});try{let{code:e}=await transform(t,this.transformOptions);return e}catch(e){return e instanceof Error&&s.warn(e.message),t}}};i.dirname(fileURLToPath(import.meta.url));var O=process.cwd();var l=class{constructor(t){this.configOptions=t;this.files=this.files.bind(this),this._serverOptions=this._serverOptions.bind(this),this._databaseOptions=this._databaseOptions.bind(this),this._networkOptions=this._networkOptions.bind(this),this._snapshotOptions=this._snapshotOptions.bind(this),this._fakerOptions=this._fakerOptions.bind(this),this.NETWORK_DEFAULT_OPTIONS=Object.freeze({delay:this.configOptions.network?.delay||0,errorRate:this.configOptions.network?.errorRate||0,timeoutRate:this.configOptions.network?.timeoutRate||0,offline:this.configOptions.network?.offline??false});}RUNTIME_SOURCE_FILENAME="runtime.js";FAKELAB_PERSIST_DIR=".fakelab";NETWORK_DEFAULT_OPTIONS;get options(){return {server:this._serverOptions,database:this._databaseOptions,network:this._networkOptions,snapshot:this._snapshotOptions,faker:this._fakerOptions}}_serverOptions(t,e){return {pathPrefix:t||this.configOptions.server?.pathPrefix||"api",port:e||this.configOptions.server?.port||5200,includeSnapshots:this.configOptions.server?.includeSnapshots??true}}_databaseOptions(){return {enabled:this.configOptions.database?.enabled??false}}_networkOptions(){let t=this.configOptions.network?.preset,e=this.configOptions.network?.presets??{};return !t||!e[t]?this.NETWORK_DEFAULT_OPTIONS:{...e[t],...this.configOptions.network??{}}}_snapshotOptions(){return {enabled:this.configOptions.snapshot?.enabled??false,sources:this.configOptions.snapshot?.sources||[]}}_fakerOptions(t){let e=(t||this.configOptions.faker?.locale)?.toLowerCase();return e&&f.includes(e)?{locale:e}:{locale:h()}}async files(t){let e=this.resolveSourcePath(t||this.configOptions.sourcePath),r=Array.from(new Set((await Promise.all(e.map(o=>this.resolveTSFiles(o)))).flat()));if(this._serverOptions().includeSnapshots){let o=await this.getSnapshotSourceFiles();r.push(...o);}if(r.length===0){let o=e.map(p=>i.basename(p));o.length===0?s.error("No source path found."):s.error("No Typescript files found in: %s",s.list(o)),process.exit(1);}return r}async initializeRuntimeConfig(t,e){let{port:r,pathPrefix:o}=this._serverOptions(e.pathPrefix,e.port),p=i.resolve(t,this.RUNTIME_SOURCE_FILENAME),b=await c.init(r,o,this.configOptions.database).prepareSource();await _.writeFile(p,b);}async getSnapshotSourceFiles(){let t=await u(".fakelab/snapshots/**/*.ts",{absolute:true,ignore:["**/*.d.ts"],cwd:O});return t.length>0&&s.info("snapshot(s): %s",s.list(t.map(e=>i.parse(e).name))),t}async tryStat(t){try{return await stat(t)}catch{return null}}async isReadable(t){try{return await access(t,constants.R_OK),!0}catch{return false}}resolveSourcePath(t){return (Array.isArray(t)?t:[t]).map(r=>m(r,{strict:true})?r:i.resolve(r))}async resolveTSFiles(t){if(m(t,{strict:true}))return s.info("source: %s",t),u(t,{absolute:true,ignore:["**/*.d.ts"]});let e=i.resolve(t),r=e.endsWith(".ts")?e:e+".ts";return (await this.tryStat(r))?.isFile()?(await this.isReadable(r)||(s.error("Cannot read file: %s",r),process.exit(1)),s.info("source: %s",r),[r]):(await this.tryStat(e))?.isDirectory()?(s.info("source: %s",e),u("**/*.ts",{cwd:e,absolute:true,ignore:["**/*.d.ts"]})):(s.warn("invalid source: [REDACTED]/%s",i.basename(r)),[])}};function R(n){return new l(n)}export{R as defineConfig};
package/lib/runtime.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import * as C__Users_P_S_Desktop_projects_digimock_types_user_ts from '../C:/Users/P.S/Desktop/projects/digimock/types/user.ts';
2
+
1
3
  declare function fetch<T extends keyof Runtime$, C extends number | undefined = undefined>(name: T, count?: C): Promise<Result$<Runtime$[T], C>>;
2
4
  declare function type$<T extends keyof Runtime$>(): Runtime$[T];
3
5
  declare function get<T extends keyof Runtime$>(name: T): Promise<Array<Runtime$[T]>>;
@@ -34,8 +36,13 @@ type SeedOptions = {
34
36
  strategy?: "reset" | "once" | "merge";
35
37
  };
36
38
  type Result$<T, CT> = CT extends number ? (CT extends 0 ? T : T[]) : T;
37
- interface Runtime$ {}
38
39
 
39
40
  type Typeof<T extends keyof Runtime$> = ReturnType<typeof type$<T>>;
40
41
 
42
+ interface Runtime$ {}
43
+
44
+ interface Runtime$ {
45
+ User: C__Users_P_S_Desktop_projects_digimock_types_user_ts.User
46
+ }
47
+
41
48
  export { type Typeof, database, fakelab };
@@ -17,7 +17,7 @@
17
17
  <% entities.forEach((entity,entityName) => { %>
18
18
  <a class="sidebar_content_list_item <%= entityName === name ? 'active' : '' %>" href="/entities/<%= entityName %>">
19
19
  <span class="item_name"><%= entityName %></span>
20
- <span class="item_filepath"><%= entity.filepath %></span>
20
+ <span class="item_filepath"><%= entity.snapshot ? "(snapshot)" : entity.filepath %></span>
21
21
  </a>
22
22
  <% }) %>
23
23
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fakelab",
3
- "version": "0.0.24",
3
+ "version": "0.0.25",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "A fast, easy-config mock server for frontend developers.",